From dfab4c9b427ed9393b7d788fa01758827daca606 Mon Sep 17 00:00:00 2001 From: Adrian Victor Date: Mon, 17 Aug 2026 07:47:11 -0300 Subject: [PATCH 1/6] Fix modal elements going out of screen. --- web/style.css | 1 + 1 file changed, 1 insertion(+) diff --git a/web/style.css b/web/style.css index af6bb79..a33767d 100644 --- a/web/style.css +++ b/web/style.css @@ -1054,6 +1054,7 @@ input:focus { display: flex; flex-direction: column; height: 100%; + overflow-x: hidden; } .modal-body h3 { From 97509c47ca7581c42dc13502c5c6e8274ae0b369 Mon Sep 17 00:00:00 2001 From: Adrian Victor Date: Mon, 17 Aug 2026 16:58:15 -0300 Subject: [PATCH 2/6] Parse WhatApp markdown and sanitize messages; remove message drift calculation; standardize spacing for body and child containers. --- web/app.html | 4 +++- web/src/parser.ts | 28 ++++++++++++++++++++++++++++ web/src/ui.ts | 25 ++++++++++++++++++++++--- web/src/utils.ts | 26 -------------------------- web/style.css | 34 ++++++++++++++++++++++------------ 5 files changed, 75 insertions(+), 42 deletions(-) create mode 100644 web/src/parser.ts diff --git a/web/app.html b/web/app.html index 2f7be0d..b614f5e 100644 --- a/web/app.html +++ b/web/app.html @@ -113,7 +113,9 @@
-

Pandora User

+
diff --git a/web/src/parser.ts b/web/src/parser.ts new file mode 100644 index 0000000..fa29ec7 --- /dev/null +++ b/web/src/parser.ts @@ -0,0 +1,28 @@ +export class Parser { + input: string; + + constructor(input: string) { + this.input = input; + } + + parse(token: string, to: string): Parser { + const esc = Parser.escapeRegex(token); + + const regex = new RegExp(`(^|\\s)${esc}(.+?)${esc}(?=\\s|$)`, "gs"); + + this.input = this.input.replace(regex, (match, pre, inner) => { + return `${pre}${to.replace("$1", inner)}`; + }); + + return this; + } + + replace(searchValue: string, replaceValue: string): Parser { + this.input = this.input.replaceAll(searchValue, replaceValue); + return this; + } + + static escapeRegex(string: string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + } +} \ No newline at end of file diff --git a/web/src/ui.ts b/web/src/ui.ts index 13f14bf..50c0b07 100644 --- a/web/src/ui.ts +++ b/web/src/ui.ts @@ -2,6 +2,7 @@ import { formatTime, normalizeId } from "./utils"; import { getChatPicture, getMessage, getMedia, getMoreChatMessages } from "./storage"; import type { Chat, Message } from "./types"; import { activeChatState } from "./states"; +import { Parser } from "./parser"; export const elements = { chatList: document.getElementById('chat-list') as HTMLUListElement, @@ -237,7 +238,10 @@ export const ui = { msgs.shift(); msgs.forEach(async msg => { - loadMoreButton.after(this.generateMessage(msg, userId, chatId)); + const message = this.generateMessage(msg, userId, chatId); + if (message) { + loadMoreButton.after(); + } }); }, @@ -245,7 +249,10 @@ export const ui = { * Append a single message (used for optimistic updates immediately upon sending) */ appendSingleMessage(msg: Message, userID: string, chatId: string, isLocal: boolean = false) { - elements.messagesContainer.appendChild(this.generateMessage(msg, userID, chatId, isLocal)) + const message = this.generateMessage(msg, userID, chatId, isLocal); + if (message) { + elements.messagesContainer.appendChild(message) + } }, generateTempMessageLink(msg: Message) { @@ -269,6 +276,7 @@ export const ui = { }, generateMessage(msg: Message, userID: string, chatId: string, isLocal: boolean = false) { + if (msg._data && msg._data.type == "gp2") return; const isOutgoing = msg.fromMe || msg.sender === 'me'; function getPrevMessageElem() { @@ -320,7 +328,17 @@ export const ui = { const contentEl = document.createElement('div'); contentEl.classList.add('message-content'); const textEl = document.createElement('div'); - textEl.innerHTML = msg.body || msg.text || ""; + const parsed = new Parser(msg.body || msg.text || "") + .parse('_', '$1') + .parse('*', '$1') + .parse('~', '$1') + .parse('```', '$1') + .parse('`', '$1') + .replace("\n", "
") + .input; + + textEl.innerHTML = parsed; + console.log(msg); contentEl.appendChild(textEl); bubble.appendChild(contentEl); @@ -354,6 +372,7 @@ export const ui = { (e.target as HTMLAnchorElement).href = objectUrl; if (media.blob.type.startsWith('image/')) { + groupDiv.classList.add('image'); a.textContent = ""; const img = document.createElement('img'); img.classList.add('message-image-attachement'); diff --git a/web/src/utils.ts b/web/src/utils.ts index 261f1bf..0e404fb 100644 --- a/web/src/utils.ts +++ b/web/src/utils.ts @@ -48,32 +48,6 @@ export function compensateMessageOrdering(messages: Message[]): Message[] { msgs.sort((a, b) => a._time - b._time); - let changed = true; - while (changed) { - changed = false; - for (let i = 0; i < msgs.length - 1; i++) { - const current = msgs[i]; - const next = msgs[i + 1]; - const timeDiff = next._time - current._time; - - // swap if outgoing message is sorted before incoming message within 30-sec window - if (current.fromMe && !next.fromMe && timeDiff >= 0 && timeDiff <= 30000) { - msgs[i] = next; - msgs[i + 1] = current; - - // advance the outgoing message timestamp to exactly 1 sec after the incoming - current._time = next._time + 1000; - if (typeof current.timestamp === 'number') { - current.timestamp = Math.floor(current._time / 1000); - } else { - current.timestamp = new Date(current._time).toISOString(); - } - - changed = true; - } - } - } - // clean temp property return msgs.map(({ _time, ...m }) => m); } diff --git a/web/style.css b/web/style.css index a33767d..f800ca5 100644 --- a/web/style.css +++ b/web/style.css @@ -50,6 +50,9 @@ --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.3), 0 4px 6px -2px rgba(0, 0, 0, 0.1); --glass-blur: blur(20px); --high-box-shadow: 2px 7px 5px rgba(0, 0, 0, 0.3), 0px -4px 10px rgba(0, 0, 0, 0.3); + --page-padding: 1.6rem; + --page-padding-small: 1rem; + --container-padding: .4rem; } body.light { @@ -124,6 +127,10 @@ input:focus { outline: none; } +li { + margin-inline-start: 1em; +} + .app-container { display: flex; flex-direction: row; @@ -179,7 +186,8 @@ input:focus { } .sidebar-header { - padding: 20px 20px 0px 20px; + padding: var(--page-padding); + padding-bottom: 0; display: flex; flex-direction: column; align-items: self-start; @@ -295,7 +303,6 @@ input:focus { #chat-search { width: 100%; - padding: 12px; border: none; font-size: 0.9rem; transition: all 0.25s ease; @@ -316,7 +323,8 @@ input:focus { display: flex; flex-direction: column; overflow-y: auto; - padding: 0 10px 10px 10px; + padding: var(--container-padding); + padding-top: 0; } .chat-list-header { @@ -357,7 +365,7 @@ input:focus { border: 1px solid transparent; } -.selected { +.chat-item.selected { background: rgba(255, 255, 255, 0.04); transform: scale(0.98) skewX(10deg); filter: blur(.1px); @@ -365,7 +373,7 @@ input:focus { } -.selectable:active { +.chat-item.selectable:active { background: rgba(255, 255, 255, 0.04); transform: scale(0.98) skewX(10deg); filter: blur(.1px); @@ -666,6 +674,10 @@ input:focus { transition: .1s; } +.message-group.image { + max-width: 30%; +} + .message-group.incoming { align-self: flex-start; } @@ -745,7 +757,6 @@ input:focus { .message-image-attachement { max-width: 100%; - max-height: 10em; } /* Chat Input Panel */ @@ -898,7 +909,7 @@ input:focus { } #profile-page .content { - padding: 1.4rem; + padding: var(--page-padding); display: flex; gap: 1em; } @@ -908,7 +919,6 @@ input:focus { } .big-title { - padding: 1.4rem; font-size: 5rem; text-wrap-mode: nowrap; overflow: hidden; @@ -1066,10 +1076,10 @@ input:focus { display: flex; justify-content: space-between; align-items: center; - padding: 1em 2em 0em 2em; + padding: var(--page-padding) var(--page-padding) 0em var(--page-padding); } -.modal-header h2 { +h2.modal-title { font-size: xx-large; font-weight: 600; } @@ -1082,7 +1092,7 @@ input:focus { overflow-y: auto; flex: 1; overflow-x: hidden; - padding: 0 2rem 0 2rem; + padding: 0 var(--page-padding) 0 var(--page-padding); } .form-group { @@ -1107,7 +1117,7 @@ input:focus { display: flex; justify-content: flex-end; gap: 12px; - padding: 1rem 2rem; + padding: var(--page-padding-small) var(--page-padding); } .btn { From 5943621dbf8d97800e264202e8b9ca7c43d9aeb6 Mon Sep 17 00:00:00 2001 From: Adrian Victor Date: Mon, 17 Aug 2026 17:05:28 -0300 Subject: [PATCH 3/6] Fix spacing in messages list; fix image message size on mobile; restore scrolling to chat screen when loading message. --- web/src/app.ts | 4 ++++ web/src/ui.ts | 1 - web/style.css | 5 +++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/web/src/app.ts b/web/src/app.ts index cdf0242..1a704de 100644 --- a/web/src/app.ts +++ b/web/src/app.ts @@ -335,6 +335,10 @@ async function handleIncomingMessage(msg: Message) { async function selectChat(chat: Chat, isPopState = false, smoothScroll = true) { if (isLoadingChat) return; + const pageEl = document.getElementById("chat-page"); + if (!pageEl) return; + mainView?.scrollTo(pageEl); + isLoadingChat = true; setActiveChatState(chat); diff --git a/web/src/ui.ts b/web/src/ui.ts index 50c0b07..82b6c2c 100644 --- a/web/src/ui.ts +++ b/web/src/ui.ts @@ -338,7 +338,6 @@ export const ui = { .input; textEl.innerHTML = parsed; - console.log(msg); contentEl.appendChild(textEl); bubble.appendChild(contentEl); diff --git a/web/style.css b/web/style.css index f800ca5..2772706 100644 --- a/web/style.css +++ b/web/style.css @@ -363,6 +363,7 @@ li { cursor: pointer; transition: all 0.2s ease; border: 1px solid transparent; + margin-left: 0px; } .chat-item.selected { @@ -1032,6 +1033,10 @@ li { .message-group { max-width: 90%; } + + .message-group.image { + max-width: 60%; + } .message-image-attachement { max-height: 20em; From c5218f3c1af5a0ba93368c73c520f9c8003a4ed1 Mon Sep 17 00:00:00 2001 From: Adrian Victor Date: Mon, 17 Aug 2026 19:45:31 -0300 Subject: [PATCH 4/6] Add user picture to desktop sidebar; change text input to textarea to allow resizing. --- web/app.html | 7 ++++--- web/src/app.ts | 8 +++++--- web/src/parser.ts | 2 +- web/src/ui.ts | 23 ++++++++++++++++++++++- web/style.css | 39 +++++++++++++++++++++++++++++++++++---- 5 files changed, 67 insertions(+), 12 deletions(-) diff --git a/web/app.html b/web/app.html index b614f5e..22caace 100644 --- a/web/app.html +++ b/web/app.html @@ -20,6 +20,7 @@ + @@ -94,11 +95,11 @@
- +
- -
diff --git a/web/src/app.ts b/web/src/app.ts index 1a704de..e8acdb4 100644 --- a/web/src/app.ts +++ b/web/src/app.ts @@ -189,6 +189,8 @@ function setupEventListeners() { ); ui.renderChatList(filtered, activeChatState, selectChat); }); + + ui.autoResizeTextArea(elements.messageInput); elements.messageForm.addEventListener('submit', (e) => { e.preventDefault(); @@ -313,9 +315,9 @@ async function handleIncomingMessage(msg: Message) { if (!msg.fromMe) { messageTone.play(); - } - if (notificationAuthorization === "granted") { - new Notification("New message", { body: msg.body || msg.text }); + if (notificationAuthorization === "granted") { + new Notification("New message", { body: msg.body || msg.text }); + } } if (activeChatState && activeChatState.id === msgChatId) { diff --git a/web/src/parser.ts b/web/src/parser.ts index fa29ec7..6cc56ff 100644 --- a/web/src/parser.ts +++ b/web/src/parser.ts @@ -10,7 +10,7 @@ export class Parser { const regex = new RegExp(`(^|\\s)${esc}(.+?)${esc}(?=\\s|$)`, "gs"); - this.input = this.input.replace(regex, (match, pre, inner) => { + this.input = this.input.replace(regex, (_match, pre, inner) => { return `${pre}${to.replace("$1", inner)}`; }); diff --git a/web/src/ui.ts b/web/src/ui.ts index 82b6c2c..fd2ee93 100644 --- a/web/src/ui.ts +++ b/web/src/ui.ts @@ -16,7 +16,7 @@ export const elements = { activeChatAvatar: document.getElementById('active-chat-avatar') as HTMLDivElement, messagesContainer: document.getElementById('messages-container') as HTMLDivElement, messageForm: document.getElementById('message-form') as HTMLFormElement, - messageInput: document.getElementById('message-input') as HTMLInputElement, + messageInput: document.getElementById('message-input') as HTMLTextAreaElement, backToSidebarBtn: document.querySelector('.chat-header') as HTMLElement, sidebar: document.querySelector('.sidebar') as HTMLElement, appContainer: document.querySelector('.app-container') as HTMLElement, @@ -465,6 +465,27 @@ export const ui = { const message = document.getElementById(msgId); if (message) message.remove(); }, + + autoResizeTextArea(element: HTMLTextAreaElement) { + const resize = () => { + element.style.height = 'auto'; + const offset = element.offsetHeight - element.clientHeight; + const newHeight = element.scrollHeight + offset; + + if (element.scrollHeight > 0) { + element.style.height = `${newHeight}px`; + } + }; + + element.addEventListener('input', resize); + + const observer = new IntersectionObserver((entries) => { + if (entries[0].isIntersecting) { + resize(); + } + }); + observer.observe(element); + }, }; export class ScrollableView { diff --git a/web/style.css b/web/style.css index 2772706..a156320 100644 --- a/web/style.css +++ b/web/style.css @@ -470,7 +470,19 @@ li { border-right: 1px solid var(--border-color); display: flex; flex-direction: column; - padding: .2em; + padding: .6em .2em; + width: 60px; + align-items: center; +} + +#desktop-aside * { + width: 38px; + height: 38px; +} + +#desktop-aside img { + margin: .2rem; + margin-top: auto; } /* Sidebar Footer */ @@ -764,7 +776,7 @@ li { .chat-input-panel { padding: .8rem; display: flex; - align-items: center; + align-items: flex-end; gap: 16px; background-color: var(--bg-main); } @@ -814,19 +826,24 @@ li { flex: 1; display: flex; gap: 12px; - align-items: center; + align-items: flex-end; min-width: 0; } #message-input { flex: 1; + overflow-y: scroll; padding: 12px 18px; background-color: var(--text-primary); border: medium solid var(--border-color); color: var(--bg-main); font-size: 0.95rem; - transition: all 0.2s ease; + transition: background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease; min-width: 0; + resize: none; + line-height: 1.3; + max-height: 6rem; + box-sizing: border-box; } #message-input:focus { @@ -836,6 +853,15 @@ li { background: rgba(255, 255, 255, 0.06); } +#chat-bottom-bar-btn { + margin-bottom: 6px; +} + +#send-button { + margin-bottom: 4px; + margin-right: 6px; +} + .send-btn { width: 44px; height: 44px; @@ -1009,6 +1035,11 @@ li { scroll-snap-align: start; scroll-snap-stop: always; } + + #message-input { + padding: 8px 12px; + max-height: 10rem; + } .extra-page { display: flex; From f87966aa156895fed6468a4c47fe896954d01b89 Mon Sep 17 00:00:00 2001 From: Adrian Victor Date: Mon, 17 Aug 2026 19:57:48 -0300 Subject: [PATCH 5/6] Fix chat collapsable bar button icon color on hover being the same as background; fix spacing in setup titles. --- web/app.html | 6 +++--- web/index.html | 8 ++++++-- web/src/app.ts | 2 ++ web/style.css | 9 ++++++++- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/web/app.html b/web/app.html index 22caace..60771c3 100644 --- a/web/app.html +++ b/web/app.html @@ -17,9 +17,9 @@
diff --git a/web/index.html b/web/index.html index ab8d213..0f827ba 100644 --- a/web/index.html +++ b/web/index.html @@ -13,7 +13,9 @@
-

Pandora

+
-

Connecting

+