From 69e0333a64dd1f907be643234afb9f9f43fe9245 Mon Sep 17 00:00:00 2001 From: Flemmli97 Date: Tue, 8 Oct 2024 15:24:20 +0200 Subject: [PATCH 1/5] feat unreads --- .../components/messaging/Conversation.svelte | 79 +++++- src/lib/lang/en.json | 4 +- src/lib/layouts/Chatbar.svelte | 3 + src/lib/state/conversation/index.ts | 30 -- src/lib/state/ui/index.ts | 5 +- src/lib/types/index.ts | 2 + src/lib/wasm/RaygunStore.ts | 1 + src/routes/chat/+page.svelte | 264 +++++++++++------- 8 files changed, 242 insertions(+), 146 deletions(-) diff --git a/src/lib/components/messaging/Conversation.svelte b/src/lib/components/messaging/Conversation.svelte index 23e709780..789daf277 100644 --- a/src/lib/components/messaging/Conversation.svelte +++ b/src/lib/components/messaging/Conversation.svelte @@ -1,32 +1,53 @@
@@ -47,6 +83,12 @@
{:else} + {#if unreads && unreads.unread > 0} +
+
+ {$_("chat.newMessageSinceAmount", { values: { amount: unreads.unread, date: $date(unreads.since, { format: "medium" }), time: $time(unreads.since) } })} +
+ {/if}
@@ -85,6 +127,29 @@ justify-content: center; } + .unreads { + position: absolute; + right: 0; + top: 0; + text-align: center; + padding: var(--padding-minimal); + padding-left: calc(var(--padding-minimal) + 30px); + background-color: var(--focus-color); + border-radius: 0 0 var(--border-radius) var(--border-radius); + z-index: 1; + .bookmark { + position: absolute; + top: 0; + left: 0; + position: relative; + height: 60px; + -webkit-transform: rotate(0deg) skew(0deg); + transform: rotate(0deg) skew(0deg); + border-left: 15px solid color-mix(in srgb, var(--focus-color) 40%, #000000); + border-right: 15px solid color-mix(in srgb, var(--focus-color) 40%, #000000); + border-bottom: 15px solid transparent; + } + } .scroll { display: flex; align-items: flex-end; diff --git a/src/lib/lang/en.json b/src/lib/lang/en.json index 49eb73e8b..e63d675ce 100644 --- a/src/lib/lang/en.json +++ b/src/lib/lang/en.json @@ -119,7 +119,9 @@ "show-participants": "Participants", "group-settings": "Settings", "replyTo": "Replying to: {user}", - "attachments-count": "Attachments: {amount}" + "attachments-count": "Attachments: {amount}", + "newMessageSince": "New messages since {time} - {date}", + "newMessageSinceAmount": "{amount} new messages since {time} - {date}" }, "community": { "title": "Satellite Community - General", diff --git a/src/lib/layouts/Chatbar.svelte b/src/lib/layouts/Chatbar.svelte index c57ac3b88..ce27d31ce 100644 --- a/src/lib/layouts/Chatbar.svelte +++ b/src/lib/layouts/Chatbar.svelte @@ -90,6 +90,9 @@ let result = replyTo ? await RaygunStoreInstance.reply(chat.id, replyTo.id, txt) : await RaygunStoreInstance.send(get(Store.state.activeChat).id, text.split("\n"), attachments) result.onSuccess(res => { + UIStore.mutateChat(chat.id, c => { + c.last_view_date = new Date() + }) ConversationStore.addPendingMessages(chat.id, res.message, txt) }) if (!isStickerOrGif) { diff --git a/src/lib/state/conversation/index.ts b/src/lib/state/conversation/index.ts index 5c37fe953..e50f85414 100644 --- a/src/lib/state/conversation/index.ts +++ b/src/lib/state/conversation/index.ts @@ -15,11 +15,6 @@ export type ConversationMessages = { messages: MessageGroup[] } -export type Unreads = { - id: string - count: number -} - class Conversations { /** * INTERNAL! @@ -29,12 +24,10 @@ class Conversations { private conversations: Writable // We use a new writable so they dont get saved to db pendingMsgConversations: Writable<{ [conversation: string]: { [id: string]: PendingMessage } }> - unreads: Writable constructor() { this.conversationsDB = [] this.conversations = writable({}) - this.unreads = writable([]) this.pendingMsgConversations = writable({}) this.loadConversations() this.conversations.subscribe(async convsStore => { @@ -425,29 +418,6 @@ class Conversations { // }, // }) } - - addUnread(chat: string) { - const unreads = get(this.unreads) - const index = unreads.findIndex(u => u.id === chat) - if (index !== -1) { - unreads[index].count++ - } else { - unreads.push({ - id: chat, - count: 1, - }) - } - this.unreads.set(unreads) - } - - clearUnreads(chat: string) { - const unreads = get(this.unreads) - const index = unreads.findIndex(u => u.id === chat) - if (index !== -1) { - unreads[index].count = 0 - } - this.unreads.set(unreads) - } } export const ConversationStore = new Conversations() diff --git a/src/lib/state/ui/index.ts b/src/lib/state/ui/index.ts index 072579e6b..6adf35f17 100644 --- a/src/lib/state/ui/index.ts +++ b/src/lib/state/ui/index.ts @@ -1,9 +1,10 @@ import { TypingIndicator, type Chat, type FontOption } from "$lib/types" import { derived, get, writable, type Writable } from "svelte/store" import { createPersistentState } from ".." -import { EmojiFont, Font, Identicon } from "$lib/enums" +import { EmojiFont, Font, Identicon, Route } from "$lib/enums" import { Store as MainStore } from "../Store" import { mchats } from "$lib/mock/users" +import { page } from "$app/stores" export interface IUIState { color: Writable @@ -142,7 +143,7 @@ class Store { } addNotification(conversationId: string) { - if (get(MainStore.state.activeChat).id !== conversationId) { + if (get(page).route.id !== Route.Chat || get(MainStore.state.activeChat).id !== conversationId) { this.mutateChat(conversationId, chat => { chat.notifications++ }) diff --git a/src/lib/types/index.ts b/src/lib/types/index.ts index bdd91c36d..762a789a7 100644 --- a/src/lib/types/index.ts +++ b/src/lib/types/index.ts @@ -169,6 +169,7 @@ export type Chat = { settings: ChatSettings creator?: string notifications: number + last_view_date: Date users: string[] typing_indicator: TypingIndicator last_message_id: string @@ -282,6 +283,7 @@ export let defaultChat: Chat = { motd: "", unread: 0, notifications: 0, + last_view_date: new Date(), kind: ChatType.DirectMessage, creator: undefined, settings: { diff --git a/src/lib/wasm/RaygunStore.ts b/src/lib/wasm/RaygunStore.ts index dc9ce2317..47e33675a 100644 --- a/src/lib/wasm/RaygunStore.ts +++ b/src/lib/wasm/RaygunStore.ts @@ -505,6 +505,7 @@ class RaygunStore { settings.audio.messageSounds ? Sounds.Notification : undefined ) } + UIStore.addNotification(conversation_id) //TODO move chat to top //TODO handle ping } diff --git a/src/routes/chat/+page.svelte b/src/routes/chat/+page.svelte index 915bb35c5..c3255f681 100644 --- a/src/routes/chat/+page.svelte +++ b/src/routes/chat/+page.svelte @@ -1,7 +1,7 @@ @@ -575,114 +591,127 @@ {#if activeCallInProgress && activeCallDid === $activeChat.id} {/if} - + total + g.messages.length, 0), since: $activeChat.last_view_date, last_viewed: messages[1][0].messages[0].id }}> {#if $activeChat !== null && $activeChat.users.length > 0} - {#if conversation} - {#each $conversation.messages as group} - Store.getUser(v)} let:resolved> - { - previewProfile = resolved - }} - remote={group.details.remote} - username={resolved.name} - subtext={getTimeAgo(group.messages[0].details.at)}> - {#each group.messages as message, idx} - {#if message.inReplyTo} - Store.getUser(v)} let:resolved> - - - {#each message.inReplyTo.text as line} - - {/each} - - - - {/if} - {#if message.text.length > 0 || message.attachments.length > 0} - - 1 || message.attachments.length > 0}> - {#if editing_message === message.id} - edit_message(message.id, editing_text ? editing_text : "")} /> - {:else} - {#each message.text as line} - {#if getValidPaymentRequest(line) != undefined} - - {:else if !line.includes(tempCDN)} - - {:else if line.includes(tempCDN)} -
- -
- {/if} - {/each} - - {#if message.attachments.length > 0} - {#each message.attachments as attachment} - {#if attachment.kind === MessageAttachmentKind.File || attachment.location.length == 0} - download_attachment(message.id, attachment)} /> - {:else if attachment.kind === MessageAttachmentKind.Image} - { - previewImage = attachment.location - }} - on:download={_ => download_attachment(message.id, attachment)} /> - {:else if attachment.kind === MessageAttachmentKind.Text} - - {:else if attachment.kind === MessageAttachmentKind.STL} - - {:else if attachment.kind === MessageAttachmentKind.Audio} - - {:else if attachment.kind === MessageAttachmentKind.Video} - + {#if messages} + {#each messages as groups, i} + {#if i == 1 && messages[1].length > 0} +
+ +
+ {$_("chat.newMessageSince", { values: { date: $date($activeChat.last_view_date, { format: "medium" }), time: $time($activeChat.last_view_date) } })} +
+ +
+ {/if} + {#each groups as group} + Store.getUser(v)} let:resolved> + { + previewProfile = resolved + }} + remote={group.details.remote} + username={resolved.name} + subtext={getTimeAgo(group.messages[0].details.at)}> + {#each group.messages as message, idx} + {#if message.inReplyTo} + Store.getUser(v)} let:resolved> + + + {#each message.inReplyTo.text as line} + + {/each} + + + + {/if} + {#if message.text.length > 0 || message.attachments.length > 0} + + 1 || message.attachments.length > 0}> + {#if editing_message === message.id} + edit_message(message.id, editing_text ? editing_text : "")} /> + {:else} + {#each message.text as line} + {#if getValidPaymentRequest(line) != undefined} + + {:else if !line.includes(tempCDN)} + + {:else if line.includes(tempCDN)} +
+ +
{/if} {/each} + + {#if message.attachments.length > 0} + {#each message.attachments as attachment} + {#if attachment.kind === MessageAttachmentKind.File || attachment.location.length == 0} + download_attachment(message.id, attachment)} /> + {:else if attachment.kind === MessageAttachmentKind.Image} + { + previewImage = attachment.location + }} + on:download={_ => download_attachment(message.id, attachment)} /> + {:else if attachment.kind === MessageAttachmentKind.Text} + + {:else if attachment.kind === MessageAttachmentKind.STL} + + {:else if attachment.kind === MessageAttachmentKind.Audio} + + {:else if attachment.kind === MessageAttachmentKind.Video} + + {/if} + {/each} + {/if} {/if} - {/if} -
- - reactTo(message.id, emoji, true)} close={close} on:openPicker={_ => (reactingTo = message.id)}> - -
- {/if} - {#if Object.keys(message.reactions).length > 0} - reactTo(message.id, emoji, true)} /> - {/if} - {/each} -
-
+
+ + reactTo(message.id, emoji, true)} close={close} on:openPicker={_ => (reactingTo = message.id)}> + +
+ {/if} + {#if Object.keys(message.reactions).length > 0} + reactTo(message.id, emoji, true)} /> + {/if} + {/each} +
+
+ {/each} {/each} {#each $pendingMessages as pending, idx} @@ -951,6 +980,29 @@ } } } + + .unreads-since-container { + display: flex; + height: fit-content; + width: 100%; + margin-bottom: var(--padding-less); + + .unreads-since-line { + width: 100%; + height: 1px; + margin: auto; + background-color: var(--color-muted); + } + .unreads-since { + text-align: center; + width: 100%; + font-size: var(--font-size-smaller); + color: var(--color-muted); + white-space: nowrap; + margin-left: var(--padding-minimal); + margin-right: var(--padding-minimal); + } + } } .add-someone { From 52bf0675ffceef22bb90b68a3337644a71bf14d9 Mon Sep 17 00:00:00 2001 From: Flemmli97 Date: Wed, 9 Oct 2024 14:38:18 +0200 Subject: [PATCH 2/5] fix bookmark bar --- src/lib/components/messaging/Conversation.svelte | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/lib/components/messaging/Conversation.svelte b/src/lib/components/messaging/Conversation.svelte index 789daf277..cdcbc0494 100644 --- a/src/lib/components/messaging/Conversation.svelte +++ b/src/lib/components/messaging/Conversation.svelte @@ -5,7 +5,7 @@ import { Appearance, Shape } from "$lib/enums" import { fade } from "svelte/transition" import { SettingsStore } from "$lib/state" - import { get } from "svelte/store" + import { derived, get } from "svelte/store" import { _, date, time } from "svelte-i18n" import { Store } from "$lib/state/Store" import { UIStore } from "$lib/state/ui" @@ -20,6 +20,11 @@ let lastUnread: { unread: number; since: Date; last_viewed: string } | undefined $: chat = Store.state.activeChat let setup: boolean = false + $: derived(chat, _ => { + if (setup) { + if (scrollContainer.scrollHeight <= scrollContainer.clientHeight) markAsRead($chat.id) + } + }) const scrollToBottom = (node: Element) => { if (node) node.scrollTop = node.scrollHeight } @@ -73,6 +78,10 @@ c.notifications = 0 }) } + + onDestroy(() => { + if (scrollContainer.scrollHeight <= scrollContainer.clientHeight) markAsRead($chat.id) + })
@@ -141,7 +150,6 @@ position: absolute; top: 0; left: 0; - position: relative; height: 60px; -webkit-transform: rotate(0deg) skew(0deg); transform: rotate(0deg) skew(0deg); From 95c018dff74896759b048d7952576ab2e77b0743 Mon Sep 17 00:00:00 2001 From: Flemmli97 Date: Thu, 10 Oct 2024 17:10:49 +0200 Subject: [PATCH 3/5] check individual messages too --- src/routes/chat/+page.svelte | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/routes/chat/+page.svelte b/src/routes/chat/+page.svelte index c3255f681..09fd3e811 100644 --- a/src/routes/chat/+page.svelte +++ b/src/routes/chat/+page.svelte @@ -296,12 +296,38 @@ } function splitUnreads(groups: MessageGroupType[]): [MessageGroupType[], MessageGroupType[]] { + let splitMessages = (group: MessageGroupType) => { + return group.messages.reduce<[MessageType[], MessageType[]]>( + ([read, unreads], message) => { + if (message.details.at > $activeChat.last_view_date) { + unreads.push(message) + } else { + read.push(message) + } + return [read, unreads] + }, + [[], []] + ) + } return groups.reduce<[MessageGroupType[], MessageGroupType[]]>( ([read, unreads], group) => { if (group.details.at > $activeChat.last_view_date) { unreads.push(group) } else { - read.push(group) + // Individual messages in a group can still be new since messages are grouped each min + let [readMessages, unreadsMessages] = splitMessages(group) + if (unreadsMessages.length > 0) { + unreads.push({ + ...group, + messages: unreadsMessages, + }) + read.push({ + ...group, + messages: readMessages, + }) + } else { + read.push(group) + } } return [read, unreads] }, From 3b5743f3612199ec9f49b20eec7aebdf3978e14f Mon Sep 17 00:00:00 2001 From: Flemmli97 Date: Fri, 11 Oct 2024 15:47:04 +0200 Subject: [PATCH 4/5] update unread when scrolled up --- src/lib/components/messaging/Conversation.svelte | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/lib/components/messaging/Conversation.svelte b/src/lib/components/messaging/Conversation.svelte index cdcbc0494..e10d0465c 100644 --- a/src/lib/components/messaging/Conversation.svelte +++ b/src/lib/components/messaging/Conversation.svelte @@ -14,6 +14,7 @@ let scrolledUp: boolean = false let showScrollToBottom: boolean = false + let clearUnreads = true export let loading: boolean = false export let unreads: { unread: number; since: Date; last_viewed: string } | undefined @@ -40,7 +41,8 @@ scrolledUp = scrollCheck(1.1) if (current != scrolledUp && !scrolledUp && unreads && unreads.unread > 0) { // Clear unreads if scrolled to the bottom - markAsRead($chat.id) + if (clearUnreads) markAsRead($chat.id) + clearUnreads = true } } @@ -51,7 +53,10 @@ scrollToBottom(scrollContainer) } // Mark as read current is already read and messages are incoming - if (setup && lastUnread !== unreads && lastUnread === undefined) markAsRead($chat.id) + if (setup && lastUnread !== unreads && lastUnread === undefined) { + if (scrolledUp) clearUnreads = false + else markAsRead($chat.id) + } lastUnread = unreads }) From 312213d24565b453ee8349394ecb5763b8d6de24 Mon Sep 17 00:00:00 2001 From: Flemmli97 Date: Fri, 11 Oct 2024 15:55:49 +0200 Subject: [PATCH 5/5] add simple unread settings toggle --- src/lib/components/messaging/ChatPreview.svelte | 9 +++++---- src/lib/lang/en.json | 4 +++- src/lib/state/settings/default.ts | 1 + src/lib/state/settings/index.ts | 1 + src/lib/state/ui/index.ts | 2 ++ src/routes/chat/+page.svelte | 2 +- src/routes/files/+page.svelte | 2 +- src/routes/friends/+page.svelte | 2 +- src/routes/settings/messages/+page.svelte | 8 ++++++++ 9 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/lib/components/messaging/ChatPreview.svelte b/src/lib/components/messaging/ChatPreview.svelte index 7f460de44..cf6235a0a 100644 --- a/src/lib/components/messaging/ChatPreview.svelte +++ b/src/lib/components/messaging/ChatPreview.svelte @@ -8,16 +8,16 @@ import ProfilePictureMany from "../profile/ProfilePictureMany.svelte" import { Store } from "$lib/state/Store" import { goto } from "$app/navigation" - import { get } from "svelte/store" + import { derived, get } from "svelte/store" import { tempCDN } from "$lib/utils/CommonVariables" import { UIStore } from "$lib/state/ui" import { _ } from "svelte-i18n" import { checkMobile } from "$lib/utils/Mobile" import { ConversationStore } from "$lib/state/conversation" + import { SettingsStore } from "$lib/state" export let chat: Chat export let cta: boolean = false - export let simpleUnreads: boolean = false export let loading: boolean const timeAgo = new TimeAgo("en-US") @@ -28,6 +28,7 @@ $: loading = chatName === "Unknown User" || ($users.length <= 2 && ($users[1]?.loading == true || $users[0].loading == true)) $: directChatPhoto = $users[1]?.profile.photo.image ?? $users[0].profile.photo.image $: chatStatus = $users.length > 2 ? Status.Offline : ($users[1]?.profile.status ?? $users[0].profile.status) + $: simpleUnreads = derived(SettingsStore.state, s => s.messaging.simpleUnreads) let timeago = getTimeAgo(chat.last_message_at) const dispatch = createEventDispatcher() @@ -98,11 +99,11 @@ {timeago} {#if !loading} - {#if chat.notifications > 0 && !simpleUnreads} + {#if chat.notifications > 0 && !$simpleUnreads} {chat.notifications} - {:else if chat.notifications > 0 && simpleUnreads} + {:else if chat.notifications > 0 && $simpleUnreads} {/if} {/if} diff --git a/src/lib/lang/en.json b/src/lib/lang/en.json index 24c8184f7..d189b08f3 100644 --- a/src/lib/lang/en.json +++ b/src/lib/lang/en.json @@ -429,7 +429,9 @@ "quick": "Quick Chat", "quickDescription": "When navigating back to the chats screen on mobile, quick chat will bring you back to your last conversation.", "showStatusWidgets": "Widget Panel", - "showStatusWidgetsDescription": "Enable the widget panel, which displays system information and other helpful data." + "showStatusWidgetsDescription": "Enable the widget panel, which displays system information and other helpful data.", + "simpleUnreads": "Simple Unreads", + "simpleUnreadsDescription": "Whether to show a simple unread badge for chats in the sidebar or not" }, "preferences": { "appLanguage": "App Language", diff --git a/src/lib/state/settings/default.ts b/src/lib/state/settings/default.ts index fd7017481..58bc0405a 100644 --- a/src/lib/state/settings/default.ts +++ b/src/lib/state/settings/default.ts @@ -131,6 +131,7 @@ export let defaultSettings = { compact: false, quick: false, identiconStyle: Identicon.BotsNeutral, + simpleUnreads: true, }, audio: { inputDevice: "Default", diff --git a/src/lib/state/settings/index.ts b/src/lib/state/settings/index.ts index b76b8e61a..cde8fda56 100644 --- a/src/lib/state/settings/index.ts +++ b/src/lib/state/settings/index.ts @@ -16,6 +16,7 @@ export interface ISettingsState { compact: boolean quick: boolean identiconStyle: Identicon + simpleUnreads: boolean } audio: { inputDevice: string diff --git a/src/lib/state/ui/index.ts b/src/lib/state/ui/index.ts index 6adf35f17..3213c2f1b 100644 --- a/src/lib/state/ui/index.ts +++ b/src/lib/state/ui/index.ts @@ -18,6 +18,7 @@ export interface IUIState { sidebarOpen: Writable chats: Writable hiddenChats: Writable + simpleUnreads: Writable emojiSelector: Writable emojiCounter: Writable<{ [emoji: string]: number }> marketOpen: Writable @@ -46,6 +47,7 @@ class Store { }, }), hiddenChats: createPersistentState("uplink.ui.hiddenChats", []), + simpleUnreads: writable(true), emojiSelector: writable(false), emojiCounter: createPersistentState("uplink.ui.emojiCounter", { "👍": 0, "👎": 0, "❤️": 0, "🖖": 0, "😂": 0 }), marketOpen: writable(false), diff --git a/src/routes/chat/+page.svelte b/src/routes/chat/+page.svelte index 09fd3e811..65983103b 100644 --- a/src/routes/chat/+page.svelte +++ b/src/routes/chat/+page.svelte @@ -482,7 +482,7 @@ onClick: () => {}, }, ]}> - + {/each} diff --git a/src/routes/files/+page.svelte b/src/routes/files/+page.svelte index 3aff04fdc..bc64e5e17 100644 --- a/src/routes/files/+page.svelte +++ b/src/routes/files/+page.svelte @@ -571,7 +571,7 @@ onClick: () => {}, }, ]}> - + {/each} {/if} diff --git a/src/routes/friends/+page.svelte b/src/routes/friends/+page.svelte index de33df59e..d0425123e 100644 --- a/src/routes/friends/+page.svelte +++ b/src/routes/friends/+page.svelte @@ -196,7 +196,7 @@ onClick: () => {}, }, ]}> - + {/each} diff --git a/src/routes/settings/messages/+page.svelte b/src/routes/settings/messages/+page.svelte index 19f3c7aee..6e409e1b2 100644 --- a/src/routes/settings/messages/+page.svelte +++ b/src/routes/settings/messages/+page.svelte @@ -44,6 +44,14 @@ SettingsStore.update({ ...settings, messaging: { ...settings.messaging, compact: on.detail } }) }} /> + + { + SettingsStore.update({ ...settings, messaging: { ...settings.messaging, simpleUnreads: on.detail } }) + }} /> +