From 3adf92ea56f7237ff8918d153d132c1ce688b236 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaqu=C3=ADn=20S=C3=A1nchez?= Date: Thu, 4 Jan 2024 20:51:32 +0100 Subject: [PATCH] feat: add LTR/RTL in hashtags and mentions support (#2541) Co-authored-by: Daniel Roe --- components/tag/TagCard.vue | 6 +- composables/content-parse.ts | 4 + composables/content-render.ts | 23 ++- pages/[[server]]/tags/[tag].vue | 2 +- .../__snapshots__/content-rich.test.ts.snap | 155 +++++++++++++++++- tests/nuxt/content-rich.test.ts | 118 +++---------- 6 files changed, 202 insertions(+), 106 deletions(-) diff --git a/components/tag/TagCard.vue b/components/tag/TagCard.vue index 1660d576..ed84bc53 100644 --- a/components/tag/TagCard.vue +++ b/components/tag/TagCard.vue @@ -39,8 +39,10 @@ function go(evt: MouseEvent | KeyboardEvent) {

- # - {{ tag.name }} + + # + {{ tag.name }} +

diff --git a/composables/content-parse.ts b/composables/content-parse.ts index 722e024f..7f6745ba 100644 --- a/composables/content-parse.ts +++ b/composables/content-parse.ts @@ -72,6 +72,8 @@ const sanitizer = sanitize({ /** * Parse raw HTML form Mastodon server to AST, * with interop of custom emojis and inline Markdown syntax + * @param html The content to parse + * @param options The parsing options */ export function parseMastodonHTML( html: string, @@ -140,6 +142,8 @@ export function parseMastodonHTML( /** * Converts raw HTML form Mastodon server to HTML for Tiptap editor + * @param html The content to parse + * @param customEmojis The custom emojis to use */ export function convertMastodonHTML(html: string, customEmojis: Record = {}) { const tree = parseMastodonHTML(html, { diff --git a/composables/content-render.ts b/composables/content-render.ts index bded3767..6206bd2b 100644 --- a/composables/content-render.ts +++ b/composables/content-render.ts @@ -1,5 +1,5 @@ -import { TEXT_NODE } from 'ultrahtml' -import type { Node } from 'ultrahtml' +import { ELEMENT_NODE, TEXT_NODE } from 'ultrahtml' +import type { ElementNode, Node } from 'ultrahtml' import { Fragment, h, isVNode } from 'vue' import type { VNode } from 'vue' import { RouterLink } from 'vue-router' @@ -98,6 +98,23 @@ function treeToVNode( return null } +function addBdiNode(node: Node) { + if (node.children.length === 1 && node.children[0].type === ELEMENT_NODE && node.children[0].name === 'bdi') + return + + const children = node.children.splice(0, node.children.length) + const bdi = { + name: 'bdi', + parent: node, + loc: node.loc, + type: ELEMENT_NODE, + attributes: {}, + children, + } satisfies ElementNode + children.forEach((n: Node) => n.parent = bdi) + node.children.push(bdi) +} + function handleMention(el: Node) { // Redirect mentions to the user page if (el.name === 'a' && el.attributes.class?.includes('mention')) { @@ -108,11 +125,13 @@ function handleMention(el: Node) { const [, server, username] = matchUser const handle = `${username}@${server.replace(/(.+\.)(.+\..+)/, '$2')}` el.attributes.href = `/${server}/@${username}` + addBdiNode(el) return h(AccountHoverWrapper, { handle, class: 'inline-block' }, () => nodeToVNode(el)) } const matchTag = href.match(TagLinkRE) if (matchTag) { const [, , name] = matchTag + addBdiNode(el) el.attributes.href = `/${currentServer.value}/tags/${name}` } } diff --git a/pages/[[server]]/tags/[tag].vue b/pages/[[server]]/tags/[tag].vue index dfbc0cfe..397a4181 100644 --- a/pages/[[server]]/tags/[tag].vue +++ b/pages/[[server]]/tags/[tag].vue @@ -28,7 +28,7 @@ onReactivated(() => {