From 97509c47ca7581c42dc13502c5c6e8274ae0b369 Mon Sep 17 00:00:00 2001 From: Adrian Victor Date: Mon, 17 Aug 2026 16:58:15 -0300 Subject: [PATCH] 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 {