diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2018-11-21 05:11:00 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2018-11-21 05:11:00 +0900 |
| commit | 79ffbf95db9d0cc019d06ab93b1bfa6ba0d4f9ae (patch) | |
| tree | 884a18e6735f8557eb392929fcfa2dc1ae09cb6d /src/client | |
| parent | Refactor checkMongoDb (#3339) (diff) | |
| download | sharkey-79ffbf95db9d0cc019d06ab93b1bfa6ba0d4f9ae.tar.gz sharkey-79ffbf95db9d0cc019d06ab93b1bfa6ba0d4f9ae.tar.bz2 sharkey-79ffbf95db9d0cc019d06ab93b1bfa6ba0d4f9ae.zip | |
Improve MFM parser (#3337)
* wip
* wip
* Refactor
* Refactor
* wip
* wip
* wip
* wip
* Refactor
* Refactor
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* Clean up
* Update misskey-flavored-markdown.ts
* wip
* wip
* wip
* wip
* Update parser.ts
* wip
* Add new test
* wip
* Add new test
* Add new test
* wip
* Refactor
* Update parse.ts
* Refactor
* Update parser.ts
* wip
Diffstat (limited to 'src/client')
19 files changed, 212 insertions, 140 deletions
diff --git a/src/client/app/boot.js b/src/client/app/boot.js index 76ea41c649..5e894a18d7 100644 --- a/src/client/app/boot.js +++ b/src/client/app/boot.js @@ -41,6 +41,7 @@ if (`${url.pathname}/`.startsWith('/dev/')) app = 'dev'; if (`${url.pathname}/`.startsWith('/auth/')) app = 'auth'; if (`${url.pathname}/`.startsWith('/admin/')) app = 'admin'; + if (`${url.pathname}/`.startsWith('/test/')) app = 'test'; //#endregion // Script version diff --git a/src/client/app/common/views/components/index.ts b/src/client/app/common/views/components/index.ts index 8569e2cf10..b8fc7c4096 100644 --- a/src/client/app/common/views/components/index.ts +++ b/src/client/app/common/views/components/index.ts @@ -17,7 +17,7 @@ import forkit from './forkit.vue'; import acct from './acct.vue'; import avatar from './avatar.vue'; import nav from './nav.vue'; -import misskeyFlavoredMarkdown from './misskey-flavored-markdown'; +import misskeyFlavoredMarkdown from './misskey-flavored-markdown.vue'; import poll from './poll.vue'; import pollEditor from './poll-editor.vue'; import reactionIcon from './reaction-icon.vue'; diff --git a/src/client/app/common/views/components/misskey-flavored-markdown.ts b/src/client/app/common/views/components/mfm.ts index 1eb738813e..b7ff5bd487 100644 --- a/src/client/app/common/views/components/misskey-flavored-markdown.ts +++ b/src/client/app/common/views/components/mfm.ts @@ -1,11 +1,39 @@ import Vue, { VNode } from 'vue'; import { length } from 'stringz'; +import { Node } from '../../../../../mfm/parser'; import parse from '../../../../../mfm/parse'; -import getAcct from '../../../../../misc/acct/render'; import MkUrl from './url.vue'; import { concat } from '../../../../../prelude/array'; import MkFormula from './formula.vue'; import MkGoogle from './google.vue'; +import { toUnicode } from 'punycode'; +import syntaxHighlight from '../../../../../mfm/syntax-highlight'; + +function getText(tokens: Node[]): string { + let text = ''; + const extract = (tokens: Node[]) => { + tokens.filter(x => x.name === 'text').forEach(x => { + text += x.props.text; + }); + tokens.filter(x => x.children).forEach(x => { + extract(x.children); + }); + }; + extract(tokens); + return text; +} + +function getChildrenCount(tokens: Node[]): number { + let count = 0; + const extract = (tokens: Node[]) => { + tokens.filter(x => x.children).forEach(x => { + count++; + extract(x.children); + }); + }; + extract(tokens); + return count; +} export default Vue.component('misskey-flavored-markdown', { props: { @@ -21,6 +49,10 @@ export default Vue.component('misskey-flavored-markdown', { type: Boolean, default: true }, + author: { + type: Object, + default: null + }, i: { type: Object, default: null @@ -31,23 +63,24 @@ export default Vue.component('misskey-flavored-markdown', { }, render(createElement) { - let ast: any[]; + if (this.text == null || this.text == '') return; + + let ast: Node[]; if (this.ast == null) { // Parse text to ast ast = parse(this.text); } else { - ast = this.ast as any[]; + ast = this.ast as Node[]; } let bigCount = 0; let motionCount = 0; - // Parse ast to DOM - const els = concat(ast.map((token): VNode[] => { - switch (token.type) { + const genEl = (ast: Node[]) => concat(ast.map((token): VNode[] => { + switch (token.name) { case 'text': { - const text = token.content.replace(/(\r\n|\n|\r)/g, '\n'); + const text = token.props.text.replace(/(\r\n|\n|\r)/g, '\n'); if (this.shouldBreak) { const x = text.split('\n') @@ -60,12 +93,12 @@ export default Vue.component('misskey-flavored-markdown', { } case 'bold': { - return [createElement('b', token.bold)]; + return [createElement('b', genEl(token.children))]; } case 'big': { bigCount++; - const isLong = length(token.big) > 10; + const isLong = length(getText(token.children)) > 10 || getChildrenCount(token.children) > 5; const isMany = bigCount > 3; return (createElement as any)('strong', { attrs: { @@ -75,12 +108,12 @@ export default Vue.component('misskey-flavored-markdown', { name: 'animate-css', value: { classes: 'tada', iteration: 'infinite' } }] - }, token.big); + }, genEl(token.children)); } case 'motion': { motionCount++; - const isLong = length(token.motion) > 10; + const isLong = length(getText(token.children)) > 10 || getChildrenCount(token.children) > 5; const isMany = motionCount > 3; return (createElement as any)('span', { attrs: { @@ -90,13 +123,14 @@ export default Vue.component('misskey-flavored-markdown', { name: 'animate-css', value: { classes: 'rubberBand', iteration: 'infinite' } }] - }, token.motion); + }, genEl(token.children)); } case 'url': { return [createElement(MkUrl, { + key: Math.random(), props: { - url: token.content, + url: token.props.url, target: '_blank', style: 'color:var(--mfmLink);' } @@ -107,75 +141,75 @@ export default Vue.component('misskey-flavored-markdown', { return [createElement('a', { attrs: { class: 'link', - href: token.url, + href: token.props.url, target: '_blank', - title: token.url, + title: token.props.url, style: 'color:var(--mfmLink);' } - }, token.title)]; + }, genEl(token.children))]; } case 'mention': { + const host = token.props.host == null && this.author && this.author.host != null ? this.author.host : token.props.host; + const canonical = host != null ? `@${token.props.username}@${toUnicode(host)}` : `@${token.props.username}`; return (createElement as any)('router-link', { + key: Math.random(), attrs: { - to: `/${token.canonical}`, - dataIsMe: (this as any).i && getAcct((this as any).i) == getAcct(token), + to: `/${canonical}`, + // TODO + //dataIsMe: (this as any).i && getAcct((this as any).i) == getAcct(token), style: 'color:var(--mfmMention);' }, directives: [{ name: 'user-preview', - value: token.canonical + value: canonical }] - }, token.canonical); + }, canonical); } case 'hashtag': { return [createElement('router-link', { + key: Math.random(), attrs: { - to: `/tags/${encodeURIComponent(token.hashtag)}`, + to: `/tags/${encodeURIComponent(token.props.hashtag)}`, style: 'color:var(--mfmHashtag);' } - }, token.content)]; + }, `#${token.props.hashtag}`)]; } - case 'code': { + case 'blockCode': { return [createElement('pre', { class: 'code' }, [ createElement('code', { domProps: { - innerHTML: token.html + innerHTML: syntaxHighlight(token.props.code) } }) ])]; } - case 'inline-code': { + case 'inlineCode': { return [createElement('code', { domProps: { - innerHTML: token.html + innerHTML: syntaxHighlight(token.props.code) } })]; } case 'quote': { - const text2 = token.quote.replace(/(\r\n|\n|\r)/g, '\n'); - if (this.shouldBreak) { - const x = text2.split('\n') - .map(t => [createElement('span', t), createElement('br')]); - x[x.length - 1].pop(); return [createElement('div', { attrs: { class: 'quote' } - }, x)]; + }, genEl(token.children))]; } else { return [createElement('span', { attrs: { class: 'quote' } - }, text2.replace(/\n/g, ' '))]; + }, genEl(token.children))]; } } @@ -184,15 +218,16 @@ export default Vue.component('misskey-flavored-markdown', { attrs: { class: 'title' } - }, token.title)]; + }, genEl(token.children))]; } case 'emoji': { const customEmojis = (this.$root.getMetaSync() || { emojis: [] }).emojis || []; return [createElement('mk-emoji', { + key: Math.random(), attrs: { - emoji: token.emoji, - name: token.name + emoji: token.props.emoji, + name: token.props.name }, props: { customEmojis: this.customEmojis || customEmojis @@ -203,8 +238,9 @@ export default Vue.component('misskey-flavored-markdown', { case 'math': { //const MkFormula = () => import('./formula.vue').then(m => m.default); return [createElement(MkFormula, { + key: Math.random(), props: { - formula: token.formula + formula: token.props.formula } })]; } @@ -212,22 +248,22 @@ export default Vue.component('misskey-flavored-markdown', { case 'search': { //const MkGoogle = () => import('./google.vue').then(m => m.default); return [createElement(MkGoogle, { + key: Math.random(), props: { - q: token.query + q: token.props.query } })]; } default: { - console.log('unknown ast type:', token.type); + console.log('unknown ast type:', token.name); return []; } } })); - // el.tag === 'br' のとき i !== 0 が保証されるため、短絡評価により els[i - 1] は配列外参照しない - const _els = els.filter((el, i) => !(el.tag === 'br' && ['div', 'pre'].includes(els[i - 1].tag))); - return createElement('span', _els); + // Parse ast to DOM + return createElement('span', genEl(ast)); } }); diff --git a/src/client/app/common/views/components/misskey-flavored-markdown.vue b/src/client/app/common/views/components/misskey-flavored-markdown.vue new file mode 100644 index 0000000000..b54f376935 --- /dev/null +++ b/src/client/app/common/views/components/misskey-flavored-markdown.vue @@ -0,0 +1,57 @@ +<template> +<mfm v-bind="$attrs" class="havbbuyv"/> +</template> + +<script lang="ts"> +import Vue from 'vue'; +import Mfm from './mfm'; + +export default Vue.extend({ + components: { + Mfm + } +}); +</script> + +<style lang="stylus" scoped> +.havbbuyv + >>> .title + display block + margin-bottom 4px + padding 4px + font-size 90% + text-align center + background var(--mfmTitleBg) + border-radius 4px + + >>> .code + margin 8px 0 + + >>> .quote + margin 8px + padding 6px 12px + color var(--mfmQuote) + border-left solid 3px var(--mfmQuoteLine) + + >>> code + padding 4px 8px + margin 0 0.5em + font-size 80% + color #525252 + background #f8f8f8 + border-radius 2px + + >>> pre > code + padding 16px + margin 0 + + >>> [data-is-me]:after + content "you" + padding 0 4px + margin-left 4px + font-size 80% + color var(--primaryForeground) + background var(--primary) + border-radius 4px + +</style> diff --git a/src/client/app/common/views/components/welcome-timeline.vue b/src/client/app/common/views/components/welcome-timeline.vue index cad09a11a6..d075f06934 100644 --- a/src/client/app/common/views/components/welcome-timeline.vue +++ b/src/client/app/common/views/components/welcome-timeline.vue @@ -14,7 +14,7 @@ </div> </header> <div class="text"> - <misskey-flavored-markdown v-if="note.text" :text="note.text" :customEmojis="note.emojis"/> + <misskey-flavored-markdown v-if="note.text" :text="note.text" :author="note.user" :custom-emojis="note.emojis"/> </div> </div> </div> diff --git a/src/client/app/common/views/pages/follow.vue b/src/client/app/common/views/pages/follow.vue index 9db53fdf8a..72b0b73e01 100644 --- a/src/client/app/common/views/pages/follow.vue +++ b/src/client/app/common/views/pages/follow.vue @@ -9,7 +9,7 @@ <router-link :to="user | userPage" class="name">{{ user | userName }}</router-link> <span class="username">@{{ user | acct }}</span> <div class="description"> - <misskey-flavored-markdown v-if="user.description" :text="user.description" :i="$store.state.i"/> + <misskey-flavored-markdown v-if="user.description" :text="user.description" :author="user" :i="$store.state.i"/> </div> </div> </main> diff --git a/src/client/app/desktop/views/components/note-detail.vue b/src/client/app/desktop/views/components/note-detail.vue index 88108d961f..37c4093355 100644 --- a/src/client/app/desktop/views/components/note-detail.vue +++ b/src/client/app/desktop/views/components/note-detail.vue @@ -46,7 +46,7 @@ <div class="text"> <span v-if="appearNote.isHidden" style="opacity: 0.5">{{ $t('private') }}</span> <span v-if="appearNote.deletedAt" style="opacity: 0.5">{{ $t('deleted') }}</span> - <misskey-flavored-markdown v-if="appearNote.text" :text="appearNote.text" :i="$store.state.i" :customEmojis="appearNote.emojis" /> + <misskey-flavored-markdown v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$store.state.i" :custom-emojis="appearNote.emojis" /> </div> <div class="files" v-if="appearNote.files.length > 0"> <mk-media-list :media-list="appearNote.files" :raw="true"/> diff --git a/src/client/app/desktop/views/components/note.vue b/src/client/app/desktop/views/components/note.vue index 26615b16a6..025d489c09 100644 --- a/src/client/app/desktop/views/components/note.vue +++ b/src/client/app/desktop/views/components/note.vue @@ -27,7 +27,7 @@ <div class="text"> <span v-if="appearNote.isHidden" style="opacity: 0.5">{{ $t('private') }}</span> <a class="reply" v-if="appearNote.reply"><fa icon="reply"/></a> - <misskey-flavored-markdown v-if="appearNote.text" :text="appearNote.text" :i="$store.state.i" :class="$style.text" :customEmojis="appearNote.emojis"/> + <misskey-flavored-markdown v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$store.state.i" :custom-emojis="appearNote.emojis"/> <a class="rp" v-if="appearNote.renote">RN:</a> </div> <div class="files" v-if="appearNote.files.length > 0"> @@ -223,24 +223,6 @@ export default Vue.extend({ overflow-wrap break-word color var(--noteText) - >>> .title - display block - margin-bottom 4px - padding 4px - font-size 90% - text-align center - background var(--mfmTitleBg) - border-radius 4px - - >>> .code - margin 8px 0 - - >>> .quote - margin 8px - padding 6px 12px - color var(--mfmQuote) - border-left solid 3px var(--mfmQuoteLine) - > .reply margin-right 8px color var(--text) @@ -322,28 +304,3 @@ export default Vue.extend({ opacity 0.7 </style> - -<style lang="stylus" module> -.text - - code - padding 4px 8px - margin 0 0.5em - font-size 80% - color #525252 - background #f8f8f8 - border-radius 2px - - pre > code - padding 16px - margin 0 - - [data-is-me]:after - content "you" - padding 0 4px - margin-left 4px - font-size 80% - color var(--primaryForeground) - background var(--primary) - border-radius 4px -</style> diff --git a/src/client/app/desktop/views/components/sub-note-content.vue b/src/client/app/desktop/views/components/sub-note-content.vue index 0007520e99..2a407bdcab 100644 --- a/src/client/app/desktop/views/components/sub-note-content.vue +++ b/src/client/app/desktop/views/components/sub-note-content.vue @@ -4,7 +4,7 @@ <span v-if="note.isHidden" style="opacity: 0.5">{{ $t('private') }}</span> <span v-if="note.deletedAt" style="opacity: 0.5">{{ $t('deleted') }}</span> <a class="reply" v-if="note.replyId"><fa icon="reply"/></a> - <misskey-flavored-markdown v-if="note.text" :text="note.text" :i="$store.state.i" :custom-emojis="note.emojis"/> + <misskey-flavored-markdown v-if="note.text" :text="note.text" :author="note.user" :i="$store.state.i" :custom-emojis="note.emojis"/> <a class="rp" v-if="note.renoteId" :href="`/notes/${note.renoteId}`">RN: ...</a> </div> <details v-if="note.files.length > 0"> diff --git a/src/client/app/desktop/views/components/user-card.vue b/src/client/app/desktop/views/components/user-card.vue index 54fa15a190..c5d925fe6d 100644 --- a/src/client/app/desktop/views/components/user-card.vue +++ b/src/client/app/desktop/views/components/user-card.vue @@ -7,7 +7,7 @@ <router-link :to="user | userPage" class="name">{{ user | userName }}</router-link> <span class="username">@{{ user | acct }}</span> <div class="description"> - <misskey-flavored-markdown v-if="user.description" :text="user.description" :i="$store.state.i"/> + <misskey-flavored-markdown v-if="user.description" :text="user.description" :author="user" :i="$store.state.i"/> </div> </div> </div> diff --git a/src/client/app/desktop/views/pages/deck/deck.user-column.vue b/src/client/app/desktop/views/pages/deck/deck.user-column.vue index 90f7e2aaaa..937166cec1 100644 --- a/src/client/app/desktop/views/pages/deck/deck.user-column.vue +++ b/src/client/app/desktop/views/pages/deck/deck.user-column.vue @@ -22,7 +22,7 @@ </header> <div class="info"> <div class="description"> - <misskey-flavored-markdown v-if="user.description" :text="user.description" :i="$store.state.i"/> + <misskey-flavored-markdown v-if="user.description" :text="user.description" :author="user" :i="$store.state.i"/> </div> <div class="counts"> <div> diff --git a/src/client/app/desktop/views/pages/user/user.header.vue b/src/client/app/desktop/views/pages/user/user.header.vue index 48b5a487f4..9eacbe3914 100644 --- a/src/client/app/desktop/views/pages/user/user.header.vue +++ b/src/client/app/desktop/views/pages/user/user.header.vue @@ -14,7 +14,7 @@ <mk-avatar class="avatar" :user="user" :disable-preview="true"/> <div class="body"> <div class="description"> - <misskey-flavored-markdown v-if="user.description" :text="user.description" :i="$store.state.i"/> + <misskey-flavored-markdown v-if="user.description" :text="user.description" :author="user" :i="$store.state.i"/> </div> <div class="info"> <span class="location" v-if="user.host === null && user.profile.location"><fa icon="map-marker"/> {{ user.profile.location }}</span> diff --git a/src/client/app/mobile/views/components/note-detail.vue b/src/client/app/mobile/views/components/note-detail.vue index f24cc0916f..61968a64d1 100644 --- a/src/client/app/mobile/views/components/note-detail.vue +++ b/src/client/app/mobile/views/components/note-detail.vue @@ -33,7 +33,7 @@ <div class="text"> <span v-if="appearNote.isHidden" style="opacity: 0.5">({{ $t('private') }})</span> <span v-if="appearNote.deletedAt" style="opacity: 0.5">({{ $t('deleted') }})</span> - <misskey-flavored-markdown v-if="appearNote.text" :text="appearNote.text" :i="$store.state.i" :customEmojis="appearNote.emojis"/> + <misskey-flavored-markdown v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$store.state.i" :custom-emojis="appearNote.emojis"/> </div> <div class="files" v-if="appearNote.files.length > 0"> <mk-media-list :media-list="appearNote.files" :raw="true"/> diff --git a/src/client/app/mobile/views/components/note.vue b/src/client/app/mobile/views/components/note.vue index 42fb7118f8..5cfcdc0f3b 100644 --- a/src/client/app/mobile/views/components/note.vue +++ b/src/client/app/mobile/views/components/note.vue @@ -23,7 +23,7 @@ <div class="text"> <span v-if="appearNote.isHidden" style="opacity: 0.5">({{ $t('private') }})</span> <a class="reply" v-if="appearNote.reply"><fa icon="reply"/></a> - <misskey-flavored-markdown v-if="appearNote.text" :text="appearNote.text" :i="$store.state.i" :class="$style.text" :custom-emojis="appearNote.emojis"/> + <misskey-flavored-markdown v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$store.state.i" :custom-emojis="appearNote.emojis"/> <a class="rp" v-if="appearNote.renote != null">RN:</a> </div> <div class="files" v-if="appearNote.files.length > 0"> @@ -188,24 +188,6 @@ export default Vue.extend({ overflow-wrap break-word color var(--noteText) - >>> .title - display block - margin-bottom 4px - padding 4px - font-size 90% - text-align center - background var(--mfmTitleBg) - border-radius 4px - - >>> .code - margin 8px 0 - - >>> .quote - margin 8px - padding 6px 12px - color var(--mfmQuote) - border-left solid 3px var(--mfmQuoteLine) - > .reply margin-right 8px color var(--noteText) @@ -215,15 +197,6 @@ export default Vue.extend({ font-style oblique color var(--renoteText) - [data-is-me]:after - content "you" - padding 0 4px - margin-left 4px - font-size 80% - color var(--primaryForeground) - background var(--primary) - border-radius 4px - .mk-url-preview margin-top 8px @@ -289,18 +262,3 @@ export default Vue.extend({ opacity 0.7 </style> - -<style lang="stylus" module> -.text - code - padding 4px 8px - margin 0 0.5em - font-size 80% - color #525252 - background #f8f8f8 - border-radius 2px - - pre > code - padding 16px - margin 0 -</style> diff --git a/src/client/app/mobile/views/components/sub-note-content.vue b/src/client/app/mobile/views/components/sub-note-content.vue index f4c86f19d2..715ddd6527 100644 --- a/src/client/app/mobile/views/components/sub-note-content.vue +++ b/src/client/app/mobile/views/components/sub-note-content.vue @@ -4,7 +4,7 @@ <span v-if="note.isHidden" style="opacity: 0.5">({{ $t('private') }})</span> <span v-if="note.deletedAt" style="opacity: 0.5">({{ $t('deleted') }})</span> <a class="reply" v-if="note.replyId"><fa icon="reply"/></a> - <misskey-flavored-markdown v-if="note.text" :text="note.text" :i="$store.state.i" :custom-emojis="note.emojis"/> + <misskey-flavored-markdown v-if="note.text" :text="note.text" :author="note.user" :i="$store.state.i" :custom-emojis="note.emojis"/> <a class="rp" v-if="note.renoteId">RN: ...</a> </div> <details v-if="note.files.length > 0"> diff --git a/src/client/app/mobile/views/pages/user.vue b/src/client/app/mobile/views/pages/user.vue index b7f0db6eb9..1f0551680e 100644 --- a/src/client/app/mobile/views/pages/user.vue +++ b/src/client/app/mobile/views/pages/user.vue @@ -20,7 +20,7 @@ <span class="followed" v-if="user.isFollowed">{{ $t('follows-you') }}</span> </div> <div class="description"> - <misskey-flavored-markdown v-if="user.description" :text="user.description" :i="$store.state.i"/> + <misskey-flavored-markdown v-if="user.description" :text="user.description" :author="user" :i="$store.state.i"/> </div> <div class="info"> <p class="location" v-if="user.host === null && user.profile.location"> diff --git a/src/client/app/test/script.ts b/src/client/app/test/script.ts new file mode 100644 index 0000000000..5818cf2913 --- /dev/null +++ b/src/client/app/test/script.ts @@ -0,0 +1,23 @@ +import VueRouter from 'vue-router'; + +// Style +import './style.styl'; + +import init from '../init'; +import Index from './views/index.vue'; + +init(launch => { + document.title = 'Misskey'; + + // Init router + const router = new VueRouter({ + mode: 'history', + base: '/test/', + routes: [ + { path: '/', component: Index }, + ] + }); + + // Launch the app + launch(router); +}); diff --git a/src/client/app/test/style.styl b/src/client/app/test/style.styl new file mode 100644 index 0000000000..ae1a28226a --- /dev/null +++ b/src/client/app/test/style.styl @@ -0,0 +1,6 @@ +@import "../app" +@import "../reset" + +html + height 100% + background var(--bg) diff --git a/src/client/app/test/views/index.vue b/src/client/app/test/views/index.vue new file mode 100644 index 0000000000..b1947ffa4a --- /dev/null +++ b/src/client/app/test/views/index.vue @@ -0,0 +1,34 @@ +<template> +<main> + <ui-card> + <div slot="title">MFM Playground</div> + <section class="fit-top"> + <ui-textarea v-model="mfm"> + <span>MFM</span> + </ui-textarea> + <div> + <misskey-flavored-markdown :text="mfm" :i="$store.state.i"/> + </div> + </section> + </ui-card> +</main> +</template> + +<script lang="ts"> +import Vue from 'vue'; + +export default Vue.extend({ + data() { + return { + mfm: '', + }; + } +}); +</script> + +<style lang="stylus" scoped> +main + max-width 700px + margin 0 auto + +</style> |