From 197ba53c60ab75281e2921bd58f968e39bf2cb90 Mon Sep 17 00:00:00 2001 From: Paul Date: Wed, 7 Jul 2021 12:45:55 +0100 Subject: [PATCH] Fix: Spoilers would not toggle. Hook events into Markdown render using ref. --- .../attachments/Attachment.module.scss | 4 +- src/components/markdown/Renderer.tsx | 129 +++++++++--------- 2 files changed, 64 insertions(+), 69 deletions(-) diff --git a/src/components/common/messaging/attachments/Attachment.module.scss b/src/components/common/messaging/attachments/Attachment.module.scss index b3253ae3..b7bf8c32 100644 --- a/src/components/common/messaging/attachments/Attachment.module.scss +++ b/src/components/common/messaging/attachments/Attachment.module.scss @@ -4,7 +4,7 @@ grid-auto-flow: row dense; width: max-content; - + border-radius: 6px; margin: .125rem 0 .125rem; @@ -31,7 +31,7 @@ } } - &.video { + &.video { .actions { padding: 10px 12px; border-radius: 6px 6px 0 0; diff --git a/src/components/markdown/Renderer.tsx b/src/components/markdown/Renderer.tsx index b501a684..1b531460 100644 --- a/src/components/markdown/Renderer.tsx +++ b/src/components/markdown/Renderer.tsx @@ -13,7 +13,7 @@ import "prismjs/themes/prism-tomorrow.css"; import { RE_MENTIONS } from "revolt.js"; import styles from "./Markdown.module.scss"; -import { useContext } from "preact/hooks"; +import { useCallback, useContext, useRef } from "preact/hooks"; import { internalEmit } from "../../lib/eventEmitter"; @@ -83,64 +83,6 @@ declare global { } } -// Handler for internal links, pushes events to React using magic. -if (typeof window !== "undefined") { - window.internalHandleURL = function (element: HTMLAnchorElement) { - const url = new URL(element.href, location.href); - const pathname = url.pathname; - - if (pathname.startsWith("/@")) { - internalEmit("Intermediate", "openProfile", pathname.substr(2)); - } else { - internalEmit("Intermediate", "navigate", pathname); - } - }; -} - -md.renderer.rules.link_open = function (tokens, idx, options, env, self) { - let internal; - const hIndex = tokens[idx].attrIndex("href"); - if (hIndex >= 0) { - try { - // For internal links, we should use our own handler to use react-router history. - // @ts-ignore - const href = tokens[idx].attrs[hIndex][1]; - const url = new URL(href, location.href); - - if (url.hostname === location.hostname) { - internal = true; - // I'm sorry. - tokens[idx].attrPush([ - "onclick", - "internalHandleURL(this); return false", - ]); - - if (url.pathname.startsWith("/@")) { - tokens[idx].attrPush(["data-type", "mention"]); - } - } - } catch (err) { - // Ignore the error, treat as normal link. - } - } - - if (!internal) { - // Add target=_blank for external links. - const aIndex = tokens[idx].attrIndex("target"); - - if (aIndex < 0) { - tokens[idx].attrPush(["target", "_blank"]); - } else { - try { - // @ts-ignore - tokens[idx].attrs[aIndex][1] = "_blank"; - } catch (_) {} - } - } - - return defaultRender(tokens, idx, options, env, self); -}; - md.renderer.rules.emoji = function (token, idx) { return generateEmoji(token[idx].content); }; @@ -172,21 +114,74 @@ export default function Renderer({ content, disallowBigEmoji }: MarkdownProps) { ? false : content.replace(RE_TWEMOJI, "").trim().length === 0; + const toggle = useCallback((ev: MouseEvent) => { + if (ev.currentTarget) { + let element = ev.currentTarget as HTMLDivElement; + if (element.classList.contains("spoiler")) { + element.classList.add("shown"); + } + } + }, []); + + const handleLink = useCallback((ev: MouseEvent) => { + ev.preventDefault(); + if (ev.currentTarget) { + const element = ev.currentTarget as HTMLAnchorElement; + const url = new URL(element.href, location.href); + const pathname = url.pathname; + + if (pathname.startsWith("/@")) { + internalEmit("Intermediate", "openProfile", pathname.substr(2)); + } else { + internalEmit("Intermediate", "navigate", pathname); + } + } + }, []); + return ( { + if (el) { + (el.querySelectorAll('.spoiler')) + .forEach(element => { + element.removeEventListener('click', toggle); + element.addEventListener('click', toggle); + }); + + (el.querySelectorAll('a')) + .forEach(element => { + element.removeEventListener('click', handleLink); + element.removeAttribute('data-type'); + element.removeAttribute('target'); + + let internal; + const href = element.href; + if (href) { + try { + const url = new URL(href, location.href); + + if (url.hostname === location.hostname) { + internal = true; + element.addEventListener('click', handleLink); + + if (url.pathname.startsWith('/@')) { + element.setAttribute('data-type', 'mention'); + } + } + } catch (err) {} + } + + if (!internal) { + element.setAttribute('target', '_blank'); + } + }); + } + }} className={styles.markdown} dangerouslySetInnerHTML={{ __html: md.render(newContent), }} data-large-emojis={useLargeEmojis} - onClick={(ev) => { - if (ev.target) { - let element = ev.currentTarget; - if (element.classList.contains("spoiler")) { - element.classList.add("shown"); - } - } - }} /> ); }