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(() => {
- #{{ tagName }}
+ #{{ tagName }}
diff --git a/tests/nuxt/__snapshots__/content-rich.test.ts.snap b/tests/nuxt/__snapshots__/content-rich.test.ts.snap
index db4639a9..e1b698c5 100644
--- a/tests/nuxt/__snapshots__/content-rich.test.ts.snap
+++ b/tests/nuxt/__snapshots__/content-rich.test.ts.snap
@@ -44,8 +44,9 @@ exports[`content-rich > code frame 2 1`] = `
class="u-url mention"
rel="nofollow noopener noreferrer"
to="/webtoo.ls/@antfu"
- >
+ >@antfu
Testing
const a = hello
@@ -56,6 +57,62 @@ exports[`content-rich > code frame empty 1`] = `" code frame no lang 1`] = `"hello world
no lang"`;
+exports[`content-rich > collapse mentions 1`] = `
+"
+ @elk
+ @elk
+ content
+ @antfu
+ @daniel
+ @sxzz
+ @patak
+ content
+
+"
+`;
+
exports[`content-rich > custom emoji 1`] = `
"Daniel Roe