Merge branch 'develop' into math-block
This commit is contained in:
@ -55,6 +55,18 @@ export default (tokens: MfmForest, mentionedRemoteUsers: INote['mentionedRemoteU
|
||||
return el;
|
||||
},
|
||||
|
||||
spin(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');
|
||||
|
@ -91,6 +91,7 @@ const mfm = P.createLanguage({
|
||||
root: r => P.alt(
|
||||
r.big,
|
||||
r.small,
|
||||
r.spin,
|
||||
r.bold,
|
||||
r.strike,
|
||||
r.italic,
|
||||
@ -101,6 +102,7 @@ const mfm = P.createLanguage({
|
||||
r.hashtag,
|
||||
r.emoji,
|
||||
r.blockCode,
|
||||
r.flip,
|
||||
r.inlineCode,
|
||||
r.quote,
|
||||
r.mathInline,
|
||||
@ -123,6 +125,7 @@ const mfm = P.createLanguage({
|
||||
r.hashtag,
|
||||
r.emoji,
|
||||
r.mathInline,
|
||||
r.spin,
|
||||
r.text
|
||||
).atLeast(1).tryParse(x), {})),
|
||||
//#endregion
|
||||
@ -141,6 +144,15 @@ const mfm = P.createLanguage({
|
||||
).atLeast(1).tryParse(x), {})),
|
||||
//#endregion
|
||||
|
||||
//#region Spin
|
||||
spin: r =>
|
||||
P.regexp(/<spin>(.+?)<\/spin>/, 1)
|
||||
.map(x => createTree('spin', P.alt(
|
||||
r.emoji,
|
||||
r.text
|
||||
).atLeast(1).tryParse(x), {})),
|
||||
//#endregion
|
||||
|
||||
//#region Block code
|
||||
blockCode: r =>
|
||||
newline.then(
|
||||
@ -163,6 +175,7 @@ const mfm = P.createLanguage({
|
||||
r.hashtag,
|
||||
r.url,
|
||||
r.link,
|
||||
r.flip,
|
||||
r.emoji,
|
||||
r.text
|
||||
).atLeast(1).tryParse(x), {})),
|
||||
@ -174,6 +187,7 @@ const mfm = P.createLanguage({
|
||||
.map(x => createTree('center', P.alt(
|
||||
r.big,
|
||||
r.small,
|
||||
r.spin,
|
||||
r.bold,
|
||||
r.strike,
|
||||
r.italic,
|
||||
@ -184,6 +198,7 @@ const mfm = P.createLanguage({
|
||||
r.mathInline,
|
||||
r.url,
|
||||
r.link,
|
||||
r.flip,
|
||||
r.text
|
||||
).atLeast(1).tryParse(x), {})),
|
||||
//#endregion
|
||||
@ -217,6 +232,23 @@ const mfm = P.createLanguage({
|
||||
}),
|
||||
//#endregion
|
||||
|
||||
//#region Flip
|
||||
flip: r =>
|
||||
P.regexp(/<flip>(.+?)<\/flip>/, 1)
|
||||
.map(x => createTree('flip', P.alt(
|
||||
r.big,
|
||||
r.small,
|
||||
r.spin,
|
||||
r.bold,
|
||||
r.strike,
|
||||
r.link,
|
||||
r.italic,
|
||||
r.motion,
|
||||
r.emoji,
|
||||
r.text
|
||||
).atLeast(1).tryParse(x), {})),
|
||||
//#endregion
|
||||
|
||||
//#region Inline code
|
||||
inlineCode: r =>
|
||||
P.regexp(/`([^´\n]+?)`/, 1)
|
||||
@ -242,6 +274,7 @@ const mfm = P.createLanguage({
|
||||
r.hashtag,
|
||||
r.url,
|
||||
r.link,
|
||||
r.flip,
|
||||
r.emoji,
|
||||
r.text
|
||||
).atLeast(1).tryParse(x), {})),
|
||||
@ -262,6 +295,7 @@ const mfm = P.createLanguage({
|
||||
return createTree('link', P.alt(
|
||||
r.big,
|
||||
r.small,
|
||||
r.spin,
|
||||
r.bold,
|
||||
r.strike,
|
||||
r.italic,
|
||||
@ -311,6 +345,7 @@ const mfm = P.createLanguage({
|
||||
.map(x => createTree('motion', P.alt(
|
||||
r.bold,
|
||||
r.small,
|
||||
r.spin,
|
||||
r.strike,
|
||||
r.italic,
|
||||
r.mention,
|
||||
@ -318,6 +353,7 @@ const mfm = P.createLanguage({
|
||||
r.emoji,
|
||||
r.url,
|
||||
r.link,
|
||||
r.flip,
|
||||
r.mathInline,
|
||||
r.text
|
||||
).atLeast(1).tryParse(x), {})),
|
||||
@ -356,6 +392,7 @@ const mfm = P.createLanguage({
|
||||
r.hashtag,
|
||||
r.url,
|
||||
r.link,
|
||||
r.flip,
|
||||
r.emoji,
|
||||
r.text
|
||||
).atLeast(1).tryParse(x), {})),
|
||||
@ -365,18 +402,20 @@ const mfm = P.createLanguage({
|
||||
title: r =>
|
||||
newline.then(P((input, i) => {
|
||||
const text = input.substr(i);
|
||||
const match = text.match(/^((【|\[)(.+?)(】|]))(\n|$)/);
|
||||
const match = text.match(/^([【\[]([^【\[】\]\n]+?)[】\]])(\n|$)/);
|
||||
if (!match) return P.makeFailure(i, 'not a title');
|
||||
const q = match[1].trim().substring(1, match[1].length - 1);
|
||||
const q = match[2].trim();
|
||||
const contents = P.alt(
|
||||
r.big,
|
||||
r.small,
|
||||
r.spin,
|
||||
r.bold,
|
||||
r.strike,
|
||||
r.italic,
|
||||
r.motion,
|
||||
r.url,
|
||||
r.link,
|
||||
r.flip,
|
||||
r.mention,
|
||||
r.hashtag,
|
||||
r.emoji,
|
||||
|
@ -1,343 +0,0 @@
|
||||
import { capitalize, toUpperCase } from '../prelude/string';
|
||||
|
||||
function escape(text: string) {
|
||||
return text
|
||||
.replace(/>/g, '>')
|
||||
.replace(/</g, '<');
|
||||
}
|
||||
|
||||
// 文字数が多い順にソートします
|
||||
// そうしないと、「function」という文字列が与えられたときに「func」が先にマッチしてしまう可能性があるためです
|
||||
const _keywords = [
|
||||
'true',
|
||||
'false',
|
||||
'null',
|
||||
'nil',
|
||||
'undefined',
|
||||
'void',
|
||||
'var',
|
||||
'const',
|
||||
'let',
|
||||
'mut',
|
||||
'dim',
|
||||
'if',
|
||||
'then',
|
||||
'else',
|
||||
'switch',
|
||||
'match',
|
||||
'case',
|
||||
'default',
|
||||
'for',
|
||||
'each',
|
||||
'in',
|
||||
'while',
|
||||
'loop',
|
||||
'continue',
|
||||
'break',
|
||||
'do',
|
||||
'goto',
|
||||
'next',
|
||||
'end',
|
||||
'sub',
|
||||
'throw',
|
||||
'try',
|
||||
'catch',
|
||||
'finally',
|
||||
'enum',
|
||||
'delegate',
|
||||
'function',
|
||||
'func',
|
||||
'fun',
|
||||
'fn',
|
||||
'return',
|
||||
'yield',
|
||||
'async',
|
||||
'await',
|
||||
'require',
|
||||
'include',
|
||||
'import',
|
||||
'imports',
|
||||
'export',
|
||||
'exports',
|
||||
'from',
|
||||
'as',
|
||||
'using',
|
||||
'use',
|
||||
'internal',
|
||||
'module',
|
||||
'namespace',
|
||||
'where',
|
||||
'select',
|
||||
'struct',
|
||||
'union',
|
||||
'new',
|
||||
'delete',
|
||||
'this',
|
||||
'super',
|
||||
'base',
|
||||
'class',
|
||||
'interface',
|
||||
'abstract',
|
||||
'static',
|
||||
'public',
|
||||
'private',
|
||||
'protected',
|
||||
'virtual',
|
||||
'partial',
|
||||
'override',
|
||||
'extends',
|
||||
'implements',
|
||||
'constructor'
|
||||
];
|
||||
|
||||
const keywords = _keywords
|
||||
.concat(_keywords.map(capitalize))
|
||||
.concat(_keywords.map(toUpperCase))
|
||||
.sort((a, b) => b.length - a.length);
|
||||
|
||||
const symbols = [
|
||||
'=',
|
||||
'+',
|
||||
'-',
|
||||
'*',
|
||||
'/',
|
||||
'%',
|
||||
'~',
|
||||
'^',
|
||||
'&',
|
||||
'|',
|
||||
'>',
|
||||
'<',
|
||||
'!',
|
||||
'?'
|
||||
];
|
||||
|
||||
type Token = {
|
||||
html: string
|
||||
next: number
|
||||
};
|
||||
|
||||
type Element = (code: string, i: number, source: string) => (Token | null);
|
||||
|
||||
const elements: Element[] = [
|
||||
// comment
|
||||
code => {
|
||||
if (code.substr(0, 2) != '//') return null;
|
||||
const match = code.match(/^\/\/(.+?)(\n|$)/);
|
||||
if (!match) return null;
|
||||
const comment = match[0];
|
||||
return {
|
||||
html: `<span class="comment">${escape(comment)}</span>`,
|
||||
next: comment.length
|
||||
};
|
||||
},
|
||||
|
||||
// block comment
|
||||
code => {
|
||||
const match = code.match(/^\/\*([\s\S]+?)\*\//);
|
||||
if (!match) return null;
|
||||
return {
|
||||
html: `<span class="comment">${escape(match[0])}</span>`,
|
||||
next: match[0].length
|
||||
};
|
||||
},
|
||||
|
||||
// string
|
||||
code => {
|
||||
if (!/^['"`]/.test(code)) return null;
|
||||
const begin = code[0];
|
||||
let str = begin;
|
||||
let thisIsNotAString = false;
|
||||
for (let i = 1; i < code.length; i++) {
|
||||
const char = code[i];
|
||||
if (char == '\\') {
|
||||
str += char;
|
||||
str += code[i + 1] || '';
|
||||
i++;
|
||||
continue;
|
||||
} else if (char == begin) {
|
||||
str += char;
|
||||
break;
|
||||
} else if (char == '\n' || i == (code.length - 1)) {
|
||||
thisIsNotAString = true;
|
||||
break;
|
||||
} else {
|
||||
str += char;
|
||||
}
|
||||
}
|
||||
if (thisIsNotAString) {
|
||||
return null;
|
||||
} else {
|
||||
return {
|
||||
html: `<span class="string">${escape(str)}</span>`,
|
||||
next: str.length
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// regexp
|
||||
code => {
|
||||
if (code[0] != '/') return null;
|
||||
let regexp = '';
|
||||
let thisIsNotARegexp = false;
|
||||
for (let i = 1; i < code.length; i++) {
|
||||
const char = code[i];
|
||||
if (char == '\\') {
|
||||
regexp += char;
|
||||
regexp += code[i + 1] || '';
|
||||
i++;
|
||||
continue;
|
||||
} else if (char == '/') {
|
||||
break;
|
||||
} else if (char == '\n' || i == (code.length - 1)) {
|
||||
thisIsNotARegexp = true;
|
||||
break;
|
||||
} else {
|
||||
regexp += char;
|
||||
}
|
||||
}
|
||||
|
||||
if (thisIsNotARegexp) return null;
|
||||
if (regexp == '') return null;
|
||||
if (regexp.startsWith(' ') && regexp.endsWith(' ')) return null;
|
||||
|
||||
return {
|
||||
html: `<span class="regexp">/${escape(regexp)}/</span>`,
|
||||
next: regexp.length + 2
|
||||
};
|
||||
},
|
||||
|
||||
// label
|
||||
code => {
|
||||
if (code[0] != '@') return null;
|
||||
const match = code.match(/^@([a-zA-Z_-]+?)\n/);
|
||||
if (!match) return null;
|
||||
const label = match[0];
|
||||
return {
|
||||
html: `<span class="label">${label}</span>`,
|
||||
next: label.length
|
||||
};
|
||||
},
|
||||
|
||||
// number
|
||||
(code, i, source) => {
|
||||
const prev = source[i - 1];
|
||||
if (prev && /[a-zA-Z]/.test(prev)) return null;
|
||||
if (!/^[\-\+]?[0-9\.]+/.test(code)) return null;
|
||||
const match = code.match(/^[\-\+]?[0-9\.]+/)[0];
|
||||
if (match) {
|
||||
return {
|
||||
html: `<span class="number">${match}</span>`,
|
||||
next: match.length
|
||||
};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
// nan
|
||||
(code, i, source) => {
|
||||
const prev = source[i - 1];
|
||||
if (prev && /[a-zA-Z]/.test(prev)) return null;
|
||||
if (code.substr(0, 3) == 'NaN') {
|
||||
return {
|
||||
html: `<span class="nan">NaN</span>`,
|
||||
next: 3
|
||||
};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
// method
|
||||
code => {
|
||||
const match = code.match(/^([a-zA-Z_-]+?)\(/);
|
||||
if (!match) return null;
|
||||
|
||||
if (match[1] == '-') return null;
|
||||
|
||||
return {
|
||||
html: `<span class="method">${match[1]}</span>`,
|
||||
next: match[1].length
|
||||
};
|
||||
},
|
||||
|
||||
// property
|
||||
(code, i, source) => {
|
||||
const prev = source[i - 1];
|
||||
if (prev != '.') return null;
|
||||
|
||||
const match = code.match(/^[a-zA-Z0-9_-]+/);
|
||||
if (!match) return null;
|
||||
|
||||
return {
|
||||
html: `<span class="property">${match[0]}</span>`,
|
||||
next: match[0].length
|
||||
};
|
||||
},
|
||||
|
||||
// keyword
|
||||
(code, i, source) => {
|
||||
const prev = source[i - 1];
|
||||
if (prev && /[a-zA-Z]/.test(prev)) return null;
|
||||
|
||||
const match = keywords.filter(k => code.substr(0, k.length) == k)[0];
|
||||
if (match) {
|
||||
if (/^[a-zA-Z]/.test(code.substr(match.length))) return null;
|
||||
return {
|
||||
html: `<span class="keyword ${match}">${match}</span>`,
|
||||
next: match.length
|
||||
};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
// symbol
|
||||
code => {
|
||||
const match = symbols.filter(s => code[0] == s)[0];
|
||||
if (match) {
|
||||
return {
|
||||
html: `<span class="symbol">${match}</span>`,
|
||||
next: 1
|
||||
};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
// TODO: specify lang
|
||||
export default (source: string, lang?: string): string => {
|
||||
let code = source;
|
||||
let html = '';
|
||||
|
||||
let i = 0;
|
||||
|
||||
function push(token: Token) {
|
||||
html += token.html;
|
||||
code = code.substr(token.next);
|
||||
i += token.next;
|
||||
}
|
||||
|
||||
while (code != '') {
|
||||
const parsed = elements.some(el => {
|
||||
const e = el(code, i, source);
|
||||
if (e) {
|
||||
push(e);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (!parsed) {
|
||||
push({
|
||||
html: escape(code[0]),
|
||||
next: 1
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return html;
|
||||
};
|
Reference in New Issue
Block a user