feat: hide redudant mentions (#1293)
This commit is contained in:
parent
da2f19fb23
commit
3132f4fdea
9 changed files with 186 additions and 30 deletions
|
@ -14,6 +14,8 @@ export interface ContentParseOptions {
|
|||
astTransforms?: Transform[]
|
||||
convertMentionLink?: boolean
|
||||
collapseMentionLink?: boolean
|
||||
status?: mastodon.v1.Status
|
||||
inReplyToStatus?: mastodon.v1.Status
|
||||
}
|
||||
|
||||
const sanitizerBasicClasses = filterClasses(/^(h-\S*|p-\S*|u-\S*|dt-\S*|e-\S*|mention|hashtag|ellipsis|invisible)$/u)
|
||||
|
@ -80,6 +82,8 @@ export function parseMastodonHTML(
|
|||
convertMentionLink = false,
|
||||
collapseMentionLink = false,
|
||||
mentions,
|
||||
status,
|
||||
inReplyToStatus,
|
||||
} = options
|
||||
|
||||
if (markdown) {
|
||||
|
@ -121,7 +125,7 @@ export function parseMastodonHTML(
|
|||
transforms.push(transformParagraphs)
|
||||
|
||||
if (collapseMentionLink)
|
||||
transforms.push(transformCollapseMentions())
|
||||
transforms.push(transformCollapseMentions(status, inReplyToStatus))
|
||||
|
||||
return transformSync(parse(html), transforms)
|
||||
}
|
||||
|
@ -443,12 +447,22 @@ function transformParagraphs(node: Node): Node | Node[] {
|
|||
return node
|
||||
}
|
||||
|
||||
function transformCollapseMentions() {
|
||||
function isMention(node: Node) {
|
||||
const child = node.children?.length === 1 ? node.children[0] : null
|
||||
return Boolean(child?.name === 'a' && child.attributes.class?.includes('mention'))
|
||||
}
|
||||
|
||||
function isSpacing(node: Node) {
|
||||
return node.type === TEXT_NODE && !node.value.trim()
|
||||
}
|
||||
|
||||
// Extract the username from a known mention node
|
||||
function getMentionHandle(node: Node): string | undefined {
|
||||
return hrefToHandle(node.children?.[0].attributes.href) // node.children?.[0]?.children?.[0]?.attributes?.['data-id']
|
||||
}
|
||||
|
||||
function transformCollapseMentions(status?: mastodon.v1.Status, inReplyToStatus?: mastodon.v1.Status): Transform {
|
||||
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 || !node.children)
|
||||
|
@ -456,12 +470,12 @@ function transformCollapseMentions() {
|
|||
const mentions: (Node | undefined)[] = []
|
||||
const children = node.children as Node[]
|
||||
for (const child of children) {
|
||||
// metion
|
||||
// mention
|
||||
if (isMention(child)) {
|
||||
mentions.push(child)
|
||||
}
|
||||
// spaces in between
|
||||
else if (child.type === TEXT_NODE && !child.value.trim()) {
|
||||
else if (isSpacing(child)) {
|
||||
mentions.push(child)
|
||||
}
|
||||
// other content, stop collapsing
|
||||
|
@ -478,21 +492,55 @@ function transformCollapseMentions() {
|
|||
if (mentions.length === 0)
|
||||
return node
|
||||
|
||||
let removeNextSpacing = false
|
||||
const contextualMentions = mentions.filter((mention) => {
|
||||
if (!mention)
|
||||
return false
|
||||
|
||||
if (removeNextSpacing && isSpacing(mention)) {
|
||||
removeNextSpacing = false
|
||||
return false
|
||||
}
|
||||
|
||||
if (isMention(mention) && inReplyToStatus) {
|
||||
const mentionHandle = getMentionHandle(mention)
|
||||
if (inReplyToStatus.account.acct === mentionHandle || inReplyToStatus.mentions.some(m => m.acct === mentionHandle))
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
const mentionsCount = contextualMentions.filter(m => m && isMention(m)).length
|
||||
|
||||
// We have a special case for single mentions that are part of a reply.
|
||||
// We already have the replying to badge in this case or the status is connected to the previous one.
|
||||
// This is needed because the status doesn't included the in Reply to handle, only the account id.
|
||||
// But this covers the majority of cases.
|
||||
const showMentions = !(mentionsCount === 0 || (mentionsCount === 1 && status?.inReplyToAccountId))
|
||||
|
||||
const contextualChildren = children.slice(mentions.length)
|
||||
return {
|
||||
...node,
|
||||
children: [h('mention-group', null, ...mentions.filter(Boolean)), ...children.slice(mentions.length)],
|
||||
children: showMentions ? [h('mention-group', null, ...contextualMentions), ...contextualChildren] : contextualChildren,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function hrefToHandle(href: string): string | undefined {
|
||||
const matchUser = href.match(UserLinkRE)
|
||||
if (matchUser) {
|
||||
const [, server, username] = matchUser
|
||||
return `${username}@${server.replace(/(.+\.)(.+\..+)/, '$2')}`
|
||||
}
|
||||
}
|
||||
|
||||
function transformMentionLink(node: Node): string | Node | (string | Node)[] | null {
|
||||
if (node.name === 'a' && node.attributes.class?.includes('mention')) {
|
||||
const href = node.attributes.href
|
||||
if (href) {
|
||||
const matchUser = href.match(UserLinkRE)
|
||||
if (matchUser) {
|
||||
const [, server, username] = matchUser
|
||||
const handle = `${username}@${server.replace(/(.+\.)(.+\..+)/, '$2')}`
|
||||
const handle = hrefToHandle(href)
|
||||
if (handle) {
|
||||
// convert to Tiptap mention node
|
||||
return h('span', { 'data-type': 'mention', 'data-id': handle }, handle)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue