From 68dd705500bd98b7b00606e861de170939e3d528 Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Tue, 27 Feb 2018 12:32:01 +0900
Subject: [PATCH] #1153

---
 src/api/common/text/elements/quote.ts         | 14 +++++
 src/api/common/text/index.ts                  | 11 ++--
 .../app/common/views/components/post-html.ts  | 54 +++++++++++++++----
 .../desktop/views/components/posts.post.vue   |  7 +++
 .../mobile/views/components/posts.post.vue    |  6 +++
 5 files changed, 77 insertions(+), 15 deletions(-)
 create mode 100644 src/api/common/text/elements/quote.ts

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({
 
 <style lang="stylus" module>
 .text
+
 	code
 		padding 4px 8px
 		margin 0 0.5em
diff --git a/src/web/app/mobile/views/components/posts.post.vue b/src/web/app/mobile/views/components/posts.post.vue
index b94d9d16b..b98fadb43 100644
--- a/src/web/app/mobile/views/components/posts.post.vue
+++ b/src/web/app/mobile/views/components/posts.post.vue
@@ -349,6 +349,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