From ae51c4a3a284b09e7afe312951b1943f5540874b Mon Sep 17 00:00:00 2001 From: Jean Date: Sat, 4 Jul 2026 03:19:21 +0000 Subject: [PATCH] messages: support ||spoiler|| tags (click to reveal) --- src/index.css | 4 ++++ src/lib/format.tsx | 33 +++++++++++++++++++++++++-------- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/src/index.css b/src/index.css index 04595bf..3f6ccfe 100644 --- a/src/index.css +++ b/src/index.css @@ -989,6 +989,10 @@ textarea.modal__input { resize: vertical; min-height: 5rem; line-height: 1.45; } .challenge__busy { font-size: .85rem; font-weight: 700; color: var(--green-d); } .challenge__link { display: inline-block; margin-top: .3rem; font-weight: 600; color: var(--accent); text-decoration: none; } .challenge__link:hover { text-decoration: underline; } + +/* ||spoiler|| — hidden until clicked */ +.spoiler { background: var(--muted); color: transparent; border-radius: 3px; padding: 0 .15em; cursor: pointer; transition: color .12s; } +.spoiler.is-shown { background: rgba(127, 127, 127, .16); color: inherit; cursor: text; } .turnstile-widget { min-height: 65px; } /* ---- compact density ---- */ diff --git a/src/lib/format.tsx b/src/lib/format.tsx index 017cd7f..f6991af 100644 --- a/src/lib/format.tsx +++ b/src/lib/format.tsx @@ -114,15 +114,32 @@ function YouTubeEmbed({ id, url }: { id: string; url: string }) { ); } +// ||text|| hides content until clicked. +function Spoiler({ text }: { text: string }) { + const [shown, setShown] = useState(false); + return ( + setShown(true)} + onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); setShown(true); } }}> + {text} + + ); +} + function linkify(text: string, selfMsg = false): ReactNode { - const parts = text.split(/(https?:\/\/[^\s]+)/g); - return parts.map((p, i) => { - if (!/^https?:\/\//.test(p)) return {p}; - if (isImageUrl(p)) return ; - if (isAudioUrl(p)) return ; - const yt = youtubeId(p); - if (yt) return ; - return {p}; + // Pull out ||spoilers|| first, then linkify the remaining runs. + return text.split(/(\|\|.+?\|\|)/g).map((seg, si) => { + const sp = /^\|\|(.+)\|\|$/.exec(seg); + if (sp) return ; + return seg.split(/(https?:\/\/[^\s]+)/g).map((p, i) => { + const k = `${si}-${i}`; + if (!/^https?:\/\//.test(p)) return {p}; + if (isImageUrl(p)) return ; + if (isAudioUrl(p)) return ; + const yt = youtubeId(p); + if (yt) return ; + return {p}; + }); }); }