Support math rendering on MFM (#3260)
This commit is contained in:
parent
d2385a0e52
commit
ad84901f39
|
@ -46,6 +46,7 @@
|
||||||
"@types/is-root": "1.0.0",
|
"@types/is-root": "1.0.0",
|
||||||
"@types/is-url": "1.2.28",
|
"@types/is-url": "1.2.28",
|
||||||
"@types/js-yaml": "3.11.2",
|
"@types/js-yaml": "3.11.2",
|
||||||
|
"@types/katex": "0.5.0",
|
||||||
"@types/koa": "2.0.46",
|
"@types/koa": "2.0.46",
|
||||||
"@types/koa-bodyparser": "5.0.1",
|
"@types/koa-bodyparser": "5.0.1",
|
||||||
"@types/koa-compress": "2.0.8",
|
"@types/koa-compress": "2.0.8",
|
||||||
|
@ -140,6 +141,7 @@
|
||||||
"jsdom": "13.0.0",
|
"jsdom": "13.0.0",
|
||||||
"json5": "2.1.0",
|
"json5": "2.1.0",
|
||||||
"json5-loader": "1.0.1",
|
"json5-loader": "1.0.1",
|
||||||
|
"katex": "0.10.0",
|
||||||
"koa": "2.6.1",
|
"koa": "2.6.1",
|
||||||
"koa-bodyparser": "4.2.1",
|
"koa-bodyparser": "4.2.1",
|
||||||
"koa-compress": "3.0.0",
|
"koa-compress": "3.0.0",
|
||||||
|
|
26
src/client/app/common/views/components/formula.vue
Normal file
26
src/client/app/common/views/components/formula.vue
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
<template>
|
||||||
|
<span v-html="compiledFormula"></span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
import * as katex from 'katex';
|
||||||
|
|
||||||
|
export default Vue.extend({
|
||||||
|
props: {
|
||||||
|
formula: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
compiledFormula(): any {
|
||||||
|
return katex.renderToString(this.formula);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
@import "../../../../../../node_modules/katex/dist/katex.min.css";
|
||||||
|
</style>
|
|
@ -1,5 +1,6 @@
|
||||||
import Vue, { VNode } from 'vue';
|
import Vue, { VNode } from 'vue';
|
||||||
import { length } from 'stringz';
|
import { length } from 'stringz';
|
||||||
|
import MkFormula from './formula.vue';
|
||||||
import parse from '../../../../../mfm/parse';
|
import parse from '../../../../../mfm/parse';
|
||||||
import getAcct from '../../../../../misc/acct/render';
|
import getAcct from '../../../../../misc/acct/render';
|
||||||
import MkUrl from './url.vue';
|
import MkUrl from './url.vue';
|
||||||
|
@ -199,6 +200,14 @@ export default Vue.component('misskey-flavored-markdown', {
|
||||||
})];
|
})];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'math': {
|
||||||
|
return [createElement(MkFormula, {
|
||||||
|
props: {
|
||||||
|
formula: token.formula
|
||||||
|
}
|
||||||
|
})];
|
||||||
|
}
|
||||||
|
|
||||||
case 'search': {
|
case 'search': {
|
||||||
return [createElement(MkGoogle, {
|
return [createElement(MkGoogle, {
|
||||||
props: {
|
props: {
|
||||||
|
|
|
@ -53,6 +53,12 @@ const handlers: { [key: string]: (window: any, token: any, mentionedRemoteUsers:
|
||||||
document.body.appendChild(element);
|
document.body.appendChild(element);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
math({ document }, { formula }) {
|
||||||
|
const element = document.createElement('code');
|
||||||
|
element.textContent = formula;
|
||||||
|
document.body.appendChild(element);
|
||||||
|
},
|
||||||
|
|
||||||
link({ document }, { url, title }) {
|
link({ document }, { url, title }) {
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.href = url;
|
a.href = url;
|
||||||
|
|
20
src/mfm/parse/elements/math.ts
Normal file
20
src/mfm/parse/elements/math.ts
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
/**
|
||||||
|
* Math
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type TextElementMath = {
|
||||||
|
type: 'math';
|
||||||
|
content: string;
|
||||||
|
formula: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function(text: string) {
|
||||||
|
const match = text.match(/^\$(.+?)\$/);
|
||||||
|
if (!match) return null;
|
||||||
|
const math = match[0];
|
||||||
|
return {
|
||||||
|
type: 'math',
|
||||||
|
content: math,
|
||||||
|
formula: match[1]
|
||||||
|
} as TextElementMath;
|
||||||
|
}
|
|
@ -8,6 +8,7 @@ import { TextElementCode } from './elements/code';
|
||||||
import { TextElementEmoji } from './elements/emoji';
|
import { TextElementEmoji } from './elements/emoji';
|
||||||
import { TextElementHashtag } from './elements/hashtag';
|
import { TextElementHashtag } from './elements/hashtag';
|
||||||
import { TextElementInlineCode } from './elements/inline-code';
|
import { TextElementInlineCode } from './elements/inline-code';
|
||||||
|
import { TextElementMath } from './elements/math';
|
||||||
import { TextElementLink } from './elements/link';
|
import { TextElementLink } from './elements/link';
|
||||||
import { TextElementMention } from './elements/mention';
|
import { TextElementMention } from './elements/mention';
|
||||||
import { TextElementQuote } from './elements/quote';
|
import { TextElementQuote } from './elements/quote';
|
||||||
|
@ -29,6 +30,7 @@ const elements = [
|
||||||
require('./elements/hashtag'),
|
require('./elements/hashtag'),
|
||||||
require('./elements/code'),
|
require('./elements/code'),
|
||||||
require('./elements/inline-code'),
|
require('./elements/inline-code'),
|
||||||
|
require('./elements/math'),
|
||||||
require('./elements/quote'),
|
require('./elements/quote'),
|
||||||
require('./elements/emoji'),
|
require('./elements/emoji'),
|
||||||
require('./elements/search'),
|
require('./elements/search'),
|
||||||
|
@ -42,6 +44,7 @@ export type TextElement = { type: 'text', content: string }
|
||||||
| TextElementEmoji
|
| TextElementEmoji
|
||||||
| TextElementHashtag
|
| TextElementHashtag
|
||||||
| TextElementInlineCode
|
| TextElementInlineCode
|
||||||
|
| TextElementMath
|
||||||
| TextElementLink
|
| TextElementLink
|
||||||
| TextElementMention
|
| TextElementMention
|
||||||
| TextElementQuote
|
| TextElementQuote
|
||||||
|
|
|
@ -210,6 +210,15 @@ describe('Text', () => {
|
||||||
assert.equal(tokens[0].content, '`var x = "Strawberry Pasta";`');
|
assert.equal(tokens[0].content, '`var x = "Strawberry Pasta";`');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('math', () => {
|
||||||
|
const fomula = 'x = {-b \\pm \\sqrt{b^2-4ac} \\over 2a}.';
|
||||||
|
const text = `$${fomula}$`;
|
||||||
|
const tokens = analyze(text);
|
||||||
|
assert.deepEqual([
|
||||||
|
{ type: 'math', content: text, formula: fomula }
|
||||||
|
], tokens);
|
||||||
|
});
|
||||||
|
|
||||||
it('search', () => {
|
it('search', () => {
|
||||||
const tokens1 = analyze('a b c 検索');
|
const tokens1 = analyze('a b c 検索');
|
||||||
assert.deepEqual([
|
assert.deepEqual([
|
||||||
|
|
Loading…
Reference in a new issue