From 3b8767d50450c3ce66311da808093f09d0e43264 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sat, 12 Oct 2024 15:15:54 +0300 Subject: [PATCH] web/timeline: add support for spoilers --- web/src/ui/timeline/TimelineEvent.css | 14 ++++++++++++++ web/src/ui/timeline/content/MessageBody.tsx | 11 +++++++++-- web/src/util/html.ts | 8 +++++++- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/web/src/ui/timeline/TimelineEvent.css b/web/src/ui/timeline/TimelineEvent.css index 0b12d01..0dd7e4b 100644 --- a/web/src/ui/timeline/TimelineEvent.css +++ b/web/src/ui/timeline/TimelineEvent.css @@ -98,6 +98,20 @@ div.html-body { vertical-align: middle; } + span[data-mx-spoiler] { + filter: blur(4px); + transition: filter .5s; + cursor: pointer; + + &.spoiler-revealed { + filter: none; + } + + &:not(.spoiler-revealed) a { + pointer-events: none; + } + } + blockquote { border-left: 2px solid #ccc; padding-left: .5rem; diff --git a/web/src/ui/timeline/content/MessageBody.tsx b/web/src/ui/timeline/content/MessageBody.tsx index 27ad6d3..cc3e948 100644 --- a/web/src/ui/timeline/content/MessageBody.tsx +++ b/web/src/ui/timeline/content/MessageBody.tsx @@ -62,6 +62,13 @@ interface LocationMessageEventContent extends BaseMessageEventContent { type MessageEventContent = TextMessageEventContent | MediaMessageEventContent | LocationMessageEventContent +const onClickHTML = (evt: React.MouseEvent) => { + if ((evt.target as HTMLElement).closest("span[data-mx-spoiler]")?.classList.toggle("spoiler-revealed")) { + // When unspoilering, don't trigger links and other clickables inside the spoiler + evt.preventDefault() + } +} + const MessageBody = ({ event }: EventContentProps) => { const content = event.content as MessageEventContent if (event.type === "m.sticker") { @@ -78,7 +85,7 @@ const MessageBody = ({ event }: EventContentProps) => { case "m.emote": case "m.notice": if (__html) { - return
+ return
} return content.body case "m.image": { @@ -86,7 +93,7 @@ const MessageBody = ({ event }: EventContentProps) => { const style = calculateMediaSize(content.info?.w, content.info?.h) let caption = null if (__html) { - caption =
+ caption =
} else if (content.body && content.filename && content.body !== content.filename) { caption = content.body } diff --git a/web/src/util/html.ts b/web/src/util/html.ts index 1a04718..660d794 100644 --- a/web/src/util/html.ts +++ b/web/src/util/html.ts @@ -106,6 +106,10 @@ export const transformTags: NonNullable delete attribs.style } + if (tagName === "span") { + attribs.title = attribs["data-mx-spoiler"] + } + // Sanitise and transform data-mx-color and data-mx-bg-color to their CSS // equivalents const customCSSMapper: Record = { @@ -184,7 +188,9 @@ export const sanitizeHtmlParams: sanitizeHtml.IOptions = { // but strip during the transformation. // custom ones first: font: ["color", "data-mx-bg-color", "data-mx-color", "style"], // custom to matrix - span: ["data-mx-maths", "data-mx-bg-color", "data-mx-color", "data-mx-spoiler", "style"], // custom to matrix + span: [ + "data-mx-maths", "data-mx-bg-color", "data-mx-color", "data-mx-spoiler", "style", "title" + ], // custom to matrix div: ["data-mx-maths"], // eslint-disable-next-line id-length a: ["href", "name", "target", "rel"], // remote target: custom to matrix