diff --git a/src/components/HTMLContent.tsx b/src/components/HTMLContent.tsx deleted file mode 100644 index a94448c..0000000 --- a/src/components/HTMLContent.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import * as htmlparser2 from "htmlparser2"; - -import type { Mention, Tag } from "@/lib/api/entities/activity"; -import type { CustomEmoji } from "@/lib/api/entities/custom_emoji"; -import { Text, type ChildNode, Element } from "domhandler"; - -interface Props { - content: string; - emoji: CustomEmoji[]; - tags?: Tag[]; - mentions?: Mention[]; -} - -const parser = new DOMParser(); - -export default function HTMLContent(props: Props) { - let { content } = props; - // emoji are always in their shortcode form - props.emoji.forEach((emoji) => { - content = content.replace(new RegExp(`:${emoji.shortcode}:`, "g"), customEmojiElement(emoji)); - }); - - const dom = htmlparser2.parseDocument(content); - const elements = parseElement(dom.children); - - console.log(dom); - - return elements; -} - -const customEmojiElement = (emoji: CustomEmoji) => { - const elem = document.createElement("x-emoji"); - elem.setAttribute("emoji", emoji.shortcode); - return elem.outerHTML; -}; - -function parseElement(elems: ChildNode[]) { - const out = []; - for (const elem of elems) { - if (elem instanceof Text) out.push(<>{elem.data}>); - } -} diff --git a/src/components/status/VulStatusContent.vue b/src/components/status/VulStatusContent.vue index d7686b3..2d7ffa5 100644 --- a/src/components/status/VulStatusContent.vue +++ b/src/components/status/VulStatusContent.vue @@ -3,7 +3,7 @@ import { ref } from "vue"; import { useI18n } from "vue-i18n"; import type Activity from "@/lib/api/entities/activity"; import { FwbButton } from "flowbite-vue"; -import HTMLContent from "../HTMLContent"; +import HTMLContent from "./content/HTMLContent"; const { status } = defineProps<{ status: Activity }>(); @@ -24,7 +24,6 @@ const isCollapsed = ref(!!status.spoiler_text); }} -
+ {dom}
+
+ {elements}
+ >
+ );
+}
+
+/** Create a custom emoji element and return its string representation.
+ * Akkoma server renders HTML but keeps emoji in their shortcode form,
+ * so we turn it into a fake custom element so parseElement knows what to do with it.
+ */
+const customEmojiElement = (emoji: CustomEmoji) => {
+ const elem = document.createElement("x-emoji");
+ elem.setAttribute("emoji", emoji.shortcode);
+ return elem.outerHTML;
+};
+
+/** Recursively parses an element into a JSX representation. */
+function parseElement(elem: ChildNode, emoji: CustomEmoji[], tags: Tag[], mentions: Mention[]) {
+ const out = [] as JSX.Element[];
+
+ if (elem instanceof Document) {
+ // root, start parsing elements
+ out.push(...(elem.children as ChildNode[]).map((e) => parseElement(e, emoji, tags, mentions)).flat());
+ } else if (elem instanceof Text) {
+ // text node, just add it to the output unmodified
+ out.push(<>{elem.data}>);
+ } else if (elem instanceof Element) {
+ console.log(elem.name, elem.attribs);
+
+ switch (elem.name) {
+ case "x-emoji":
+ // Turn