aboutsummaryrefslogtreecommitdiffstats
path: root/src/public-chat.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/public-chat.js')
-rw-r--r--src/public-chat.js26
1 files changed, 25 insertions, 1 deletions
diff --git a/src/public-chat.js b/src/public-chat.js
index 21b372a..5732f4a 100644
--- a/src/public-chat.js
+++ b/src/public-chat.js
@@ -35,6 +35,30 @@ function h(tag, attrs = {}, ...children) {
return e;
}
+// Message text, safely: content stays text nodes (relay content must never
+// reach innerHTML); URLs become links, image URLs render inline. The app's
+// richer noteBody also resolves nostr: mentions — here they stay dim stubs,
+// since this page has no profile screens to open.
+const LINK_SPLIT = /(https?:\/\/[^\s]+|nostr:(?:npub|nprofile|note|nevent|naddr)1[a-z0-9]+)/gi;
+function linkedBody(text) {
+ const out = [];
+ for (const part of String(text || '').split(LINK_SPLIT)) {
+ if (!part) continue;
+ if (/^https?:\/\//i.test(part)) {
+ if (/\.(png|jpe?g|gif|webp|avif)(\?[^\s]*)?$/i.test(part)) {
+ out.push(h('img', { src: part, class: 'note-img', loading: 'lazy',
+ onError: (e) => { e.target.style.display = 'none'; } }));
+ } else {
+ out.push(h('a', { href: part, target: '_blank', rel: 'noopener noreferrer' },
+ part.length > 64 ? part.slice(0, 61) + '…' : part));
+ }
+ } else if (/^nostr:/i.test(part)) {
+ out.push(h('span', { class: 'faint' }, part.slice(6, 18) + '…'));
+ } else out.push(part);
+ }
+ return out;
+}
+
export function mountPublicChat() {
const root = document.getElementById('app');
const rootBytes = hexToBytes(COMMUNITY.community_root);
@@ -220,7 +244,7 @@ export function mountPublicChat() {
h('span', { class: 'chat-name' + (m.author === COMMUNITY.owner ? ' owner' : '') }, nameOf(m.author)),
m.author === COMMUNITY.owner ? h('span', { class: 'chat-badge' }, t('msgAdmin')) : null,
h('span', { class: 'chat-time' }, timeAgo(ms / 1000))),
- h('div', { class: 'chat-bubble' }, content,
+ h('div', { class: 'chat-bubble' }, ...linkedBody(content),
edit && edit.author === m.author ? h('span', { class: 'chat-edited' }, ' ' + t('msgEdited')) : null),
reacts && reacts.size ? h('div', { class: 'chat-reacts' },
...[...[...reacts.values()].reduce((m2, e) => m2.set(e, (m2.get(e) || 0) + 1), new Map()).entries()]