Parse WhatApp markdown and sanitize messages; remove message drift calculation; standardize spacing for body and child containers.
This commit is contained in:
parent
dfab4c9b42
commit
97509c47ca
5 changed files with 75 additions and 42 deletions
|
|
@ -113,7 +113,9 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section class="extra-page" id="profile-page">
|
<section class="extra-page" id="profile-page">
|
||||||
|
<div class="modal-header">
|
||||||
<h2 class="big-title" data-content="app-user">Pandora User</h2>
|
<h2 class="big-title" data-content="app-user">Pandora User</h2>
|
||||||
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<img id="profile-page-picture" data-resource="app-user-image">
|
<img id="profile-page-picture" data-resource="app-user-image">
|
||||||
<div id="profile-page-user-info">
|
<div id="profile-page-user-info">
|
||||||
|
|
|
||||||
28
web/src/parser.ts
Normal file
28
web/src/parser.ts
Normal file
|
|
@ -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, "\\$&");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ import { formatTime, normalizeId } from "./utils";
|
||||||
import { getChatPicture, getMessage, getMedia, getMoreChatMessages } from "./storage";
|
import { getChatPicture, getMessage, getMedia, getMoreChatMessages } from "./storage";
|
||||||
import type { Chat, Message } from "./types";
|
import type { Chat, Message } from "./types";
|
||||||
import { activeChatState } from "./states";
|
import { activeChatState } from "./states";
|
||||||
|
import { Parser } from "./parser";
|
||||||
|
|
||||||
export const elements = {
|
export const elements = {
|
||||||
chatList: document.getElementById('chat-list') as HTMLUListElement,
|
chatList: document.getElementById('chat-list') as HTMLUListElement,
|
||||||
|
|
@ -237,7 +238,10 @@ export const ui = {
|
||||||
msgs.shift();
|
msgs.shift();
|
||||||
|
|
||||||
msgs.forEach(async msg => {
|
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)
|
* Append a single message (used for optimistic updates immediately upon sending)
|
||||||
*/
|
*/
|
||||||
appendSingleMessage(msg: Message, userID: string, chatId: string, isLocal: boolean = false) {
|
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) {
|
generateTempMessageLink(msg: Message) {
|
||||||
|
|
@ -269,6 +276,7 @@ export const ui = {
|
||||||
},
|
},
|
||||||
|
|
||||||
generateMessage(msg: Message, userID: string, chatId: string, isLocal: boolean = false) {
|
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';
|
const isOutgoing = msg.fromMe || msg.sender === 'me';
|
||||||
|
|
||||||
function getPrevMessageElem() {
|
function getPrevMessageElem() {
|
||||||
|
|
@ -320,7 +328,17 @@ export const ui = {
|
||||||
const contentEl = document.createElement('div');
|
const contentEl = document.createElement('div');
|
||||||
contentEl.classList.add('message-content');
|
contentEl.classList.add('message-content');
|
||||||
const textEl = document.createElement('div');
|
const textEl = document.createElement('div');
|
||||||
textEl.innerHTML = msg.body || msg.text || "";
|
const parsed = new Parser(msg.body || msg.text || "")
|
||||||
|
.parse('_', '<i>$1</i>')
|
||||||
|
.parse('*', '<b>$1</b>')
|
||||||
|
.parse('~', '<s>$1</s>')
|
||||||
|
.parse('```', '<span style="font-family: monospace;">$1</span>')
|
||||||
|
.parse('`', '<code>$1</code>')
|
||||||
|
.replace("\n", "<br>")
|
||||||
|
.input;
|
||||||
|
|
||||||
|
textEl.innerHTML = parsed;
|
||||||
|
console.log(msg);
|
||||||
contentEl.appendChild(textEl);
|
contentEl.appendChild(textEl);
|
||||||
bubble.appendChild(contentEl);
|
bubble.appendChild(contentEl);
|
||||||
|
|
||||||
|
|
@ -354,6 +372,7 @@ export const ui = {
|
||||||
(e.target as HTMLAnchorElement).href = objectUrl;
|
(e.target as HTMLAnchorElement).href = objectUrl;
|
||||||
|
|
||||||
if (media.blob.type.startsWith('image/')) {
|
if (media.blob.type.startsWith('image/')) {
|
||||||
|
groupDiv.classList.add('image');
|
||||||
a.textContent = "";
|
a.textContent = "";
|
||||||
const img = document.createElement('img');
|
const img = document.createElement('img');
|
||||||
img.classList.add('message-image-attachement');
|
img.classList.add('message-image-attachement');
|
||||||
|
|
|
||||||
|
|
@ -48,32 +48,6 @@ export function compensateMessageOrdering(messages: Message[]): Message[] {
|
||||||
|
|
||||||
msgs.sort((a, b) => a._time - b._time);
|
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
|
// clean temp property
|
||||||
return msgs.map(({ _time, ...m }) => m);
|
return msgs.map(({ _time, ...m }) => m);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
--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);
|
--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);
|
--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 {
|
body.light {
|
||||||
|
|
@ -124,6 +127,10 @@ input:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin-inline-start: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
.app-container {
|
.app-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|
@ -179,7 +186,8 @@ input:focus {
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-header {
|
.sidebar-header {
|
||||||
padding: 20px 20px 0px 20px;
|
padding: var(--page-padding);
|
||||||
|
padding-bottom: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: self-start;
|
align-items: self-start;
|
||||||
|
|
@ -295,7 +303,6 @@ input:focus {
|
||||||
|
|
||||||
#chat-search {
|
#chat-search {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 12px;
|
|
||||||
border: none;
|
border: none;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
transition: all 0.25s ease;
|
transition: all 0.25s ease;
|
||||||
|
|
@ -316,7 +323,8 @@ input:focus {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding: 0 10px 10px 10px;
|
padding: var(--container-padding);
|
||||||
|
padding-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-list-header {
|
.chat-list-header {
|
||||||
|
|
@ -357,7 +365,7 @@ input:focus {
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selected {
|
.chat-item.selected {
|
||||||
background: rgba(255, 255, 255, 0.04);
|
background: rgba(255, 255, 255, 0.04);
|
||||||
transform: scale(0.98) skewX(10deg);
|
transform: scale(0.98) skewX(10deg);
|
||||||
filter: blur(.1px);
|
filter: blur(.1px);
|
||||||
|
|
@ -365,7 +373,7 @@ input:focus {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.selectable:active {
|
.chat-item.selectable:active {
|
||||||
background: rgba(255, 255, 255, 0.04);
|
background: rgba(255, 255, 255, 0.04);
|
||||||
transform: scale(0.98) skewX(10deg);
|
transform: scale(0.98) skewX(10deg);
|
||||||
filter: blur(.1px);
|
filter: blur(.1px);
|
||||||
|
|
@ -666,6 +674,10 @@ input:focus {
|
||||||
transition: .1s;
|
transition: .1s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.message-group.image {
|
||||||
|
max-width: 30%;
|
||||||
|
}
|
||||||
|
|
||||||
.message-group.incoming {
|
.message-group.incoming {
|
||||||
align-self: flex-start;
|
align-self: flex-start;
|
||||||
}
|
}
|
||||||
|
|
@ -745,7 +757,6 @@ input:focus {
|
||||||
|
|
||||||
.message-image-attachement {
|
.message-image-attachement {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
max-height: 10em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Chat Input Panel */
|
/* Chat Input Panel */
|
||||||
|
|
@ -898,7 +909,7 @@ input:focus {
|
||||||
}
|
}
|
||||||
|
|
||||||
#profile-page .content {
|
#profile-page .content {
|
||||||
padding: 1.4rem;
|
padding: var(--page-padding);
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 1em;
|
gap: 1em;
|
||||||
}
|
}
|
||||||
|
|
@ -908,7 +919,6 @@ input:focus {
|
||||||
}
|
}
|
||||||
|
|
||||||
.big-title {
|
.big-title {
|
||||||
padding: 1.4rem;
|
|
||||||
font-size: 5rem;
|
font-size: 5rem;
|
||||||
text-wrap-mode: nowrap;
|
text-wrap-mode: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
@ -1066,10 +1076,10 @@ input:focus {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
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-size: xx-large;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
@ -1082,7 +1092,7 @@ input:focus {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
padding: 0 2rem 0 2rem;
|
padding: 0 var(--page-padding) 0 var(--page-padding);
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-group {
|
.form-group {
|
||||||
|
|
@ -1107,7 +1117,7 @@ input:focus {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
padding: 1rem 2rem;
|
padding: var(--page-padding-small) var(--page-padding);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue