From 3bdbbcadd9772818c00339faa17e0f4a71dda6bc Mon Sep 17 00:00:00 2001 From: Johann150 Date: Wed, 24 Aug 2022 23:16:53 +0200 Subject: [PATCH] adjust MFM to HTML conversion Removed the misc/get-note-html module which was only used in one place. Instead of it, the general MFM to HTML functionality has been improved to take care of the use cases of that module as well. Co-authored-by: Francis Dinh --- packages/backend/src/mfm/to-html.ts | 45 +++++++++++++------ .../remote/activitypub/misc/get-note-html.ts | 8 ---- .../src/remote/activitypub/renderer/note.ts | 8 ++-- .../src/remote/activitypub/renderer/person.ts | 3 +- 4 files changed, 35 insertions(+), 29 deletions(-) delete mode 100644 packages/backend/src/remote/activitypub/misc/get-note-html.ts diff --git a/packages/backend/src/mfm/to-html.ts b/packages/backend/src/mfm/to-html.ts index b1429f184..af399e81f 100644 --- a/packages/backend/src/mfm/to-html.ts +++ b/packages/backend/src/mfm/to-html.ts @@ -1,17 +1,29 @@ import { JSDOM } from 'jsdom'; import * as mfm from 'mfm-js'; import config from '@/config/index.js'; +import { UserProfiles } from '@/models/index.js'; +import { extractMentions } from '@/misc/extract-mentions.js'; import { intersperse } from '@/prelude/array.js'; -import { IMentionedRemoteUsers } from '@/models/entities/note.js'; -export function toHtml(nodes: mfm.MfmNode[] | null, mentionedRemoteUsers: IMentionedRemoteUsers = []) { +// Transforms MFM to HTML, given the MFM text and a list of user IDs that are +// mentioned in the text. If the list of mentions is not given, all mentions +// from the text will be extracted. +export async function toHtml(mfmText: string, mentions?: string[]): string | null { + const nodes = mfm.parse(mfmText); if (nodes == null) { return null; } - const { window } = new JSDOM(''); + const mentionedUsers = await UserProfiles.createQueryBuilder("user_profiles") + .leftJoin('user_profile.user', 'user') + .select('user.username') + .addSelect('user.host') + // links should preferably use user friendly urls, only fall back to AP ids + .addSelect('COALESCE(user_profile.url, user.uri)', 'url') + .where('userId IN (:...ids)', { ids: mentions ?? extractMentions(nodes) }) + .getManyRaw(); - const doc = window.document; + const doc = new JSDOM('').window.document; function appendChildren(children: mfm.MfmNode[], targetElement: any): void { if (children) { @@ -106,18 +118,23 @@ export function toHtml(nodes: mfm.MfmNode[] | null, mentionedRemoteUsers: IMenti }, mention(node) { - // Mastodon microformat: span.h-card > a.u-url.mention - const a = doc.createElement('a'); const { username, host, acct } = node.props; - const remoteUserInfo = mentionedRemoteUsers.find(remoteUser => remoteUser.username === username && remoteUser.host === host); - a.href = remoteUserInfo?.url ?? remoteUserInfo?.uri ?? `${config.url}/${acct}`; - a.className = 'u-url mention'; - a.textContent = acct; + const userInfo = mentionedUsers.find(user => user.username === username && user.host === host); + if (userInfo != null) { + // Mastodon microformat: span.h-card > a.u-url.mention + const a = doc.createElement('a'); + a.href = userInfo.url ?? `${config.url}/${acct}`; + a.className = 'u-url mention'; + a.textContent = acct; - const card = doc.createElement('span'); - card.className = 'h-card'; - card.appendChild(a); - return card; + const card = doc.createElement('span'); + card.className = 'h-card'; + card.appendChild(a); + return card; + } else { + // this user does not actually exist + return doc.createTextNode(acct); + } }, quote(node) { diff --git a/packages/backend/src/remote/activitypub/misc/get-note-html.ts b/packages/backend/src/remote/activitypub/misc/get-note-html.ts deleted file mode 100644 index 389039ebe..000000000 --- a/packages/backend/src/remote/activitypub/misc/get-note-html.ts +++ /dev/null @@ -1,8 +0,0 @@ -import * as mfm from 'mfm-js'; -import { Note } from '@/models/entities/note.js'; -import { toHtml } from '../../../mfm/to-html.js'; - -export default function(note: Note) { - if (!note.text) return ''; - return toHtml(mfm.parse(note.text), JSON.parse(note.mentionedRemoteUsers)); -} diff --git a/packages/backend/src/remote/activitypub/renderer/note.ts b/packages/backend/src/remote/activitypub/renderer/note.ts index f705aabac..e662c0e92 100644 --- a/packages/backend/src/remote/activitypub/renderer/note.ts +++ b/packages/backend/src/remote/activitypub/renderer/note.ts @@ -5,7 +5,7 @@ import { DriveFile } from '@/models/entities/drive-file.js'; import { DriveFiles, Notes, Users, Emojis, Polls } from '@/models/index.js'; import { Emoji } from '@/models/entities/emoji.js'; import { Poll } from '@/models/entities/poll.js'; -import toHtml from '../misc/get-note-html.js'; +import { toHtml } from '@/mfm/to-html.js'; import renderEmoji from './emoji.js'; import renderMention from './mention.js'; import renderHashtag from './hashtag.js'; @@ -97,9 +97,7 @@ export default async function renderNote(note: Note, dive = true, isTalk = false const summary = note.cw === '' ? String.fromCharCode(0x200B) : note.cw; - const content = toHtml(Object.assign({}, note, { - text: apText, - })); + const content = await toHtml(apText, note.mentions); const emojis = await getEmojis(note.emojis); const apemojis = emojis.map(emoji => renderEmoji(emoji)); @@ -112,7 +110,7 @@ export default async function renderNote(note: Note, dive = true, isTalk = false const asPoll = poll ? { type: 'Question', - content: toHtml(Object.assign({}, note, { text })), + content: await toHtml(text, note.mentions), [poll.expiresAt && poll.expiresAt < new Date() ? 'closed' : 'endTime']: poll.expiresAt, [poll.multiple ? 'anyOf' : 'oneOf']: poll.choices.map((text, i) => ({ type: 'Note', diff --git a/packages/backend/src/remote/activitypub/renderer/person.ts b/packages/backend/src/remote/activitypub/renderer/person.ts index 213741143..7de957882 100644 --- a/packages/backend/src/remote/activitypub/renderer/person.ts +++ b/packages/backend/src/remote/activitypub/renderer/person.ts @@ -1,5 +1,4 @@ import { URL } from 'node:url'; -import * as mfm from 'mfm-js'; import config from '@/config/index.js'; import { ILocalUser } from '@/models/entities/user.js'; import { toHtml } from '@/mfm/to-html.js'; @@ -66,7 +65,7 @@ export async function renderPerson(user: ILocalUser) { url: `${config.url}/@${user.username}`, preferredUsername: user.username, name: user.name, - summary: profile.description ? toHtml(mfm.parse(profile.description)) : null, + summary: profile.description ? await toHtml(profile.description) : null, icon: avatar ? renderImage(avatar) : null, image: banner ? renderImage(banner) : null, tag,