7199e6f4e0
* Update reaction.vue * fix bug * wip * wip * wjio * wip * Revert "wip" This reverts commit e427f2160adf4e8a4147006e25a89854edab0033. * wip * wip * wip * Update init.ts * Update drive-window.vue * wip * wip * Use PascalCase for components * Use PascalCase for components * update dep * wip * wip * wip * Update init.ts * wip * Update paging.ts * Update test.vue * watch deep * wip * lint * wip * wip * wip * wip * wiop * wip * Update webpack.config.ts * alllow null poll * wip * wip * wip * wiop * UI redesign & refactor (#6714) * wip * wip * wip * wip * wip * Update drive.vue * Update word-mute.vue * wip * wip * wip * clean up * wip * Update default.vue * wip * Update notes.vue * Update mfm.ts * Update index.home.vue * Update post-form.vue * Update post-form-attaches.vue * wip * Update post-form.vue * Update sidebar.vue * wip * wip * Update index.vue * wip * Update default.vue * Update index.vue * Update index.vue * wip * Update post-form-attaches.vue * Update note.vue * wip * clean up * Update notes.vue * wip * wip * Update ja-JP.yml * wip * wip * Update index.vue * wip * wip * wip * wip * wip * wip * wip * wip * Update default.vue * wip * Update _dark.json5 * wip * wip * wip * clean up * wip * wip * Update index.vue * Update test.vue * wip * wip * fix * wip * wip * wip * wip * clena yop * wip * wip * Update store.ts * Update messaging-room.vue * Update default.widgets.vue * fix * wip * wip * Update modal.vue * wip * Update os.ts * Update os.ts * Update deck.vue * Update init.ts * wip * Update ja-JP.yml * v-sizeは単にwindowのresizeを監視するだけで良いかもしれない * Update modal.vue * wip * Update tooltip.ts * wip * wip * wip * wip * wip * Update image-viewer.vue * wip * wip * Update style.scss * Update style.scss * Update visitor.vue * wip * Update init.ts * Update init.ts * wip * wip * Update visitor.vue * Update visitor.vue * Update visitor.vue * Update visitor.vue * wip * wip * Update modal.vue * Update header.vue * Update menu.vue * Update about.vue * Update about-misskey.vue * wip * wip * Update visitor.vue * Update tooltip.ts * wip * Update drive.vue * wip * Update style.scss * Update header.vue * wip * wip * Update users.user.vue * Update announcements.vue * wip * wip * wip * Update emojis.vue * wip * Update emojis.vue * Update style.scss * Update users.vue * wip * Update style.scss * wip * Update welcome.entrance.vue * Update radio.vue * Update size.ts * Update emoji-edit-dialog.vue * wip * Update emojis.vue * wip * Update emojis.vue * Update emojis.vue * Update emojis.vue * wip * wip * wip * wip * Update file-dialog.vue * wip * wip * Update token-generate-window.vue * Update notification-setting-window.vue * wip * wip * Update _error_.vue * Update ja-JP.yml * wip * wip * Update store.ts * Update emojis.vue * Update emojis.vue * Update emojis.vue * Update announcements.vue * Update store.ts * wip * Update page-editor.vue * wip * wip * Update modal.vue * wip * Update select-file.ts * Update timeline.vue * Update emojis.vue * Update os.ts * wip * Update user-select.vue * Update mfm.ts * Update get-file-info.ts * Update drive.vue * Update init.ts * Update mfm.ts * wip * wip * Update window.vue * Update note.vue * wip * wip * Update user-info.vue * wip * wip * wip * wip * wip * Update header.vue * Update header.vue * wip * Update explore.vue * wip * wip * wip * Update webpack.config.ts * wip * wip * wip * wip * wip * wip * Update autocomplete.ts * wip * wip * wip * Update toast.vue * wip * Update post-form-dialog.vue * wip * wip * wip * wip * wip * Update users.vue * wip * Update explore.vue * wip * wip * wip * Update package.json * wip * Update icon-dialog.vue * wip * wip * Update user-preview.ts * wip * wip * wip * wip * wip * Update instance.vue * Update user-name.vue * Update federation.vue * Update instance.vue * wip * wip * Update tag.vue * wip * wip * wip * wip * wip * Update instance.vue * wip * Update os.ts * Update os.ts * wip * wip * wip * Update router.ts * wip * Update init.ts * Update note.vue * Update messages.vue * wip * wip * wip * wip * wip * google * wip * wip * wip * wip * Update theme-editor.vue * wip * wip * Update room.vue * Update channel-editor.vue * wip * Update window.vue * Update window.vue * wip * Update window.vue * Update window.vue * wip * Update menu.vue * wip * wip * wip * wip * Update messaging-room.vue * wip * Update post-form.vue * Update default.widgets.vue * Update window.vue * wip
184 lines
4.4 KiB
TypeScript
184 lines
4.4 KiB
TypeScript
import { JSDOM } from 'jsdom';
|
|
import config from '../config';
|
|
import { intersperse } from '../prelude/array';
|
|
import { MfmForest, MfmTree } from './prelude';
|
|
import { IMentionedRemoteUsers } from '../models/entities/note';
|
|
import { wellKnownServices } from '../well-known-services';
|
|
|
|
export function toHtml(tokens: MfmForest | null, mentionedRemoteUsers: IMentionedRemoteUsers = []) {
|
|
if (tokens == null) {
|
|
return null;
|
|
}
|
|
|
|
const { window } = new JSDOM('');
|
|
|
|
const doc = window.document;
|
|
|
|
function appendChildren(children: MfmForest, targetElement: any): void {
|
|
for (const child of children.map(t => handlers[t.node.type](t))) targetElement.appendChild(child);
|
|
}
|
|
|
|
const handlers: { [key: string]: (token: MfmTree) => any } = {
|
|
bold(token) {
|
|
const el = doc.createElement('b');
|
|
appendChildren(token.children, el);
|
|
return el;
|
|
},
|
|
|
|
big(token) {
|
|
const el = doc.createElement('strong');
|
|
appendChildren(token.children, el);
|
|
return el;
|
|
},
|
|
|
|
small(token) {
|
|
const el = doc.createElement('small');
|
|
appendChildren(token.children, el);
|
|
return el;
|
|
},
|
|
|
|
strike(token) {
|
|
const el = doc.createElement('del');
|
|
appendChildren(token.children, el);
|
|
return el;
|
|
},
|
|
|
|
italic(token) {
|
|
const el = doc.createElement('i');
|
|
appendChildren(token.children, el);
|
|
return el;
|
|
},
|
|
|
|
motion(token) {
|
|
const el = doc.createElement('i');
|
|
appendChildren(token.children, el);
|
|
return el;
|
|
},
|
|
|
|
spin(token) {
|
|
const el = doc.createElement('i');
|
|
appendChildren(token.children, el);
|
|
return el;
|
|
},
|
|
|
|
jump(token) {
|
|
const el = doc.createElement('i');
|
|
appendChildren(token.children, el);
|
|
return el;
|
|
},
|
|
|
|
flip(token) {
|
|
const el = doc.createElement('span');
|
|
appendChildren(token.children, el);
|
|
return el;
|
|
},
|
|
|
|
blockCode(token) {
|
|
const pre = doc.createElement('pre');
|
|
const inner = doc.createElement('code');
|
|
inner.innerHTML = token.node.props.code;
|
|
pre.appendChild(inner);
|
|
return pre;
|
|
},
|
|
|
|
center(token) {
|
|
const el = doc.createElement('div');
|
|
appendChildren(token.children, el);
|
|
return el;
|
|
},
|
|
|
|
emoji(token) {
|
|
return doc.createTextNode(token.node.props.emoji ? token.node.props.emoji : `\u200B:${token.node.props.name}:\u200B`);
|
|
},
|
|
|
|
hashtag(token) {
|
|
const a = doc.createElement('a');
|
|
a.href = `${config.url}/tags/${token.node.props.hashtag}`;
|
|
a.textContent = `#${token.node.props.hashtag}`;
|
|
a.setAttribute('rel', 'tag');
|
|
return a;
|
|
},
|
|
|
|
inlineCode(token) {
|
|
const el = doc.createElement('code');
|
|
el.textContent = token.node.props.code;
|
|
return el;
|
|
},
|
|
|
|
mathInline(token) {
|
|
const el = doc.createElement('code');
|
|
el.textContent = token.node.props.formula;
|
|
return el;
|
|
},
|
|
|
|
mathBlock(token) {
|
|
const el = doc.createElement('code');
|
|
el.textContent = token.node.props.formula;
|
|
return el;
|
|
},
|
|
|
|
link(token) {
|
|
const a = doc.createElement('a');
|
|
a.href = token.node.props.url;
|
|
appendChildren(token.children, a);
|
|
return a;
|
|
},
|
|
|
|
mention(token) {
|
|
const a = doc.createElement('a');
|
|
const { username, host, acct } = token.node.props;
|
|
const wellKnown = wellKnownServices.find(x => x[0] === host);
|
|
if (wellKnown) {
|
|
a.href = wellKnown[1](username);
|
|
} else {
|
|
const remoteUserInfo = mentionedRemoteUsers.find(remoteUser => remoteUser.username === username && remoteUser.host === host);
|
|
a.href = remoteUserInfo ? (remoteUserInfo.url ? remoteUserInfo.url : remoteUserInfo.uri) : `${config.url}/${acct}`;
|
|
a.className = 'u-url mention';
|
|
}
|
|
a.textContent = acct;
|
|
return a;
|
|
},
|
|
|
|
quote(token) {
|
|
const el = doc.createElement('blockquote');
|
|
appendChildren(token.children, el);
|
|
return el;
|
|
},
|
|
|
|
title(token) {
|
|
const el = doc.createElement('h1');
|
|
appendChildren(token.children, el);
|
|
return el;
|
|
},
|
|
|
|
text(token) {
|
|
const el = doc.createElement('span');
|
|
const nodes = (token.node.props.text as string).split(/\r\n|\r|\n/).map(x => doc.createTextNode(x) as Node);
|
|
|
|
for (const x of intersperse<Node | 'br'>('br', nodes)) {
|
|
el.appendChild(x === 'br' ? doc.createElement('br') : x);
|
|
}
|
|
|
|
return el;
|
|
},
|
|
|
|
url(token) {
|
|
const a = doc.createElement('a');
|
|
a.href = token.node.props.url;
|
|
a.textContent = token.node.props.url;
|
|
return a;
|
|
},
|
|
|
|
search(token) {
|
|
const a = doc.createElement('a');
|
|
a.href = `https://www.google.com/search?q=${token.node.props.query}`;
|
|
a.textContent = token.node.props.content;
|
|
return a;
|
|
}
|
|
};
|
|
|
|
appendChildren(tokens, doc.body);
|
|
|
|
return `<p>${doc.body.innerHTML}</p>`;
|
|
}
|