3673 lines
193 KiB
HTML
3673 lines
193 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>FLO Messenger</title>
|
|
<script id="floGlobals">
|
|
/* Constants for FLO blockchain operations !!Make sure to add this at begining!! */
|
|
const floGlobals = {
|
|
blockchain: "FLO",
|
|
application: "messenger",
|
|
adminID: "FMRsefPydWznGWneLqi4ABeQAJeFvtS3aQ"
|
|
}
|
|
</script>
|
|
<script src="scripts/lib.js"></script>
|
|
<script src="scripts/floCrypto.js"></script>
|
|
<script src="scripts/floBlockchainAPI.js"></script>
|
|
<script src="scripts/compactIDB.js"></script>
|
|
<script src="scripts/floCloudAPI.js"></script>
|
|
<script src="scripts/floDapps.js"></script>
|
|
<script src="scripts/messenger.js"></script>
|
|
<script id="onLoadStartUp">
|
|
function onLoadStartUp() {
|
|
|
|
document.body.classList.remove('hide-completely')
|
|
isPinSet = false;
|
|
|
|
floDapps.setCustomPrivKeyInput(signIn)
|
|
showPage('loading_page')
|
|
getRef('emoji_picker').shadowRoot.append(style);
|
|
|
|
//clear Rendered Elements
|
|
let elementsToReset = ['inbox_mail_container', 'sent_mail_container', 'contacts_container', 'chat_container', 'messages_container',
|
|
'receiver_name', 'mail_contact_list'
|
|
]
|
|
//, "backup_info"
|
|
elementsToReset.forEach(e => clearElement(getRef(e)))
|
|
|
|
|
|
chatMutationObserver.observe(getRef('messages_container'), {childList: true, subtree: true})
|
|
//invoke the startup functions
|
|
floDapps.launchStartUp().then(result => {
|
|
console.log(result)
|
|
if(!isPinSet){
|
|
showFrame(2)
|
|
}
|
|
|
|
getRef("greet_tag").textContent = myFloID
|
|
|
|
getRef('accent_color_selector').colors = selectedColors
|
|
|
|
if(localStorage.getItem(`accent-color${myFloID}`)){
|
|
const color = localStorage.getItem(`accent-color${myFloID}`)
|
|
getRef('accent_color_selector').selectedColor = color
|
|
document.body.style.setProperty('--accent-color', color);
|
|
}
|
|
else{
|
|
getRef('accent_color_selector').selectedColor = '#3D5AFE'
|
|
}
|
|
|
|
//load messages from IDB and render them
|
|
console.log(`Loading Data! Please Wait...`)
|
|
messenger.initUserDB().then(result => {
|
|
console.log(result)
|
|
//Check for availble bg image
|
|
setBgImage()
|
|
messenger.loadDataFromIDB().then(data => {
|
|
console.log(data)
|
|
floGlobals.appendix = data.appendix;
|
|
floGlobals.groups = data.groups;
|
|
floGlobals.chats = data.chats
|
|
floGlobals['marked'] = data.marked
|
|
renderChatList()
|
|
renderMailList(data.mails, false)
|
|
renderMarked(data.marked)
|
|
messenger.setUIcallbacks(renderDirectUI, renderGroupUI)
|
|
messenger.requestDirectInbox()
|
|
.then(r => console.log("DirectConn:", r))
|
|
.catch(e => console.error("Request error:", e))
|
|
messenger.requestGroupInbox()
|
|
.then(r => console.log(r))
|
|
console.log(`Load Successful!`)
|
|
if(isPinSet){
|
|
loadPage()
|
|
}
|
|
}).catch(error => {
|
|
//console.error(`Failed to load data`)
|
|
notify(error, "error")
|
|
})
|
|
})
|
|
|
|
}).catch(error => notify(error, "error"))
|
|
}
|
|
</script>
|
|
<link rel="shortcut icon" href="assets/messenger-favicon_1.png" type="image/png">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com">
|
|
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700;900&display=swap" rel="stylesheet">
|
|
<link rel="stylesheet" href="css/main.min.css">
|
|
<link rel="stylesheet" href="css/style.css">
|
|
</head>
|
|
|
|
<body data-theme="dark" onload="onLoadStartUp()" class="hide-completely">
|
|
<audio id="notification_sound">
|
|
<source src="https://rmservices.duckdns.org/files/notification-sound.mp3" type="audio/mpeg">
|
|
<source src="https://rmservices.duckdns.org/files/notification-sound.ogg" type="audio/ogg">
|
|
</audio>
|
|
<sm-popup id="confirmation_popup">
|
|
<h4 id="confirm_title"></h4>
|
|
<p id="confirm_message"></p>
|
|
<div class="flex align-center">
|
|
<sm-button variant="no-outline" class="cancel-btn">Cancel</sm-button>
|
|
<sm-button variant="no-outline" class="submit-btn">OK</button>
|
|
</div>
|
|
</sm-popup>
|
|
<sm-popup id="prompt_popup">
|
|
<h4 id="prompt_title"></h4>
|
|
<p id="prompt_message"></p>
|
|
<sm-input id="prompt_input"></sm-input>
|
|
<div class="flex align-center">
|
|
<sm-button variant="no-outline" class="cancel-btn">Cancel</sm-button>
|
|
<sm-button variant="no-outline" class="submit-btn" type="submit">OK</button>
|
|
</div>
|
|
</sm-popup>
|
|
<sm-notifications id="notification_drawer"></sm-notifications>
|
|
<div id="landing_page" class="grid page hide-completely">
|
|
<header class="logo-section align-center">
|
|
<svg class="main-logo" viewBox="0 0 27.25 32">
|
|
<title>RanchiMall</title>
|
|
<path
|
|
d="M27.14,30.86c-.74-2.48-3-4.36-8.25-6.94a20,20,0,0,1-4.2-2.49,6,6,0,0,1-1.25-1.67,4,4,0,0,1,0-2.26c.37-1.08.79-1.57,3.89-4.55a11.66,11.66,0,0,0,3.34-4.67,6.54,6.54,0,0,0,.05-2.82C20,3.6,18.58,2,16.16.49c-.89-.56-1.29-.64-1.3-.24a3,3,0,0,1-.3.72l-.3.55L13.42.94C13,.62,12.4.26,12.19.15c-.4-.2-.73-.18-.72.05a9.39,9.39,0,0,1-.61,1.33s-.14,0-.27-.13C8.76.09,8-.27,8,.23A11.73,11.73,0,0,1,6.76,2.6C4.81,5.87,2.83,7.49.77,7.49c-.89,0-.88,0-.61,1,.22.85.33.92,1.09.69A5.29,5.29,0,0,0,3,8.33c.23-.17.45-.29.49-.26a2,2,0,0,1,.22.63A1.31,1.31,0,0,0,4,9.34a5.62,5.62,0,0,0,2.27-.87L7,8l.13.55c.19.74.32.82,1,.65a7.06,7.06,0,0,0,3.46-2.47l.6-.71-.06.64c-.17,1.63-1.3,3.42-3.39,5.42L6.73,14c-3.21,3.06-3,5.59.6,8a46.77,46.77,0,0,0,4.6,2.41c.28.13,1,.52,1.59.87,3.31,2,4.95,3.92,4.95,5.93a2.49,2.49,0,0,0,.07.77h0c.09.09,0,.1.9-.14a2.61,2.61,0,0,0,.83-.32,3.69,3.69,0,0,0-.55-1.83A11.14,11.14,0,0,0,17,26.81a35.7,35.7,0,0,0-5.1-2.91C9.37,22.64,8.38,22,7.52,21.17a3.53,3.53,0,0,1-1.18-2.48c0-1.38.71-2.58,2.5-4.23,2.84-2.6,3.92-3.91,4.67-5.65a3.64,3.64,0,0,0,.42-2A3.37,3.37,0,0,0,13.61,5l-.32-.74.29-.48c.17-.27.37-.63.46-.8l.15-.3.44.64a5.92,5.92,0,0,1,1,2.81,5.86,5.86,0,0,1-.42,1.94c0,.12-.12.3-.15.4a9.49,9.49,0,0,1-.67,1.1,28,28,0,0,1-4,4.29C8.62,15.49,8.05,16.44,8,17.78a3.28,3.28,0,0,0,1.11,2.76c.95,1,2.07,1.74,5.25,3.32,3.64,1.82,5.22,2.9,6.41,4.38A4.78,4.78,0,0,1,21.94,31a3.21,3.21,0,0,0,.14.92,1.06,1.06,0,0,0,.43-.05l.83-.22.46-.12-.06-.46c-.21-1.53-1.62-3.25-3.94-4.8a37.57,37.57,0,0,0-5.22-2.82A13.36,13.36,0,0,1,11,21.19a3.36,3.36,0,0,1-.8-4.19c.41-.85.83-1.31,3.77-4.15,2.39-2.31,3.43-4.13,3.43-6a5.85,5.85,0,0,0-2.08-4.29c-.23-.21-.44-.43-.65-.65A2.5,2.5,0,0,1,15.27.69a10.6,10.6,0,0,1,2.91,2.78A4.16,4.16,0,0,1,19,6.16a4.91,4.91,0,0,1-.87,3c-.71,1.22-1.26,1.82-4.27,4.67a9.47,9.47,0,0,0-2.07,2.6,2.76,2.76,0,0,0-.33,1.54,2.76,2.76,0,0,0,.29,1.47c.57,1.21,2.23,2.55,4.65,3.73a32.41,32.41,0,0,1,5.82,3.24c2.16,1.6,3.2,3.16,3.2,4.8a1.94,1.94,0,0,0,.09.76,4.54,4.54,0,0,0,1.66-.4C27.29,31.42,27.29,31.37,27.14,30.86ZM6.1,7h0a3.77,3.77,0,0,1-1.46.45L4,7.51l.68-.83a25.09,25.09,0,0,0,3-4.82A12,12,0,0,1,8.28.76c.11-.12.77.32,1.53,1l.63.58-.57.84A10.34,10.34,0,0,1,6.1,7Zm5.71-1.78A9.77,9.77,0,0,1,9.24,7.18h0a5.25,5.25,0,0,1-1.17.28l-.58,0,.65-.78a21.29,21.29,0,0,0,2.1-3.12c.22-.41.42-.76.44-.79s.5.43.9,1.24L12,5ZM13.41,3a2.84,2.84,0,0,1-.45.64,11,11,0,0,1-.9-.91l-.84-.9.19-.45c.34-.79.39-.8,1-.31A9.4,9.4,0,0,1,13.8,2.33q-.18.34-.39.69Z" />
|
|
</svg>
|
|
<h4>
|
|
RanchiMall
|
|
</h4>
|
|
</header>
|
|
<div id="landing">
|
|
<div class="left">
|
|
<h4>
|
|
FLO Messenger
|
|
</h4>
|
|
<h1 class="title-font">
|
|
Truly Secure, Private and Reliable.
|
|
</h1>
|
|
<p>A messenger made with <strong>Blockchain</strong> and <strong>Open Source</strong> technologies. Take
|
|
back control of your data that belongs to you and you alone.</p>
|
|
<div class="flex">
|
|
<sm-button variant="primary" onclick="showPage('sign_in_page')">Sign In</sm-button>
|
|
<sm-button onclick="initOnBoarding()">Get started</sm-button>
|
|
</div>
|
|
</div>
|
|
<div id="landing_illustration" class="right">
|
|
<img src="assets/message-background.svg" alt="">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<section id="sign_in_page" class="grid page hide-completely">
|
|
<div id="sign_in" class="flex direction-column">
|
|
<h2>Sign in</h2>
|
|
<p>Enter your <span id="type_of_key">FLO private key</span> to continue.</p>
|
|
<form class="flex direction-column" action="" onsubmit="return false">
|
|
<pin-input id="get_pin" class="hide-completely"></pin-input>
|
|
<sm-input id="private_key_input_field" type="password" placeholder="FLO private key" privateKey animate>
|
|
</sm-input>
|
|
<sm-button id="sign_in_button" width="cover" variant="primary" disable>continue</sm-button>
|
|
</form>
|
|
<sm-button id="remove_account" class="hide-completely" onclick="signOut()">Remove Account</sm-button>
|
|
</div>
|
|
</section>
|
|
<div id="on_boarding_page" class="page grid hide-completely">
|
|
<div id="frame_1" class="frame">
|
|
<h2 class="h2">Get started</h2>
|
|
<strong class="warning">Don't forget to store them securely. <br> Once lost private key can't be recovered
|
|
along with your data!</strong>
|
|
<sm-button id="generate_flo_id" onclick="generateId()" variant="primary">Get your FLO credentials
|
|
</sm-button>
|
|
<section id="credentials_section" class="hide-completely">
|
|
<h5>FLO ID (User ID)</h5>
|
|
<div class="copy-row">
|
|
<h4 id="generated_id" class="copy"></h4>
|
|
<svg class="icon" onclick="copyToClipboard(this, 'Copied FLO ID')" viewBox="0 0 64 64">
|
|
<title>Copy</title>
|
|
<rect x="16" y="16" width="48" height="48" rx="6" />
|
|
<path d="M.5,47.52V6.5a6,6,0,0,1,6-6h41" />
|
|
</svg>
|
|
</div>
|
|
<h5>Private key (Password)</h5>
|
|
<div class="copy-row">
|
|
<h4 id="generated_key" class="copy"></h4>
|
|
<svg class="icon" onclick="copyToClipboard(this, 'Copied private key')" viewBox="0 0 64 64">
|
|
<title>Copy</title>
|
|
<rect x="16" y="16" width="48" height="48" rx="6" />
|
|
<path d="M.5,47.52V6.5a6,6,0,0,1,6-6h41" />
|
|
</svg>
|
|
</div>
|
|
</section>
|
|
<sm-button id="sign_in_with" variant="primary" class="hide-completely">Next</sm-button>
|
|
</div>
|
|
<div id="frame_2" class="frame">
|
|
<h2 class="h2">Set pin</h2>
|
|
<p>*This pin is saved on this browser only, it won't work anywhere else.</p>
|
|
<form class="flex direction-column" onsubmit="return false">
|
|
<pin-input id="first_pin"></pin-input>
|
|
<pin-input id="confirm_pin"></pin-input>
|
|
<p id="pin_error" class="danger hide-completely">Pin doesn't match</p>
|
|
<sm-button variant="primary" id="set_pin_button" onclick="setPin()" disable>Set pin</sm-button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
<div id="loading_page" class="page hide-completely">
|
|
<svg class="page__loader" viewBox="0 0 512 512">
|
|
<defs>
|
|
<style>
|
|
.a {
|
|
fill: #fff;
|
|
}
|
|
|
|
.b {
|
|
fill: #ccc;
|
|
}
|
|
|
|
.c {
|
|
fill: none;
|
|
}
|
|
|
|
.c,
|
|
.d,
|
|
.e {
|
|
stroke: #000;
|
|
stroke-miterlimit: 10;
|
|
stroke-width: 20;
|
|
}
|
|
|
|
.d {
|
|
fill: #ed1c24;
|
|
}
|
|
|
|
.e {
|
|
fill: #d30d41;
|
|
}
|
|
</style>
|
|
</defs>
|
|
<title>mascot</title>
|
|
<path class="a"
|
|
d="M506,367.94v51.19a52,52,0,0,1-52,52H130.4a52,52,0,0,1-52-52V183.69L9.58,40.88,454,43.24a52,52,0,0,1,52,52.05V225.38" />
|
|
<path class="b"
|
|
d="M506,98.69V431.58c.57,56.06-149.95,44-149.95,44a52,52,0,0,0,52-52V44.86L454,46.65A52,52,0,0,1,506,98.69Z" />
|
|
<line class="c" x1="506" y1="273.75" x2="506" y2="327.21" />
|
|
<path class="c"
|
|
d="M506,367.94v51.19a52,52,0,0,1-52,52H130.4a52,52,0,0,1-52-52V183.69L9.58,40.88,454,43.24a52,52,0,0,1,52,52.05V225.38" />
|
|
<path class="d" d="M361.83,340a69.63,69.63,0,1,1-139.26,0C222.57,301.5,361.83,301.5,361.83,340Z" />
|
|
<path class="c" d="M152,242.43a34.32,34.32,0,0,1,64.68.2" />
|
|
<path class="c" d="M367.79,242.43a34.32,34.32,0,0,1,64.68.2" />
|
|
<path class="e" d="M325,314.13c21,4.22,36.85,12.82,36.85,25.8a69.7,69.7,0,0,1-41.09,63.58" />
|
|
</svg>
|
|
<div class="shadow"></div>
|
|
<h4 class="page__tag-line">Getting everything ready, Hang on.</h4>
|
|
</div>
|
|
<sm-popup id="add_contact_popup">
|
|
<header class="popup-header" slot="header">
|
|
<svg class="icon" viewBox="0 0 64 64" onclick="this.closest('sm-popup').hide()">
|
|
<title>Close</title>
|
|
<line x1="64" y1="0" x2="0" y2="64" />
|
|
<line x1="64" y1="64" x2="0" y2="0" />
|
|
</svg>
|
|
<h4>Add contact</h4>
|
|
<sm-button id="add_contact_button" variant="primary" disable>Add</sm-button>
|
|
</header>
|
|
<sm-input id="add_contact_floID" floId placeholder="FLO address" animate required></sm-input>
|
|
<sm-input id="add_contact_name" placeholder="Name" animate required></sm-input>
|
|
</sm-popup>
|
|
<sm-popup id="compose_mail_popup">
|
|
<header class="popup-header" slot="header">
|
|
<svg class="icon" viewBox="0 0 64 64" onclick="this.closest('sm-popup').hide()">
|
|
<title>Close</title>
|
|
<line x1="64" y1="0" x2="0" y2="64" />
|
|
<line x1="64" y1="64" x2="0" y2="0" />
|
|
</svg>
|
|
<h4>Compose Mail</h4>
|
|
<sm-button id="send_mail_button" variant="primary" disable>Send</sm-button>
|
|
</header>
|
|
<div id="auto_complete_contact" class="flex direction-column">
|
|
<sm-input id="send_mail_to" placeholder="To" animate required></sm-input>
|
|
<div id="mail_contact_list" class="hide-completely contact-list"></div>
|
|
</div>
|
|
<sm-input id="subject_of_mail" placeholder="Subject" animate></sm-input>
|
|
<textarea id="mail_content" placeholder="Type a mail" name="" id="" rows="10" required></textarea>
|
|
</sm-popup>
|
|
<sm-popup id="reply_mail_popup">
|
|
<header class="popup-header" slot="header">
|
|
<svg class="icon" viewBox="0 0 64 64" onclick="this.closest('sm-popup').hide()">
|
|
<title>Close</title>
|
|
<line x1="64" y1="0" x2="0" y2="64" />
|
|
<line x1="64" y1="64" x2="0" y2="0" />
|
|
</svg>
|
|
<h4>Reply</h4>
|
|
<sm-button id="reply_mail_button" variant="primary" disable>Send</sm-button>
|
|
</header>
|
|
<sm-input id="subject_of_reply_mail" placeholder="Subject" animate></sm-input>
|
|
<textarea id="reply_mail_content" placeholder="Type a mail" id="" rows="10" required></textarea>
|
|
</sm-popup>
|
|
<!-- Contact popup -->
|
|
<sm-popup id="contact_details_popup">
|
|
<header class="popup-header" slot="header">
|
|
<svg class="icon" onclick="this.closest('sm-popup').hide()" viewBox="0 0 64 64">
|
|
<title>close</title>
|
|
<line x1="64" y1="0" x2="0" y2="64" />
|
|
<line x1="64" y1="64" x2="0" y2="0" />
|
|
</svg>
|
|
</header>
|
|
<div class="flex direction-column align-center">
|
|
<div id="contact_initial" class="initial flex align-center"></div>
|
|
<text-field id="contact_name"></text-field>
|
|
</div>
|
|
<section class="popup-section">
|
|
<h5>FLO ID</h5>
|
|
<div class="copy-row grid">
|
|
<h4 id="contact_flo_id" class="copy"></h4>
|
|
<svg class="icon" onclick="copyToClipboard(this, 'Copied FLO ID')" viewBox="0 0 64 64">
|
|
<title>Copy</title>
|
|
<rect x="16" y="16" width="48" height="48" rx="6" />
|
|
<path d="M.5,47.52V6.5a6,6,0,0,1,6-6h41" />
|
|
</svg>
|
|
</div>
|
|
</section>
|
|
<ul id="contact_options">
|
|
<li id="add_as_contact_option" class="option" onclick="addAsContact()">
|
|
Add as contact
|
|
</li>
|
|
<li id="mark_read_option" class="option" onclick="markAsRead()">
|
|
Mark as read
|
|
</li>
|
|
<li id="mark_unread_option" class="option" onclick="markAsUnread()">
|
|
Mark as unread
|
|
</li>
|
|
<li class="option" onclick="clearChat()">
|
|
Clear this chat
|
|
</li>
|
|
<li id="delete_chat_option" class="option" onclick="deleteChat()">
|
|
Delete this chat
|
|
</li>
|
|
</ul>
|
|
</sm-popup>
|
|
|
|
<!-- all contacts popup -->
|
|
|
|
<sm-popup id="contacts_popup">
|
|
<header class="popup-header" slot="header">
|
|
<svg class="icon" onclick="this.closest('sm-popup').hide()" viewBox="0 0 64 64">
|
|
<title>close</title>
|
|
<line x1="64" y1="0" x2="0" y2="64" />
|
|
<line x1="64" y1="64" x2="0" y2="0" />
|
|
</svg>
|
|
<h4>Select contact to add</h4>
|
|
<sm-button id="add_members_button" variant="primary" disable>Add</sm-button>
|
|
</header>
|
|
<p class="warning">*Contacts that haven't yet replied to you, can't be added to a group. So they won't be
|
|
visible here.</p>
|
|
<div id="popup_contacts_container" class="observe-empty-state"></div>
|
|
<div class="empty-state">
|
|
<p>You don't have any other contacts to show.</p>
|
|
</div>
|
|
</sm-popup>
|
|
|
|
<!-- Templates -->
|
|
|
|
<template id="mail_card_template">
|
|
<li class="mail-card interact">
|
|
<div class="initial flex align-center"></div>
|
|
<h5 class="sender"></h5>
|
|
<h5 class="date"></h5>
|
|
<h4 class="subject text-overflow"></h4>
|
|
<p class="description"></p>
|
|
</li>
|
|
</template>
|
|
|
|
<template id="mail_template">
|
|
<div class="mail">
|
|
<header class="mail-header flex direction-column">
|
|
<div class="flex space-between">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon back hide-on-desktop" onclick="goto('mails')"
|
|
viewBox="0 0 64 64">
|
|
<line x1="1" y1="32" x2="64" y2="32" />
|
|
<polyline points="29.64 60.97 0.65 32 29.64 3.03" />
|
|
</svg>
|
|
<h5 class="date justify-right"></h5>
|
|
</div>
|
|
<div class="mail-details flex direction-column">
|
|
<h4 class="sender-name"></h4>
|
|
<h5 class="flo-id text-overflow"></h5>
|
|
</div>
|
|
</header>
|
|
<h4 class="mail-subject"></h4>
|
|
<p class="mail-content"></p>
|
|
</div>
|
|
</template>
|
|
|
|
<template id="contact_template">
|
|
<div class="contact">
|
|
<div class="initial flex align-center"></div>
|
|
<h4 class="name"></h4>
|
|
</div>
|
|
</template>
|
|
|
|
<template id="message_template">
|
|
<div class="message">
|
|
<p class="message-body"></p>
|
|
<time class="time"></time>
|
|
</div>
|
|
</template>
|
|
<div id="navbar_backdrop" class="hide" onclick="toggleDrawer()"></div>
|
|
<main id="main_page" class="page grid hide-completely">
|
|
<nav id="main_navbar" class="flex">
|
|
<div class="logo-section" title="RanchiMall FLO Messenger">
|
|
<svg class="main-logo" viewBox="0 0 27.25 32">
|
|
<title>RanchiMall</title>
|
|
<path
|
|
d="M27.14,30.86c-.74-2.48-3-4.36-8.25-6.94a20,20,0,0,1-4.2-2.49,6,6,0,0,1-1.25-1.67,4,4,0,0,1,0-2.26c.37-1.08.79-1.57,3.89-4.55a11.66,11.66,0,0,0,3.34-4.67,6.54,6.54,0,0,0,.05-2.82C20,3.6,18.58,2,16.16.49c-.89-.56-1.29-.64-1.3-.24a3,3,0,0,1-.3.72l-.3.55L13.42.94C13,.62,12.4.26,12.19.15c-.4-.2-.73-.18-.72.05a9.39,9.39,0,0,1-.61,1.33s-.14,0-.27-.13C8.76.09,8-.27,8,.23A11.73,11.73,0,0,1,6.76,2.6C4.81,5.87,2.83,7.49.77,7.49c-.89,0-.88,0-.61,1,.22.85.33.92,1.09.69A5.29,5.29,0,0,0,3,8.33c.23-.17.45-.29.49-.26a2,2,0,0,1,.22.63A1.31,1.31,0,0,0,4,9.34a5.62,5.62,0,0,0,2.27-.87L7,8l.13.55c.19.74.32.82,1,.65a7.06,7.06,0,0,0,3.46-2.47l.6-.71-.06.64c-.17,1.63-1.3,3.42-3.39,5.42L6.73,14c-3.21,3.06-3,5.59.6,8a46.77,46.77,0,0,0,4.6,2.41c.28.13,1,.52,1.59.87,3.31,2,4.95,3.92,4.95,5.93a2.49,2.49,0,0,0,.07.77h0c.09.09,0,.1.9-.14a2.61,2.61,0,0,0,.83-.32,3.69,3.69,0,0,0-.55-1.83A11.14,11.14,0,0,0,17,26.81a35.7,35.7,0,0,0-5.1-2.91C9.37,22.64,8.38,22,7.52,21.17a3.53,3.53,0,0,1-1.18-2.48c0-1.38.71-2.58,2.5-4.23,2.84-2.6,3.92-3.91,4.67-5.65a3.64,3.64,0,0,0,.42-2A3.37,3.37,0,0,0,13.61,5l-.32-.74.29-.48c.17-.27.37-.63.46-.8l.15-.3.44.64a5.92,5.92,0,0,1,1,2.81,5.86,5.86,0,0,1-.42,1.94c0,.12-.12.3-.15.4a9.49,9.49,0,0,1-.67,1.1,28,28,0,0,1-4,4.29C8.62,15.49,8.05,16.44,8,17.78a3.28,3.28,0,0,0,1.11,2.76c.95,1,2.07,1.74,5.25,3.32,3.64,1.82,5.22,2.9,6.41,4.38A4.78,4.78,0,0,1,21.94,31a3.21,3.21,0,0,0,.14.92,1.06,1.06,0,0,0,.43-.05l.83-.22.46-.12-.06-.46c-.21-1.53-1.62-3.25-3.94-4.8a37.57,37.57,0,0,0-5.22-2.82A13.36,13.36,0,0,1,11,21.19a3.36,3.36,0,0,1-.8-4.19c.41-.85.83-1.31,3.77-4.15,2.39-2.31,3.43-4.13,3.43-6a5.85,5.85,0,0,0-2.08-4.29c-.23-.21-.44-.43-.65-.65A2.5,2.5,0,0,1,15.27.69a10.6,10.6,0,0,1,2.91,2.78A4.16,4.16,0,0,1,19,6.16a4.91,4.91,0,0,1-.87,3c-.71,1.22-1.26,1.82-4.27,4.67a9.47,9.47,0,0,0-2.07,2.6,2.76,2.76,0,0,0-.33,1.54,2.76,2.76,0,0,0,.29,1.47c.57,1.21,2.23,2.55,4.65,3.73a32.41,32.41,0,0,1,5.82,3.24c2.16,1.6,3.2,3.16,3.2,4.8a1.94,1.94,0,0,0,.09.76,4.54,4.54,0,0,0,1.66-.4C27.29,31.42,27.29,31.37,27.14,30.86ZM6.1,7h0a3.77,3.77,0,0,1-1.46.45L4,7.51l.68-.83a25.09,25.09,0,0,0,3-4.82A12,12,0,0,1,8.28.76c.11-.12.77.32,1.53,1l.63.58-.57.84A10.34,10.34,0,0,1,6.1,7Zm5.71-1.78A9.77,9.77,0,0,1,9.24,7.18h0a5.25,5.25,0,0,1-1.17.28l-.58,0,.65-.78a21.29,21.29,0,0,0,2.1-3.12c.22-.41.42-.76.44-.79s.5.43.9,1.24L12,5ZM13.41,3a2.84,2.84,0,0,1-.45.64,11,11,0,0,1-.9-.91l-.84-.9.19-.45c.34-.79.39-.8,1-.31A9.4,9.4,0,0,1,13.8,2.33q-.18.34-.39.69Z" />
|
|
</svg>
|
|
<!-- <img src="assets/messenger-favicon_1.png" alt=""> -->
|
|
<h5 class="label">FLO Messenger</h5>
|
|
</div>
|
|
<div class="navbar-item flex badge align-center active" data-notifications="0" id="chat_page_button"
|
|
data-target="chat_page" title="Chat">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 64 64">
|
|
<path d="M21.31,51a6.62,6.62,0,0,0,6.62,6.62H52.1A6.62,6.62,0,0,0,58.72,51V27.74l4.41-7.55H51" />
|
|
<path
|
|
d="M5.33,37.51V14L.87,6.4H36.44a6.69,6.69,0,0,1,6.68,6.69V37.51a6.69,6.69,0,0,1-6.68,6.69H12A6.69,6.69,0,0,1,5.33,37.51Z" />
|
|
</svg>
|
|
<h5 class="label">Chat</h5>
|
|
</div>
|
|
<div class="navbar-item flex badge align-center" id="mail_page_button" data-notifications="0"
|
|
data-target="mail_page" title="Mail">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 64 64">
|
|
<rect x="0.5" y="5.72" width="63" height="52.56" rx="5" />
|
|
<polyline points="12.25 21.36 32.03 32.03 51.75 21.42" />
|
|
</svg>
|
|
<h5 class="label">Mail</h5>
|
|
</div>
|
|
<div class="navbar-item flex align-center" data-target="settings_page" title="Settings">
|
|
<svg class="icon" viewBox="0 0 64 64">
|
|
<path
|
|
d="M41,62.92a1.7,1.7,0,0,1-1.45-.83L37,57.7a2.63,2.63,0,0,0-2.27-1.34h-.26a23.91,23.91,0,0,1-5,0h-.26A2.63,2.63,0,0,0,27,57.7l-2.54,4.39a1.67,1.67,0,0,1-1.44.83,1.72,1.72,0,0,1-.83-.22L10.33,55.86a1.67,1.67,0,0,1-.61-2.27l2.54-4.41a2.61,2.61,0,0,0-.12-2.85A23.77,23.77,0,0,1,9.65,42,2.65,2.65,0,0,0,7.24,40.5H2.17A1.67,1.67,0,0,1,.5,38.83V25.17A1.67,1.67,0,0,1,2.17,23.5H7.24A2.64,2.64,0,0,0,9.65,22a25,25,0,0,1,2.49-4.31,2.63,2.63,0,0,0,.12-2.85l-2.54-4.4a1.67,1.67,0,0,1,.61-2.27L22.17,1.3A1.72,1.72,0,0,1,23,1.08a1.67,1.67,0,0,1,1.44.83L27,6.3a2.63,2.63,0,0,0,2.27,1.34h.26a23.91,23.91,0,0,1,5,0h.26A2.63,2.63,0,0,0,37,6.3l2.53-4.39A1.7,1.7,0,0,1,41,1.08a1.72,1.72,0,0,1,.83.22L53.67,8.14a1.67,1.67,0,0,1,.61,2.27l-2.54,4.41a2.61,2.61,0,0,0,.12,2.85A24.46,24.46,0,0,1,54.35,22a2.65,2.65,0,0,0,2.41,1.52h5.07a1.67,1.67,0,0,1,1.67,1.67V38.83a1.67,1.67,0,0,1-1.67,1.67H56.76A2.63,2.63,0,0,0,54.35,42a24.63,24.63,0,0,1-2.49,4.31,2.63,2.63,0,0,0-.12,2.85l2.54,4.4a1.68,1.68,0,0,1-.61,2.27L41.83,62.7A1.72,1.72,0,0,1,41,62.92Z" />
|
|
<path
|
|
d="M23,1.58h0a1.16,1.16,0,0,1,1,.58l2.54,4.39a3.14,3.14,0,0,0,2.7,1.59l.31,0C30.37,8,31.19,8,32,8s1.63,0,2.44.12l.31,0a3.14,3.14,0,0,0,2.7-1.59L40,2.16a1.16,1.16,0,0,1,1-.58,1.1,1.1,0,0,1,.58.16L53.42,8.57a1.16,1.16,0,0,1,.42,1.59L51.3,14.57a3.15,3.15,0,0,0,.15,3.4,23.69,23.69,0,0,1,2.45,4.21A3.12,3.12,0,0,0,56.76,24h5.07A1.17,1.17,0,0,1,63,25.17V38.83A1.17,1.17,0,0,1,61.83,40H56.76a3.11,3.11,0,0,0-2.86,1.82,24.33,24.33,0,0,1-2.44,4.23,3.11,3.11,0,0,0-.15,3.39l2.53,4.4a1.13,1.13,0,0,1,.12.88,1.17,1.17,0,0,1-.54.71L41.58,62.26a1.08,1.08,0,0,1-.58.16,1.16,1.16,0,0,1-1-.58l-2.54-4.39a3.14,3.14,0,0,0-2.7-1.59l-.31,0C33.63,56,32.81,56,32,56s-1.63,0-2.44-.12l-.31,0a3.14,3.14,0,0,0-2.7,1.59L24,61.84a1.16,1.16,0,0,1-1,.58,1.1,1.1,0,0,1-.58-.16L10.58,55.43a1.16,1.16,0,0,1-.42-1.59l2.54-4.41a3.15,3.15,0,0,0-.15-3.4,23.69,23.69,0,0,1-2.45-4.21A3.12,3.12,0,0,0,7.24,40H2.17A1.17,1.17,0,0,1,1,38.83V25.17A1.17,1.17,0,0,1,2.17,24H7.24a3.11,3.11,0,0,0,2.86-1.82A24.33,24.33,0,0,1,12.54,18a3.11,3.11,0,0,0,.15-3.39l-2.53-4.4a1.16,1.16,0,0,1,.42-1.59L22.42,1.74A1.08,1.08,0,0,1,23,1.58m0-1a2.11,2.11,0,0,0-1.08.29L10.08,7.7a2.17,2.17,0,0,0-.79,3l2.54,4.4a2.15,2.15,0,0,1-.1,2.31,24.92,24.92,0,0,0-2.54,4.4A2.13,2.13,0,0,1,7.24,23H2.17A2.17,2.17,0,0,0,0,25.17V38.83A2.17,2.17,0,0,0,2.17,41H7.24a2.13,2.13,0,0,1,1.95,1.23,25.25,25.25,0,0,0,2.55,4.39,2.13,2.13,0,0,1,.09,2.31L9.29,53.34a2.17,2.17,0,0,0,.79,3l11.84,6.83a2.11,2.11,0,0,0,1.08.29,2.17,2.17,0,0,0,1.88-1.08L27.41,58a2.14,2.14,0,0,1,1.84-1.09h.21a24.88,24.88,0,0,0,5.08,0h.21A2.14,2.14,0,0,1,36.59,58l2.53,4.39A2.17,2.17,0,0,0,41,63.42a2.11,2.11,0,0,0,1.08-.29L53.92,56.3a2.17,2.17,0,0,0,.79-3l-2.54-4.4a2.15,2.15,0,0,1,.1-2.31,24.92,24.92,0,0,0,2.54-4.4A2.13,2.13,0,0,1,56.76,41h5.07A2.17,2.17,0,0,0,64,38.83V25.17A2.17,2.17,0,0,0,61.83,23H56.76a2.13,2.13,0,0,1-1.95-1.23,25.25,25.25,0,0,0-2.55-4.39,2.13,2.13,0,0,1-.09-2.31l2.54-4.41a2.17,2.17,0,0,0-.79-3L42.08.87A2.11,2.11,0,0,0,41,.58a2.17,2.17,0,0,0-1.88,1.08L36.59,6.05a2.14,2.14,0,0,1-1.84,1.09h-.21a24.88,24.88,0,0,0-5.08,0h-.21a2.14,2.14,0,0,1-1.84-1.09L24.88,1.66A2.17,2.17,0,0,0,23,.58Z" />
|
|
<circle cx="32" cy="32" r="11.5" />
|
|
<path d="M32,21A11,11,0,1,1,21,32,11,11,0,0,1,32,21m0-1A12,12,0,1,0,44,32,12,12,0,0,0,32,20Z" />
|
|
</svg>
|
|
<h5 class="label">Settings</h5>
|
|
</div>
|
|
</nav>
|
|
<section id="chat_page" class="sub-page grid">
|
|
<div id="contacts" class="grid">
|
|
<header class="grid header">
|
|
<div class="flex align-center">
|
|
<svg class="hamburger-menu-button icon hide-on-desktop" onclick="toggleDrawer()"
|
|
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
|
<line y1="10.66" x2="64" y2="10.66" />
|
|
<line y1="32" x2="64" y2="32" />
|
|
<line y1="53.34" x2="64" y2="53.34" />
|
|
</svg>
|
|
<h4>FLO Messenger</h4>
|
|
<svg class="icon" onclick="toggleSearch('chat_search_field')" viewBox="0 0 64 64">
|
|
<title>Search</title>
|
|
<path
|
|
d="M25.69,1A24.7,24.7,0,0,1,43.15,43.15,24.7,24.7,0,0,1,8.23,8.22,24.53,24.53,0,0,1,25.69,1m0-1A25.7,25.7,0,1,0,43.85,7.51,25.64,25.64,0,0,0,25.69,0Z" />
|
|
<line x1="63.65" y1="63.66" x2="43.36" y2="43.37" />
|
|
</svg>
|
|
<sm-menu align-options="right">
|
|
<sm-menu-option onclick="initGroupCreation()">
|
|
Create new group
|
|
</sm-menu-option>
|
|
</sm-menu>
|
|
</div>
|
|
<div id="chat_search_field" class="expanding-search flex align-center">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon back"
|
|
onclick="toggleSearch('chat_search_field')" viewBox="0 0 64 64">
|
|
<title>back-arrow</title>
|
|
<line x1="1" y1="32" x2="64" y2="32" />
|
|
<polyline points="29.64 60.97 0.65 32 29.64 3.03" />
|
|
</svg>
|
|
<sm-input id="search_chats" type="search" placeholder="Search">
|
|
<!-- <svg slot="icon" class="icon" viewBox="0 0 64 64">
|
|
<title>Search</title>
|
|
<path d="M25.69,1A24.7,24.7,0,0,1,43.15,43.15,24.7,24.7,0,0,1,8.23,8.22,24.53,24.53,0,0,1,25.69,1m0-1A25.7,25.7,0,1,0,43.85,7.51,25.64,25.64,0,0,0,25.69,0Z"/>
|
|
<line x1="63.65" y1="63.66" x2="43.36" y2="43.37"/>
|
|
</svg> -->
|
|
</sm-input>
|
|
</div>
|
|
</header>
|
|
<sm-button variant="primary" id="new_message_button" onclick="showContacts({show: true})"
|
|
class="fab round">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 64 64">
|
|
<title>Send new message</title>
|
|
<path
|
|
d="M55.76,20.13,59,23.34l3.5-3.48a3.26,3.26,0,0,0,.05-4.63l0-.05-.59-.56a3.3,3.3,0,0,0-4.67,0l-21.1,21a2.34,2.34,0,0,0-.63,1.19l-.9,4.63a.8.8,0,0,0,.66.91.57.57,0,0,0,.26,0l4.65-.88a2.36,2.36,0,0,0,1.22-.61l15.7-15.63" />
|
|
<path d="M48.05,49.79a6,6,0,0,1-6,6H6.47a6,6,0,0,1-6-6V14.21a6,6,0,0,1,6-6H42" />
|
|
</svg>
|
|
New chat
|
|
</sm-button>
|
|
<div id="chat_container" class="flex observe-empty-state"></div>
|
|
<div id="new_conversation" class="flex direction-column empty-state">
|
|
<svg class="icon new-conversation align-center" viewBox="0 0 512 512">
|
|
<title>new conversation</title>
|
|
<path
|
|
d="M304.11,403.5H101.42a51,51,0,0,1-51-51v-191L6.87,84.82H424.36a51,51,0,0,1,51,51v86.72" />
|
|
<ellipse cx="423.3" cy="342.48" rx="84.7" />
|
|
<line x1="423.3" y1="306.34" x2="423.3" y2="379.64" />
|
|
<line x1="459.95" y1="342.99" x2="386.65" y2="342.99" />
|
|
</svg>
|
|
<h4>Start your first conversation</h4>
|
|
<p class="light-text">Tap/click on 'New chat' to add or select a contact.</p>
|
|
</div>
|
|
<div id="all_contacts" class="flex direction-column hide-completely">
|
|
<header class="grid header">
|
|
<div class="flex align-center">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon back"
|
|
onclick="showContacts({show: false})" viewBox="0 0 64 64">
|
|
<title>back-arrow</title>
|
|
<line x1="1" y1="32" x2="64" y2="32" />
|
|
<polyline points="29.64 60.97 0.65 32 29.64 3.03" />
|
|
</svg>
|
|
<sm-input id="search_contacts" type="search" placeholder="Enter name or FLO ID">
|
|
<!-- <svg slot="icon" class="icon" viewBox="0 0 64 64">
|
|
<title>Search</title>
|
|
<path d="M25.69,1A24.7,24.7,0,0,1,43.15,43.15,24.7,24.7,0,0,1,8.23,8.22,24.53,24.53,0,0,1,25.69,1m0-1A25.7,25.7,0,1,0,43.85,7.51,25.64,25.64,0,0,0,25.69,0Z"/>
|
|
<line x1="63.65" y1="63.66" x2="43.36" y2="43.37"/>
|
|
</svg> -->
|
|
</sm-input>
|
|
</div>
|
|
</header>
|
|
<div id="selected_contacts" class="hide-completely">
|
|
<h4>Select group members</h4>
|
|
<div id="selected_contacts_container" class="observe-empty-state"></div>
|
|
<p class="warning empty-state">*Contacts that haven't yet replied to you, can't be added to a
|
|
group. So they won't be visible here.</p>
|
|
</div>
|
|
<div class="scrolling-wrapper">
|
|
<div id="all_contacts_options">
|
|
<div id="create_group_option" class="option interact" onclick="initGroupCreation()">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon filled" viewBox="0 0 64 64">
|
|
<path
|
|
d="M13.61,19.27c-1.63,0-4.72-2.35-5.33-3.57A21.71,21.71,0,0,1,6.93,8.37S6.67,2.3,13.61,2.3A6.38,6.38,0,0,1,20.3,8.37,21.71,21.71,0,0,1,19,15.7c-.62,1.22-3.7,3.57-5.34,3.57" />
|
|
<path
|
|
d="M32,22.92c-2.21,0-6.37-3.17-7.2-4.82A29.42,29.42,0,0,1,23,8.21S22.62,0,32,0c8.68,0,9,8.21,9,8.21A29.42,29.42,0,0,1,39.2,18.1c-.82,1.65-5,4.82-7.2,4.82" />
|
|
<path
|
|
d="M14.82,27.75c.76-.33,1.54-.65,2.3-1,2.49-1,4.85-2,6.22-3.44C21.07,22.41,19,21.43,18,19.6a8.85,8.85,0,0,1-4.41,2.75,8.85,8.85,0,0,1-4.4-2.75C7.9,22.05,4.63,23,1.55,24.33c-1.26.53-2.3,7.32-.84,7.32A24.66,24.66,0,0,0,11.57,35C11.89,32,12.86,28.57,14.82,27.75Z" />
|
|
<path
|
|
d="M49.15,34.3A14.85,14.85,0,1,0,64,49.15,14.85,14.85,0,0,0,49.15,34.3Zm8,16.31H50.58v6.61H47.66V50.61H41.05V47.69h6.61V41.08h2.92v6.61h6.61Z" />
|
|
<path
|
|
d="M48.93,30.42a1.56,1.56,0,0,0-.64-.66c-4.16-1.83-8.57-3.08-10.34-6.4a12,12,0,0,1-6,3.73,12,12,0,0,1-5.95-3.73c-1.77,3.32-6.18,4.57-10.34,6.4-1.7.71-3.11,9.88-1.13,9.88a33,33,0,0,0,16.5,4.52A18.73,18.73,0,0,1,48.93,30.42Z" />
|
|
</svg>
|
|
Create new group
|
|
</div>
|
|
<div id="add_contact_option" class="option interact"
|
|
onclick="showPopup('add_contact_popup')">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon filled" viewBox="0 0 64 64">
|
|
<path
|
|
d="M49.15,34.3A14.85,14.85,0,1,0,64,49.15,14.85,14.85,0,0,0,49.15,34.3Zm8,16.31H50.58v6.61H47.66V50.61H41.05V47.69h6.61V41.08h2.92v6.61h6.61Z" />
|
|
<path
|
|
d="M21.36,26.63C18.79,26.63,14,23,13,21s-2.12-7.45-2.12-11.49c0,0-.41-9.53,10.49-9.53C31.45,0,31.85,9.53,31.85,9.53c0,4-1.17,9.58-2.12,11.49s-5.8,5.61-8.37,5.61" />
|
|
<path
|
|
d="M28.78,49.15A20.28,20.28,0,0,1,36.7,33.07c-3.6-1.49-6.88-3-8.42-5.93a14,14,0,0,1-6.92,4.33,14,14,0,0,1-6.92-4.33c-2,3.85-7.18,5.3-12,7.43-2,.82-3.61,11.48-1.31,11.48a38.44,38.44,0,0,0,19.34,5.26h1.8a37.94,37.94,0,0,0,6.6-.59C28.82,50.2,28.78,49.68,28.78,49.15Z" />
|
|
</svg>
|
|
Add contact
|
|
</div>
|
|
</div>
|
|
<div id="contacts_container" class="observe-empty-state"></div>
|
|
<div class="empty-state">
|
|
<p>No contacts found.</p>
|
|
</div>
|
|
</div>
|
|
<sm-button id="skip_members_button" variant="primary" class="fab round hide-completely"
|
|
onclick="skipToGroupCreation()">
|
|
Skip
|
|
</sm-button>
|
|
</div>
|
|
<div id="group_creation_panel" class="flex direction-column hide-completely">
|
|
<header class="grid header">
|
|
<div class="flex align-center">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon back" onclick="backToContacts()"
|
|
viewBox="0 0 64 64">
|
|
<title>back-arrow</title>
|
|
<line x1="1" y1="32" x2="64" y2="32" />
|
|
<polyline points="29.64 60.97 0.65 32 29.64 3.03" />
|
|
</svg>
|
|
<h4>Create group</h4>
|
|
</div>
|
|
</header>
|
|
<form action="" onsubmit="return false">
|
|
<div class="grid">
|
|
<svg class="icon group-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
|
<path
|
|
d="M13.61,28.09c-1.63,0-4.72-2.35-5.33-3.58a21.65,21.65,0,0,1-1.35-7.32s-.26-6.07,6.68-6.07a6.38,6.38,0,0,1,6.69,6.07A21.65,21.65,0,0,1,19,24.51c-.62,1.23-3.7,3.58-5.34,3.58" />
|
|
<path
|
|
d="M50.39,28.09c-1.64,0-4.72-2.35-5.34-3.58a21.9,21.9,0,0,1-1.35-7.32s-.26-6.07,6.69-6.07a6.37,6.37,0,0,1,6.68,6.07,21.65,21.65,0,0,1-1.35,7.32c-.61,1.23-3.7,3.58-5.33,3.58" />
|
|
<path
|
|
d="M32,31.74c-2.21,0-6.37-3.17-7.2-4.83A29.3,29.3,0,0,1,23,17s-.35-8.21,9-8.21c8.68,0,9,8.21,9,8.21a29.3,29.3,0,0,1-1.83,9.88c-.82,1.66-5,4.83-7.2,4.83" />
|
|
<path
|
|
d="M48.29,38.58c-4.16-1.83-8.57-3.08-10.34-6.4a12,12,0,0,1-6,3.73,12,12,0,0,1-5.95-3.73c-1.77,3.32-6.18,4.57-10.34,6.4-1.7.71-3.11,9.88-1.13,9.88A33.06,33.06,0,0,0,31.23,53h1.54a33.06,33.06,0,0,0,16.65-4.53C51.4,48.46,50,39.29,48.29,38.58Z" />
|
|
<path
|
|
d="M14.82,36.57c.76-.33,1.54-.65,2.3-1,2.49-1,4.85-2,6.22-3.44C21.07,31.23,19,30.25,18,28.41a8.83,8.83,0,0,1-4.41,2.76,8.83,8.83,0,0,1-4.4-2.76c-1.31,2.46-4.58,3.38-7.66,4.74-1.26.52-2.3,7.31-.84,7.31a24.55,24.55,0,0,0,10.86,3.31C11.89,40.81,12.86,37.39,14.82,36.57Z" />
|
|
<path
|
|
d="M62.45,33.15c-3.08-1.36-6.35-2.28-7.66-4.74a8.83,8.83,0,0,1-4.4,2.76A8.83,8.83,0,0,1,46,28.41c-1,1.84-3,2.82-5.32,3.76,1.37,1.43,3.73,2.41,6.22,3.44.76.31,1.54.63,2.26,1,2,.83,3,4.25,3.29,7.21a24.55,24.55,0,0,0,10.86-3.31C64.75,40.46,63.71,33.67,62.45,33.15Z" />
|
|
</svg>
|
|
<sm-input id="group_name_field" placeholder="Group name" animate required></sm-input>
|
|
<sm-textarea id="group_description_field" placeholder="Group description"></sm-textarea>
|
|
</div>
|
|
<sm-button id="create_group_button" variant="primary" class="fab round" type="submit" disable>
|
|
Create
|
|
</sm-button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
<div id="chat" class="grid hide-on-mobile hide-completely">
|
|
<div id="chat_left">
|
|
<header id="chat_header" class="grid align-center">
|
|
<svg class="icon hide-on-desktop back-button" onclick="goto('chats')"
|
|
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
|
<title>Go to chat page</title>
|
|
<line x1="1" y1="32" x2="64" y2="32" />
|
|
<polyline points="29.64 60.97 0.65 32 29.64 3.03" />
|
|
</svg>
|
|
<div class="flex align-center interact" onclick="showChatDetails({show: true})">
|
|
<div id="receiver_initial" class="initial flex align-center"></div>
|
|
<h4 id="receiver_name"></h4>
|
|
</div>
|
|
<sm-button id="video_call_button" class="hide-completely" onclick="createOffer()">Call
|
|
</sm-button>
|
|
</header>
|
|
<section id="chat_middle" class="flex direction-column">
|
|
<div id="chat_first_child"></div>
|
|
<h5 id="warn_no_encryption">Messages are not encrypted until receiver replies</h5>
|
|
<section id="messages_container" class="flex direction-column">
|
|
</section>
|
|
<div id="scroll_to_bottom" onclick="scrollToBottom()">
|
|
<svg class="icon" viewBox="0 0 64 64">
|
|
<title></title>
|
|
<polyline points="63.65 15.99 32 47.66 0.35 15.99" />
|
|
</svg>
|
|
</div>
|
|
</section>
|
|
<footer id="chat_footer" class="grid">
|
|
<emoji-picker id="emoji_picker" class="hide-completely"></emoji-picker>
|
|
<div class="flex">
|
|
<svg id="emoji_toggle" onclick="toggleEmoji('toggle')" xmlns="http://www.w3.org/2000/svg"
|
|
viewBox="0 0 64 64">
|
|
<path
|
|
d="M32,0A32,32,0,1,0,64,32,32,32,0,0,0,32,0ZM43.84,17.51a4.92,4.92,0,1,1-4.92,4.92A4.92,4.92,0,0,1,43.84,17.51Zm-23.62-.06a5,5,0,1,1-5,5A5,5,0,0,1,20.22,17.45ZM32,54.42A19.68,19.68,0,0,1,12.31,34.73H51.69A19.68,19.68,0,0,1,32,54.42Z" />
|
|
</svg>
|
|
<sm-textarea id="type_message" placeholder="Type a message" class="rest"></sm-textarea>
|
|
<svg xmlns="http://www.w3.org/2000/svg" id="send_message_button" class="icon"
|
|
viewBox="0 0 64 64">
|
|
<path
|
|
d="M63.34,31,3.07,4.71A2.19,2.19,0,0,0,.18,7.58L8.94,28.29,42.18,32,8.94,35.71.18,56.42a2.19,2.19,0,0,0,2.89,2.87L63.34,33A1.09,1.09,0,0,0,63.34,31Z" />
|
|
</svg>
|
|
</div>
|
|
</footer>
|
|
</div>
|
|
<div id="chat_details_panel" class="hide-completely">
|
|
<header class="flex align-center">
|
|
<svg class="icon" onclick="showChatDetails({show: false})" viewBox="0 0 64 64">
|
|
<title>close</title>
|
|
<line x1="64" y1="0" x2="0" y2="64" />
|
|
<line x1="64" y1="64" x2="0" y2="0" />
|
|
</svg>
|
|
</header>
|
|
<div id="chat_profile" class="card">
|
|
<div id="chat_dp" class="initial flex align-center"></div>
|
|
<text-field id="chat_name"></text-field>
|
|
<p id="last_interaction_time"></p>
|
|
</div>
|
|
<div id="chat_flo_id_card" class="card">
|
|
<h4 class="h4" id="chat_type"></h4>
|
|
<div class="copy-row grid">
|
|
<h4 id="chat_flo_id" class="copy"></h4>
|
|
<svg class="icon" onclick="copyToClipboard(this, 'Copied FLO ID')" viewBox="0 0 64 64">
|
|
<title>Copy</title>
|
|
<rect x="16" y="16" width="48" height="48" rx="6" />
|
|
<path d="M.5,47.52V6.5a6,6,0,0,1,6-6h41" />
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
<div id="group_description_card" class="card hide-completely">
|
|
<h4 class="h4">Group description</h4>
|
|
<text-field id="group_description"></text-field>
|
|
</div>
|
|
<div id="group_members_card" class="card hide-completely">
|
|
<div class="flex align-center">
|
|
<h4 class="h4">Group members</h4>
|
|
<sm-button id="edit_group_button"
|
|
class="admin-option hide-completely round small justify-right"
|
|
onclick="editGroupMembers()">Edit</sm-button>
|
|
</div>
|
|
<p id="remove_members_tip" class="tip hide-completely">Select members to remove or add new
|
|
members</p>
|
|
<div id="member_options" class="flex hide-completely">
|
|
<sm-button id="remove_members_button" class="danger hide-completely"
|
|
onclick="removeGroupMembers()">Remove selected</sm-button>
|
|
<sm-button id="init_add_members_button" onclick="showPopup('contacts_popup')">Add member
|
|
</sm-button>
|
|
</div>
|
|
<div id="group_members_list"></div>
|
|
</div>
|
|
<div class="card">
|
|
<sm-button class="danger" onclick="clearChat()">Clear chat</sm-button>
|
|
<sm-button id="delete_chat_button" class="danger" onclick="deleteChat()">Delete chat</sm-button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
<section class="sub-page grid hide-completely" id="mail_page">
|
|
<div id="mails" class="grid">
|
|
<header class="grid header">
|
|
<div class="flex align-center">
|
|
<svg class="hamburger-menu-button icon hide-on-desktop" onclick="toggleDrawer()"
|
|
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
|
<line y1="10.66" x2="64" y2="10.66" />
|
|
<line y1="32" x2="64" y2="32" />
|
|
<line y1="53.34" x2="64" y2="53.34" />
|
|
</svg>
|
|
<h4>Mail</h4>
|
|
<sm-select id="mail_type_selector">
|
|
<sm-option value="inbox">Inbox</sm-option>
|
|
<sm-option value="sent">Sent</sm-option>
|
|
</sm-select>
|
|
</div>
|
|
</header>
|
|
<sm-button variant="primary" id="new_mail_button" class="fab round">
|
|
<svg class="icon" viewBox="0 0 64 64">
|
|
<path
|
|
d="M46.73,14.81l7,7,7.65-7.6A7.15,7.15,0,0,0,61.39,4L60.11,2.77a7.23,7.23,0,0,0-10.19,0L3.87,48.57a5,5,0,0,0-1.39,2.6L.53,61.27a1.74,1.74,0,0,0,2,2l10.15-1.94A5.06,5.06,0,0,0,15.34,60L49.6,25.9" />
|
|
</svg>
|
|
New Mail
|
|
</sm-button>
|
|
<ul id="inbox_mail_container" class="mail-container flex observe-empty-state"></ul>
|
|
<div class="empty-state flex direction-column align-center">
|
|
<svg class="icon new-conversation" viewBox="0 0 512 512">
|
|
<path
|
|
d="M227,26.16,20.5,177.56a49,49,0,0,0-20,39.53V446.35a49,49,0,0,0,49,49h413a49,49,0,0,0,49-49h0V217.09a49,49,0,0,0-20-39.53L285,26.16A49,49,0,0,0,227,26.16Z" />
|
|
<path d="M71,250.29l166.31,86.88a39,39,0,0,0,36.22,0L441,249.66" />
|
|
</svg>
|
|
<h4>All your received mails will appear here.</h4>
|
|
<p class="light-text">Tap/click on 'New Mail' button below to compose new mail.</p>
|
|
</div>
|
|
<ul id="sent_mail_container" class="mail-container flex observe-empty-state hide-completely"></ul>
|
|
<div class="empty-state flex direction-column align-center">
|
|
<svg class="icon new-conversation" viewBox="0 0 512 512">
|
|
<path
|
|
d="M227,26.16,20.5,177.56a49,49,0,0,0-20,39.53V446.35a49,49,0,0,0,49,49h413a49,49,0,0,0,49-49h0V217.09a49,49,0,0,0-20-39.53L285,26.16A49,49,0,0,0,227,26.16Z" />
|
|
<path d="M71,250.29l166.31,86.88a39,39,0,0,0,36.22,0L441,249.66" />
|
|
</svg>
|
|
<h4>All your sent mails will appear here.</h4>
|
|
<p class="light-text">Tap/click on 'New Mail' button below to compose new mail.</p>
|
|
</div>
|
|
</div>
|
|
<div id="mail" class="flex hide-on-mobile hide-completely">
|
|
<div id="mail_container"></div>
|
|
<div class="flex">
|
|
<sm-button id="prev_mail">View Previous Mail</sm-button>
|
|
<sm-button id="show_reply_popup">reply</sm-button>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
<section id="settings_page" class="sub-page hide-completely">
|
|
<aside id="settings_sidebar">
|
|
<header class="grid header">
|
|
<div class="flex align-center">
|
|
<svg class="hamburger-menu-button icon hide-on-desktop" onclick="toggleDrawer()"
|
|
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
|
<line y1="10.66" x2="64" y2="10.66" />
|
|
<line y1="32" x2="64" y2="32" />
|
|
<line y1="53.34" x2="64" y2="53.34" />
|
|
</svg>
|
|
<h4>Settings</h4>
|
|
</div>
|
|
</header>
|
|
<div class="sidebar-item interact active" data-target="profile_panel">Account</div>
|
|
<div class="sidebar-item interact" data-target="chat_panel">chat</div>
|
|
<div class="sidebar-item interact" data-target="personalise_panel">personalise</div>
|
|
<div class="sidebar-item interact" data-target="backup_panel">backup & restore</div>
|
|
<div class="sidebar-item interact" data-target="about_panel">About</div>
|
|
</aside>
|
|
<div id="settings_panel" class=" hide-on-mobile">
|
|
<header id="settings_header" class="flex align-center hide-on-desktop">
|
|
<svg id="back_settings" onclick='hidePanel()' xmlns="http://www.w3.org/2000/svg" class="icon back"
|
|
viewBox="0 0 64 64">
|
|
<title>Go back</title>
|
|
<line x1="1" y1="32" x2="64" y2="32" />
|
|
<polyline points="29.64 60.97 0.65 32 29.64 3.03" />
|
|
</svg>
|
|
<h4 id="settings_title"></h4>
|
|
</header>
|
|
<div id="profile_panel" class="panel">
|
|
<section>
|
|
<h4>My FLO ID</h4>
|
|
<div class="copy-row">
|
|
<p id="greet_tag" class="copy"></p>
|
|
<svg class="icon" onclick="copyToClipboard(this, 'FLO ID Copied')" viewBox="0 0 64 64">
|
|
<title>Copy</title>
|
|
<rect x="16" y="16" width="48" height="48" rx="6" />
|
|
<path d="M.5,47.52V6.5a6,6,0,0,1,6-6h41" />
|
|
</svg>
|
|
</div>
|
|
</section>
|
|
<section>
|
|
<h4>Clear data</h4>
|
|
<p><strong></strong>This can't be undone.</strong> Make sure you have stored the PRIVATE KEY and
|
|
backed up the data.</p>
|
|
<sm-button id="clear_data">Clear Data</sm-button>
|
|
</section>
|
|
<section>
|
|
<h4>Sign out</h4>
|
|
<p>*Remember to store your <strong>PRIVATE KEY </strong> before signing out.</p>
|
|
<sm-button id="sign_out" onclick="signOut()">Sign Out</sm-button>
|
|
</section>
|
|
</div>
|
|
<div id="chat_panel" class="panel hide-completely">
|
|
<sm-switch id="is_enter_send_toggle">
|
|
<div slot="left" class="flex direction-column">
|
|
<h4>Send by Enter</h4>
|
|
<p>If this toggle is ON then pressing 'Enter' key will send messages</p>
|
|
</div>
|
|
</sm-switch>
|
|
<sm-switch id="haptic_feedback_switcher">
|
|
<div slot="left" class="flex direction-column">
|
|
<h4>Haptic feedback</h4>
|
|
<p>Turn haptic feedback (vibrations) on/off.</p>
|
|
</div>
|
|
</sm-switch>
|
|
</div>
|
|
<div id="personalise_panel" class="panel hide-completely">
|
|
<section>
|
|
<h4>Chat preview</h4>
|
|
<div id="chat_preview">
|
|
<div class="message sent">
|
|
<p class="message-body">Hey there!</p>
|
|
<time class="time">12:00PM</time>
|
|
</div>
|
|
<div class="message received">
|
|
<p class="message-body">How are you?</p>
|
|
<time class="time">12:00PM</time>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
<sm-switch id="theme_switcher">
|
|
<div slot="left" class="flex direction-column">
|
|
<h4>Dark mode</h4>
|
|
<p>Toggle dark mode on/off.<br><strong>*Setting applies to this browser only</strong></p>
|
|
</div>
|
|
</sm-switch>
|
|
<section>
|
|
<h4>Set chat and mail background image</h4>
|
|
<div id="bg_preview_container" class="flex">
|
|
<div id="selected_bg_preview" class="bg-preview hide-completely" onclick="setBgImage()">
|
|
<img src="" alt="background perview" class="bg-preview__image">
|
|
</div>
|
|
<div id="default_bg_preview" class="bg-preview bg-preview--selected"
|
|
onclick="setDefaultBg()">Default</div>
|
|
</div>
|
|
<label class="select-file">
|
|
<sm-button id="select_bg_button">Select background</sm-button>
|
|
<input type="file" id="select_bg_image" accept="image/*" />
|
|
</label>
|
|
</section>
|
|
<section>
|
|
<h4>Accent color</h4>
|
|
<color-grid id="accent_color_selector"></color-grid>
|
|
</section>
|
|
</div>
|
|
<div id="backup_panel" class="panel hide-completely">
|
|
<section>
|
|
<h4>Backup data</h4>
|
|
<p>Create a backup of contacts, conversations and mails. Which can later be used to restore
|
|
these in case of data is cleared. </p>
|
|
<sm-button id="backup_data">Backup Data</sm-button>
|
|
<span id="backup_info"></span>
|
|
</section>
|
|
<section>
|
|
<h4>Restore backup</h4>
|
|
<p>Select backup file with extension '.json'. Which was downloaded when backup was performed.
|
|
</p>
|
|
<label class="select-file">
|
|
<sm-button>Select File</sm-button>
|
|
<input type="file" id="restore_data" accept=".json" />
|
|
</label>
|
|
</section>
|
|
</div>
|
|
<div id="about_panel" class="panel hide-completely">
|
|
<section>
|
|
<p>Created by RanchiMall, a Blockchain incorporated entity</p>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
<div id="incoming_call_page" class="page">
|
|
<div id="caller_dp"></div>
|
|
<sm-button id="pick_up_call" onclick="createAnswer()">Answer</sm-button>
|
|
</div>
|
|
<div id="video_call_page" class="page">
|
|
<video id="my_video" autoplay></video>
|
|
<video id="their_video" autoplay></video>
|
|
<div id="call_controls" class="flex">
|
|
<sm-button id="mute_audio" class="circular-button" onclick="muteAudio()" title="Toggle audio">
|
|
<svg class="icon filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
|
<rect x="20.01" width="23.97" height="44.59" rx="11.99" />
|
|
<path
|
|
d="M50.47,39.88A4,4,0,0,0,45,41.42a14.88,14.88,0,0,1-25.92,0,4,4,0,1,0-7,4A23,23,0,0,0,28,56.72V60a4.05,4.05,0,1,0,8.1,0V56.72A23,23,0,0,0,52,45.39,4,4,0,0,0,50.47,39.88Z" />
|
|
</svg>
|
|
</sm-button>
|
|
<sm-button id="end_call" onclick="endCall()">End call</sm-button>
|
|
<sm-button id="pause_video" class="circular-button" onclick="pauseVideo()" title="Toggle video">
|
|
<svg class="icon filled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
|
<path
|
|
d="M56.37,13.81l-8.09,4.68v-2a7.06,7.06,0,0,0-7.06-7H7.06A7.06,7.06,0,0,0,0,16.46V47.54a7.06,7.06,0,0,0,7.06,7.05H41.22a7.06,7.06,0,0,0,7.06-7.05v-2l8.09,4.68A5.09,5.09,0,0,0,64,45.78V18.22A5.09,5.09,0,0,0,56.37,13.81Z" />
|
|
</svg>
|
|
</sm-button>
|
|
</div>
|
|
</div>
|
|
<script src="scripts/components.js"></script>
|
|
<script src="scripts/script.js"></script>
|
|
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
|
|
<script type="module" src="https://cdn.jsdelivr.net/npm/emoji-picker-element@^1/index.js"></script>
|
|
<script id="WEBRTC">
|
|
|
|
const myVideo = document.getElementById('my_video')
|
|
const theirVideo = document.getElementById('their_video')
|
|
|
|
let dataChannel
|
|
let peerConnection
|
|
let localStream
|
|
let remoteSdp = ''
|
|
const server = { urls: "stun:stun.l.google.com:19302" };
|
|
|
|
async function initPeerConnection() {
|
|
peerConnection = new RTCPeerConnection({ iceServers: [server] });
|
|
peerConnection.ontrack = (e) => theirVideo.srcObject = e.streams[0];
|
|
peerConnection.ondatachannel = e => initDataChannel(dataChannel = e.channel);
|
|
peerConnection.oniceconnectionstatechange = e => console.log(peerConnection.iceConnectionState);
|
|
|
|
try {
|
|
localStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true })
|
|
localStream.getTracks().forEach(track => peerConnection.addTrack(track, localStream));
|
|
}
|
|
catch (err) {
|
|
console.log("An error occurred: " + err);
|
|
}
|
|
}
|
|
|
|
function initDataChannel() {
|
|
dataChannel.onopen = () => console.log("Chat!");
|
|
dataChannel.onmessage = e => console.log(e.data);
|
|
}
|
|
|
|
async function createOffer() {
|
|
await initPeerConnection()
|
|
showPage('video_call_page')
|
|
initDataChannel(dataChannel = peerConnection.createDataChannel("chat"));
|
|
try {
|
|
myVideo.srcObject = localStream
|
|
const offer = await peerConnection.createOffer()
|
|
peerConnection.setLocalDescription(offer)
|
|
}
|
|
catch (err) {
|
|
console.log(err)
|
|
}
|
|
peerConnection.onicecandidate = e => {
|
|
if (e.candidate) return;
|
|
// Send invitation
|
|
const sdp = peerConnection.localDescription.sdp
|
|
messenger.sendMessage(sdp, activeChat.floID).then(data => {
|
|
console.log('offer sent')
|
|
}).catch(error => notify(error, "error"));
|
|
}
|
|
}
|
|
|
|
async function createAnswer() {
|
|
await initPeerConnection()
|
|
if (peerConnection.signalingState !== "stable") return;
|
|
button.disabled = offer.disabled = true;
|
|
let desc = new RTCSessionDescription({ type: "offer", sdp: remoteSdp });
|
|
try {
|
|
await peerConnection.setRemoteDescription(desc)
|
|
const answer = await peerConnection.createAnswer()
|
|
peerConnection.setLocalDescription(answer)
|
|
}
|
|
catch (err) {
|
|
console.log(err)
|
|
}
|
|
peerConnection.onicecandidate = e => {
|
|
if (e.candidate) return;
|
|
const sdp = peerConnection.localDescription.sdp
|
|
messenger.sendMessage(sdp, activeChat.floID, 'answer').then(data => {
|
|
console.log('answer sent')
|
|
}).catch(error => notify(error, "error"));
|
|
};
|
|
}
|
|
|
|
function startVideoCall(answer) {
|
|
if (peerConnection.signalingState != "have-local-offer") return;
|
|
let desc = new RTCSessionDescription({ type: "answer", sdp: answer });
|
|
peerConnection.setRemoteDescription(desc).catch(log);
|
|
}
|
|
|
|
function endCall() {
|
|
remoteSdp = ''
|
|
peerConnection.close()
|
|
}
|
|
/*
|
|
chat.onkeypress = e => {
|
|
if (!enterPressed(e)) return;
|
|
dataChannel.send(chat.value);
|
|
log(chat.value);
|
|
chat.value = "";
|
|
}; */
|
|
</script>
|
|
<script id="standard_UI_functions">
|
|
|
|
const domRefs = {}
|
|
|
|
function getRef(elementId) {
|
|
if (!domRefs.elementId)
|
|
domRefs[elementId] = document.getElementById(elementId)
|
|
return domRefs[elementId]
|
|
}
|
|
|
|
|
|
//Checks for internet connection status
|
|
if (!navigator.onLine)
|
|
notify('There seems to be a problem connecting to the internet, Please check you internet connection.', 'error', '', true)
|
|
window.addEventListener('offline', () => {
|
|
notify('There seems to be a problem connecting to the internet, Please check you internet connection.', 'error', true, true)
|
|
})
|
|
window.addEventListener('online', () => {
|
|
getRef('notification_drawer').clearAll()
|
|
notify('We are back online.', 'success')
|
|
})
|
|
|
|
const themeSwitcher = getRef('theme_switcher'),
|
|
hapticFeedbackSwitcher = getRef('haptic_feedback_switcher')
|
|
|
|
if (localStorage.theme === "dark") {
|
|
nightlight()
|
|
themeSwitcher.checked = true;
|
|
} else {
|
|
daylight()
|
|
themeSwitcher.checked = false;
|
|
}
|
|
|
|
function daylight() {
|
|
document.body.setAttribute("data-theme", "light");
|
|
}
|
|
|
|
function nightlight() {
|
|
document.body.setAttribute("data-theme", "dark");
|
|
}
|
|
themeSwitcher.addEventListener('change', function (e) {
|
|
if (this.checked) {
|
|
nightlight();
|
|
localStorage.setItem("theme", "dark");
|
|
}
|
|
else {
|
|
daylight();
|
|
localStorage.setItem("theme", "light");
|
|
}
|
|
})
|
|
|
|
let isHapticOn
|
|
if (hapticFeedbackSwitcher) {
|
|
if (localStorage.getItem('haptic') === null)
|
|
localStorage.setItem("haptic", "on");
|
|
|
|
if (localStorage.haptic === "on") {
|
|
isHapticOn = true
|
|
hapticFeedbackSwitcher.checked = true;
|
|
} else {
|
|
isHapticOn = false
|
|
hapticFeedbackSwitcher.checked = false;
|
|
}
|
|
hapticFeedbackSwitcher.addEventListener('change', function (e) {
|
|
if (this.checked) {
|
|
isHapticOn = true
|
|
localStorage.setItem("haptic", "on");
|
|
}
|
|
else {
|
|
isHapticOn = false
|
|
localStorage.setItem("haptic", "off");
|
|
}
|
|
})
|
|
}
|
|
|
|
// function required for popups or modals to appear
|
|
class Stack {
|
|
constructor() {
|
|
this.items = [];
|
|
}
|
|
push(element) {
|
|
this.items.push(element);
|
|
}
|
|
pop() {
|
|
if (this.items.length == 0)
|
|
return "Underflow";
|
|
return this.items.pop();
|
|
}
|
|
peek() {
|
|
return this.items[this.items.length - 1];
|
|
}
|
|
}
|
|
let popupStack = new Stack(),
|
|
zIndex = 10;
|
|
|
|
async function showPopup(popup, pinned) {
|
|
zIndex++
|
|
getRef(popup).setAttribute('style', `z-index: ${zIndex}`)
|
|
popupStack = getRef(popup).show(pinned, popupStack)
|
|
return getRef(popup);
|
|
}
|
|
|
|
// hides the popup or modal
|
|
function hidePopup() {
|
|
if (popupStack.peek() === undefined)
|
|
return;
|
|
popupStack.peek().popup.hide()
|
|
}
|
|
|
|
document.addEventListener('popupopened', async e => {
|
|
let thisPopup = e.detail.popup,
|
|
firstInput = thisPopup.querySelector('sm-input')
|
|
|
|
//pushes popup as seprate entry in history
|
|
history.pushState({ type: 'popup' }, null, null)
|
|
|
|
if (window.innerWidth > 720 && firstInput)
|
|
firstInput.focusIn()
|
|
|
|
switch (e.detail.popup.id) {
|
|
case 'contact_details_popup':
|
|
if (floGlobals.contacts.hasOwnProperty(clickedContact.floID)) {
|
|
getRef('add_as_contact_option').classList.add('hide-completely')
|
|
}
|
|
else {
|
|
getRef('add_as_contact_option').classList.remove('hide-completely')
|
|
}
|
|
|
|
const markUnread = floGlobals.marked[clickedContact.floID]?.includes('unread')
|
|
if (markUnread) {
|
|
getRef('mark_read_option').classList.remove('hide-completely')
|
|
getRef('mark_unread_option').classList.add('hide-completely')
|
|
}
|
|
else {
|
|
getRef('mark_read_option').classList.add('hide-completely')
|
|
getRef('mark_unread_option').classList.remove('hide-completely')
|
|
}
|
|
if (clickedContact.chatCard.closest('#chat_container')) {
|
|
getRef('contact_options').classList.remove('hide-completely')
|
|
}
|
|
else {
|
|
getRef('contact_options').classList.add('hide-completely')
|
|
}
|
|
if (clickedContact.isGroup) {
|
|
getRef("contact_initial").innerHTML = `
|
|
<svg class="icon group-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><path d="M13.61,28.09c-1.63,0-4.72-2.35-5.33-3.58a21.65,21.65,0,0,1-1.35-7.32s-.26-6.07,6.68-6.07a6.38,6.38,0,0,1,6.69,6.07A21.65,21.65,0,0,1,19,24.51c-.62,1.23-3.7,3.58-5.34,3.58"/><path d="M50.39,28.09c-1.64,0-4.72-2.35-5.34-3.58a21.9,21.9,0,0,1-1.35-7.32s-.26-6.07,6.69-6.07a6.37,6.37,0,0,1,6.68,6.07,21.65,21.65,0,0,1-1.35,7.32c-.61,1.23-3.7,3.58-5.33,3.58"/><path d="M32,31.74c-2.21,0-6.37-3.17-7.2-4.83A29.3,29.3,0,0,1,23,17s-.35-8.21,9-8.21c8.68,0,9,8.21,9,8.21a29.3,29.3,0,0,1-1.83,9.88c-.82,1.66-5,4.83-7.2,4.83"/><path d="M48.29,38.58c-4.16-1.83-8.57-3.08-10.34-6.4a12,12,0,0,1-6,3.73,12,12,0,0,1-5.95-3.73c-1.77,3.32-6.18,4.57-10.34,6.4-1.7.71-3.11,9.88-1.13,9.88A33.06,33.06,0,0,0,31.23,53h1.54a33.06,33.06,0,0,0,16.65-4.53C51.4,48.46,50,39.29,48.29,38.58Z"/><path d="M14.82,36.57c.76-.33,1.54-.65,2.3-1,2.49-1,4.85-2,6.22-3.44C21.07,31.23,19,30.25,18,28.41a8.83,8.83,0,0,1-4.41,2.76,8.83,8.83,0,0,1-4.4-2.76c-1.31,2.46-4.58,3.38-7.66,4.74-1.26.52-2.3,7.31-.84,7.31a24.55,24.55,0,0,0,10.86,3.31C11.89,40.81,12.86,37.39,14.82,36.57Z"/><path d="M62.45,33.15c-3.08-1.36-6.35-2.28-7.66-4.74a8.83,8.83,0,0,1-4.4,2.76A8.83,8.83,0,0,1,46,28.41c-1,1.84-3,2.82-5.32,3.76,1.37,1.43,3.73,2.41,6.22,3.44.76.31,1.54.63,2.26,1,2,.83,3,4.25,3.29,7.21a24.55,24.55,0,0,0,10.86-3.31C64.75,40.46,63.71,33.67,62.45,33.15Z"/></svg>
|
|
`
|
|
if (floGlobals.groups[clickedContact['floID']].admin === myFloID)
|
|
getRef('contact_name').disabled = false
|
|
else
|
|
getRef('contact_name').disabled = true
|
|
getRef('delete_chat_option').classList.add('hide-completely')
|
|
}
|
|
else {
|
|
getRef('contact_name').disabled = false
|
|
getRef('contact_initial').textContent = clickedContact['name'].charAt(0)
|
|
getRef('delete_chat_option').classList.remove('hide-completely')
|
|
}
|
|
getRef('contact_initial').setAttribute('style', `background: ${clickedContact['chatCard'].getAttribute('background-color')}`)
|
|
getRef('contact_name').value = clickedContact['name']
|
|
getRef('contact_flo_id').textContent = clickedContact['floID']
|
|
break;
|
|
case 'contacts_popup':
|
|
const contacts = []
|
|
for (contact in floGlobals.contacts) {
|
|
if (!floGlobals.groups[activeChat.floID].members.includes(contact) && contact in floGlobals.pubKeys) {
|
|
contacts.push(contact)
|
|
}
|
|
}
|
|
contacts.forEach(member => {
|
|
frag.append(render.contactCard(member, { type: 'contact', contactOnly: true }))
|
|
})
|
|
getRef('popup_contacts_container').append(frag)
|
|
getRef('popup_contacts_container').querySelectorAll('.contact').forEach(cont => {
|
|
cont.classList.add('selectable')
|
|
})
|
|
isRemovingMember = false
|
|
break
|
|
}
|
|
})
|
|
|
|
document.addEventListener('popupclosed', e => {
|
|
popupStack = e.detail.popupStack
|
|
let thisPopup = e.detail.popup
|
|
switch (e.detail.popup.id) {
|
|
case 'contact_details_popup':
|
|
clickedContact['name'] = getRef('contact_name').value.trim()
|
|
getRef('contact_name').revert()
|
|
break;
|
|
case 'contacts_popup':
|
|
getRef('popup_contacts_container').innerHTML = ''
|
|
isRemovingMember = true
|
|
membersToAdd.clear()
|
|
break;
|
|
}
|
|
})
|
|
|
|
window.addEventListener('popstate', e => {
|
|
if (!e.state) return
|
|
console.log(e.state)
|
|
switch (e.state.type) {
|
|
case 'popup':
|
|
hidePopup()
|
|
break;
|
|
case 'page':
|
|
showPage(e.state.id)
|
|
break;
|
|
case 'subpage':
|
|
showPage(e.state.id, true)
|
|
break;
|
|
}
|
|
})
|
|
|
|
function setAttributes(el, attrs) {
|
|
for (key in attrs) {
|
|
el.setAttribute(key, attrs[key]);
|
|
}
|
|
}
|
|
|
|
function randomHsl(saturation = 80, lightness = 80) {
|
|
let hue = Math.random() * 360
|
|
let color = {
|
|
primary: `hsla( ${hue}, ${saturation}%, ${lightness}%, 1)`,
|
|
light: `hsla( ${hue}, ${saturation}%, 90%, 0.6)`
|
|
}
|
|
return color;
|
|
}
|
|
|
|
const selectedColors = ['#FF1744', '#F50057', '#8E24AA', '#5E35B1', '#3F51B5', '#3D5AFE', '#00B0FF', '#00BCD4', '#16c79a', '#66BB6A', '#8BC34A', '#11698e', '#FF6F00', '#FF9100', '#FF3D00']
|
|
function randomColor() {
|
|
return selectedColors[Math.floor(Math.random() * selectedColors.length)]
|
|
}
|
|
|
|
const contactsInfo = {}
|
|
function contactColor(floID) {
|
|
if (!contactsInfo[floID]) {
|
|
contactsInfo[floID] = randomColor()
|
|
}
|
|
return contactsInfo[floID]
|
|
}
|
|
|
|
function clearElements(array = []) {
|
|
array.forEach(item => {
|
|
getRef(item).innerHTML = ``
|
|
})
|
|
}
|
|
|
|
//Function for displaying toast notifications. pass in error for mode param if you want to show an error.
|
|
function notify(message, mode, pinned, sound) {
|
|
|
|
if (mode === 'error')
|
|
console.error(message)
|
|
else
|
|
console.log(message)
|
|
getRef('notification_drawer').push(message, mode, pinned)
|
|
if (navigator.onLine && sound) {
|
|
getRef('notification_sound').currentTime = 0;
|
|
getRef('notification_sound').play();
|
|
}
|
|
}
|
|
|
|
// displays a popup for asking permission. Use this instead of JS confirm
|
|
let confirmation = (title, message, cancelText = 'Cancel', confirmText = 'OK') => {
|
|
return new Promise(resolve => {
|
|
showPopup('confirmation_popup')
|
|
getRef('confirm_title').textContent = title;
|
|
getRef('confirm_message').textContent = message;
|
|
let cancelButton = getRef('confirmation_popup').children[2].children[0],
|
|
submitButton = getRef('confirmation_popup').children[2].children[1]
|
|
submitButton.textContent = confirmText
|
|
cancelButton.textContent = cancelText
|
|
submitButton.onclick = () => {
|
|
hidePopup()
|
|
resolve(true);
|
|
}
|
|
cancelButton.onclick = () => {
|
|
hidePopup()
|
|
resolve(false);
|
|
}
|
|
})
|
|
}
|
|
|
|
// displays a popup for asking user input. Use this instead of JS prompt
|
|
async function getPromptInput(title, message, showText = true, trueBtn = "Ok", falseBtn = "Cancel") {
|
|
showPopup('prompt_popup')
|
|
getRef('prompt_title').textContent = title;
|
|
let input = getRef('prompt_input');
|
|
input.setAttribute("placeholder", message)
|
|
let buttons = getRef('prompt_popup').querySelectorAll("sm-button");
|
|
if (showText)
|
|
input.setAttribute("type", "text")
|
|
else
|
|
input.setAttribute("type", "password")
|
|
input.focusIn()
|
|
buttons[0].textContent = falseBtn;
|
|
buttons[1].textContent = trueBtn;
|
|
return new Promise((resolve, reject) => {
|
|
buttons[0].onclick = () => {
|
|
hidePopup()
|
|
return;
|
|
}
|
|
buttons[1].onclick = () => {
|
|
let value = input.value;
|
|
hidePopup()
|
|
resolve(value)
|
|
}
|
|
})
|
|
}
|
|
|
|
const currentYear = new Date().getFullYear()
|
|
function getFormatedTime(time, relative) {
|
|
try {
|
|
if (String(time).indexOf('_'))
|
|
time = String(time).split('_')[0]
|
|
const intTime = parseInt(time)
|
|
if (String(intTime).length < 13)
|
|
time *= 1000
|
|
let timeFrag = new Date(intTime).toString().split(' '),
|
|
day = timeFrag[0],
|
|
month = timeFrag[1],
|
|
date = timeFrag[2],
|
|
year = timeFrag[3],
|
|
minutes = new Date(intTime).getMinutes(),
|
|
hours = new Date(intTime).getHours(),
|
|
currentTime = new Date().toString().split(' ')
|
|
|
|
minutes = minutes < 10 ? `0${minutes}` : minutes
|
|
let finalHours = ``;
|
|
if (hours > 12)
|
|
finalHours = `${hours - 12}:${minutes}`
|
|
else if (hours === 0)
|
|
finalHours = `12:${minutes}`
|
|
else
|
|
finalHours = `${hours}:${minutes}`
|
|
|
|
finalHours = hours >= 12 ? `${finalHours} PM` : `${finalHours} AM`
|
|
if (relative) {
|
|
if (year == currentYear) {
|
|
if (currentTime[1] === month) {
|
|
const dateDiff = (parseInt(currentTime[2]) - parseInt(date))
|
|
if (dateDiff === 0)
|
|
return `${finalHours}`;
|
|
else if (dateDiff === 1)
|
|
return `Yesterday`;
|
|
else if (dateDiff > 1 && dateDiff < 8)
|
|
return currentTime[0];
|
|
else
|
|
return ` ${date} ${month}`;
|
|
}
|
|
else
|
|
return ` ${date} ${month}`;
|
|
}
|
|
else
|
|
return `${month} ${year}`;
|
|
}
|
|
else
|
|
return `${finalHours} ${month} ${date} ${year}`;
|
|
} catch (e) {
|
|
console.error(e);
|
|
return time;
|
|
}
|
|
}
|
|
|
|
function getContactName(floID) {
|
|
let name
|
|
if (floGlobals.contacts[floID])
|
|
name = floGlobals.contacts[floID]
|
|
else if (floGlobals.groups[floID])
|
|
name = floGlobals.groups[floID].name
|
|
else if (floID === myFloID)
|
|
name = 'You'
|
|
else
|
|
name = floID
|
|
return name
|
|
}
|
|
|
|
function areInputsValid(parent) {
|
|
let allInputs = parent.querySelectorAll("sm-input:not([disable]), pin-input:not([disable])"),
|
|
inputsFilled = [...allInputs].every(input => {
|
|
if (input.hasAttribute('floId')) {
|
|
if (floCrypto.validateAddr(input.value.trim())) return true
|
|
}
|
|
else if (input.hasAttribute('privateKey')) {
|
|
if (floCrypto.getPubKeyHex(input.value.trim())) return true
|
|
}
|
|
else
|
|
return input.isValid
|
|
})
|
|
let allRadios = parent.querySelectorAll("input[type='radio']")
|
|
if (allRadios.length)
|
|
return inputsFilled && parent.querySelector('[checked]')
|
|
else
|
|
return inputsFilled
|
|
}
|
|
|
|
function formValidation(formElement) {
|
|
let parent = formElement.closest('sm-popup') || formElement.closest('form'),
|
|
submitBtn
|
|
if (parent)
|
|
submitBtn = parent.querySelector("button[type='submit'], sm-button[variant='primary']");
|
|
if (!submitBtn) return;
|
|
if (areInputsValid(parent))
|
|
submitBtn.disabled = false;
|
|
else
|
|
submitBtn.disabled = true;
|
|
}
|
|
|
|
window.addEventListener('load', () => {
|
|
document.addEventListener('input', e => {
|
|
if (e.target.closest('sm-input')) {
|
|
let input = e.target.closest('sm-input')
|
|
formValidation(input)
|
|
if (input.value === '')
|
|
input.setValidity('')
|
|
let validityState = input.validity
|
|
if (input.hasAttribute('floId')) {
|
|
if (floCrypto.validateAddr(input.value.trim()) || input.value.trim() === '')
|
|
input.setValidity('')
|
|
else
|
|
input.setValidity('Invalid FLO address.')
|
|
}
|
|
else if (input.hasAttribute('privateKey')) {
|
|
if (floCrypto.getPubKeyHex(input.value.trim()) || input.value.trim() === '')
|
|
input.setValidity('')
|
|
else
|
|
input.setValidity('Invalid FLO private key.')
|
|
}
|
|
}
|
|
else if (e.target.closest('pin-input')) {
|
|
formValidation(e.target.closest('pin-input'))
|
|
}
|
|
})
|
|
document.addEventListener('keyup', (e) => {
|
|
if (e.target.closest('sm-input, pin-input')) {
|
|
if (e.key === 'Enter') {
|
|
let parent = e.target.closest('sm-popup') || e.target.closest('form')
|
|
parent.querySelector("button[type='submit'], sm-button[variant='primary'], sm-button[type='submit']")
|
|
.click();
|
|
}
|
|
}
|
|
if (e.code === 'Escape') {
|
|
hidePopup()
|
|
}
|
|
})
|
|
|
|
document.addEventListener('pointerdown', (e) => {
|
|
if (e.target.closest('sm-button, .interact')) {
|
|
createRipple(e, e.target.closest('sm-button, .interact'))
|
|
}
|
|
})
|
|
|
|
})
|
|
function createRipple(event, target) {
|
|
const circle = document.createElement("span");
|
|
const diameter = Math.max(target.clientWidth, target.clientHeight);
|
|
const radius = diameter / 2;
|
|
const targetDimensions = target.getBoundingClientRect()
|
|
circle.style.width = circle.style.height = `${diameter}px`;
|
|
circle.style.left = `${event.clientX - (targetDimensions.left + radius)}px`;
|
|
circle.style.top = `${event.clientY - (targetDimensions.top + radius)}px`;
|
|
circle.classList.add("ripple");
|
|
const rippleAnimation = circle.animate([
|
|
|
|
{
|
|
transform: 'scale(3)',
|
|
opacity: 0
|
|
}
|
|
],
|
|
{
|
|
duration: 1000,
|
|
fill: "forwards",
|
|
easing: 'ease-out'
|
|
})
|
|
target.append(circle);
|
|
rippleAnimation.onfinish = () => {
|
|
circle.remove()
|
|
}
|
|
}
|
|
|
|
function generateId() {
|
|
getRef('generate_flo_id').classList.add('hide-completely')
|
|
getRef('sign_in_with').classList.remove('hide-completely')
|
|
getRef('credentials_section').classList.remove('hide-completely')
|
|
let { floID, privKey } = floCrypto.generateNewID()
|
|
getRef('generated_id').textContent = floID
|
|
getRef('generated_key').textContent = privKey
|
|
getRef('private_key_input_field').value = privKey
|
|
}
|
|
|
|
const signIn = (type) => {
|
|
return new Promise((resolve, reject) => {
|
|
showPage('landing_page')
|
|
if (type === "PRIVATE_KEY") {
|
|
getRef('get_pin').setAttribute('disable', '')
|
|
getRef('get_pin').classList.add('hide-completely')
|
|
getRef('private_key_input_field').removeAttribute('disable')
|
|
getRef('private_key_input_field').classList.remove('hide-completely')
|
|
getRef("type_of_key").textContent = 'FLO private key'
|
|
getRef("remove_account").classList.add("hide-completely");
|
|
setTimeout(() => {
|
|
getRef('private_key_input_field').focusIn()
|
|
}, 100)
|
|
} else if (type === "PIN/Password") {
|
|
getRef('get_pin').removeAttribute('disable')
|
|
getRef('get_pin').classList.remove('hide-completely')
|
|
getRef('private_key_input_field').setAttribute('disable', '')
|
|
getRef('private_key_input_field').classList.add('hide-completely')
|
|
getRef("type_of_key").textContent = 'PIN'
|
|
getRef("remove_account").classList.remove("hide-completely");
|
|
showPage('sign_in_page')
|
|
isPinSet = true;
|
|
setTimeout(() => {
|
|
getRef('get_pin').focusIn()
|
|
}, 100);
|
|
}
|
|
getRef('sign_in_button').addEventListener('clicked', () => {
|
|
let key
|
|
if (type === "PRIVATE_KEY") {
|
|
key = getRef('private_key_input_field').value;
|
|
setTimeout(() => {
|
|
getRef('private_key_input_field').value = ''
|
|
}, 300);
|
|
}
|
|
else if (type === "PIN/Password") {
|
|
key = getRef('get_pin').value;
|
|
setTimeout(() => {
|
|
getRef('get_pin').clear()
|
|
}, 300);
|
|
}
|
|
showPage('loading_page')
|
|
resolve(key)
|
|
})
|
|
getRef('sign_in_with').addEventListener('clicked', () => {
|
|
// showFrame(2)
|
|
resolve(getRef('generated_key').textContent)
|
|
})
|
|
})
|
|
}
|
|
|
|
async function signOut() {
|
|
if (await confirmation('Sign Out', 'Are you sure you want to Sign out?', "Stay Signed In", "Sign Out")) {
|
|
await floDapps.clearCredentials()
|
|
setTimeout(() => {
|
|
onLoadStartUp()
|
|
getRef('notification_drawer').clearAll()
|
|
}, 1000);
|
|
}
|
|
}
|
|
async function loadPage() {
|
|
if (location.hash === '') {
|
|
history.pushState({ type: 'subpage', id: 'chat_page' }, null, '#chat_page')
|
|
showPage('chat_page', true)
|
|
}
|
|
else {
|
|
showPage(location.hash.split('#')[1], true)
|
|
}
|
|
}
|
|
function debounce(func, wait, immediate) {
|
|
let timeout;
|
|
return function () {
|
|
let context = this, args = arguments;
|
|
let later = function () {
|
|
timeout = null;
|
|
if (!immediate) func.apply(context, args);
|
|
};
|
|
let callNow = immediate && !timeout;
|
|
clearTimeout(timeout);
|
|
timeout = setTimeout(later, wait);
|
|
if (callNow) func.apply(context, args);
|
|
};
|
|
};
|
|
</script>
|
|
|
|
<script>
|
|
let activePage = {},
|
|
activeChatPage = getRef('contacts'),
|
|
activeMailPage = getRef('mails'),
|
|
activeChat = {},
|
|
activeMail,
|
|
frag = document.createDocumentFragment()
|
|
|
|
// render elements
|
|
const render = {
|
|
mailCard(floID, ref, subject, timestamp, content, markUnread) {
|
|
let card = getRef('mail_card_template').content.cloneNode(true),
|
|
cardContainer = card.firstElementChild
|
|
let mailSummery = content.split(' ')
|
|
mailSummery.splice(16)
|
|
mailSummery = mailSummery.join(' ')
|
|
cardContainer.setAttribute("name", ref);
|
|
let contact;
|
|
if (Array.isArray(floID)) {
|
|
for (let f of floID)
|
|
if (floGlobals.contacts[f]) {
|
|
contact = floGlobals.contacts[f]
|
|
break;
|
|
}
|
|
contact = contact || `${floID[0].substring(12)}...`;
|
|
if (floID.length > 1)
|
|
contact = `${contact} & ${floID.length - 1} others(s)`
|
|
floID = floID.join(', ')
|
|
} else
|
|
contact = floGlobals.contacts[floID] || floID
|
|
if (markUnread)
|
|
cardContainer.classList.add('unread')
|
|
|
|
let initial = cardContainer.querySelector('.initial')
|
|
let color = contactColor(floID)
|
|
initial.textContent = contact.charAt(0)
|
|
cardContainer.setAttribute("background-color", color)
|
|
initial.setAttribute(`style`, `background-color: ${color}`)
|
|
cardContainer.querySelector('.sender').textContent = contact
|
|
cardContainer.querySelector('.subject').textContent = subject
|
|
cardContainer.querySelector('.date').textContent = getFormatedTime(timestamp, true)
|
|
cardContainer.querySelector('.description').textContent = mailSummery
|
|
return card
|
|
},
|
|
|
|
mail(from, to, subject, timestamp, content) {
|
|
let card = getRef('mail_template').content.cloneNode(true),
|
|
cardContainer = card.querySelector('.mail')
|
|
let senderName, floID
|
|
if (from === myFloID) {
|
|
let count = 0, list = [];
|
|
to.forEach(f => floGlobals.contacts[f] ? list.push(floGlobals.contacts[f]) : count++)
|
|
senderName = `To : ${list.join(', ')}`
|
|
if (count) {
|
|
if (list.length)
|
|
senderName = `${senderName} & ${count} other(s)`
|
|
else
|
|
senderName = `${senderName} ${count} Unknown people`
|
|
}
|
|
floID = to.join(', ')
|
|
} else {
|
|
senderName = `${floGlobals.contacts[from] || ''}`;
|
|
floID = from
|
|
}
|
|
card.querySelector('.sender-name').textContent = senderName
|
|
card.querySelector('.flo-id').textContent = floID
|
|
card.querySelector('.mail-subject').textContent = subject
|
|
card.querySelector('.date').textContent = getFormatedTime(timestamp);
|
|
card.querySelector('.mail-content').textContent = content
|
|
return card
|
|
},
|
|
contactCard(floID, options = {}) {
|
|
let { name, type, prepend = false, markUnread = false, contactOnly = false, isAdmin = false, isSelected = false } = options
|
|
let card = getRef('contact_template').content.cloneNode(true),
|
|
cardContainer = card.firstElementChild
|
|
if (!name)
|
|
name = getContactName(floID)
|
|
|
|
cardContainer.setAttribute("name", name)
|
|
cardContainer.setAttribute("flo-id", floID)
|
|
cardContainer.setAttribute("search-tags", `${name}${floID}`)
|
|
let initial = card.querySelector('.initial')
|
|
let color = contactColor(floID)
|
|
cardContainer.setAttribute("background-color", color)
|
|
initial.setAttribute(`style`, `background-color: ${color}`)
|
|
|
|
if (type === 'group') {
|
|
initial.innerHTML = `
|
|
<svg class="icon group-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><path d="M13.61,28.09c-1.63,0-4.72-2.35-5.33-3.58a21.65,21.65,0,0,1-1.35-7.32s-.26-6.07,6.68-6.07a6.38,6.38,0,0,1,6.69,6.07A21.65,21.65,0,0,1,19,24.51c-.62,1.23-3.7,3.58-5.34,3.58"/><path d="M50.39,28.09c-1.64,0-4.72-2.35-5.34-3.58a21.9,21.9,0,0,1-1.35-7.32s-.26-6.07,6.69-6.07a6.37,6.37,0,0,1,6.68,6.07,21.65,21.65,0,0,1-1.35,7.32c-.61,1.23-3.7,3.58-5.33,3.58"/><path d="M32,31.74c-2.21,0-6.37-3.17-7.2-4.83A29.3,29.3,0,0,1,23,17s-.35-8.21,9-8.21c8.68,0,9,8.21,9,8.21a29.3,29.3,0,0,1-1.83,9.88c-.82,1.66-5,4.83-7.2,4.83"/><path d="M48.29,38.58c-4.16-1.83-8.57-3.08-10.34-6.4a12,12,0,0,1-6,3.73,12,12,0,0,1-5.95-3.73c-1.77,3.32-6.18,4.57-10.34,6.4-1.7.71-3.11,9.88-1.13,9.88A33.06,33.06,0,0,0,31.23,53h1.54a33.06,33.06,0,0,0,16.65-4.53C51.4,48.46,50,39.29,48.29,38.58Z"/><path d="M14.82,36.57c.76-.33,1.54-.65,2.3-1,2.49-1,4.85-2,6.22-3.44C21.07,31.23,19,30.25,18,28.41a8.83,8.83,0,0,1-4.41,2.76,8.83,8.83,0,0,1-4.4-2.76c-1.31,2.46-4.58,3.38-7.66,4.74-1.26.52-2.3,7.31-.84,7.31a24.55,24.55,0,0,0,10.86,3.31C11.89,40.81,12.86,37.39,14.82,36.57Z"/><path d="M62.45,33.15c-3.08-1.36-6.35-2.28-7.66-4.74a8.83,8.83,0,0,1-4.4,2.76A8.83,8.83,0,0,1,46,28.41c-1,1.84-3,2.82-5.32,3.76,1.37,1.43,3.73,2.41,6.22,3.44.76.31,1.54.63,2.26,1,2,.83,3,4.25,3.29,7.21a24.55,24.55,0,0,0,10.86-3.31C64.75,40.46,63.71,33.67,62.45,33.15Z"/></svg>
|
|
`
|
|
cardContainer.querySelector('.name').textContent = floGlobals.groups[floID].name
|
|
}
|
|
else {
|
|
cardContainer.querySelector('.name').textContent = name !== 'Unknown' ? name : floID
|
|
initial.textContent = name !== 'Unknown' ? name.charAt(0) : floID.charAt(0)
|
|
}
|
|
|
|
if (contactOnly || type === 'contact') {
|
|
if (isAdmin) {
|
|
let adminTag = document.createElement('p')
|
|
adminTag.textContent = 'Admin'
|
|
cardContainer.classList.add('admin')
|
|
adminTag.classList.add('admin-tag')
|
|
cardContainer.append(adminTag)
|
|
}
|
|
}
|
|
else {
|
|
//render chat card for newly added contact
|
|
if (type === 'chat')
|
|
cardContainer.classList.add('chat')
|
|
else
|
|
cardContainer.classList.add('group')
|
|
if (markUnread)
|
|
cardContainer.classList.add('unread')
|
|
messenger.getChat(floID).then(chat => {
|
|
let lastMessage = { message: '', time: 0 }
|
|
if (Object.values(chat).length) {
|
|
for (msg of Object.values(chat).reverse()) {
|
|
if (msg.message) {
|
|
lastMessage = msg
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (type === 'group' && lastMessage.time === 0) {
|
|
lastMessage.time = floGlobals.groups[floID].created
|
|
}
|
|
let lastText = document.createElement('p')
|
|
if ((type === 'chat' && lastMessage.category === 'sent') || (type === 'group' && lastMessage.sender === myFloID)) {
|
|
lastText.textContent = `You: ${lastMessage.message}`
|
|
}
|
|
else {
|
|
lastText.textContent = lastMessage.message
|
|
}
|
|
lastText.classList.add('last-message')
|
|
cardContainer.append(lastText)
|
|
cardContainer.innerHTML += `
|
|
<h5 class="time">${getFormatedTime(lastMessage.time, true)}</h5>
|
|
<svg class="icon menu" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
|
<circle cx="5.59" cy="32" r="5.59"/>
|
|
<circle cx="58.41" cy="32" r="5.59"/>
|
|
<circle cx="31.89" cy="32" r="5.59"/>
|
|
</svg>`
|
|
})
|
|
.catch(error => console.error(error))
|
|
if (prepend) {
|
|
activeChat['floID'] = floID
|
|
if (activeChat['chatCard'])
|
|
activeChat['chatCard'].classList.remove('active')
|
|
}
|
|
}
|
|
if (isSelected)
|
|
addTick(cardContainer, { animate: false })
|
|
return card
|
|
},
|
|
messageBubble(msg) {
|
|
let { admin = false, newMembers = [], rmMembers = [], groupID, name, description, sender, floID, message, time: timestamp, category, unconfirmed = false, updateChatCard = false } = msg
|
|
let card = getRef('message_template').content.cloneNode(true),
|
|
cardContainer = card.querySelector('.message'),
|
|
messageContent = cardContainer.children[0],
|
|
messageTime = cardContainer.children[1]
|
|
|
|
if (activeChat.isGroup) {
|
|
floID = groupID
|
|
category = sender === myFloID ? 'sent' : 'received'
|
|
}
|
|
|
|
if (message) {
|
|
if (updateChatCard) {
|
|
if (category === 'offer') {
|
|
showPage('video_call_page')
|
|
remoteSdp = message
|
|
}
|
|
else if (category === 'answer') {
|
|
startVideoCall(message)
|
|
return
|
|
}
|
|
}
|
|
else if (category === 'offer') {
|
|
let eventCard = document.createElement('p')
|
|
eventCard.classList.add('group-event-card')
|
|
eventCard.textContent = `You called ${getContactName(floID)}`
|
|
return eventCard
|
|
}
|
|
else if (category === 'answer') {
|
|
let eventCard = document.createElement('p')
|
|
eventCard.classList.add('group-event-card')
|
|
eventCard.textContent = `${getContactName(floID)} called you`
|
|
return eventCard
|
|
}
|
|
cardContainer.id = `${floID}_${timestamp}`
|
|
if (unconfirmed)
|
|
cardContainer.classList.add('unconfirmed')
|
|
|
|
cardContainer.classList.add(category)
|
|
|
|
if (sender) {
|
|
if (sender !== myFloID && lastSender !== sender) {
|
|
let senderName = document.createElement('div')
|
|
senderName.classList.add('sender-name')
|
|
senderName.style.color = contactColor(sender)
|
|
senderName.textContent = getContactName(sender)
|
|
cardContainer.prepend(senderName)
|
|
|
|
cardContainer.classList.add('distinct-sender')
|
|
|
|
messageContent = cardContainer.children[1]
|
|
messageTime = cardContainer.children[2]
|
|
}
|
|
lastSender = sender
|
|
}
|
|
|
|
if (hasURL(message)) {
|
|
const chunks = message.split(' ')
|
|
const chunksLength = chunks.length - 1
|
|
chunks.forEach((chunk, index) => {
|
|
if (hasURL(chunk)) {
|
|
const anchorTag = document.createElement('a')
|
|
anchorTag.href = /^https?:\/\//i.test(chunk) ? chunk : `http://${chunk}`
|
|
anchorTag.target = "_blank"
|
|
anchorTag.rel = "noopener"
|
|
anchorTag.textContent = chunksLength !== index ? `${chunk} ` : chunk
|
|
messageContent.append(anchorTag)
|
|
}
|
|
else {
|
|
const text = chunksLength !== index ? `${chunk} ` : chunk
|
|
const textNode = document.createTextNode(text)
|
|
messageContent.append(textNode)
|
|
}
|
|
})
|
|
}
|
|
else {
|
|
let [messageBody, isOnlyEmoji] = isEmoji(message)
|
|
if (isOnlyEmoji)
|
|
cardContainer.classList.add('big-emoji')
|
|
messageContent.append(messageBody)
|
|
}
|
|
let time = new Date(timestamp).toString(),
|
|
minutes = String(new Date(timestamp).getMinutes()),
|
|
hours = new Date(timestamp).getHours(),
|
|
year = new Date(timestamp).getFullYear()
|
|
minutes = minutes.length === 1 ? `0${minutes}` : minutes
|
|
let finalHours = hours - 12 > 0 ? `${hours - 12}:${minutes} pm` : `${hours}:${minutes} am`
|
|
|
|
messageTime.textContent = finalHours
|
|
if (currentFloID !== floID) {
|
|
currentDate = null
|
|
lastSender = null
|
|
currentFloID = floID
|
|
renderedDates.clear()
|
|
}
|
|
|
|
if (!renderedDates.has(`${time.slice(4, 10)} ${year}`) && `${time.slice(4, 10)} ${year}` !== currentDate) {
|
|
let dateCard = document.createElement('h5')
|
|
dateCard.classList.add('date-card')
|
|
dateCard.textContent = new Date().getFullYear() !== year ? `${time.slice(4, 10)} ${year}` : time.slice(4, 10)
|
|
currentDate = `${time.slice(4, 10)} ${year}`
|
|
let frag = document.createDocumentFragment()
|
|
frag.append(dateCard, card)
|
|
renderedDates.set(currentDate, currentDate)
|
|
return frag
|
|
}
|
|
else
|
|
return card;
|
|
}
|
|
else if (admin) {
|
|
if (newMembers.length) {
|
|
const cards = document.createDocumentFragment()
|
|
const { admin } = floGlobals.groups[groupID]
|
|
newMembers.forEach(member => {
|
|
let eventCard = document.createElement('p')
|
|
eventCard.classList.add('group-event-card')
|
|
let eventMessage = ''
|
|
if (member === myFloID)
|
|
eventMessage = `${getContactName(admin)} added you`
|
|
else
|
|
eventMessage = `${getContactName(admin)} added ${getContactName(member)}`
|
|
eventCard.textContent = eventMessage
|
|
cards.append(eventCard)
|
|
})
|
|
return cards
|
|
}
|
|
else if (rmMembers.length) {
|
|
const cards = document.createDocumentFragment()
|
|
const { admin } = floGlobals.groups[groupID]
|
|
rmMembers.forEach(member => {
|
|
let eventCard = document.createElement('p')
|
|
eventCard.classList.add('group-event-card')
|
|
let eventMessage = ''
|
|
if (member === myFloID)
|
|
eventMessage = `${getContactName(admin)} removed you`
|
|
else
|
|
eventMessage = `${getContactName(admin)} removed ${getContactName(member)}`
|
|
eventCard.textContent = eventMessage
|
|
cards.append(eventCard)
|
|
})
|
|
return cards
|
|
}
|
|
else if (name) {
|
|
let eventCard = document.createElement('p')
|
|
eventCard.classList.add('group-event-card')
|
|
eventCard.textContent = `Changed group name to '${name}'`
|
|
return eventCard
|
|
}
|
|
else if (description) {
|
|
let eventCard = document.createElement('p')
|
|
eventCard.classList.add('group-event-card')
|
|
eventCard.textContent = `Changed group description to '${description}'`
|
|
return eventCard
|
|
}
|
|
}
|
|
|
|
},
|
|
}
|
|
|
|
let currentDate,
|
|
currentFloID,
|
|
renderedDates = new Map(),
|
|
lastSender
|
|
|
|
const hasURL = text => /[(http(s)?):\/\/(www\.)?a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/.test(text)
|
|
|
|
const isEmoji = (txt) => {
|
|
const rx = /([\uD800-\uDBFF][\uDC00-\uDFFF](?:[\u200D\uFE0F][\uD800-\uDBFF][\uDC00-\uDFFF]){2,}|\uD83D\uDC69(?:\u200D(?:(?:\uD83D\uDC69\u200D)?\uD83D\uDC67|(?:\uD83D\uDC69\u200D)?\uD83D\uDC66)|\uD83C[\uDFFB-\uDFFF])|\uD83D\uDC69\u200D(?:\uD83D\uDC69\u200D)?\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC69\u200D(?:\uD83D\uDC69\u200D)?\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67])|\uD83C\uDFF3\uFE0F\u200D\uD83C\uDF08|(?:\uD83C[\uDFC3\uDFC4\uDFCA]|\uD83D[\uDC6E\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6]|\uD83E[\uDD26\uDD37-\uDD39\uDD3D\uDD3E\uDDD6-\uDDDD])(?:\uD83C[\uDFFB-\uDFFF])\u200D[\u2640\u2642]\uFE0F|\uD83D\uDC69(?:\uD83C[\uDFFB-\uDFFF])\u200D(?:\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92])|(?:\uD83C[\uDFC3\uDFC4\uDFCA]|\uD83D[\uDC6E\uDC6F\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6]|\uD83E[\uDD26\uDD37-\uDD39\uDD3C-\uDD3E\uDDD6-\uDDDF])\u200D[\u2640\u2642]\uFE0F|\uD83C\uDDFD\uD83C\uDDF0|\uD83C\uDDF6\uD83C\uDDE6|\uD83C\uDDF4\uD83C\uDDF2|\uD83C\uDDE9(?:\uD83C[\uDDEA\uDDEC\uDDEF\uDDF0\uDDF2\uDDF4\uDDFF])|\uD83C\uDDF7(?:\uD83C[\uDDEA\uDDF4\uDDF8\uDDFA\uDDFC])|\uD83C\uDDE8(?:\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDEE\uDDF0-\uDDF5\uDDF7\uDDFA-\uDDFF])|(?:\u26F9|\uD83C[\uDFCB\uDFCC]|\uD83D\uDD75)(?:\uFE0F\u200D[\u2640\u2642]|(?:\uD83C[\uDFFB-\uDFFF])\u200D[\u2640\u2642])\uFE0F|(?:\uD83D\uDC41\uFE0F\u200D\uD83D\uDDE8|\uD83D\uDC69(?:\uD83C[\uDFFB-\uDFFF])\u200D[\u2695\u2696\u2708]|\uD83D\uDC69\u200D[\u2695\u2696\u2708]|\uD83D\uDC68(?:(?:\uD83C[\uDFFB-\uDFFF])\u200D[\u2695\u2696\u2708]|\u200D[\u2695\u2696\u2708]))\uFE0F|\uD83C\uDDF2(?:\uD83C[\uDDE6\uDDE8-\uDDED\uDDF0-\uDDFF])|\uD83D\uDC69\u200D(?:\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D(?:\uD83D[\uDC68\uDC69])|\uD83D[\uDC68\uDC69]))|\uD83C\uDDF1(?:\uD83C[\uDDE6-\uDDE8\uDDEE\uDDF0\uDDF7-\uDDFB\uDDFE])|\uD83C\uDDEF(?:\uD83C[\uDDEA\uDDF2\uDDF4\uDDF5])|\uD83C\uDDED(?:\uD83C[\uDDF0\uDDF2\uDDF3\uDDF7\uDDF9\uDDFA])|\uD83C\uDDEB(?:\uD83C[\uDDEE-\uDDF0\uDDF2\uDDF4\uDDF7])|[#\*0-9]\uFE0F\u20E3|\uD83C\uDDE7(?:\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEF\uDDF1-\uDDF4\uDDF6-\uDDF9\uDDFB\uDDFC\uDDFE\uDDFF])|\uD83C\uDDE6(?:\uD83C[\uDDE8-\uDDEC\uDDEE\uDDF1\uDDF2\uDDF4\uDDF6-\uDDFA\uDDFC\uDDFD\uDDFF])|\uD83C\uDDFF(?:\uD83C[\uDDE6\uDDF2\uDDFC])|\uD83C\uDDF5(?:\uD83C[\uDDE6\uDDEA-\uDDED\uDDF0-\uDDF3\uDDF7-\uDDF9\uDDFC\uDDFE])|\uD83C\uDDFB(?:\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDEE\uDDF3\uDDFA])|\uD83C\uDDF3(?:\uD83C[\uDDE6\uDDE8\uDDEA-\uDDEC\uDDEE\uDDF1\uDDF4\uDDF5\uDDF7\uDDFA\uDDFF])|\uD83C\uDFF4\uDB40\uDC67\uDB40\uDC62(?:\uDB40\uDC77\uDB40\uDC6C\uDB40\uDC73|\uDB40\uDC73\uDB40\uDC63\uDB40\uDC74|\uDB40\uDC65\uDB40\uDC6E\uDB40\uDC67)\uDB40\uDC7F|\uD83D\uDC68(?:\u200D(?:\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D)?\uD83D\uDC68|(?:(?:\uD83D[\uDC68\uDC69])\u200D)?\uD83D\uDC66\u200D\uD83D\uDC66|(?:(?:\uD83D[\uDC68\uDC69])\u200D)?\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92])|(?:\uD83C[\uDFFB-\uDFFF])\u200D(?:\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]))|\uD83C\uDDF8(?:\uD83C[\uDDE6-\uDDEA\uDDEC-\uDDF4\uDDF7-\uDDF9\uDDFB\uDDFD-\uDDFF])|\uD83C\uDDF0(?:\uD83C[\uDDEA\uDDEC-\uDDEE\uDDF2\uDDF3\uDDF5\uDDF7\uDDFC\uDDFE\uDDFF])|\uD83C\uDDFE(?:\uD83C[\uDDEA\uDDF9])|\uD83C\uDDEE(?:\uD83C[\uDDE8-\uDDEA\uDDF1-\uDDF4\uDDF6-\uDDF9])|\uD83C\uDDF9(?:\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDED\uDDEF-\uDDF4\uDDF7\uDDF9\uDDFB\uDDFC\uDDFF])|\uD83C\uDDEC(?:\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEE\uDDF1-\uDDF3\uDDF5-\uDDFA\uDDFC\uDDFE])|\uD83C\uDDFA(?:\uD83C[\uDDE6\uDDEC\uDDF2\uDDF3\uDDF8\uDDFE\uDDFF])|\uD83C\uDDEA(?:\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDED\uDDF7-\uDDFA])|\uD83C\uDDFC(?:\uD83C[\uDDEB\uDDF8])|(?:\u26F9|\uD83C[\uDFCB\uDFCC]|\uD83D\uDD75)(?:\uD83C[\uDFFB-\uDFFF])|(?:\uD83C[\uDFC3\uDFC4\uDFCA]|\uD83D[\uDC6E\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6]|\uD83E[\uDD26\uDD37-\uDD39\uDD3D\uDD3E\uDDD6-\uDDDD])(?:\uD83C[\uDFFB-\uDFFF])|(?:[\u261D\u270A-\u270D]|\uD83C[\uDF85\uDFC2\uDFC7]|\uD83D[\uDC42\uDC43\uDC46-\uDC50\uDC66\uDC67\uDC70\uDC72\uDC74-\uDC76\uDC78\uDC7C\uDC83\uDC85\uDCAA\uDD74\uDD7A\uDD90\uDD95\uDD96\uDE4C\uDE4F\uDEC0\uDECC]|\uD83E[\uDD18-\uDD1C\uDD1E\uDD1F\uDD30-\uDD36\uDDD1-\uDDD5])(?:\uD83C[\uDFFB-\uDFFF])|\uD83D\uDC68(?:\u200D(?:(?:(?:\uD83D[\uDC68\uDC69])\u200D)?\uD83D\uDC67|(?:(?:\uD83D[\uDC68\uDC69])\u200D)?\uD83D\uDC66)|\uD83C[\uDFFB-\uDFFF])|(?:[\u261D\u26F9\u270A-\u270D]|\uD83C[\uDF85\uDFC2-\uDFC4\uDFC7\uDFCA-\uDFCC]|\uD83D[\uDC42\uDC43\uDC46-\uDC50\uDC66-\uDC69\uDC6E\uDC70-\uDC78\uDC7C\uDC81-\uDC83\uDC85-\uDC87\uDCAA\uDD74\uDD75\uDD7A\uDD90\uDD95\uDD96\uDE45-\uDE47\uDE4B-\uDE4F\uDEA3\uDEB4-\uDEB6\uDEC0\uDECC]|\uD83E[\uDD18-\uDD1C\uDD1E\uDD1F\uDD26\uDD30-\uDD39\uDD3D\uDD3E\uDDD1-\uDDDD])(?:\uD83C[\uDFFB-\uDFFF])?|(?:[\u231A\u231B\u23E9-\u23EC\u23F0\u23F3\u25FD\u25FE\u2614\u2615\u2648-\u2653\u267F\u2693\u26A1\u26AA\u26AB\u26BD\u26BE\u26C4\u26C5\u26CE\u26D4\u26EA\u26F2\u26F3\u26F5\u26FA\u26FD\u2705\u270A\u270B\u2728\u274C\u274E\u2753-\u2755\u2757\u2795-\u2797\u27B0\u27BF\u2B1B\u2B1C\u2B50\u2B55]|\uD83C[\uDC04\uDCCF\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE1A\uDE2F\uDE32-\uDE36\uDE38-\uDE3A\uDE50\uDE51\uDF00-\uDF20\uDF2D-\uDF35\uDF37-\uDF7C\uDF7E-\uDF93\uDFA0-\uDFCA\uDFCF-\uDFD3\uDFE0-\uDFF0\uDFF4\uDFF8-\uDFFF]|\uD83D[\uDC00-\uDC3E\uDC40\uDC42-\uDCFC\uDCFF-\uDD3D\uDD4B-\uDD4E\uDD50-\uDD67\uDD7A\uDD95\uDD96\uDDA4\uDDFB-\uDE4F\uDE80-\uDEC5\uDECC\uDED0-\uDED2\uDEEB\uDEEC\uDEF4-\uDEF8]|\uD83E[\uDD10-\uDD3A\uDD3C-\uDD3E\uDD40-\uDD45\uDD47-\uDD4C\uDD50-\uDD6B\uDD80-\uDD97\uDDC0\uDDD0-\uDDE6])|(?:[#\*0-9\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23E9-\u23F3\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB-\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u261D\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u2648-\u2653\u2660\u2663\u2665\u2666\u2668\u267B\u267F\u2692-\u2697\u2699\u269B\u269C\u26A0\u26A1\u26AA\u26AB\u26B0\u26B1\u26BD\u26BE\u26C4\u26C5\u26C8\u26CE\u26CF\u26D1\u26D3\u26D4\u26E9\u26EA\u26F0-\u26F5\u26F7-\u26FA\u26FD\u2702\u2705\u2708-\u270D\u270F\u2712\u2714\u2716\u271D\u2721\u2728\u2733\u2734\u2744\u2747\u274C\u274E\u2753-\u2755\u2757\u2763\u2764\u2795-\u2797\u27A1\u27B0\u27BF\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B50\u2B55\u3030\u303D\u3297\u3299]|\uD83C[\uDC04\uDCCF\uDD70\uDD71\uDD7E\uDD7F\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE02\uDE1A\uDE2F\uDE32-\uDE3A\uDE50\uDE51\uDF00-\uDF21\uDF24-\uDF93\uDF96\uDF97\uDF99-\uDF9B\uDF9E-\uDFF0\uDFF3-\uDFF5\uDFF7-\uDFFF]|\uD83D[\uDC00-\uDCFD\uDCFF-\uDD3D\uDD49-\uDD4E\uDD50-\uDD67\uDD6F\uDD70\uDD73-\uDD7A\uDD87\uDD8A-\uDD8D\uDD90\uDD95\uDD96\uDDA4\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA-\uDE4F\uDE80-\uDEC5\uDECB-\uDED2\uDEE0-\uDEE5\uDEE9\uDEEB\uDEEC\uDEF0\uDEF3-\uDEF8]|\uD83E[\uDD10-\uDD3A\uDD3C-\uDD3E\uDD40-\uDD45\uDD47-\uDD4C\uDD50-\uDD6B\uDD80-\uDD97\uDDC0\uDDD0-\uDDE6])\uFE0F)/;
|
|
const res = txt.split(rx).filter(Boolean)
|
|
const messageBody = document.createDocumentFragment()
|
|
const length = res.length
|
|
let isOnlyEmoji = false
|
|
res.forEach(section => {
|
|
if (section.match(rx)) {
|
|
const span = document.createElement('span')
|
|
if (length === 1)
|
|
isOnlyEmoji = true
|
|
else
|
|
span.classList.add('text-emoji')
|
|
span.textContent = section
|
|
messageBody.append(span)
|
|
}
|
|
else if (length === 2 && !/\w/.test(section)) {
|
|
isOnlyEmoji = true
|
|
}
|
|
else {
|
|
messageBody.append(section)
|
|
}
|
|
})
|
|
return [messageBody, isOnlyEmoji]
|
|
}
|
|
|
|
|
|
function renderDirectUI(data) {
|
|
renderMessages(data.messages, { updateChatCard: true });
|
|
renderMailList(data.mails)
|
|
//let order = Object.keys(data.messages).map(a => a.split('_')).sort((a, b) => a[0] - b[0]).map(a => a[1])
|
|
if (Object.keys(data.messages).length) {
|
|
document.title = `New message(s)`
|
|
}
|
|
if (Object.keys(data.mails).length && activePage.button !== getRef('mail_page_button')) {
|
|
document.title = `New mail(s)`
|
|
}
|
|
}
|
|
|
|
function renderGroupUI(data) {
|
|
renderMessages(data.messages, { updateChatCard: true });
|
|
}
|
|
|
|
window.addEventListener('focus', e => {
|
|
if (activeChat.chatCard) {
|
|
if (!chatScrollInfo.isScrolledUp) {
|
|
scrollToBottom()
|
|
}
|
|
}
|
|
})
|
|
window.addEventListener('blur', e => {
|
|
console.log('blured')
|
|
})
|
|
|
|
document.addEventListener('keyup', e => {
|
|
if (e.target.closest('#send_mail_to')) {
|
|
if (e.code === 'ArrowDown') {
|
|
for (child of getRef('mail_contact_list').children) {
|
|
if (!child.classList.contains('hide-completely')) {
|
|
child.focus()
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if (e.code === 'Enter' && getRef('mail_contact_list').firstElementChild) {
|
|
for (child of getRef('mail_contact_list').children) {
|
|
if (!child.classList.contains('hide-completely')) {
|
|
child.click()
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (e.target.closest('#search_chats')) {
|
|
if (e.code === 'ArrowDown') {
|
|
for (child of getRef('chat_container').children) {
|
|
if (!child.classList.contains('hide-completely')) {
|
|
child.focus()
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if (e.code === 'Enter' && getRef('contacts_container').firstElementChild) {
|
|
for (child of getRef('contacts_container').children) {
|
|
if (!child.classList.contains('hide-completely')) {
|
|
child.click()
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (e.target.closest('.contact')) {
|
|
if (e.code === 'ArrowDown' || e.code === 'ArrowRight') {
|
|
if (document.activeElement.nextElementSibling)
|
|
document.activeElement.nextElementSibling.focus()
|
|
else
|
|
getRef('mail_contact_list').firstElementChild.focus()
|
|
}
|
|
if (e.code === 'ArrowUp' || e.code === 'ArrowLeft') {
|
|
if (document.activeElement.previousElementSibling)
|
|
document.activeElement.previousElementSibling.focus()
|
|
else
|
|
getRef('mail_contact_list').lastElementChild.focus()
|
|
}
|
|
if (e.code === 'Enter' || e.code === 'Space') {
|
|
getRef('send_mail_to').value = document.activeElement.getAttribute('flo-id')
|
|
getRef('mail_contact_list').classList.add('hide-completely')
|
|
}
|
|
}
|
|
})
|
|
|
|
const chatScrollInfo = {};
|
|
let debounceTimer
|
|
getRef('chat_middle').addEventListener('scroll', function () {
|
|
if (debounceTimer) {
|
|
window.clearTimeout(debounceTimer)
|
|
}
|
|
debounceTimer = setTimeout(e => {
|
|
chatScrollInfo['scrollTop'] = this.scrollTop
|
|
chatScrollInfo['scrollheight'] = this.scrollHeight
|
|
if ((this.scrollHeight > this.clientHeight) && (this.scrollHeight - this.clientHeight - this.scrollTop >= 100)) {
|
|
chatScrollInfo['isScrolledUp'] = true
|
|
getRef('scroll_to_bottom').classList.add('no-transformations')
|
|
}
|
|
else {
|
|
chatScrollInfo['isScrolledUp'] = false
|
|
getRef('scroll_to_bottom').classList.remove('no-transformations', 'new-message')
|
|
}
|
|
}, 100)
|
|
}, { passive: true })
|
|
|
|
getRef('send_mail_to').addEventListener('input', function () {
|
|
getRef('mail_contact_list').classList.remove('hide-completely')
|
|
if (this.value.trim !== '') {
|
|
for (child of getRef('mail_contact_list').children) {
|
|
if (child.getAttribute('name').toUpperCase().indexOf(this.value.toUpperCase()) > -1) {
|
|
child.classList.remove('hide-completely')
|
|
}
|
|
else {
|
|
child.classList.add('hide-completely')
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
getRef('search_chats').addEventListener('input', function (e) {
|
|
const contacts = getRef('chat_container').querySelectorAll('.contact')
|
|
contacts.forEach(child => {
|
|
if (child.getAttribute('search-tags').toLowerCase().includes(this.value.toLowerCase())) {
|
|
child.classList.remove('hide-completely')
|
|
}
|
|
else {
|
|
child.classList.add('hide-completely')
|
|
}
|
|
})
|
|
})
|
|
|
|
getRef('search_contacts').addEventListener('input', function () {
|
|
const contacts = {}
|
|
for (contact in floGlobals.contacts) {
|
|
if (contact.toLowerCase().includes(this.value.toLowerCase()) || floGlobals.contacts[contact].toLowerCase().includes(this.value.toLowerCase())) {
|
|
contacts[contact] = floGlobals.contacts[contact]
|
|
if (isCreatingGroup && !(contact in floGlobals.pubKeys))
|
|
delete contacts[contact]
|
|
}
|
|
}
|
|
getRef('contacts_container').innerHTML = ''
|
|
renderContactList(contacts)
|
|
})
|
|
|
|
document.addEventListener('click', e => {
|
|
if (e.target.closest('.sidebar-item')) {
|
|
let target = e.target.closest('.sidebar-item')
|
|
if (target.dataset.target)
|
|
showPanel(target, target.dataset.target)
|
|
}
|
|
if (e.target.closest('.navbar-item') && activePage.button !== e.target.closest('.navbar-item')) {
|
|
const targetPage = e.target.closest('.navbar-item').dataset.target
|
|
showPage(targetPage, true)
|
|
history.pushState({ type: 'subpage', id: targetPage }, null, `#${targetPage}`)
|
|
}
|
|
if (e.target.closest('#send_mail_to') || e.target.closest('#mail_contact_list')) {
|
|
getRef('mail_contact_list').classList.remove('hide-completely')
|
|
}
|
|
else {
|
|
getRef('mail_contact_list').classList.add('hide-completely')
|
|
}
|
|
if (e.target.closest('#new_mail_button')) {
|
|
showPopup('compose_mail_popup')
|
|
}
|
|
|
|
//Detect click on send mail option
|
|
if (e.target.closest(".send-mail-option")) {
|
|
let floID;
|
|
showPopup('compose_mail_popup')
|
|
floID = e.target.closest(".contact").getAttribute("flo-id");
|
|
getRef('send_mail_to').value = floID;
|
|
return false;
|
|
}
|
|
|
|
// detect click outside emoji panel and emoji button
|
|
if (isEmojiPickerOpen && (!e.target.closest('#emoji_picker') && !e.target.closest('#emoji_toggle') && !e.target.closest('#type_message'))) {
|
|
toggleEmoji('hide')
|
|
}
|
|
})
|
|
|
|
getRef('chat_page').addEventListener('contextmenu', e => {
|
|
if (e.target.closest('.contact')) {
|
|
e.preventDefault()
|
|
}
|
|
})
|
|
|
|
let holdTimeout
|
|
let holdThreshold = 500
|
|
|
|
getRef('chat_page').addEventListener('touchstart', e => {
|
|
if (e.target.closest('.contact')) {
|
|
holdTimeout = setTimeout(() => {
|
|
if (isHapticOn)
|
|
navigator.vibrate(100)
|
|
let contact = e.target.closest(".contact")
|
|
clickedContact['chatCard'] = contact
|
|
clickedContact['floID'] = contact.getAttribute("flo-id")
|
|
clickedContact['name'] = contact.getAttribute("name")
|
|
clickedContact['isGroup'] = floGlobals.groups.hasOwnProperty(clickedContact['floID'])
|
|
showPopup('contact_details_popup')
|
|
}, 500)
|
|
getRef('chat_page').addEventListener('touchmove', handleTouchMove)
|
|
}
|
|
})
|
|
|
|
function handleTouchMove(e) {
|
|
if (e.target.closest('.contact')) {
|
|
clearTimeout(holdTimeout)
|
|
}
|
|
}
|
|
|
|
getRef('chat_page').addEventListener('touchend', e => {
|
|
if (e.target.closest('.contact')) {
|
|
clearTimeout(holdTimeout)
|
|
getRef('chat_page').removeEventListener('touchmove', handleTouchMove)
|
|
}
|
|
})
|
|
|
|
getRef('mail_contact_list').addEventListener('click', e => {
|
|
if (e.target.closest('.contact')) {
|
|
getRef('send_mail_to').value = e.target.closest('.contact').getAttribute('flo-id')
|
|
getRef('mail_contact_list').classList.add('hide-completely')
|
|
}
|
|
})
|
|
|
|
|
|
const clickedContact = {}
|
|
|
|
getRef('chat_page').addEventListener('click', e => {
|
|
//detect click on chat cards
|
|
if (e.target.closest(".contact")) {
|
|
let contact = e.target.closest(".contact")
|
|
clickedContact['chatCard'] = contact
|
|
clickedContact['floID'] = contact.getAttribute("flo-id")
|
|
clickedContact['name'] = contact.getAttribute("name")
|
|
clickedContact['isGroup'] = floGlobals.groups.hasOwnProperty(clickedContact['floID'])
|
|
|
|
if (clickedContact['floID'] === myFloID) return
|
|
|
|
if (e.target.closest(".selectable")) {
|
|
if (isRemovingMember)
|
|
selectMemberToRemove(e.target.closest(".selectable"))
|
|
}
|
|
else if (isCreatingGroup) {
|
|
// code for adding group members
|
|
selectContact(contact)
|
|
}
|
|
else if (e.target.closest(".initial") || e.target.closest(".menu")) {
|
|
showPopup('contact_details_popup')
|
|
}
|
|
else {
|
|
createRipple(e, contact)
|
|
contact.classList.remove('unread')
|
|
if (activeChat['chatCard'] === contact && window.innerWidth > 640) return
|
|
showChatDetails({ show: false, animate: false })
|
|
document.title = `FLO Messenger`
|
|
getRef('chat').classList.remove('hide-completely')
|
|
viewConversation(contact)
|
|
if (activeChat['chatCard'])
|
|
activeChat['chatCard'].classList.remove('active')
|
|
contact.classList.add('active')
|
|
activeChat['chatCard'] = contact
|
|
if (activeChatPage.id === 'contacts') {
|
|
getRef('chat').classList.remove('hide-on-mobile')
|
|
getRef('contacts').classList.add('hide-on-mobile')
|
|
activeChatPage = getRef('chat')
|
|
getRef('main_navbar').classList.add('hide-on-mobile')
|
|
}
|
|
}
|
|
}
|
|
else if (e.target.closest('.contact-preview')) {
|
|
removeSelectedContact(e.target.closest('.contact-preview').getAttribute('flo-id'))
|
|
}
|
|
})
|
|
getRef('contacts_popup').addEventListener('click', e => {
|
|
//detect click on contacts
|
|
if (e.target.closest(".selectable")) {
|
|
selectMemberToAdd(e.target.closest(".selectable"))
|
|
}
|
|
})
|
|
|
|
// Function to show all contacts side drawer
|
|
let contactsDrawerAnimation
|
|
function showContacts(options) {
|
|
const { show = true, onlyValid = false } = options
|
|
let contacts = {}
|
|
if (show) {
|
|
getRef('contacts_container').innerHTML = ''
|
|
// Show contacts which replied
|
|
if (onlyValid) {
|
|
for (contact in floGlobals.contacts) {
|
|
if (contact in floGlobals.pubKeys) {
|
|
contacts[contact] = floGlobals.contacts[contact]
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
contacts = floGlobals.contacts
|
|
}
|
|
renderContactList(contacts)
|
|
getRef('all_contacts').classList.remove('hide-completely')
|
|
contactsDrawerAnimation = animateTo(getRef('all_contacts'), [
|
|
{ transform: 'translateY(2rem)' },
|
|
{ transform: 'translateY(0)' },
|
|
], {
|
|
duration: 300,
|
|
easing: 'ease'
|
|
})
|
|
}
|
|
else {
|
|
isCreatingGroup = false
|
|
clearAllMembers()
|
|
contactsDrawerAnimation.reverse()
|
|
contactsDrawerAnimation.onfinish = () => {
|
|
getRef('all_contacts').classList.add('hide-completely')
|
|
getRef('all_contacts_options').classList.remove('hide-completely')
|
|
getRef('selected_contacts').classList.add('hide-completely')
|
|
getRef('skip_members_button').classList.add('hide-completely')
|
|
getRef('contacts_container').innerHTML = ''
|
|
}
|
|
}
|
|
}
|
|
|
|
function transformScroll(event) {
|
|
if (!event.deltaY) {
|
|
return;
|
|
}
|
|
|
|
event.currentTarget.scrollLeft += event.deltaY + event.deltaX;
|
|
event.preventDefault();
|
|
}
|
|
|
|
getRef('selected_contacts_container').addEventListener('wheel', transformScroll);
|
|
|
|
const selectedGroupMembers = new Set()
|
|
function selectContact(contact) {
|
|
if (!selectedGroupMembers.has(clickedContact.floID)) {
|
|
selectedGroupMembers.add(clickedContact.floID)
|
|
const clonedInitial = contact.querySelector('.initial').cloneNode(true);
|
|
const clonedName = contact.querySelector('.name').cloneNode(true);
|
|
const preview = document.createElement('div')
|
|
preview.classList.add('contact-preview')
|
|
preview.setAttribute('flo-id', clickedContact['floID']);
|
|
preview.append(clonedInitial, clonedName)
|
|
addCross(preview)
|
|
getRef('selected_contacts_container').append(preview)
|
|
preview.scrollIntoView({ behavior: "smooth", inline: "end" });
|
|
|
|
preview.animate(
|
|
[
|
|
{ transform: 'scale(0)' },
|
|
{ transform: 'scale(1)' },
|
|
],
|
|
{
|
|
duration: 300,
|
|
easing: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)',
|
|
fill: 'forwards'
|
|
}
|
|
)
|
|
addTick(contact)
|
|
}
|
|
else {
|
|
removeSelectedContact(clickedContact.floID)
|
|
}
|
|
if (selectedGroupMembers.size) {
|
|
getRef('skip_members_button').textContent = 'Next'
|
|
}
|
|
else {
|
|
getRef('skip_members_button').textContent = 'Skip'
|
|
}
|
|
}
|
|
|
|
function removeSelectedContact(floID) {
|
|
selectedGroupMembers.delete(floID)
|
|
const relatedContact = getRef('contacts_container').querySelector(`[flo-id="${floID}"]`)
|
|
const relatedPreview = getRef('selected_contacts_container').querySelector(`[flo-id="${floID}"]`)
|
|
if (relatedContact)
|
|
removeTick(relatedContact)
|
|
relatedPreview.animate(
|
|
[
|
|
{ transform: 'scale(1)' },
|
|
{ transform: 'scale(0)' },
|
|
],
|
|
{
|
|
duration: 150,
|
|
easing: 'ease',
|
|
fill: 'forwards'
|
|
}
|
|
).onfinish = () => {
|
|
relatedPreview.remove()
|
|
}
|
|
}
|
|
|
|
function clearAllMembers() {
|
|
getRef('selected_contacts_container').innerHTML = ''
|
|
selectedGroupMembers.clear()
|
|
}
|
|
let isCreatingGroup = false
|
|
function initGroupCreation() {
|
|
isCreatingGroup = true
|
|
showContacts({ show: true, onlyValid: true })
|
|
getRef('all_contacts_options').classList.add('hide-completely')
|
|
getRef('selected_contacts').classList.remove('hide-completely')
|
|
getRef('skip_members_button').classList.remove('hide-completely')
|
|
}
|
|
|
|
function skipToGroupCreation() {
|
|
const animOptions = {
|
|
duration: 300,
|
|
fill: 'forwards',
|
|
easing: 'ease'
|
|
}
|
|
getRef('all_contacts').animate(
|
|
[
|
|
{ transform: 'translateX(0)' },
|
|
{ transform: 'translateX(-25%)' },
|
|
],
|
|
animOptions
|
|
).onfinish = () => {
|
|
getRef('all_contacts').classList.add('hide-completely')
|
|
}
|
|
getRef('group_creation_panel').classList.remove('hide-completely')
|
|
getRef('group_creation_panel').animate(
|
|
[
|
|
{ transform: 'translateX(100%)' },
|
|
{ transform: 'translateX(0)' },
|
|
],
|
|
animOptions
|
|
)
|
|
}
|
|
function backToContacts() {
|
|
const animOptions = {
|
|
duration: 300,
|
|
fill: 'forwards',
|
|
easing: 'ease'
|
|
}
|
|
getRef('group_creation_panel').animate(
|
|
[
|
|
{ transform: 'translateX(0)' },
|
|
{ transform: 'translateX(100%)' },
|
|
],
|
|
animOptions
|
|
).onfinish = () => {
|
|
getRef('group_creation_panel').classList.add('hide-completely')
|
|
}
|
|
getRef('all_contacts').classList.remove('hide-completely')
|
|
getRef('all_contacts').animate(
|
|
[
|
|
{ transform: 'translateX(-25%)' },
|
|
{ transform: 'translateX(0)' },
|
|
],
|
|
animOptions
|
|
)
|
|
}
|
|
|
|
document.getElementById('create_group_button').addEventListener('clicked', createGroup)
|
|
function createGroup() {
|
|
const groupName = getRef('group_name_field').value.trim()
|
|
const groupDescription = getRef('group_description_field').value.trim()
|
|
messenger.createGroup(groupName, groupDescription)
|
|
.then(groupInfo => {
|
|
isCreatingGroup = false
|
|
getRef('chat_container').prepend(render.contactCard(groupInfo.groupID, { type: 'group' }))
|
|
getRef('chat_container').children[0].click()
|
|
getRef('group_creation_panel').animate(
|
|
[
|
|
{ transform: 'translateY(0)' },
|
|
{ transform: 'translateY(2rem)' },
|
|
],
|
|
{
|
|
duration: 150,
|
|
fill: 'forwards',
|
|
easing: 'ease'
|
|
}
|
|
).onfinish = () => {
|
|
getRef('all_contacts').classList.add('hide-completely')
|
|
getRef('group_creation_panel').classList.add('hide-completely')
|
|
getRef('all_contacts_options').classList.remove('hide-completely')
|
|
getRef('selected_contacts').classList.add('hide-completely')
|
|
}
|
|
notify('Group created', 'success')
|
|
if (selectedGroupMembers.size) {
|
|
messenger.addGroupMembers(groupInfo.groupID, [...selectedGroupMembers])
|
|
.then(res => {
|
|
clearAllMembers()
|
|
})
|
|
.catch(err => console.error(err))
|
|
}
|
|
})
|
|
.catch(err => console.error(err))
|
|
}
|
|
|
|
let isEmojiPickerOpen = false
|
|
function toggleEmoji(mode) {
|
|
switch (mode) {
|
|
case 'toggle':
|
|
isEmojiPickerOpen = true
|
|
getRef('emoji_toggle').classList.toggle('active')
|
|
getRef('emoji_picker').classList.toggle('hide-completely')
|
|
break;
|
|
case 'hide':
|
|
isEmojiPickerOpen = false
|
|
getRef('emoji_toggle').classList.remove('active')
|
|
getRef('emoji_picker').classList.add('hide-completely')
|
|
break;
|
|
}
|
|
getRef('scroll_to_bottom').setAttribute('style', `bottom: calc(${window.innerHeight - getRef('chat_footer').getBoundingClientRect().top}px - .5rem)`)
|
|
if (!chatScrollInfo.isScrolledUp)
|
|
scrollToBottom()
|
|
}
|
|
|
|
getRef('emoji_picker').addEventListener('emoji-click', e => {
|
|
const clickedEmoji = e.detail.unicode
|
|
getRef('type_message').value += clickedEmoji
|
|
if (window.innerWidth > 640) {
|
|
setTimeout(() => {
|
|
getRef('type_message').focusIn()
|
|
}, 0);
|
|
}
|
|
})
|
|
|
|
const style = document.createElement('style');
|
|
style.textContent = `
|
|
.emoji-menu{
|
|
border-top: solid rgba(var(--text-color), 0.2) 1px;
|
|
background: rgba(var(--foreground-color), 0.6);
|
|
}
|
|
@media (hover: hover){
|
|
::-webkit-scrollbar{
|
|
width: 0.5rem;
|
|
height: 0.5rem;
|
|
}
|
|
|
|
::-webkit-scrollbar-thumb{
|
|
background: rgba(var(--text-color), 0.3);
|
|
border-radius: 1rem;
|
|
&:hover{
|
|
background: rgba(var(--text-color), 0.5);
|
|
}
|
|
}
|
|
}`
|
|
|
|
function copyToClipboard(element, message, parent = '.copy-row') {
|
|
const parentElement = element.closest(parent)
|
|
const copyTarget = parentElement.querySelector('.copy')
|
|
if (copyTarget.tagName === 'SM-INPUT' || copyTarget.tagName === 'INPUT' || copyTarget.tagName === 'TEXTAREA')
|
|
copyText = copyTarget.value
|
|
else
|
|
copyText = copyTarget.textContent
|
|
navigator.clipboard.writeText(copyText).then(() => {
|
|
notify(`${message}`, 'success')
|
|
})
|
|
.catch(error => {
|
|
notify(error, 'error')
|
|
})
|
|
}
|
|
|
|
let isEnterSend = true
|
|
if (localStorage.getItem('isEnterSend') === null)
|
|
localStorage.setItem('isEnterSend', 'true')
|
|
|
|
if (localStorage.isEnterSend === 'true') {
|
|
isEnterSend = true
|
|
getRef('is_enter_send_toggle').checked = true;
|
|
} else {
|
|
isEnterSend = false
|
|
getRef('is_enter_send_toggle').checked = false;
|
|
}
|
|
|
|
getRef('is_enter_send_toggle').addEventListener('change', function () {
|
|
if (this.checked) {
|
|
isEnterSend = true
|
|
localStorage.setItem("isEnterSend", 'true');
|
|
}
|
|
else {
|
|
isEnterSend = false
|
|
localStorage.setItem("isEnterSend", 'false');
|
|
}
|
|
})
|
|
|
|
function updateHeight() {
|
|
if (window.innerWidth < 640) {
|
|
getRef('chat').style.height = window.innerHeight + 'px'
|
|
}
|
|
else {
|
|
getRef('chat').style.height = '100vh'
|
|
}
|
|
}
|
|
|
|
function goto(page) {
|
|
if (page === 'chats') {
|
|
getRef('chat').classList.add('hide-on-mobile')
|
|
getRef('contacts').classList.remove('hide-on-mobile')
|
|
activeChatPage = getRef('contacts')
|
|
}
|
|
if (page === 'mails') {
|
|
getRef('mail').classList.add('hide-on-mobile')
|
|
getRef('mails').classList.remove('hide-on-mobile')
|
|
activeMailPage = getRef('mails')
|
|
}
|
|
getRef('main_navbar').classList.remove('hide-on-mobile')
|
|
}
|
|
|
|
getRef("mails").addEventListener('click', function (e) {
|
|
if (e.target.closest(".mail-card")) {
|
|
getRef('mail_page_button').setAttribute('data-notifications', '0')
|
|
getRef('mail').classList.remove('hide-completely')
|
|
e.target.closest(".mail-card").classList.remove('unread')
|
|
viewMail(e.target.closest(".mail-card").getAttribute("name"));
|
|
if (activeMail)
|
|
activeMail.classList.remove('active')
|
|
e.target.closest(".mail-card").classList.add('active')
|
|
activeMail = e.target.closest(".mail-card")
|
|
if (activeMailPage.id === 'mails') {
|
|
getRef('mail').classList.remove('hide-on-mobile')
|
|
getRef('mails').classList.add('hide-on-mobile')
|
|
activeMailPage = getRef('mail')
|
|
getRef('main_navbar').classList.add('hide-on-mobile')
|
|
}
|
|
}
|
|
})
|
|
|
|
getRef("prev_mail").addEventListener('click', function (e) {
|
|
viewMail(this.dataset["value"], false);
|
|
})
|
|
|
|
getRef('send_message_button').addEventListener('click', sendMessage)
|
|
|
|
getRef('type_message').addEventListener('input', e => {
|
|
if (getRef('type_message').value.trim() !== '')
|
|
getRef('send_message_button').classList.add('active')
|
|
else
|
|
getRef('send_message_button').classList.remove('active')
|
|
})
|
|
getRef('type_message').addEventListener('keydown', e => {
|
|
if (getRef('type_message').value.trim() === '' && e.code === "Enter") {
|
|
e.preventDefault()
|
|
return
|
|
}
|
|
else {
|
|
if (e.code === "Enter" && e.shiftKey) {
|
|
e.preventDefault()
|
|
getRef('type_message').value += '\r\n';
|
|
}
|
|
else if (!e.shiftKey && e.code === "Enter" && isEnterSend) {
|
|
e.preventDefault()
|
|
sendMessage()
|
|
}
|
|
}
|
|
})
|
|
|
|
getRef("clear_data").addEventListener('click', async function (e) {
|
|
if (await confirmation('Clear Data?',
|
|
`Are you sure you want to clear stored data?`, 'No', "Clear")) {
|
|
messenger.clearUserData().then(result => {
|
|
notify("Successfully Cleared local data", 'success')
|
|
setTimeout(onLoadStartUp, 2000)
|
|
}).catch(error => notify(error, "error"))
|
|
}
|
|
});
|
|
|
|
getRef('add_contact_button').addEventListener("clicked", addContact)
|
|
|
|
getRef('show_reply_popup').addEventListener("click", () => {
|
|
showPopup('reply_mail_popup')
|
|
})
|
|
|
|
getRef('reply_mail_button').addEventListener("clicked", replyMail)
|
|
|
|
getRef("backup_data").addEventListener("click", function (e) {
|
|
let backupInfoText = getRef("backup_info")
|
|
backupInfoText.classList.remove("error")
|
|
backupInfoText.textContent = `Generating the backup file! Please wait...`;
|
|
messenger.backupData().then(blob => {
|
|
let anchor = document.createElement('a')
|
|
anchor.setAttribute("download", `BackupFor_${myFloID}_${Date.now()}.json`)
|
|
anchor.setAttribute("href", URL.createObjectURL(blob))
|
|
backupInfoText.textContent =
|
|
`Backup file generated! If the download didn't start automatically, click `;
|
|
anchor.textContent = `here.`;
|
|
backupInfoText.append(anchor);
|
|
anchor.click();
|
|
}).catch(error => {
|
|
backupInfoText.classList.add("error")
|
|
backupInfoText.textContent = `Unable to generate backup file! Try again later...`;
|
|
notify("Backup data Unsuccessful!", "error", error);
|
|
})
|
|
})
|
|
|
|
getRef("restore_data").addEventListener("change", async function (e) {
|
|
let file = this.files[0]
|
|
notify(`Retrieving backup data! Please wait.`);
|
|
if (!file) {
|
|
notify(`No files selected!`);
|
|
return;
|
|
}
|
|
messenger.parseBackup(file).then(async data => {
|
|
if (await confirmation('Restore Data?',
|
|
`Found: ${Object.keys(data.contacts).length} Contacts,\n ${Object.keys(data.messages).length} Messages, ${Object.keys(data.mails).length} Mails.`,
|
|
'Cancel', "Restore"
|
|
)) {
|
|
notify(`Restoring data! Please wait.`);
|
|
messenger.restoreData(data).then(result => {
|
|
notify("Data restore completed successful! Initiating reload, Please wait", 'success')
|
|
setTimeout(onLoadStartUp, 2000)
|
|
}).catch(error => {
|
|
notify("Failed to restore data! Try again later", "error", error);
|
|
});
|
|
}
|
|
}).catch(error => {
|
|
notify("Retrive data Unsuccessful!", "error", error);
|
|
})
|
|
})
|
|
|
|
function sendMessage() {
|
|
if (window.innerWidth > 640)
|
|
getRef('type_message').focusIn()
|
|
let receiver = activeChat['floID']
|
|
let container;
|
|
let message = getRef('type_message').value.trim();
|
|
getRef('type_message').value = ''
|
|
if (message === '') return
|
|
let time = Date.now()
|
|
let msgObj = { message, time, unconfirmed: true }
|
|
if (activeChat.isGroup) {
|
|
msgObj['groupID'] = activeChat.floID
|
|
msgObj['sender'] = myFloID
|
|
}
|
|
else {
|
|
msgObj['floID'] = activeChat.floID
|
|
msgObj['category'] = 'sent'
|
|
}
|
|
getRef('messages_container').append(render.messageBubble(msgObj))
|
|
const contact = getRef('chat_container').querySelector(`.chat[flo-id="${receiver}"], .group[flo-id="${receiver}"]`)
|
|
if (contact) {
|
|
if (activeChat['chatCard'] !== getRef('chat_container').children[0]) {
|
|
const cloneContact = contact.cloneNode(true)
|
|
contact.remove()
|
|
activeChat['chatCard'] = cloneContact
|
|
getRef('chat_container').prepend(cloneContact)
|
|
animateTo(getRef('chat_container').children[0], [
|
|
{ transform: 'translateY(1rem)' },
|
|
{ transform: 'none' },
|
|
],
|
|
{
|
|
easing: 'ease',
|
|
duration: 300
|
|
}
|
|
)
|
|
}
|
|
}
|
|
else {
|
|
messenger.addChat(receiver)
|
|
getRef('chat_container').prepend(render.contactCard(receiver, { type: 'chat', prepend: true }))
|
|
getRef('chat_container').children[0].classList.add('active')
|
|
activeChat['chatCard'] = getRef('chat_container').children[0]
|
|
}
|
|
scrollToBottom()
|
|
if (activeChat.isGroup)
|
|
messenger.sendGroupMessage(message, receiver).then(data => {
|
|
getRef('messages_container').querySelector(`#${receiver}_${time}`).classList.remove('unconfirmed')
|
|
activeChat.chatCard.querySelector('.last-message').textContent = `You: ${message}`
|
|
activeChat.chatCard.querySelector('.time').textContent = getFormatedTime(Date.now(), true)
|
|
}).catch(error => notify(error, "error"));
|
|
else
|
|
messenger.sendMessage(message, receiver).then(data => {
|
|
getRef('messages_container').querySelector(`#${receiver}_${time}`).classList.remove('unconfirmed')
|
|
activeChat.chatCard.querySelector('.last-message').textContent = `You: ${message}`
|
|
activeChat.chatCard.querySelector('.time').textContent = getFormatedTime(Date.now(), true)
|
|
}).catch(error => notify(error, "error"));
|
|
}
|
|
|
|
function removeElement(element) {
|
|
element.parentNode.removeChild(element);
|
|
}
|
|
|
|
function clearElement(element) {
|
|
element.innerHTML = '';
|
|
return element;
|
|
}
|
|
|
|
function addContact() {
|
|
let floID = getRef('add_contact_floID').value.trim();
|
|
let name = getRef('add_contact_name').value.trim();
|
|
if (floID === myFloID) {
|
|
notify(`you can't add your own FLO ID as contact`, 'error')
|
|
return
|
|
}
|
|
if (floGlobals.contacts.hasOwnProperty(floID)) {
|
|
notify(`Contact already saved`, 'error')
|
|
return
|
|
}
|
|
messenger.storeContact(floID, name).then(result => {
|
|
getRef('contacts_container').append(render.contactCard(floID, { name, type: 'contact' }))
|
|
hidePopup()
|
|
notify(`Added Contact: ${floID}`)
|
|
}).catch(error => notify(error, "error"));
|
|
}
|
|
|
|
function renderContactList(contactList = {}) {
|
|
const frag = document.createDocumentFragment()
|
|
for (floID in contactList) {
|
|
let isSelected = selectedGroupMembers.has(floID)
|
|
frag.append(render.contactCard(floID, { type: 'contact', isSelected }))
|
|
}
|
|
getRef('contacts_container').append(frag)
|
|
}
|
|
|
|
async function renderChatList() {
|
|
const frag = document.createDocumentFragment()
|
|
getRef('chat_container').innerHTML = ''
|
|
for (floID of messenger.getChatOrder().mixed) {
|
|
const markUnread = floGlobals.marked[floID]?.includes('unread')
|
|
let type
|
|
if (floGlobals.chats[floID])
|
|
type = 'chat'
|
|
else if (floGlobals.groups[floID])
|
|
type = 'group'
|
|
frag.append(render.contactCard(floID, { type, markUnread }))
|
|
}
|
|
getRef('chat_container').append(frag)
|
|
}
|
|
|
|
function renderMarked(data) {
|
|
for (let d in data) {
|
|
let element = document.getElementsByName(d)[0]
|
|
if (element)
|
|
data[d].forEach(mark => element.classList.add(mark))
|
|
}
|
|
}
|
|
|
|
function scrollToBottom(smooth = false) {
|
|
if (activeChat.chatCard) {
|
|
messenger.removeMark(activeChat.floID, 'unread')
|
|
activeChat.chatCard.classList.remove('unread')
|
|
}
|
|
getRef('scroll_to_bottom').classList.remove('new-message')
|
|
setTimeout(() => {
|
|
getRef('chat_middle').scrollTo({ top: getRef('chat_middle').scrollHeight, behavior: smooth ? 'smooth' : undefined })
|
|
}, smooth ? 300 : 0);
|
|
}
|
|
|
|
let startIndex = 0,
|
|
endIndex = 0
|
|
function renderMessages(data, options) {
|
|
let { markUnread = true, updateChatCard = false, reRender = false, lazyLoad = false } = options
|
|
let messages
|
|
if (reRender) {
|
|
activeChat['allMessages'] = Object.values(data)
|
|
startIndex = activeChat['allMessages'].length > 20 ? activeChat['allMessages'].length - 20 : 0
|
|
endIndex = activeChat['allMessages'].length
|
|
messages = activeChat['allMessages']
|
|
renderedDates.clear()
|
|
}
|
|
else if (lazyLoad) {
|
|
messages = activeChat['allMessages']
|
|
endIndex = startIndex
|
|
startIndex = endIndex > 20 ? endIndex - 20 : 0
|
|
markUnread = false
|
|
}
|
|
else {
|
|
messages = Object.values(data)
|
|
if (messages.length) {
|
|
startIndex = 0
|
|
endIndex = messages.length
|
|
}
|
|
}
|
|
|
|
if (messages && messages.length) {
|
|
for (let i = startIndex; i < endIndex; i++) {
|
|
let { floID, groupID, sender, message, time, category } = messages[i]
|
|
//Stops message from rendering in wrong chat window
|
|
if (activeChat['floID'] && (activeChat['floID'] === floID || activeChat['floID'] === groupID)) {
|
|
// Stops message rendering if message is sent from original user causing duplication
|
|
if (updateChatCard && activeChat.isGroup && message && sender === myFloID) {
|
|
messenger.removeMark(groupID, 'unread')
|
|
return
|
|
}
|
|
frag.append(render.messageBubble({ ...messages[i], updateChatCard }))
|
|
}
|
|
const contact = getRef('chat_container').querySelector(`.contact[flo-id='${floID || groupID}']`)
|
|
if (markUnread && contact) {
|
|
contact.classList.add("unread");
|
|
if (contact !== getRef('chat_container').children[0]) {
|
|
const cloneContact = contact.cloneNode(true)
|
|
contact.remove()
|
|
getRef('chat_container').prepend(cloneContact)
|
|
animateTo(getRef('chat_container').children[0], [
|
|
{ transform: 'translateY(1rem)' },
|
|
{ transform: 'none' },
|
|
],
|
|
{
|
|
easing: 'ease',
|
|
duration: 300
|
|
}
|
|
)
|
|
}
|
|
}
|
|
if (updateChatCard) {
|
|
let chatCard
|
|
if (!contact) {
|
|
getRef('chat_container').prepend(render.contactCard(floID, { type: 'chat', markUnread: true }))
|
|
chatCard = getRef('chat_container').firstElementChild
|
|
}
|
|
else {
|
|
chatCard = contact
|
|
let finalMessage
|
|
if (floGlobals.contacts[sender])
|
|
finalMessage = `${floGlobals.contacts[sender]}: ${message}`
|
|
else if (sender === myFloID)
|
|
finalMessage = `You: ${message}`
|
|
else
|
|
finalMessage = message
|
|
chatCard.querySelector('.last-message').textContent = finalMessage
|
|
chatCard.querySelector('.time').textContent = getFormatedTime(time, true)
|
|
}
|
|
|
|
if (activeChat.floID === (floID || groupID)) {
|
|
if (chatScrollInfo.isScrolledUp)
|
|
getRef('scroll_to_bottom').classList.add('new-message')
|
|
else {
|
|
if (document.hasFocus()) {
|
|
messenger.removeMark((floID || groupID), 'unread')
|
|
setTimeout(() => {
|
|
document.title = 'FLO Messenger'
|
|
activeChat.chatCard.classList.remove('unread')
|
|
}, 1000);
|
|
}
|
|
}
|
|
}
|
|
if (!document.hasFocus() && navigator.onLine) {
|
|
getRef('notification_sound').currentTime = 0;
|
|
getRef('notification_sound').play();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!lazyLoad && !reRender) {
|
|
endIndex = messages.length
|
|
getRef('messages_container').append(frag)
|
|
if (!chatScrollInfo['isScrolledUp']) {
|
|
setTimeout(() => {
|
|
scrollToBottom(true)
|
|
}, 100);
|
|
}
|
|
}
|
|
if (reRender || lazyLoad) {
|
|
currentDate = null
|
|
lastSender = null
|
|
chatScrollInfo['scrollTop'] = getRef('chat_middle').scrollTop
|
|
chatScrollInfo['scrollHeight'] = getRef('chat_middle').scrollHeight
|
|
getRef('messages_container').prepend(frag)
|
|
}
|
|
if (reRender) {
|
|
scrollToBottom()
|
|
}
|
|
}
|
|
|
|
//checks for added elements in chat
|
|
const chatMutationObserver = new MutationObserver(
|
|
(mutations, observer) => {
|
|
for (const mutation of mutations) {
|
|
if (mutation.type === 'childList' && mutation.addedNodes.length && getRef('messages_container').firstElementChild) {
|
|
chatMessageObserver.observe(getRef('messages_container').firstElementChild)
|
|
chatScrollInfo['scrollTop'] += (getRef('chat_middle').scrollHeight - chatScrollInfo['scrollHeight'])
|
|
chatScrollInfo['scrollHeight'] = getRef('chat_middle').scrollHeight
|
|
getRef('chat_middle').scrollTo({ top: chatScrollInfo['scrollTop'] })
|
|
}
|
|
}
|
|
}
|
|
)
|
|
|
|
//Lazy loading for chat messages
|
|
const chatMessageObserver = new IntersectionObserver(
|
|
(entries, observer) => {
|
|
if (entries[0].isIntersecting) {
|
|
renderMessages('', { lazyLoad: true })
|
|
observer.disconnect()
|
|
}
|
|
}
|
|
)
|
|
|
|
async function viewConversation(contact) {
|
|
getRef('messages_container').innerHTML = ''
|
|
let floID = clickedContact['floID'],
|
|
name = contact.getAttribute('name'),
|
|
textColor = contact.getAttribute('text-color'),
|
|
backgroundColor = contact.getAttribute('background-color')
|
|
|
|
activeChat['floID'] = floID
|
|
activeChat['isGroup'] = floGlobals.groups[floID] ? true : false
|
|
getRef("chat_dp").setAttribute('style', `color: ${textColor}; background-color: ${backgroundColor};`)
|
|
getRef("receiver_name").textContent = getContactName(floID);
|
|
if (activeChat.isGroup) {
|
|
getRef("receiver_initial").innerHTML = `
|
|
<svg class="icon group-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><path d="M13.61,28.09c-1.63,0-4.72-2.35-5.33-3.58a21.65,21.65,0,0,1-1.35-7.32s-.26-6.07,6.68-6.07a6.38,6.38,0,0,1,6.69,6.07A21.65,21.65,0,0,1,19,24.51c-.62,1.23-3.7,3.58-5.34,3.58"/><path d="M50.39,28.09c-1.64,0-4.72-2.35-5.34-3.58a21.9,21.9,0,0,1-1.35-7.32s-.26-6.07,6.69-6.07a6.37,6.37,0,0,1,6.68,6.07,21.65,21.65,0,0,1-1.35,7.32c-.61,1.23-3.7,3.58-5.33,3.58"/><path d="M32,31.74c-2.21,0-6.37-3.17-7.2-4.83A29.3,29.3,0,0,1,23,17s-.35-8.21,9-8.21c8.68,0,9,8.21,9,8.21a29.3,29.3,0,0,1-1.83,9.88c-.82,1.66-5,4.83-7.2,4.83"/><path d="M48.29,38.58c-4.16-1.83-8.57-3.08-10.34-6.4a12,12,0,0,1-6,3.73,12,12,0,0,1-5.95-3.73c-1.77,3.32-6.18,4.57-10.34,6.4-1.7.71-3.11,9.88-1.13,9.88A33.06,33.06,0,0,0,31.23,53h1.54a33.06,33.06,0,0,0,16.65-4.53C51.4,48.46,50,39.29,48.29,38.58Z"/><path d="M14.82,36.57c.76-.33,1.54-.65,2.3-1,2.49-1,4.85-2,6.22-3.44C21.07,31.23,19,30.25,18,28.41a8.83,8.83,0,0,1-4.41,2.76,8.83,8.83,0,0,1-4.4-2.76c-1.31,2.46-4.58,3.38-7.66,4.74-1.26.52-2.3,7.31-.84,7.31a24.55,24.55,0,0,0,10.86,3.31C11.89,40.81,12.86,37.39,14.82,36.57Z"/><path d="M62.45,33.15c-3.08-1.36-6.35-2.28-7.66-4.74a8.83,8.83,0,0,1-4.4,2.76A8.83,8.83,0,0,1,46,28.41c-1,1.84-3,2.82-5.32,3.76,1.37,1.43,3.73,2.41,6.22,3.44.76.31,1.54.63,2.26,1,2,.83,3,4.25,3.29,7.21a24.55,24.55,0,0,0,10.86-3.31C64.75,40.46,63.71,33.67,62.45,33.15Z"/></svg>
|
|
`
|
|
getRef('video_call_button').classList.add('hide-completely')
|
|
}
|
|
else {
|
|
getRef("receiver_initial").textContent = getContactName(floID).charAt(0);
|
|
// getRef('video_call_button').classList.remove('hide-completely')
|
|
}
|
|
getRef("receiver_initial").setAttribute('style', `color: ${textColor}; background-color: ${backgroundColor};`)
|
|
if (floGlobals.pubKeys[floID] || activeChat.isGroup)
|
|
getRef("warn_no_encryption").classList.add("hide-completely");
|
|
else
|
|
getRef("warn_no_encryption").classList.remove("hide-completely");
|
|
renderMessages(await messenger.getChat(floID), { markUnread: false, reRender: true })
|
|
messenger.removeMark(floID, "unread");
|
|
if (this.scrollHeight <= this.clientHeight) {
|
|
chatScrollInfo['isScrolledUp'] = false
|
|
getRef('scroll_to_bottom').classList.remove('no-transformations')
|
|
}
|
|
}
|
|
|
|
function renderMailList(mails, markUnread = true) {
|
|
const inboxMailFrag = document.createDocumentFragment()
|
|
const sentMailFrag = document.createDocumentFragment()
|
|
for (let m in mails) {
|
|
let { from, to, prev, ref, subject, time, content } = mails[m]
|
|
if (from === myFloID)
|
|
sentMailFrag.prepend(render.mailCard(to, ref, subject, time, content, markUnread))
|
|
else if (to.includes(myFloID))
|
|
inboxMailFrag.prepend(render.mailCard(from, ref, subject, time, content, markUnread))
|
|
}
|
|
getRef('inbox_mail_container').prepend(inboxMailFrag)
|
|
getRef('sent_mail_container').prepend(sentMailFrag)
|
|
}
|
|
|
|
function viewMail(mailRef, newView = true) {
|
|
//stop rerendering if same mail is already open
|
|
if (mailRef === (activeMail ? activeMail.getAttribute('name') : '')) return
|
|
//clear the container
|
|
if (newView)
|
|
clearElement(getRef("mail_container"))
|
|
|
|
messenger.getMail(mailRef).then(result => {
|
|
let { from, to, prev, ref, subject, time, content } = result
|
|
//append the contents to mail container
|
|
getRef("mail_container").append(render.mail(from, to, subject, time, content));
|
|
//add prop for previous mail (if available)
|
|
let prevMail = getRef("prev_mail");
|
|
prevMail.dataset["value"] = prev;
|
|
prevMail.style.display = prev ? 'block' : 'none';
|
|
//set values for reply mail form if new view
|
|
if (newView) {
|
|
getRef('reply_mail_popup').dataset["to"] = (from === myFloID ? to.join(',') : from)
|
|
getRef('reply_mail_popup').dataset["prev"] = mailRef;
|
|
getRef('subject_of_reply_mail').value = subject.startsWith("Re: ") ? subject : `Re: ${subject}`;
|
|
getRef("show_reply_popup").classList.remove("hide-completely");
|
|
}
|
|
messenger.removeMark(mailRef, "unread");
|
|
}).catch(error => notify("Unable to read mail", "error", error))
|
|
}
|
|
|
|
getRef('send_mail_button').addEventListener('clicked', sendMail)
|
|
|
|
function sendMail() {
|
|
let to = getRef('send_mail_to').value.split(",");
|
|
let subject = getRef('subject_of_mail').value;
|
|
let content = getRef('mail_content').value
|
|
let recipients = [];
|
|
try {
|
|
to.forEach(id => {
|
|
let tmp = id.trim();
|
|
if (!floCrypto.validateAddr(tmp))
|
|
throw "Invalid Address: " + tmp
|
|
if (!recipients.includes(tmp))
|
|
recipients.push(tmp);
|
|
})
|
|
messenger.sendMail(subject, content, recipients).then(result => {
|
|
notify(`Mail(s) sent!`)
|
|
renderMailList(result)
|
|
hidePopup()
|
|
}).catch(error => notify("Failed to send mail!", "error", error))
|
|
} catch (error) {
|
|
notify(error, "error")
|
|
}
|
|
}
|
|
|
|
function replyMail() {
|
|
let recipient = getRef('reply_mail_popup').dataset.to;
|
|
if (recipient.includes(','))
|
|
recipient = recipient.split(',')
|
|
let subject = getRef('subject_of_reply_mail').value;
|
|
let content = getRef('reply_mail_content').value;
|
|
let prev = getRef('reply_mail_popup').dataset.prev;
|
|
messenger.sendMail(subject, content, recipient, prev).then(result => {
|
|
notify(`Mail replied!`);
|
|
renderMailList(result)
|
|
hidePopup()
|
|
}).catch(error => notify("Failed to reply mail!", "error", error))
|
|
}
|
|
|
|
document.getElementById('mail_type_selector').addEventListener('change', e => {
|
|
document.querySelectorAll('.mail-container').forEach(container => container.classList.add('hide-completely'))
|
|
getRef(`${e.detail.value}_mail_container`).classList.remove('hide-completely')
|
|
})
|
|
|
|
const allPanels = document.querySelectorAll('.panel'),
|
|
allSidebarItems = document.querySelectorAll('.sidebar-item')
|
|
|
|
|
|
const flyOutLeft = [
|
|
{
|
|
transform: 'translateX(0)',
|
|
opacity: 1
|
|
},
|
|
{
|
|
transform: 'translateX(-1rem)',
|
|
opacity: 0
|
|
},
|
|
]
|
|
|
|
const flyInLeft = [
|
|
{
|
|
transform: 'translateX(1rem)',
|
|
opacity: 0
|
|
},
|
|
{
|
|
transform: 'translateX(0)',
|
|
opacity: 1
|
|
},
|
|
]
|
|
|
|
const flyOutRight = [
|
|
{
|
|
transform: 'translateX(0)',
|
|
opacity: 1
|
|
},
|
|
{
|
|
transform: 'translateX(1rem)',
|
|
opacity: 0
|
|
},
|
|
]
|
|
|
|
const flyInRight = [
|
|
{
|
|
transform: 'translateX(-1rem)',
|
|
opacity: 0
|
|
},
|
|
{
|
|
transform: 'translateX(0)',
|
|
opacity: 1
|
|
},
|
|
]
|
|
|
|
const animOptions = {
|
|
duration: 150,
|
|
easing: 'ease'
|
|
}
|
|
|
|
const windowSizeObserver = new ResizeObserver(entries => {
|
|
updateHeight()
|
|
if (entries[0].borderBoxSize[0].inlineSize > 640) {
|
|
getRef('settings_sidebar').style = ''
|
|
getRef('settings_panel').style = ''
|
|
}
|
|
})
|
|
|
|
windowSizeObserver.observe(document.body)
|
|
|
|
function animateTo(element, animation, options) {
|
|
const anime = element.animate(animation, { ...options, fill: 'both' })
|
|
anime.addEventListener('finish', () => {
|
|
anime.commitStyles()
|
|
anime.cancel()
|
|
})
|
|
return anime
|
|
}
|
|
|
|
function showPanel(item, panel) {
|
|
getRef('settings_title').textContent = item.textContent
|
|
if (window.innerWidth < 720) {
|
|
animateTo(getRef('settings_sidebar'), flyOutLeft, animOptions).onfinish = () => {
|
|
animateTo(getRef('settings_panel'), flyInLeft, animOptions)
|
|
getRef('settings_sidebar').style = ''
|
|
getRef('settings_sidebar').classList.add('hide-on-mobile')
|
|
getRef('settings_panel').classList.remove('hide-on-mobile')
|
|
}
|
|
}
|
|
else {
|
|
allSidebarItems.forEach(panel => panel.classList.remove('active'))
|
|
item.classList.add('active')
|
|
}
|
|
allPanels.forEach(panel => panel.classList.add('hide-completely'))
|
|
getRef(panel).classList.remove('hide-completely')
|
|
}
|
|
|
|
function hidePanel() {
|
|
animateTo(getRef('settings_panel'), flyOutRight, animOptions).onfinish = () => {
|
|
getRef('settings_title').textContent = ''
|
|
getRef('settings_panel').style = ''
|
|
animateTo(getRef('settings_sidebar'), flyInRight, animOptions)
|
|
getRef('settings_panel').classList.add('hide-on-mobile')
|
|
getRef('settings_sidebar').classList.remove('hide-on-mobile')
|
|
}
|
|
}
|
|
|
|
function toggleDrawer() {
|
|
getRef('main_navbar').classList.toggle('no-transformations')
|
|
getRef('navbar_backdrop').classList.toggle('hide')
|
|
}
|
|
|
|
|
|
document.addEventListener('contentchanged', e => {
|
|
if (e.target.closest('#contact_name')) {
|
|
changeContactName(e.detail.value.trim())
|
|
}
|
|
else if (e.target.closest('#group_description')) {
|
|
messenger.changeGroupDescription(activeChat.floID, e.detail.value.trim())
|
|
.then(res => {
|
|
notify('Changed group description', 'success')
|
|
})
|
|
.catch(error => notify(error, "error"));
|
|
}
|
|
else if (e.target.closest('#chat_name')) {
|
|
changeContactName(e.detail.value.trim(), true)
|
|
}
|
|
})
|
|
|
|
async function changeContactName(name, isChat = false) {
|
|
let isGroup,
|
|
floID
|
|
if (isChat) {
|
|
activeChat['name'] = name
|
|
if (activeChat['name'] === '')
|
|
activeChat['name'] = 'Unknown'
|
|
isGroup = activeChat.isGroup
|
|
floID = activeChat.floID
|
|
name = activeChat['name']
|
|
}
|
|
else {
|
|
clickedContact['name'] = name
|
|
if (clickedContact['name'] === '')
|
|
clickedContact['name'] = 'Unknown'
|
|
isGroup = clickedContact.isGroup
|
|
floID = clickedContact.floID
|
|
name = clickedContact['name']
|
|
}
|
|
if (isGroup) {
|
|
messenger.changeGroupName(floID, name).then(res => {
|
|
updatechatCards({ name, floID, isGroup: true })
|
|
notify('Changed group name', 'success')
|
|
})
|
|
.catch(error => notify(error, "error"));
|
|
}
|
|
else {
|
|
messenger.storeContact(floID, name).then(result => {
|
|
updatechatCards({ name, floID, isGroup: false })
|
|
notify('Changed contact name', 'success')
|
|
})
|
|
.catch(error => notify(error, "error"));
|
|
}
|
|
}
|
|
|
|
function updatechatCards({ name, floID, isGroup = false }) {
|
|
if (activeChat.floID && activeChat.floID === clickedContact.floID) {
|
|
getRef('receiver_name').textContent = name
|
|
getRef('chat_name').value = name
|
|
}
|
|
if (!isGroup) {
|
|
getRef('contact_initial').textContent = name.charAt(0)
|
|
if (activeChat.floID && activeChat.floID === clickedContact.floID) {
|
|
getRef('receiver_initial').textContent = name.charAt(0)
|
|
getRef('chat_dp').textContent = name.charAt(0)
|
|
}
|
|
}
|
|
document.querySelectorAll(`.contact[flo-id="${floID}"]`).forEach(contact => {
|
|
if (!isGroup) {
|
|
contact.children[0].textContent = name.charAt(0)
|
|
contact.children[1].textContent = name
|
|
}
|
|
contact.setAttribute('name', name)
|
|
})
|
|
}
|
|
|
|
function toggleSearch(target) {
|
|
getRef(target).classList.toggle('expand')
|
|
if (getRef(target).classList.contains('expand'))
|
|
getRef(target).children[1].focusIn()
|
|
else
|
|
getRef(target).querySelector('sm-input').value = ''
|
|
}
|
|
|
|
let isChatDetailsOpen = false
|
|
function showChatDetails({ show, animate = true }) {
|
|
if (show) {
|
|
if (isChatDetailsOpen) return
|
|
const floID = activeChat.floID
|
|
isChatDetailsOpen = true
|
|
getRef("chat_name").value = getContactName(floID);
|
|
getRef("chat_flo_id").textContent = floID
|
|
if (activeChat.isGroup) {
|
|
getRef("chat_dp").innerHTML = `
|
|
<svg class="icon group-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><path d="M13.61,28.09c-1.63,0-4.72-2.35-5.33-3.58a21.65,21.65,0,0,1-1.35-7.32s-.26-6.07,6.68-6.07a6.38,6.38,0,0,1,6.69,6.07A21.65,21.65,0,0,1,19,24.51c-.62,1.23-3.7,3.58-5.34,3.58"/><path d="M50.39,28.09c-1.64,0-4.72-2.35-5.34-3.58a21.9,21.9,0,0,1-1.35-7.32s-.26-6.07,6.69-6.07a6.37,6.37,0,0,1,6.68,6.07,21.65,21.65,0,0,1-1.35,7.32c-.61,1.23-3.7,3.58-5.33,3.58"/><path d="M32,31.74c-2.21,0-6.37-3.17-7.2-4.83A29.3,29.3,0,0,1,23,17s-.35-8.21,9-8.21c8.68,0,9,8.21,9,8.21a29.3,29.3,0,0,1-1.83,9.88c-.82,1.66-5,4.83-7.2,4.83"/><path d="M48.29,38.58c-4.16-1.83-8.57-3.08-10.34-6.4a12,12,0,0,1-6,3.73,12,12,0,0,1-5.95-3.73c-1.77,3.32-6.18,4.57-10.34,6.4-1.7.71-3.11,9.88-1.13,9.88A33.06,33.06,0,0,0,31.23,53h1.54a33.06,33.06,0,0,0,16.65-4.53C51.4,48.46,50,39.29,48.29,38.58Z"/><path d="M14.82,36.57c.76-.33,1.54-.65,2.3-1,2.49-1,4.85-2,6.22-3.44C21.07,31.23,19,30.25,18,28.41a8.83,8.83,0,0,1-4.41,2.76,8.83,8.83,0,0,1-4.4-2.76c-1.31,2.46-4.58,3.38-7.66,4.74-1.26.52-2.3,7.31-.84,7.31a24.55,24.55,0,0,0,10.86,3.31C11.89,40.81,12.86,37.39,14.82,36.57Z"/><path d="M62.45,33.15c-3.08-1.36-6.35-2.28-7.66-4.74a8.83,8.83,0,0,1-4.4,2.76A8.83,8.83,0,0,1,46,28.41c-1,1.84-3,2.82-5.32,3.76,1.37,1.43,3.73,2.41,6.22,3.44.76.31,1.54.63,2.26,1,2,.83,3,4.25,3.29,7.21a24.55,24.55,0,0,0,10.86-3.31C64.75,40.46,63.71,33.67,62.45,33.15Z"/></svg>
|
|
`
|
|
getRef("last_interaction_time").textContent = `Created ${getFormatedTime(floGlobals.groups[floID].created)}`;
|
|
getRef("chat_type").textContent = `Group FLO ID`;
|
|
|
|
getRef('group_members_list').innerHTML = ''
|
|
floGlobals.groups[floID].members.forEach(member => {
|
|
let isAdmin = floGlobals.groups[floID].admin === member ? true : false
|
|
frag.append(render.contactCard(member, { type: 'contact', contactOnly: true, isAdmin }))
|
|
})
|
|
getRef('group_members_list').append(frag)
|
|
|
|
getRef("group_description_card").classList.remove('hide-completely')
|
|
getRef("group_members_card").classList.remove('hide-completely')
|
|
|
|
getRef('delete_chat_button').classList.add('hide-completely')
|
|
|
|
getRef("group_description").value = floGlobals.groups[floID].description === '' ? 'Add group description' : floGlobals.groups[floID].description;
|
|
|
|
if (floGlobals.groups[activeChat['floID']].admin === myFloID) {
|
|
getRef("chat_name").disabled = false
|
|
getRef('group_description').disabled = false
|
|
getRef('edit_group_button').classList.remove('hide-completely')
|
|
}
|
|
else {
|
|
getRef("chat_name").disabled = true
|
|
getRef('group_description').disabled = true
|
|
getRef('edit_group_button').classList.add('hide-completely')
|
|
}
|
|
}
|
|
else {
|
|
getRef("chat_dp").textContent = getContactName(floID).charAt(0);
|
|
getRef("last_interaction_time").textContent = ``;
|
|
|
|
getRef("chat_type").textContent = `FLO ID`;
|
|
|
|
getRef("group_description_card").classList.add('hide-completely')
|
|
getRef("group_members_card").classList.add('hide-completely')
|
|
|
|
getRef('delete_chat_button').classList.remove('hide-completely')
|
|
}
|
|
|
|
getRef('chat').classList.add('expand-side-panel')
|
|
getRef('chat_left').classList.add('hide-on-medium')
|
|
getRef('chat_details_panel').classList.remove('hide-completely')
|
|
animateTo(getRef('chat_details_panel'), [
|
|
{ transform: 'translateX(100%)' },
|
|
{ transform: 'translateX(0)' },
|
|
],
|
|
{
|
|
duration: animate ? 300 : 0,
|
|
easing: 'ease'
|
|
})
|
|
}
|
|
else if (isChatDetailsOpen) {
|
|
isChatDetailsOpen = false
|
|
animateTo(getRef('chat_details_panel'), [
|
|
{ transform: 'translateX(0)' },
|
|
{ transform: 'translateX(100%)' },
|
|
],
|
|
{
|
|
duration: animate ? 150 : 0,
|
|
easing: 'ease'
|
|
}).onfinish = () => {
|
|
getRef('chat').classList.remove('expand-side-panel')
|
|
getRef('chat_left').classList.remove('hide-on-medium')
|
|
getRef('chat_details_panel').classList.add('hide-completely')
|
|
}
|
|
editGroupMembers()
|
|
}
|
|
}
|
|
|
|
function addAsContact() {
|
|
showPopup('add_contact_popup')
|
|
getRef('add_contact_floID').value = clickedContact.floID
|
|
}
|
|
|
|
function markAsUnread() {
|
|
clickedContact.chatCard.classList.add('unread')
|
|
messenger.addMark(clickedContact.floID, 'unread')
|
|
hidePopup()
|
|
}
|
|
|
|
function markAsRead() {
|
|
clickedContact.chatCard.classList.remove('unread')
|
|
messenger.removeMark(clickedContact.floID, 'unread')
|
|
hidePopup()
|
|
}
|
|
|
|
async function clearChat() {
|
|
if (await confirmation('Clear chat?', `Are you sure to clear this chat?`, 'No', "Yes")) {
|
|
messenger.clearChat(clickedContact.floID).then(result => {
|
|
getRef('messages_container').innerHTML = ''
|
|
hidePopup()
|
|
notify('Chat cleared', 'success')
|
|
})
|
|
}
|
|
}
|
|
|
|
async function deleteChat() {
|
|
if (await confirmation('Delete chat?', `Are you sure to delete this chat?`, 'No', "Yes")) {
|
|
messenger.rmChat(clickedContact.floID).then(result => {
|
|
clickedContact.chatCard.remove()
|
|
clickedContact.chatCard = ''
|
|
hidePopup()
|
|
getRef('chat').classList.add('hide-completely')
|
|
notify('Chat deleted', 'success')
|
|
})
|
|
}
|
|
}
|
|
|
|
let isGroupEditable = false
|
|
let isRemovingMember = false
|
|
function editGroupMembers() {
|
|
if (!isChatDetailsOpen && !isGroupEditable) return
|
|
if (isGroupEditable) {
|
|
getRef('group_members_list').querySelectorAll('.contact').forEach(contact => {
|
|
if (contact.classList.contains('selectable')) {
|
|
contact.classList.remove('selectable')
|
|
if (membersToRemove.has(contact.getAttribute('flo-id')))
|
|
removeTick(contact)
|
|
}
|
|
else if (contact.classList.contains('admin')) {
|
|
contact.classList.remove('hide-completely')
|
|
}
|
|
})
|
|
membersToRemove.clear()
|
|
getRef('edit_group_button').textContent = 'Edit'
|
|
getRef('remove_members_tip').classList.add('hide-completely')
|
|
getRef('member_options').classList.add('hide-completely')
|
|
isGroupEditable = false
|
|
isRemovingMember = false
|
|
}
|
|
else {
|
|
getRef('group_members_list').querySelectorAll('.contact').forEach(contact => {
|
|
if (contact.classList.contains('admin')) {
|
|
contact.classList.add('hide-completely')
|
|
}
|
|
else {
|
|
contact.classList.add('selectable')
|
|
}
|
|
})
|
|
getRef('edit_group_button').textContent = 'Done'
|
|
getRef('remove_members_tip').classList.remove('hide-completely')
|
|
getRef('member_options').classList.remove('hide-completely')
|
|
getRef('remove_members_button').classList.add('hide-completely')
|
|
getRef('init_add_members_button').classList.remove('hide-completely')
|
|
isGroupEditable = true
|
|
isRemovingMember = true
|
|
}
|
|
}
|
|
|
|
const membersToRemove = new Set()
|
|
function selectMemberToRemove(contact) {
|
|
const floID = contact.getAttribute('flo-id')
|
|
if (membersToRemove.has(floID)) {
|
|
membersToRemove.delete(floID)
|
|
removeTick(contact)
|
|
}
|
|
else {
|
|
membersToRemove.add(floID)
|
|
addTick(contact)
|
|
}
|
|
if (membersToRemove.size) {
|
|
getRef('remove_members_tip').classList.add('hide-completely')
|
|
getRef('init_add_members_button').classList.add('hide-completely')
|
|
getRef('remove_members_button').classList.remove('hide-completely')
|
|
}
|
|
else {
|
|
getRef('remove_members_tip').classList.remove('hide-completely')
|
|
getRef('init_add_members_button').classList.remove('hide-completely')
|
|
getRef('remove_members_button').classList.add('hide-completely')
|
|
}
|
|
}
|
|
|
|
const membersToAdd = new Set()
|
|
function selectMemberToAdd(contact) {
|
|
const floID = contact.getAttribute('flo-id')
|
|
if (membersToAdd.has(floID)) {
|
|
membersToAdd.delete(floID)
|
|
removeTick(contact)
|
|
}
|
|
else {
|
|
membersToAdd.add(floID)
|
|
addTick(contact)
|
|
}
|
|
if (membersToAdd.size) {
|
|
getRef('add_members_button').disabled = false
|
|
}
|
|
else {
|
|
getRef('add_members_button').disabled = true
|
|
}
|
|
}
|
|
|
|
function addTick(contact, options = {}) {
|
|
const { animate = true } = options
|
|
contact.classList.add('selected')
|
|
const initial = contact.querySelector('.initial')
|
|
const tick = document.createElement('div');
|
|
tick.innerHTML = `
|
|
<svg class="icon" viewBox="0 0 64 64">
|
|
<polyline points="0.35 31.82 21.45 52.98 63.65 10.66"/>
|
|
</svg>
|
|
`
|
|
tick.classList.add('tick')
|
|
initial.append(tick)
|
|
if (animate)
|
|
tick.animate(
|
|
[
|
|
{ transform: 'scale(0)' },
|
|
{ transform: 'scale(1)' },
|
|
],
|
|
{
|
|
duration: 300,
|
|
fill: 'forwards',
|
|
easing: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)'
|
|
}
|
|
)
|
|
}
|
|
function removeTick(contact) {
|
|
contact.classList.remove('selected')
|
|
const tick = contact.querySelector('.tick')
|
|
tick.animate([
|
|
{ transform: 'scale(1)' },
|
|
{ transform: 'scale(0)' },
|
|
],
|
|
{
|
|
duration: 150,
|
|
fill: 'forwards',
|
|
}
|
|
).onfinish = () => {
|
|
tick.remove()
|
|
}
|
|
}
|
|
function addCross(contact) {
|
|
const cross = document.createElement('div');
|
|
cross.innerHTML = `
|
|
<svg class="icon" viewBox="0 0 64 64">
|
|
<title>Remove</title>
|
|
<line x1="64" y1="0" x2="0" y2="64"/>
|
|
<line x1="64" y1="64" x2="0" y2="0"/>
|
|
</svg>
|
|
`
|
|
cross.classList.add('tick')
|
|
contact.append(cross)
|
|
}
|
|
|
|
document.getElementById('add_members_button').addEventListener('clicked', addGroupMembers)
|
|
|
|
function addGroupMembers() {
|
|
messenger.addGroupMembers(activeChat.floID, [...membersToAdd])
|
|
.then(res => {
|
|
membersToAdd.forEach(member => {
|
|
frag.append(render.contactCard(member, { type: 'contact', contactOnly: true }))
|
|
})
|
|
getRef('group_members_list').append(frag)
|
|
hidePopup()
|
|
})
|
|
.catch(err => console.log(err))
|
|
}
|
|
|
|
function removeGroupMembers() {
|
|
messenger.rmGroupMembers(activeChat.floID, [...membersToRemove])
|
|
.then(res => {
|
|
getRef('group_members_list').querySelectorAll('.selected').forEach(contact => {
|
|
contact.remove()
|
|
})
|
|
editGroupMembers()
|
|
})
|
|
.catch(err => console.log(err))
|
|
}
|
|
|
|
function showPage(targetPage, subpage) {
|
|
if (subpage) {
|
|
document.querySelectorAll('.sub-page').forEach(page => page.classList.add('hide-completely'))
|
|
document.querySelectorAll(`.navbar-item`).forEach(item => item.classList.remove('active'))
|
|
const targetButton = document.querySelector(`.navbar-item[data-target="${targetPage}"]`)
|
|
targetButton.classList.add('active')
|
|
if (activePage.page) {
|
|
activePage.page.classList.add('hide-completely')
|
|
activePage.button.classList.remove('active')
|
|
}
|
|
activePage.button = targetButton
|
|
activePage.page = getRef(targetPage);
|
|
if (getRef('main_page').classList.contains('hide-completely')) {
|
|
document.querySelectorAll('.page').forEach(page => page.classList.add('hide-completely'))
|
|
getRef('main_page').classList.remove('hide-completely')
|
|
}
|
|
if (getRef('main_navbar').classList.contains('no-transformations')) {
|
|
getRef('main_navbar').classList.remove('no-transformations')
|
|
getRef('navbar_backdrop').classList.add('hide')
|
|
}
|
|
}
|
|
else {
|
|
document.querySelectorAll('.page').forEach(page => page.classList.add('hide-completely'))
|
|
switch (targetPage) {
|
|
case 'sign_in_page':
|
|
break;
|
|
}
|
|
}
|
|
getRef(targetPage).classList.remove('hide-completely')
|
|
}
|
|
|
|
function initOnBoarding() {
|
|
console.log('called')
|
|
showFrame(1)
|
|
}
|
|
|
|
function showFrame(frameNo) {
|
|
if (getRef('on_boarding_page').classList.contains('hide-completely')) {
|
|
showPage('on_boarding_page')
|
|
}
|
|
const frames = ['frame_1', 'frame_2', 'frame_3']
|
|
document.querySelectorAll('.frame').forEach(frame => frame.classList.add('hide-completely'))
|
|
getRef(frames[frameNo - 1]).classList.remove('hide-completely')
|
|
switch (frames[frameNo - 1]) {
|
|
case 'frame_2':
|
|
getRef('first_pin').focusIn()
|
|
break;
|
|
}
|
|
}
|
|
|
|
async function setPin() {
|
|
try {
|
|
const firstPin = getRef('first_pin').value
|
|
const confirmPin = getRef('confirm_pin').value
|
|
if (firstPin === confirmPin) {
|
|
let value = getRef('confirm_pin').value
|
|
getRef('pin_error').classList.add('hide-completely')
|
|
if (isPinSet) {
|
|
floDapps.securePrivKey(value).then(result => {
|
|
notify("Pin changed", 'success');
|
|
getRef('first_pin').clear()
|
|
getRef('confirm_pin').clear()
|
|
})
|
|
}
|
|
else {
|
|
floDapps.securePrivKey(value).then(result => {
|
|
getRef('first_pin').clear()
|
|
getRef('confirm_pin').clear()
|
|
isPinSet = true;
|
|
showPage('chat_page', true)
|
|
})
|
|
}
|
|
}
|
|
else {
|
|
getRef('pin_error').classList.remove('hide-completely')
|
|
getRef('pin_error').animate(
|
|
[
|
|
{
|
|
transform: 'translateX(0)'
|
|
},
|
|
{
|
|
transform: 'translateX(-1rem)'
|
|
},
|
|
{
|
|
transform: 'translateX(1rem)'
|
|
},
|
|
{
|
|
transform: 'translateX(-0.5rem)'
|
|
},
|
|
{
|
|
transform: 'translateX(0.5rem)'
|
|
},
|
|
{
|
|
transform: 'translateX(0)'
|
|
}
|
|
],
|
|
{
|
|
duration: 600,
|
|
easing: 'ease'
|
|
}
|
|
)
|
|
}
|
|
}
|
|
catch (error) {
|
|
notify("Setting pin Failed", "error", error)
|
|
}
|
|
}
|
|
|
|
document.addEventListener('colorselected', e => {
|
|
const color = e.detail.value
|
|
localStorage.setItem(`accent-color${myFloID}`, color);
|
|
document.body.style.setProperty('--accent-color', color);
|
|
})
|
|
|
|
document.getElementById('select_bg_image').addEventListener('change', function (e) {
|
|
console
|
|
compactIDB.writeData('userSettings', this.files[0], 'bgImage')
|
|
.then(async res => {
|
|
setBgImage()
|
|
notify('Background applied', 'success')
|
|
})
|
|
.catch(err => console.error(err))
|
|
})
|
|
|
|
async function setBgImage() {
|
|
try {
|
|
const image = await compactIDB.readData('userSettings', 'bgImage')
|
|
if (image) {
|
|
const url = URL.createObjectURL(image)
|
|
getRef('chat').style.background = `linear-gradient(rgba(var(--foreground-color), 0.6), rgba(var(--foreground-color), 0.6)), url(${url}) center no-repeat`
|
|
getRef('mail').style.background = `linear-gradient(rgba(var(--foreground-color), 0.6), rgba(var(--foreground-color), 0.6)), url(${url}) center no-repeat`
|
|
getRef('chat_preview').style.background = `linear-gradient(rgba(var(--foreground-color), 0.6), rgba(var(--foreground-color), 0.6)), url(${url}) center no-repeat`
|
|
getRef('chat').style.backgroundSize = 'cover'
|
|
getRef('mail').style.backgroundSize = 'cover'
|
|
getRef('chat_preview').style.backgroundSize = 'cover'
|
|
getRef('chat').classList.add('has-bg-image')
|
|
getRef('mail').classList.add('has-bg-image')
|
|
getRef('chat_preview').classList.add('has-bg-image')
|
|
getRef('selected_bg_preview').firstElementChild.src = url
|
|
getRef('selected_bg_preview').classList.add('bg-preview--selected')
|
|
getRef('selected_bg_preview').classList.remove('hide-completely')
|
|
getRef('default_bg_preview').classList.remove('bg-preview--selected')
|
|
getRef('select_bg_button').textContent = 'Change background'
|
|
}
|
|
}
|
|
catch (err) {
|
|
console.error(err)
|
|
}
|
|
}
|
|
|
|
function setDefaultBg() {
|
|
getRef('chat').style.background = ``
|
|
getRef('mail').style.background = ``
|
|
getRef('chat_preview').style.background = ``
|
|
getRef('chat').classList.remove('has-bg-image')
|
|
getRef('mail').classList.remove('has-bg-image')
|
|
getRef('chat_preview').classList.remove('has-bg-image')
|
|
getRef('selected_bg_preview').classList.remove('bg-preview--selected')
|
|
getRef('default_bg_preview').classList.add('bg-preview--selected')
|
|
notify('Default background applied', 'success')
|
|
}
|
|
|
|
</script>
|
|
</body>
|
|
|
|
</html> |