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 <normandy@biribiri.dev>
This commit is contained in:
parent
f94ed5e8a6
commit
3bdbbcadd9
4 changed files with 35 additions and 29 deletions
|
@ -1,17 +1,29 @@
|
||||||
import { JSDOM } from 'jsdom';
|
import { JSDOM } from 'jsdom';
|
||||||
import * as mfm from 'mfm-js';
|
import * as mfm from 'mfm-js';
|
||||||
import config from '@/config/index.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 { 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) {
|
if (nodes == null) {
|
||||||
return 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 {
|
function appendChildren(children: mfm.MfmNode[], targetElement: any): void {
|
||||||
if (children) {
|
if (children) {
|
||||||
|
@ -106,18 +118,23 @@ export function toHtml(nodes: mfm.MfmNode[] | null, mentionedRemoteUsers: IMenti
|
||||||
},
|
},
|
||||||
|
|
||||||
mention(node) {
|
mention(node) {
|
||||||
// Mastodon microformat: span.h-card > a.u-url.mention
|
|
||||||
const a = doc.createElement('a');
|
|
||||||
const { username, host, acct } = node.props;
|
const { username, host, acct } = node.props;
|
||||||
const remoteUserInfo = mentionedRemoteUsers.find(remoteUser => remoteUser.username === username && remoteUser.host === host);
|
const userInfo = mentionedUsers.find(user => user.username === username && user.host === host);
|
||||||
a.href = remoteUserInfo?.url ?? remoteUserInfo?.uri ?? `${config.url}/${acct}`;
|
if (userInfo != null) {
|
||||||
a.className = 'u-url mention';
|
// Mastodon microformat: span.h-card > a.u-url.mention
|
||||||
a.textContent = acct;
|
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');
|
const card = doc.createElement('span');
|
||||||
card.className = 'h-card';
|
card.className = 'h-card';
|
||||||
card.appendChild(a);
|
card.appendChild(a);
|
||||||
return card;
|
return card;
|
||||||
|
} else {
|
||||||
|
// this user does not actually exist
|
||||||
|
return doc.createTextNode(acct);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
quote(node) {
|
quote(node) {
|
||||||
|
|
|
@ -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));
|
|
||||||
}
|
|
|
@ -5,7 +5,7 @@ import { DriveFile } from '@/models/entities/drive-file.js';
|
||||||
import { DriveFiles, Notes, Users, Emojis, Polls } from '@/models/index.js';
|
import { DriveFiles, Notes, Users, Emojis, Polls } from '@/models/index.js';
|
||||||
import { Emoji } from '@/models/entities/emoji.js';
|
import { Emoji } from '@/models/entities/emoji.js';
|
||||||
import { Poll } from '@/models/entities/poll.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 renderEmoji from './emoji.js';
|
||||||
import renderMention from './mention.js';
|
import renderMention from './mention.js';
|
||||||
import renderHashtag from './hashtag.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 summary = note.cw === '' ? String.fromCharCode(0x200B) : note.cw;
|
||||||
|
|
||||||
const content = toHtml(Object.assign({}, note, {
|
const content = await toHtml(apText, note.mentions);
|
||||||
text: apText,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const emojis = await getEmojis(note.emojis);
|
const emojis = await getEmojis(note.emojis);
|
||||||
const apemojis = emojis.map(emoji => renderEmoji(emoji));
|
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 ? {
|
const asPoll = poll ? {
|
||||||
type: 'Question',
|
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.expiresAt && poll.expiresAt < new Date() ? 'closed' : 'endTime']: poll.expiresAt,
|
||||||
[poll.multiple ? 'anyOf' : 'oneOf']: poll.choices.map((text, i) => ({
|
[poll.multiple ? 'anyOf' : 'oneOf']: poll.choices.map((text, i) => ({
|
||||||
type: 'Note',
|
type: 'Note',
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { URL } from 'node:url';
|
import { URL } from 'node:url';
|
||||||
import * as mfm from 'mfm-js';
|
|
||||||
import config from '@/config/index.js';
|
import config from '@/config/index.js';
|
||||||
import { ILocalUser } from '@/models/entities/user.js';
|
import { ILocalUser } from '@/models/entities/user.js';
|
||||||
import { toHtml } from '@/mfm/to-html.js';
|
import { toHtml } from '@/mfm/to-html.js';
|
||||||
|
@ -66,7 +65,7 @@ export async function renderPerson(user: ILocalUser) {
|
||||||
url: `${config.url}/@${user.username}`,
|
url: `${config.url}/@${user.username}`,
|
||||||
preferredUsername: user.username,
|
preferredUsername: user.username,
|
||||||
name: user.name,
|
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,
|
icon: avatar ? renderImage(avatar) : null,
|
||||||
image: banner ? renderImage(banner) : null,
|
image: banner ? renderImage(banner) : null,
|
||||||
tag,
|
tag,
|
||||||
|
|
Loading…
Reference in a new issue