feat: collapse mentions (#1034)

This commit is contained in:
Anthony Fu 2023-01-13 01:08:56 +01:00 committed by GitHub
parent d39ea9a6de
commit 36ae8be40a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 145 additions and 15 deletions

View file

@ -13,6 +13,7 @@ export interface ContentParseOptions {
replaceUnicodeEmoji?: boolean
astTransforms?: Transform[]
convertMentionLink?: boolean
collapseMentionLink?: boolean
}
const sanitizerBasicClasses = filterClasses(/^(h-\S*|p-\S*|u-\S*|dt-\S*|e-\S*|mention|hashtag|ellipsis|invisible)$/u)
@ -48,6 +49,7 @@ export function parseMastodonHTML(
markdown = true,
replaceUnicodeEmoji = true,
convertMentionLink = false,
collapseMentionLink = false,
mentions,
} = options
@ -89,6 +91,9 @@ export function parseMastodonHTML(
transforms.push(transformParagraphs)
if (collapseMentionLink)
transforms.push(transformCollapseMentions())
return transformSync(parse(html), transforms)
}
@ -174,16 +179,16 @@ export function treeToText(input: Node): string {
// Strings get converted to text nodes.
// The input node's children have been transformed before the node itself
// gets transformed.
type Transform = (node: Node) => (Node | string)[] | Node | string | null
type Transform = (node: Node, root: Node) => (Node | string)[] | Node | string | null
// Helpers for transforming (filtering, modifying, ...) a parsed HTML tree
// by running the given chain of transform functions one-by-one.
function transformSync(doc: Node, transforms: Transform[]) {
function visit(node: Node, transform: Transform, isRoot = false) {
function visit(node: Node, transform: Transform, root: Node) {
if (Array.isArray(node.children)) {
const children = [] as (Node | string)[]
for (let i = 0; i < node.children.length; i++) {
const result = visit(node.children[i], transform)
const result = visit(node.children[i], transform, root)
if (Array.isArray(result))
children.push(...result)
@ -198,11 +203,11 @@ function transformSync(doc: Node, transforms: Transform[]) {
return value
})
}
return isRoot ? node : transform(node)
return transform(node, root)
}
for (const transform of transforms)
doc = visit(doc, transform, true) as Node
doc = visit(doc, transform, doc) as Node
return doc
}
@ -376,6 +381,48 @@ function transformParagraphs(node: Node): Node | Node[] {
return node
}
function transformCollapseMentions() {
let processed = false
function isMention(node: Node) {
const child = node.children?.length === 1 ? node.children[0] : null
return Boolean(child?.name === 'a' && child.attributes.class?.includes('mention'))
}
return (node: Node, root: Node): Node | Node[] => {
if (processed || node.parent !== root)
return node
const metions: (Node | undefined)[] = []
const children = node.children as Node[]
for (const child of children) {
// metion
if (isMention(child)) {
metions.push(child)
}
// spaces in between
else if (child.type === TEXT_NODE && !child.value.trim()) {
metions.push(child)
}
// other content, stop collapsing
else {
if (child.type === TEXT_NODE)
child.value = child.value.trimStart()
// remove <br> after mention
if (child.name === 'br')
metions.push(undefined)
break
}
}
processed = true
if (metions.length === 0)
return node
return {
...node,
children: [h('mention-group', null, ...metions.filter(Boolean)), ...children.slice(metions.length)],
}
}
}
function transformMentionLink(node: Node): string | Node | (string | Node)[] | null {
if (node.name === 'a' && node.attributes.class?.includes('mention')) {
const href = node.attributes.href