Feat (user): Add Shift + Enter shortcut to add new line while typing message

UI (user): Merge dm, group and notes into one chat

UI (user): Unknown contact now shows FLO ID instead of 'Unknown'

- Other miron UI chages
This commit is contained in:
sairaj mote 2021-01-19 03:02:59 +05:30
parent 37756ff554
commit be0d18f8e5
4 changed files with 295 additions and 227 deletions

View File

@ -577,15 +577,16 @@ sm-button[variant=primary] .icon {
padding: 0.8rem 1.5rem;
align-items: center;
flex-shrink: 0;
}
.contact.chat {
grid-template-columns: auto 1fr auto;
grid-template-areas: "dp . menu" "dp . .";
overflow: hidden;
}
.contact:not(.chat) {
grid-template-columns: auto 1fr;
grid-template-areas: "dp .";
}
.contact.chat, .contact.group {
grid-template-columns: auto 1fr auto;
grid-template-areas: "dp . time" "dp . menu";
}
.contact .initial {
grid-area: dp;
}
@ -593,9 +594,11 @@ sm-button[variant=primary] .icon {
font-size: 1em;
font-weight: 500;
color: rgba(var(--text-color), 0.8);
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.contact .last-message {
grid-column: 2/4;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
@ -603,11 +606,16 @@ sm-button[variant=primary] .icon {
font-size: 0.9em;
color: rgba(var(--text-color), 0.9);
}
.contact .menu {
grid-area: menu;
justify-self: flex-end;
padding: 0.2rem;
fill: rgba(var(--text-color), 1);
}
.contact .time {
font-weight: 400;
opacity: 0.8;
grid-area: menu;
align-self: flex-start;
grid-area: time;
}
#contact_details_popup > .flex:first-of-type {
@ -644,6 +652,7 @@ sm-button[variant=primary] .icon {
border-radius: 0.5rem;
max-width: 30ch;
padding: 0.6rem 1.2rem;
overflow-wrap: break-word;
}
#contact_details_popup #contact_name:focus {
outline: none;
@ -666,8 +675,7 @@ sm-button[variant=primary] .icon {
color: #111;
}
.mail-card.unread::before,
.contact.unread .initial::before {
.mail-card.unread::before {
content: "";
position: absolute;
padding: 0.4rem;
@ -677,6 +685,23 @@ sm-button[variant=primary] .icon {
background: var(--accent-color);
}
.contact .initial::after {
content: "";
position: absolute;
height: calc(100% + 0.8rem);
width: calc(100% + 0.8rem);
border: solid var(--accent-color) 0.2rem;
border-radius: 100%;
transform: scale(0.8);
opacity: 0;
transition: transform 0.3s, opacity 0.3s;
}
.contact.unread .initial::after {
transform: scale(1);
opacity: 1;
}
.mail-card.unread .time,
.contact.unread .time {
color: var(--accent-color);
@ -815,7 +840,7 @@ sm-button[variant=primary] .icon {
justify-content: flex-start;
flex-direction: row;
flex: none;
padding: 1rem 1.2rem;
padding: 1rem;
}
#main_navbar .navbar-item .icon {
height: 1.2rem;
@ -887,7 +912,7 @@ sm-button[variant=primary] .icon {
background: rgba(var(--foreground-color), 1);
}
#contacts #all_contacts .header {
padding-bottom: 1rem;
padding-top: 0.7rem;
}
#contacts .option {
padding: 1rem 1.5rem;
@ -936,9 +961,10 @@ sm-button[variant=primary] .icon {
overflow-y: hidden;
}
#contacts .header, #mails .header, #settings_page .header {
padding: 1rem 1.5rem 0 1.5rem;
padding: 1rem 1.5rem 1rem 1.5rem;
position: relative;
gap: 0.5rem;
min-height: 4rem;
}
#contacts .header sm-tab::part(tab), #mails .header sm-tab::part(tab), #settings_page .header sm-tab::part(tab) {
padding: 0.8rem 1rem;
@ -948,28 +974,16 @@ sm-button[variant=primary] .icon {
width: 100%;
padding: 0.7rem 1.5rem;
background: rgba(var(--foreground-color), 1);
transform: scale(0.9);
opacity: 0;
pointer-events: none;
transition: opacity 0.3s;
transition: opacity 0.3s, transform 0.3s;
}
#contacts .header .expanding-search.expand, #mails .header .expanding-search.expand, #settings_page .header .expanding-search.expand {
transform: none;
opacity: 1;
pointer-events: all;
}
#contacts .header .expanding-search.expand sm-input,
#contacts .header .expanding-search.expand .back, #mails .header .expanding-search.expand sm-input,
#mails .header .expanding-search.expand .back, #settings_page .header .expanding-search.expand sm-input,
#settings_page .header .expanding-search.expand .back {
transform: none;
}
#contacts .header .expanding-search sm-input,
#contacts .header .expanding-search .back, #mails .header .expanding-search sm-input,
#mails .header .expanding-search .back, #settings_page .header .expanding-search sm-input,
#settings_page .header .expanding-search .back {
transform: translateX(0.5rem);
transition: transform 0.3s;
}
#contacts .header sm-input, #mails .header sm-input, #settings_page .header sm-input {
margin: 0;
width: 100%;
@ -981,10 +995,11 @@ sm-button[variant=primary] .icon {
}
#contacts .header sm-input::part(input), #mails .header sm-input::part(input), #settings_page .header sm-input::part(input) {
border-radius: 3rem;
padding: 0.3rem 0.8rem;
padding: 0.2rem 0.8rem;
}
#contacts .header h4, #mails .header h4, #settings_page .header h4 {
text-transform: capitalize;
font-weight: 500;
}
#contacts .header .icon, #mails .header .icon, #settings_page .header .icon {
-webkit-tap-highlight-color: transparent;
@ -1023,10 +1038,12 @@ sm-button[variant=primary] .icon {
}
#chat {
height: 100vh;
height: 100%;
}
#chat header {
padding: 0.5rem 1rem;
padding: 1rem;
min-height: 4rem;
grid-template-columns: auto 1fr auto;
}
#chat header .back-button {
padding: 0.1rem;
@ -1037,11 +1054,18 @@ sm-button[variant=primary] .icon {
#chat header .initial {
cursor: pointer;
margin-right: 1rem;
height: 2.2rem;
width: 2.2rem;
}
#chat header h4 {
font-weight: 500;
font-size: 0.9rem;
}
#chat header #receiver_name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
#chat #scroll_to_bottom {
position: fixed;
right: 0;
@ -1057,7 +1081,7 @@ sm-button[variant=primary] .icon {
transform: scale(0);
transition: transform 0.3s;
}
#chat footer #toggle_emoji {
#chat footer #emoji_toggle {
align-self: center;
padding: 0.6rem;
width: 2.6rem;
@ -1065,10 +1089,10 @@ sm-button[variant=primary] .icon {
border-radius: 2rem;
cursor: pointer;
}
#chat footer #toggle_emoji path {
#chat footer #emoji_toggle path {
fill: rgba(var(--text-color), 0.5);
}
#chat footer #toggle_emoji.active path {
#chat footer #emoji_toggle.active path {
fill: var(--accent-color);
}
#chat footer .flex {
@ -1113,7 +1137,7 @@ sm-button[variant=primary] .icon {
align-items: center;
gap: 0.5rem;
width: 100%;
font-size: 0.9rem;
font-size: 0.92rem;
max-width: max-content;
margin-bottom: 0.2rem;
margin-top: 0.8rem;
@ -1131,7 +1155,7 @@ sm-button[variant=primary] .icon {
hyphens: auto;
white-space: pre-wrap;
padding: 0.6em 1em;
line-height: 1.5;
line-height: 1.6;
}
#chat .message .message-body a {
color: inherit;
@ -1218,7 +1242,7 @@ sm-button[variant=primary] .icon {
font-size: 2.6rem;
}
#chat_container {
#messages_container {
flex: 1;
padding: 0 1rem;
}
@ -1281,8 +1305,8 @@ sm-button[variant=primary] .icon {
stroke: rgba(var(--text-color), 0.4);
}
#messages_container,
#chat_container,
#dm_container,
#inbox_mail_container,
#sent_mail_container,
#mail {
@ -1292,16 +1316,16 @@ sm-button[variant=primary] .icon {
overflow-y: auto;
}
#dm_container:empty {
#chat_container:empty {
display: none;
}
#dm_container:not(:empty) ~ .empty-state {
#chat_container:not(:empty) ~ .empty-state {
display: none;
}
#dm_container {
padding-bottom: 5rem;
#chat_container {
padding-bottom: 6rem;
}
sm-tab-panels {
@ -1461,18 +1485,11 @@ sm-panel {
transition: transform 0.3s, opacity 0.3s;
}
#dm_container,
#chat_container,
#contact_container {
gap: 0.2rem;
}
#chat header {
padding: 1rem;
}
#chat header .initial {
width: 2rem;
height: 2rem;
}
#chat .message {
width: fit-content;
max-width: 90%;
@ -1554,7 +1571,7 @@ sm-panel {
}
#main_navbar .navbar-item {
margin: 0 0.5rem;
border-radius: 0.5rem;
border-radius: 0.8rem;
}
#main_navbar .navbar-item .icon {
margin-right: 0;
@ -1637,9 +1654,9 @@ sm-panel {
}
#chat header {
padding: 0.5rem 1.5rem;
padding: 0.8rem 1.5rem;
}
#chat #chat_container {
#chat #messages_container {
padding: 1rem 5rem;
}
}
@ -1649,17 +1666,21 @@ sm-panel {
cursor: pointer;
}
.contact sm-menu {
.contact .menu {
opacity: 0;
transition: opacity 0.3s;
}
.contact:hover sm-menu,
sm-menu:focus-within {
.contact:hover .menu {
opacity: 1;
}
.emoji:hover {
background: rgba(var(--text-color), 0.1);
}
}
@media (hover: none) {
.contact .menu {
display: none;
}
}

2
css/main.min.css vendored

File diff suppressed because one or more lines are too long

View File

@ -526,15 +526,17 @@ sm-button[variant="primary"]{
padding: 0.8rem 1.5rem;
align-items: center;
flex-shrink: 0;
&.chat{
grid-template-columns: auto 1fr auto;
grid-template-areas: 'dp . menu'
'dp . .';
}
overflow: hidden;
&:not(.chat){
grid-template-columns: auto 1fr;
grid-template-areas: 'dp .';
}
&.chat,
&.group{
grid-template-columns: auto 1fr auto;
grid-template-areas: 'dp . time'
'dp . menu';
}
.initial{
grid-area: dp;
}
@ -542,9 +544,11 @@ sm-button[variant="primary"]{
font-size: 1em;
font-weight: 500;
color: rgba(var(--text-color), 0.8);
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.last-message{
grid-column: 2/4;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
@ -552,11 +556,16 @@ sm-button[variant="primary"]{
font-size: 0.9em;
color: rgba(var(--text-color), 0.9);
}
.menu{
grid-area: menu;
justify-self: flex-end;
padding: 0.2rem;
fill: rgba(var(--text-color), 1);
}
.time{
font-weight: 400;
opacity: 0.8;
grid-area: menu;
align-self: flex-start;
grid-area: time;
}
}
#contact_details_popup{
@ -596,6 +605,7 @@ sm-button[variant="primary"]{
border-radius: 0.5rem;
max-width: 30ch;
padding: 0.6rem 1.2rem;
overflow-wrap: break-word;
&:focus{
outline: none;
background: rgba(var(--text-color), 0.1);
@ -616,8 +626,7 @@ sm-button[variant="primary"]{
background: rgb(255, 253, 141);
color: #111;
}
.mail-card.unread::before,
.contact.unread .initial::before{
.mail-card.unread::before{
content: '';
position: absolute;
padding: 0.4rem;
@ -626,6 +635,21 @@ sm-button[variant="primary"]{
left: 0;
background: var(--accent-color);
}
.contact .initial::after{
content: '';
position: absolute;
height: calc(100% + 0.8rem);
width: calc(100% + 0.8rem);
border: solid var(--accent-color) 0.2rem;
border-radius: 100%;
transform: scale(0.8);
opacity: 0;
transition: transform 0.3s, opacity 0.3s;
}
.contact.unread .initial::after{
transform: scale(1);
opacity: 1;
}
.mail-card.unread,
.contact.unread{
.time{
@ -757,7 +781,7 @@ sm-button[variant="primary"]{
justify-content: flex-start;
flex-direction: row;
flex: none;
padding: 1rem 1.2rem;
padding: 1rem;
.icon{
height: 1.2rem;
width: 2.4rem;
@ -827,7 +851,7 @@ sm-button[variant="primary"]{
z-index: 1;
background: rgba(var(--foreground-color), 1);
.header{
padding-bottom: 1rem;
padding-top: 0.7rem;
}
}
.option{
@ -874,9 +898,10 @@ sm-button[variant="primary"]{
overflow-y: hidden;
.header{
//background: rgba(var(--text-color), 0.06);
padding: 1rem 1.5rem 0 1.5rem;
padding: 1rem 1.5rem 1rem 1.5rem;
position: relative;
gap: 0.5rem;
min-height: 4rem;
// margin-bottom: 1rem;
sm-tab::part(tab){
padding: 0.8rem 1rem;
@ -886,22 +911,14 @@ sm-button[variant="primary"]{
width: 100%;
padding: 0.7rem 1.5rem;
background: rgba(var(--foreground-color), 1);
transform: scale(0.9);
opacity: 0;
pointer-events: none;
transition: opacity 0.3s;
transition: opacity 0.3s, transform 0.3s;
&.expand{
transform: none;
opacity: 1;
pointer-events: all;
sm-input,
.back{
transform: none;
}
}
sm-input,
.back{
transform: translateX(0.5rem);
transition: transform 0.3s;
}
}
sm-input{
@ -915,10 +932,11 @@ sm-button[variant="primary"]{
}
sm-input::part(input){
border-radius: 3rem;
padding: 0.3rem 0.8rem;
padding: 0.2rem 0.8rem;
}
h4{
text-transform: capitalize;
font-weight: 500;
}
.icon{
-webkit-tap-highlight-color: transparent;
@ -959,9 +977,11 @@ sm-button[variant="primary"]{
overflow-y: hidden;
}
#chat{
height: 100vh;
height: 100%;
header{
padding: 0.5rem 1rem;
padding: 1rem;
min-height: 4rem;
grid-template-columns: auto 1fr auto;
.back-button{
padding: 0.1rem;
stroke-width: 8;
@ -971,11 +991,18 @@ sm-button[variant="primary"]{
.initial{
cursor: pointer;
margin-right: 1rem;
height: 2.2rem;
width: 2.2rem;
}
h4{
font-weight: 500;
font-size: 0.9rem;
}
#receiver_name{
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
#scroll_to_bottom{
position: fixed;
@ -993,7 +1020,7 @@ sm-button[variant="primary"]{
transition: transform 0.3s;
}
footer{
#toggle_emoji{
#emoji_toggle{
align-self: center;
padding: 0.6rem;
width: 2.6rem;
@ -1050,7 +1077,7 @@ sm-button[variant="primary"]{
align-items: center;
gap: 0.5rem;
width: 100%;
font-size: 0.9rem;
font-size: 0.92rem;
max-width: max-content;
margin-bottom: 0.2rem;
margin-top: 0.8rem;
@ -1067,7 +1094,7 @@ sm-button[variant="primary"]{
hyphens: auto;
white-space: pre-wrap;
padding: 0.6em 1em;
line-height: 1.5;
line-height: 1.6;
a{
color: inherit;
}
@ -1158,7 +1185,7 @@ sm-button[variant="primary"]{
font-size: 2.6rem;
}
}
#chat_container{
#messages_container{
flex: 1;
padding: 0 1rem;
}
@ -1217,8 +1244,8 @@ sm-button[variant="primary"]{
stroke-width: 16;
stroke: rgba(var(--text-color), 0.4);
}
#messages_container,
#chat_container,
#dm_container,
#inbox_mail_container,
#sent_mail_container,
#mail{
@ -1227,14 +1254,14 @@ sm-button[variant="primary"]{
height: 100%;
overflow-y: auto;
}
#dm_container:empty{
#chat_container:empty{
display: none;
}
#dm_container:not(:empty) ~ .empty-state{
#chat_container:not(:empty) ~ .empty-state{
display: none;
}
#dm_container{
padding-bottom: 5rem;
#chat_container{
padding-bottom: 6rem;
}
sm-tab-panels{
overflow: hidden auto;
@ -1384,18 +1411,11 @@ sm-panel{
z-index: 2;
transition: transform 0.3s, opacity 0.3s;
}
#dm_container,
#chat_container,
#contact_container{
gap: 0.2rem;
}
#chat{
header{
padding: 1rem;
.initial{
width: 2rem;
height: 2rem;
}
}
.message{
width: fit-content;
max-width: 90%;
@ -1472,7 +1492,7 @@ sm-panel{
}
.navbar-item{
margin: 0 0.5rem;
border-radius: 0.5rem;
border-radius: 0.8rem;
.icon{
margin-right: 0;
}
@ -1549,9 +1569,9 @@ sm-panel{
}
#chat{
header{
padding: 0.5rem 1.5rem;
padding: 0.8rem 1.5rem;
}
#chat_container{
#messages_container{
padding: 1rem 5rem;
}
}
@ -1561,15 +1581,19 @@ sm-panel{
background: rgba(var(--text-color), 0.06);
cursor: pointer;
}
.contact sm-menu{
.contact .menu{
opacity: 0;
transition: opacity 0.3s;
}
.contact:hover sm-menu,
sm-menu:focus-within{
.contact:hover .menu{
opacity: 1;
}
.emoji:hover{
background: rgba(var(--text-color), 0.1);
}
}
@media (hover: none){
.contact .menu{
display: none;
}
}

View File

@ -11,7 +11,7 @@
<link rel="stylesheet" href="css/main.min.css">
</head>
<body data-theme="light" onload="onLoadStartUp()">
<body data-theme="light" 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">
@ -277,12 +277,9 @@
</template>
<template id="contact_template">
<div class="contact interact">
<div class="contact">
<div class="initial flex align-center"></div>
<h4 class="name"></h4>
<!--<sm-menu align-options="right">
<sm-menu-option class="send-mail-option">Send mail</sm-menu-option>
</sm-menu>-->
</div>
</template>
@ -339,7 +336,7 @@
<line y1="32" x2="64" y2="32"/>
<line y1="53.34" x2="64" y2="53.34"/>
</svg>
<h4>Chat</h4>
<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"/>
@ -363,11 +360,6 @@
</svg>
</sm-input>
</div>
<sm-tab-header target="chat_panels">
<sm-tab>Direct</sm-tab>
<sm-tab>Groups</sm-tab>
<sm-tab>Notes</sm-tab>
</sm-tab-header>
</header>
<sm-button variant="primary" id="new_message_button" onclick="showContacts()" class="fab round">
<svg xmlns="http://www.w3.org/2000/svg"class="icon" viewBox="0 0 64 64">
@ -377,24 +369,18 @@
</svg>
New chat
</sm-button>
<sm-tab-panels id="chat_panels">
<sm-panel>
<div id="dm_container" class="flex"></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>Add a contact for getting started</h4>
<p class="light-text">Tap/click on added contact to start a conversation</p>
</div>
</sm-panel>
<sm-panel></sm-panel>
<sm-panel></sm-panel>
</sm-tab-panels>
<div id="chat_container" class="flex"></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>Add a contact for getting started</h4>
<p class="light-text">Tap/click on added contact to start a conversation</p>
</div>
<div id="all_contacts" class="flex direction-column hide-completely">
<header class="grid header">
<div class="flex align-center">
@ -440,17 +426,19 @@
</div>
</div>
<div id="chat" class="flex direction-column hide-on-mobile hide-completely">
<header class="flex align-center">
<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 id="receiver_initial" onclick="showPopup('contact_details_popup')" class="initial flex align-center"></div>
<h4 id="receiver_name"></h4>
<div class="flex align-center interact" onclick="showPopup('contact_details_popup')">
<div id="receiver_initial" class="initial flex align-center"></div>
<h4 id="receiver_name"></h4>
</div>
</header>
<h5 id="warn_no_encryption">Messages are not encrypted until receiver replies</h5>
<section id="chat_container" class="flex direction-column">
<section id="messages_container" class="flex direction-column">
</section>
<svg id="scroll_to_bottom" onclick="scrollToBottom()" class="icon" viewBox="0 0 64 64">
<title></title>
@ -459,7 +447,7 @@
<footer class="grid">
<div id="emoji_picker" class="hide-completely"></div>
<div class="flex">
<svg id="toggle_emoji" onclick="toggleEmoji(this)" 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>
<svg id="emoji_toggle" onclick="toggleEmoji()" 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"/>
@ -777,7 +765,7 @@
return color;
}
const selectedColors = ['#FF1744', '#F50057', '#8E24AA', '#5E35B1', '#3F51B5', '#3D5AFE', '#00B0FF', '#00BCD4', '#1DE9B6', '#66BB6A', '#8BC34A', '#FBC02D', '#FF6F00', '#FF9100', '#FF3D00']
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)]
}
@ -984,32 +972,32 @@
}
})
function createRipple(event, target) {
const circle = document.createElement("span");
const diameter = Math.max(target.clientWidth, target.clientHeight);
const radius = diameter / 2;
circle.style.width = circle.style.height = `${diameter}px`;
circle.style.left = `${event.clientX - (target.getBoundingClientRect().left + radius)}px`;
circle.style.top = `${event.clientY - (target.getBoundingClientRect().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 createRipple(event, target) {
const circle = document.createElement("span");
const diameter = Math.max(target.clientWidth, target.clientHeight);
const radius = diameter / 2;
circle.style.width = circle.style.height = `${diameter}px`;
circle.style.left = `${event.clientX - (target.getBoundingClientRect().left + radius)}px`;
circle.style.top = `${event.clientY - (target.getBoundingClientRect().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')
@ -1107,12 +1095,13 @@
</script>
<script id="onLoadStartUp">
function onLoadStartUp() {
document.body.classList.remove('hide-completely')
privKeyNotSecured = true;
//display loading screen
getRef('loading_page').classList.remove("hide-completely")
//clear Rendered Elements
let elementsToReset = ['inbox_mail_container', 'sent_mail_container', 'contacts_container', 'dm_container', 'chat_container',
let elementsToReset = ['inbox_mail_container', 'sent_mail_container', 'contacts_container', 'chat_container', 'messages_container',
'receiver_name', 'mail_contact_list'
]
//, "backup_info"
@ -1133,6 +1122,7 @@
floGlobals.appendix = data.appendix;
floGlobals.groups = data.groups;
floGlobals.chats = data.chats
floGlobals['marked'] = data.marked
renderContactList(floGlobals.contacts)
renderChatList()
renderMailList(data.mails, false)
@ -1235,14 +1225,15 @@
card.querySelector('.mail-content').textContent = content
return card
},
async contactCard(floID, name, type, prepend = false){
async contactCard(floID, name, type, options = {}){
let {prepend = false, markUnread = false} = options
let card = getRef('contact_template').content.cloneNode(true),
cardContainer = card.firstElementChild
cardContainer.setAttribute("name", name || 'Unknown')
cardContainer.setAttribute("flo-id", floID)
cardContainer.querySelector('.name').textContent = name || 'Unknown'
cardContainer.querySelector('.name').textContent = name || floID
let initial = card.querySelector('.initial')
initial.textContent = name ? name.charAt(0) : 'U'
initial.textContent = name ? name.charAt(0) : floID.charAt(0)
let color = contactColor(floID)
//cardContainer.setAttribute("text-color", randomColor.primary)
cardContainer.setAttribute("background-color", color)
@ -1255,23 +1246,36 @@
}
else{
//render chat card for newly added contact
cardContainer.classList.add('chat')
if(prepend){
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){
lastMessage = Object.values(chat).pop()
}
else if(type === 'group'){
lastMessage.time = floGlobals.groups[floID].created
}
cardContainer.innerHTML += `
<h5 class="last-message"></h5>
<h5 class="time"></h5>`
getRef('dm_container').prepend(card);
getRef('dm_container').children[0].click()
<p class="last-message">${lastMessage.message}</p>
<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){
getRef('chat_container').prepend(card);
getRef('chat_container').children[0].click()
}
else{
messenger.getChat(floID).then(chat => {
const lastMessage = Object.values(chat).pop()
cardContainer.innerHTML += `
<p class="last-message">${lastMessage.message}</p>
<h5 class="time">${getFormatedTime(lastMessage.time, true)}</h5>`
})
.catch(error => console.log(error))
getRef('dm_container').append(card);
getRef('chat_container').append(card);
}
}
},
@ -1409,7 +1413,7 @@
}
if(e.target.closest('#search_chats')){
if(e.code === 'ArrowDown'){
for(child of getRef('dm_container').children){
for(child of getRef('chat_container').children){
if(!child.classList.contains('hide-completely')){
child.focus()
break
@ -1446,7 +1450,7 @@
})
const chatScrollInfo = {};
getRef('chat_container').addEventListener('scroll', function() {
getRef('messages_container').addEventListener('scroll', function() {
chatScrollInfo['scrollTop'] = this.scrollTop
chatScrollInfo['scrollheight'] = this.scrollHeight
if((this.scrollHeight > this.clientHeight) && (this.scrollHeight - this.clientHeight - this.scrollTop >= 100)){
@ -1475,7 +1479,7 @@
getRef('search_chats').addEventListener('input', function() {
if(this.value.trim !== ''){
for(child of getRef('dm_container').children) {
for(child of getRef('chat_container').children) {
if(child.getAttribute('name').toUpperCase().indexOf(this.value.toUpperCase()) > -1){
child.classList.remove('hide-completely')
}
@ -1486,7 +1490,7 @@
}
})
document.addEventListener('pointerdown', e => {
document.addEventListener('click', e => {
if(e.target.closest('.sidebar-item')){
let target = e.target.closest('.sidebar-item')
if(target.dataset.target)
@ -1521,9 +1525,6 @@
if(e.target.closest('#new_mail_button')){
showPopup('compose_mail_popup')
}
if(e.target.closest('#chat')){
messenger.removeMark(activeChat['receiver'], "unread");
}
//Detect click on send mail option
if (e.target.closest(".send-mail-option")){
@ -1552,6 +1553,15 @@
}, 0);
}
}
// 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'))){
isEmojiPickerOpen = false
getRef('emoji_toggle').classList.remove('active')
getRef('emoji_picker').classList.add('hide-completely')
getRef('scroll_to_bottom').setAttribute('style', `top: calc(${getRef('messages_container').getBoundingClientRect().height}px - 1.5rem`)
if(!chatScrollInfo.scrolledUp)
scrollToBottom()
}
})
let clickedContact = {}
@ -1562,13 +1572,14 @@
clickedContact['card'] = contact
clickedContact['floID'] = contact.getAttribute("flo-id")
clickedContact['name'] = contact.getAttribute("name")
if(e.target.closest(".initial") || e.target.closest("sm-menu")){
if(e.target.closest(".initial") || e.target.closest(".icon")){
showPopup('contact_details_popup')
}
else if(isCreatingGroup){
// code for adding group members
}
else{
createRipple(e, contact)
contact.classList.remove('unread')
if(activeChat['chatCard'] === contact && window.innerWidth > 640) return
document.title = `FLO Messenger`
@ -1641,6 +1652,7 @@
}
let renderedEmojis = ''
let emojiSectionCounter = 0
let isEmojiPickerOpen = false
let emojiSections = ['Smileys', 'Gestures and Body Parts', 'People and Fantasy', 'Clothing and Accessories', 'Animals & Nature', 'Food & Drink', 'Activity and Sports']
renderedEmojis += `
<section id="recent_emoji_section">
@ -1664,14 +1676,19 @@
emojis += `<span class="emoji">${emoji}</span>`
})
getRef('recent_emoji_section').innerHTML = ''
getRef('recent_emoji_section').innerHTML = emojis
if(recentEmojis.length)
getRef('recent_emoji_section').innerHTML = emojis
}
renderRecentEmojis()
function toggleEmoji(button){
button.classList.toggle('active')
getRef('emoji_toggle').classList.toggle('active')
getRef('emoji_picker').classList.toggle('hide-completely')
getRef('scroll_to_bottom').setAttribute('style', `top: calc(${getRef('chat_container').getBoundingClientRect().height}px - 1.5rem`)
getRef('scroll_to_bottom').setAttribute('style', `top: calc(${getRef('messages_container').getBoundingClientRect().height}px - 1.5rem`)
if(getRef('emoji_picker').classList.contains('hide-completely'))
isEmojiPickerOpen = false
else
isEmojiPickerOpen = true
if(!chatScrollInfo.scrolledUp)
scrollToBottom()
}
@ -1721,7 +1738,7 @@
getRef('all_contacts').classList.remove('hide-completely')
contactsDrawerAnimation = animateTo(getRef('all_contacts'), [
{transform: 'translateY(2rem)'},
{transform: 'none'},
{transform: 'translateY(0)'},
],{
duration: 300,
easing: 'ease'
@ -1800,11 +1817,11 @@
return
}
else{
if(e.code === "Enter" && (e.code === "ShiftLeft" || e.code === "ShiftRight")){
if(e.code === "Enter" && e.shiftKey){
e.preventDefault()
getRef('type_message').value += '\r\n';
}
else if(e.code === "Enter" && isEnterSend){
else if(!e.shiftKey && e.code === "Enter" && isEnterSend){
e.preventDefault()
sendMessage()
}
@ -1906,20 +1923,20 @@
getRef('type_message').focusIn()
let receiver = activeChat['receiver']
let container;
let message = getRef('type_message').value;
let message = getRef('type_message').value.trim();
getRef('type_message').value = ''
if(message.trim() === '') return
if(message === '') return
let time = Date.now()
getRef('chat_container').append(render.messageBubble(receiver, message, time, 'sent', true))
getRef('messages_container').append(render.messageBubble(receiver, message, time, 'sent', true))
scrollToBottom(true)
const contact = getRef('dm_container').querySelector(`.chat[flo-id="${receiver}"]`)
const contact = getRef('chat_container').querySelector(`.chat[flo-id="${receiver}"]`)
if(contact){
if(activeChat['chatCard'] !== getRef('dm_container').children[0]){
if(activeChat['chatCard'] !== getRef('chat_container').children[0]){
const cloneContact = contact.cloneNode(true)
contact.remove()
activeChat['chatCard'] = cloneContact
getRef('dm_container').prepend(cloneContact)
animateTo(getRef('dm_container').children[0], [
getRef('chat_container').prepend(cloneContact)
animateTo(getRef('chat_container').children[0], [
{transform: 'translateY(1rem)'},
{transform: 'none'},
],
@ -1932,7 +1949,7 @@
}
else{
messenger.addChat(receiver)
render.contactCard(receiver, floGlobals.contacts[receiver], 'chat', true)
render.contactCard(receiver, floGlobals.contacts[receiver], 'chat', {prepend: true})
}
messenger.sendMessage(message, receiver).then(data => {
getRef(`${receiver}_${time}`).classList.remove('unconfirmed')
@ -1974,11 +1991,17 @@
}
async function renderChatList() {
getRef('dm_container').innerHTML = ''
for (floID of messenger.getChatOrder().direct){
render.contactCard(floID, floGlobals.contacts[floID], 'chat')
getRef('chat_container').innerHTML = ''
for (floID of messenger.getChatOrder().mixed){
const markUnread = floGlobals.marked[floID]?.[0] === 'unread' ? true : false
let type
if(floGlobals.chats[floID])
type = 'chat'
else if(floGlobals.groups[floID])
type = 'group'
render.contactCard(floID, floGlobals.contacts[floID], type, {markUnread})
}
chatMutationObserver.observe(getRef('chat_container'), {childList: true})
chatMutationObserver.observe(getRef('messages_container'), {childList: true})
}
function renderMarked(data) {
@ -1990,7 +2013,7 @@
}
function scrollToBottom(smooth = false){
getRef('chat_container').scrollTo({top: getRef('chat_container').scrollHeight, behaviour: smooth ? 'smooth': ''})
getRef('messages_container').scrollTo({top: getRef('messages_container').scrollHeight, behaviour: smooth ? 'smooth': ''})
}
let startIndex = 0,
@ -2021,14 +2044,14 @@
//Stops message from rendering in wrong chat window
if(activeChat['receiver'] && activeChat['receiver'] === floID)
frag.append(render.messageBubble(floID, message, time, category))
const contact = getRef('dm_container').querySelector(`.chat[flo-id='${floID}']`)
const contact = getRef('chat_container').querySelector(`.chat[flo-id='${floID}']`)
if (markUnread && contact){
contact.classList.add("unread");
if(activeChat['chatCard'] !== getRef('dm_container').children[0] || contact !== getRef('dm_container').children[0]){
if(contact !== getRef('chat_container').children[0]){
const cloneContact = contact.cloneNode(true)
contact.remove()
getRef('dm_container').prepend(cloneContact)
animateTo(getRef('dm_container').children[0], [
getRef('chat_container').prepend(cloneContact)
animateTo(getRef('chat_container').children[0], [
{transform: 'translateY(1rem)'},
{transform: 'none'},
],
@ -2040,7 +2063,7 @@
}
}
if(updateChatCard){
const chatCard = getRef('dm_container').querySelector(`[flo-id="${floID}"]`)
const chatCard = getRef('chat_container').querySelector(`[flo-id="${floID}"]`)
chatCard.querySelector('.last-message').textContent = message
chatCard.querySelector('.time').textContent = getFormatedTime(time, true)
}
@ -2048,15 +2071,15 @@
endIndex = startIndex
if(!lazyLoad && !reRender){
endIndex = messages.length
getRef('chat_container').append(frag)
getRef('messages_container').append(frag)
if(!chatScrollInfo['scrolledUp'])
scrollToBottom()
}
if(reRender || lazyLoad){
currentDate = null
chatScrollInfo['scrollTop'] = getRef('chat_container').scrollTop
chatScrollInfo['scrollHeight'] = getRef('chat_container').scrollHeight
getRef('chat_container').prepend(frag)
chatScrollInfo['scrollTop'] = getRef('messages_container').scrollTop
chatScrollInfo['scrollHeight'] = getRef('messages_container').scrollHeight
getRef('messages_container').prepend(frag)
}
if(reRender){
scrollToBottom()
@ -2068,10 +2091,10 @@
(mutations, observer) => {
for(const mutation of mutations) {
if (mutation.type === 'childList' && mutation.addedNodes.length) {
chatMessageObserver.observe(getRef('chat_container').firstElementChild)
chatScrollInfo['scrollTop'] += (getRef('chat_container').scrollHeight - chatScrollInfo['scrollHeight'])
chatScrollInfo['scrollHeight'] = getRef('chat_container').scrollHeight
getRef('chat_container').scrollTo({top: chatScrollInfo['scrollTop']})
chatMessageObserver.observe(getRef('messages_container').firstElementChild)
chatScrollInfo['scrollTop'] += (getRef('messages_container').scrollHeight - chatScrollInfo['scrollHeight'])
chatScrollInfo['scrollHeight'] = getRef('messages_container').scrollHeight
getRef('messages_container').scrollTo({top: chatScrollInfo['scrollTop']})
}
}
}
@ -2088,7 +2111,7 @@
)
async function viewConversation(contact) {
getRef('chat_container').innerHTML = ''
getRef('messages_container').innerHTML = ''
let floID = clickedContact['floID'],
name = contact.getAttribute('name'),
textColor = contact.getAttribute('text-color'),
@ -2096,7 +2119,7 @@
activeChat['receiver'] = floID
getRef("receiver_initial").textContent = floGlobals.contacts[floID] ? floGlobals.contacts[floID].charAt(0) : name.charAt(0) || ' ';
getRef("receiver_initial").setAttribute('style', `color: ${textColor}; background-color: ${backgroundColor};`)
getRef("receiver_name").textContent = floGlobals.contacts[floID] || name || ' ';
getRef("receiver_name").textContent = floGlobals.contacts[floID] || floID || ' ';
if (floGlobals.pubKeys[floID])
getRef("warn_no_encryption").classList.add("hide-completely");
else
@ -2268,11 +2291,11 @@
windowSizeObserver.observe(document.body)
function animateTo(element, animation, options){
const anime = element.animate(animation, {...options})
anime.onfinish = () => {
const anime = element.animate(animation, {...options, fill: 'both'})
anime.addEventListener('finish', () => {
anime.commitStyles()
anime.cancel()
}
})
return anime
}