From 4de62220e3f0e2ed759d31b633ef267b12ec16b3 Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Sun, 27 Jan 2019 16:31:00 +0900
Subject: [PATCH] [MFM] Add flip syntax

Resolve #4002
---
 CHANGELOG.md                                  |  1 +
 src/client/app/common/views/components/mfm.ts |  8 +++++++
 src/mfm/html.ts                               |  6 +++++
 src/mfm/parser.ts                             | 24 +++++++++++++++++++
 test/mfm.ts                                   |  9 +++++++
 5 files changed, 48 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index f8291a1357..e9319075c0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,7 @@ unreleased
 * 管理画面のモデレーションのUIを強化
 * 管理画面からリモートユーザーの情報を更新できるように
 * 回転構文の追加
+* 左右反転構文の追加
 * シンタックスハイライトの強化
 * 引用投稿を削除したとき単なるRenoteとしてタイムラインに残る問題を修正
 * イタリック構文の判定の改善
diff --git a/src/client/app/common/views/components/mfm.ts b/src/client/app/common/views/components/mfm.ts
index f6f95deb24..a3849e9607 100644
--- a/src/client/app/common/views/components/mfm.ts
+++ b/src/client/app/common/views/components/mfm.ts
@@ -135,6 +135,14 @@ export default Vue.component('misskey-flavored-markdown', {
 					}, genEl(token.children));
 				}
 
+				case 'flip': {
+					return (createElement as any)('span', {
+						attrs: {
+							style: 'display: inline-block; transform: scaleX(-1);'
+						},
+					}, genEl(token.children));
+				}
+
 				case 'url': {
 					return [createElement(MkUrl, {
 						key: Math.random(),
diff --git a/src/mfm/html.ts b/src/mfm/html.ts
index a40ff19ac8..568a39bc7b 100644
--- a/src/mfm/html.ts
+++ b/src/mfm/html.ts
@@ -61,6 +61,12 @@ export default (tokens: MfmForest, mentionedRemoteUsers: INote['mentionedRemoteU
 			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');
diff --git a/src/mfm/parser.ts b/src/mfm/parser.ts
index 5cd9fc04c2..1cf1edfa4e 100644
--- a/src/mfm/parser.ts
+++ b/src/mfm/parser.ts
@@ -102,6 +102,7 @@ const mfm = P.createLanguage({
 		r.hashtag,
 		r.emoji,
 		r.blockCode,
+		r.flip,
 		r.inlineCode,
 		r.quote,
 		r.math,
@@ -173,6 +174,7 @@ const mfm = P.createLanguage({
 			r.hashtag,
 			r.url,
 			r.link,
+			r.flip,
 			r.emoji,
 			r.text
 		).atLeast(1).tryParse(x), {})),
@@ -195,6 +197,7 @@ const mfm = P.createLanguage({
 			r.math,
 			r.url,
 			r.link,
+			r.flip,
 			r.text
 		).atLeast(1).tryParse(x), {})),
 	//#endregion
@@ -228,6 +231,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)
@@ -253,6 +273,7 @@ const mfm = P.createLanguage({
 			r.hashtag,
 			r.url,
 			r.link,
+			r.flip,
 			r.emoji,
 			r.text
 		).atLeast(1).tryParse(x), {})),
@@ -325,6 +346,7 @@ const mfm = P.createLanguage({
 			r.emoji,
 			r.url,
 			r.link,
+			r.flip,
 			r.math,
 			r.text
 		).atLeast(1).tryParse(x), {})),
@@ -363,6 +385,7 @@ const mfm = P.createLanguage({
 			r.hashtag,
 			r.url,
 			r.link,
+			r.flip,
 			r.emoji,
 			r.text
 		).atLeast(1).tryParse(x), {})),
@@ -385,6 +408,7 @@ const mfm = P.createLanguage({
 				r.motion,
 				r.url,
 				r.link,
+				r.flip,
 				r.mention,
 				r.hashtag,
 				r.emoji,
diff --git a/test/mfm.ts b/test/mfm.ts
index d8cba8ee15..e48bba09ef 100644
--- a/test/mfm.ts
+++ b/test/mfm.ts
@@ -244,6 +244,15 @@ describe('MFM', () => {
 			]);
 		});
 
+		it('flip', () => {
+			const tokens = analyze('<flip>foo</flip>');
+			assert.deepStrictEqual(tokens, [
+				tree('flip', [
+					text('flip')
+				], {}),
+			]);
+		});
+
 		it('spin', () => {
 			const tokens = analyze('<spin>:foo:</spin>');
 			assert.deepStrictEqual(tokens, [