diff --git a/src/api/common/text/elements/quote.ts b/src/api/common/text/elements/quote.ts new file mode 100644 index 000000000..cc8cfffdc --- /dev/null +++ b/src/api/common/text/elements/quote.ts @@ -0,0 +1,14 @@ +/** + * Quoted text + */ + +module.exports = text => { + const match = text.match(/^"([\s\S]+?)\n"/); + if (!match) return null; + const quote = match[0]; + return { + type: 'quote', + content: quote, + quote: quote.substr(1, quote.length - 2).trim(), + }; +}; diff --git a/src/api/common/text/index.ts b/src/api/common/text/index.ts index 47127e864..1e2398dc3 100644 --- a/src/api/common/text/index.ts +++ b/src/api/common/text/index.ts @@ -10,6 +10,7 @@ const elements = [ require('./elements/hashtag'), require('./elements/code'), require('./elements/inline-code'), + require('./elements/quote'), require('./elements/emoji') ]; @@ -33,12 +34,12 @@ export default (source: string) => { // パース while (source != '') { const parsed = elements.some(el => { - let tokens = el(source, i); - if (tokens) { - if (!Array.isArray(tokens)) { - tokens = [tokens]; + let _tokens = el(source, i); + if (_tokens) { + if (!Array.isArray(_tokens)) { + _tokens = [_tokens]; } - tokens.forEach(push); + _tokens.forEach(push); return true; } else { return false; diff --git a/src/web/app/common/views/components/post-html.ts b/src/web/app/common/views/components/post-html.ts index 3676e9e6a..dae118e82 100644 --- a/src/web/app/common/views/components/post-html.ts +++ b/src/web/app/common/views/components/post-html.ts @@ -3,6 +3,10 @@ import * as emojilib from 'emojilib'; import { url } from '../../../config'; import MkUrl from './url.vue'; +const flatten = list => list.reduce( + (a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), [] +); + export default Vue.component('mk-post-html', { props: { ast: { @@ -19,20 +23,16 @@ export default Vue.component('mk-post-html', { } }, render(createElement) { - const els = [].concat.apply([], (this as any).ast.map(token => { + const els = flatten((this as any).ast.map(token => { switch (token.type) { case 'text': const text = token.content.replace(/(\r\n|\n|\r)/g, '\n'); if ((this as any).shouldBreak) { - if (text.indexOf('\n') != -1) { - const x = text.split('\n') - .map(t => [createElement('span', t), createElement('br')]); - x[x.length - 1].pop(); - return x; - } else { - return createElement('span', text); - } + const x = text.split('\n') + .map(t => t == '' ? [createElement('br')] : [createElement('span', t), createElement('br')]); + x[x.length - 1].pop(); + return x; } else { return createElement('span', text.replace(/\n/g, ' ')); } @@ -91,12 +91,46 @@ export default Vue.component('mk-post-html', { case 'inline-code': return createElement('code', token.html); + case 'quote': + const text2 = token.quote.replace(/(\r\n|\n|\r)/g, '\n'); + + if ((this as any).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); + } else { + return createElement('span', { + attrs: { + class: 'quote' + } + }, text2.replace(/\n/g, ' ')); + } + case 'emoji': const emoji = emojilib.lib[token.emoji]; return createElement('span', emoji ? emoji.char : token.content); + + default: + console.log('unknown ast type:', token.type); } })); - return createElement('span', els); + const _els = []; + els.forEach((el, i) => { + if (el.tag == 'br') { + if (els[i - 1].tag != 'div') { + _els.push(el); + } + } else { + _els.push(el); + } + }); + + return createElement('span', _els); } }); diff --git a/src/web/app/desktop/views/components/posts.post.vue b/src/web/app/desktop/views/components/posts.post.vue index 647590e25..118884fcd 100644 --- a/src/web/app/desktop/views/components/posts.post.vue +++ b/src/web/app/desktop/views/components/posts.post.vue @@ -416,6 +416,12 @@ export default Vue.extend({ font-size 1.1em color #717171 + >>> .quote + margin 8px + padding 6px 12px + color #aaa + border-left solid 3px #eee + .mk-url-preview margin-top 8px @@ -512,6 +518,7 @@ export default Vue.extend({