web/timeline: add support for spoilers

This commit is contained in:
Tulir Asokan 2024-10-12 15:15:54 +03:00
parent 989b0fa0e5
commit 3b8767d504
3 changed files with 30 additions and 3 deletions

View file

@ -98,6 +98,20 @@ div.html-body {
vertical-align: middle; 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 { blockquote {
border-left: 2px solid #ccc; border-left: 2px solid #ccc;
padding-left: .5rem; padding-left: .5rem;

View file

@ -62,6 +62,13 @@ interface LocationMessageEventContent extends BaseMessageEventContent {
type MessageEventContent = TextMessageEventContent | MediaMessageEventContent | LocationMessageEventContent type MessageEventContent = TextMessageEventContent | MediaMessageEventContent | LocationMessageEventContent
const onClickHTML = (evt: React.MouseEvent<HTMLDivElement>) => {
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 MessageBody = ({ event }: EventContentProps) => {
const content = event.content as MessageEventContent const content = event.content as MessageEventContent
if (event.type === "m.sticker") { if (event.type === "m.sticker") {
@ -78,7 +85,7 @@ const MessageBody = ({ event }: EventContentProps) => {
case "m.emote": case "m.emote":
case "m.notice": case "m.notice":
if (__html) { if (__html) {
return <div className="html-body" dangerouslySetInnerHTML={{ __html }}/> return <div onClick={onClickHTML} className="html-body" dangerouslySetInnerHTML={{ __html }}/>
} }
return content.body return content.body
case "m.image": { case "m.image": {
@ -86,7 +93,7 @@ const MessageBody = ({ event }: EventContentProps) => {
const style = calculateMediaSize(content.info?.w, content.info?.h) const style = calculateMediaSize(content.info?.w, content.info?.h)
let caption = null let caption = null
if (__html) { if (__html) {
caption = <div className="html-body" dangerouslySetInnerHTML={{ __html }}/> caption = <div onClick={onClickHTML} className="html-body" dangerouslySetInnerHTML={{ __html }}/>
} else if (content.body && content.filename && content.body !== content.filename) { } else if (content.body && content.filename && content.body !== content.filename) {
caption = content.body caption = content.body
} }

View file

@ -106,6 +106,10 @@ export const transformTags: NonNullable<sanitizeHtml.IOptions["transformTags"]>
delete attribs.style 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 // Sanitise and transform data-mx-color and data-mx-bg-color to their CSS
// equivalents // equivalents
const customCSSMapper: Record<string, string> = { const customCSSMapper: Record<string, string> = {
@ -184,7 +188,9 @@ export const sanitizeHtmlParams: sanitizeHtml.IOptions = {
// but strip during the transformation. // but strip during the transformation.
// custom ones first: // custom ones first:
font: ["color", "data-mx-bg-color", "data-mx-color", "style"], // custom to matrix 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"], div: ["data-mx-maths"],
// eslint-disable-next-line id-length // eslint-disable-next-line id-length
a: ["href", "name", "target", "rel"], // remote target: custom to matrix a: ["href", "name", "target", "rel"], // remote target: custom to matrix