Refactor MFM

Co-authored-by: syuilo syuilotan@yahoo.co.jp
This commit is contained in:
Aya Morisawa
2018-12-20 19:41:04 +09:00
parent e0b107a3a0
commit e9f8897fe2
14 changed files with 588 additions and 518 deletions

View File

@ -2,10 +2,10 @@ const jsdom = require('jsdom');
const { JSDOM } = jsdom;
import config from '../config';
import { INote } from '../models/note';
import { Node } from './parser';
import { intersperse } from '../prelude/array';
import { MfmForest, MfmTree } from './parser';
export default (tokens: Node[], mentionedRemoteUsers: INote['mentionedRemoteUsers'] = []) => {
export default (tokens: MfmForest, mentionedRemoteUsers: INote['mentionedRemoteUsers'] = []) => {
if (tokens == null) {
return null;
}
@ -14,11 +14,11 @@ export default (tokens: Node[], mentionedRemoteUsers: INote['mentionedRemoteUser
const doc = window.document;
function appendChildren(children: Node[], targetElement: any): void {
for (const child of children.map(n => handlers[n.name](n))) targetElement.appendChild(child);
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: Node) => any } = {
const handlers: { [key: string]: (token: MfmTree) => any } = {
bold(token) {
const el = doc.createElement('b');
appendChildren(token.children, el);
@ -58,7 +58,7 @@ export default (tokens: Node[], mentionedRemoteUsers: INote['mentionedRemoteUser
blockCode(token) {
const pre = doc.createElement('pre');
const inner = doc.createElement('code');
inner.innerHTML = token.props.code;
inner.innerHTML = token.node.props.code;
pre.appendChild(inner);
return pre;
},
@ -70,39 +70,39 @@ export default (tokens: Node[], mentionedRemoteUsers: INote['mentionedRemoteUser
},
emoji(token) {
return doc.createTextNode(token.props.emoji ? token.props.emoji : `:${token.props.name}:`);
return doc.createTextNode(token.node.props.emoji ? token.node.props.emoji : `:${token.node.props.name}:`);
},
hashtag(token) {
const a = doc.createElement('a');
a.href = `${config.url}/tags/${token.props.hashtag}`;
a.textContent = `#${token.props.hashtag}`;
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.props.code;
el.textContent = token.node.props.code;
return el;
},
math(token) {
const el = doc.createElement('code');
el.textContent = token.props.formula;
el.textContent = token.node.props.formula;
return el;
},
link(token) {
const a = doc.createElement('a');
a.href = token.props.url;
a.href = token.node.props.url;
appendChildren(token.children, a);
return a;
},
mention(token) {
const a = doc.createElement('a');
const { username, host, acct } = token.props;
const { username, host, acct } = token.node.props;
switch (host) {
case 'github.com':
a.href = `https://github.com/${username}`;
@ -133,7 +133,7 @@ export default (tokens: Node[], mentionedRemoteUsers: INote['mentionedRemoteUser
text(token) {
const el = doc.createElement('span');
const nodes = (token.props.text as string).split('\n').map(x => doc.createTextNode(x));
const nodes = (token.node.props.text as string).split('\n').map(x => doc.createTextNode(x));
for (const x of intersperse('br', nodes)) {
el.appendChild(x === 'br' ? doc.createElement('br') : x);
@ -144,15 +144,15 @@ export default (tokens: Node[], mentionedRemoteUsers: INote['mentionedRemoteUser
url(token) {
const a = doc.createElement('a');
a.href = token.props.url;
a.textContent = token.props.url;
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/?#q=${token.props.query}`;
a.textContent = token.props.content;
a.href = `https://www.google.com/?#q=${token.node.props.query}`;
a.textContent = token.node.props.content;
return a;
}
};

View File

@ -1,40 +1,36 @@
import parser, { Node, plainParser } from './parser';
import parser, { plainParser, MfmForest, MfmTree } from './parser';
import * as A from '../prelude/array';
import * as S from '../prelude/string';
import { createTree, createLeaf } from '../prelude/tree';
export default (source: string, plainText = false): Node[] => {
function concatTextTrees(ts: MfmForest): MfmTree {
return createLeaf({ type: 'text', props: { text: S.concat(ts.map(x => x.node.props.text)) } });
}
function concatIfTextTrees(ts: MfmForest): MfmForest {
return ts[0].node.type === 'text' ? [concatTextTrees(ts)] : ts;
}
function concatConsecutiveTextTrees(ts: MfmForest): MfmForest {
const us = A.concat(A.groupOn(t => t.node.type, ts).map(concatIfTextTrees));
return us.map(t => createTree(t.node, concatConsecutiveTextTrees(t.children)));
}
function isEmptyTextTree(t: MfmTree): boolean {
return t.node.type == 'text' && t.node.props.text === '';
}
function removeEmptyTextNodes(ts: MfmForest): MfmForest {
return ts
.filter(t => !isEmptyTextTree(t))
.map(t => createTree(t.node, removeEmptyTextNodes(t.children)));
}
export default (source: string, plainText = false): MfmForest => {
if (source == null || source == '') {
return null;
}
let nodes: Node[] = plainText ? plainParser.root.tryParse(source) : parser.root.tryParse(source);
const combineText = (es: Node[]): Node =>
({ name: 'text', props: { text: S.concat(es.map(e => e.props.text)) } });
const concatText = (nodes: Node[]): Node[] =>
A.concat(A.groupOn(x => x.name, nodes).map(es =>
es[0].name === 'text' ? [combineText(es)] : es
));
const concatTextRecursive = (es: Node[]): void => {
for (const x of es.filter(x => x.children)) {
x.children = concatText(x.children);
concatTextRecursive(x.children);
}
};
nodes = concatText(nodes);
concatTextRecursive(nodes);
const removeEmptyTextNodes = (nodes: Node[]) => {
for (const n of nodes.filter(n => n.children)) {
n.children = removeEmptyTextNodes(n.children);
}
return nodes.filter(n => !(n.name == 'text' && n.props.text == ''));
};
nodes = removeEmptyTextNodes(nodes);
return nodes;
const raw = plainText ? plainParser.root.tryParse(source) : parser.root.tryParse(source) as MfmForest;
return removeEmptyTextNodes(concatConsecutiveTextTrees(raw));
};

File diff suppressed because one or more lines are too long