From 5dce81c0dbbd6531ecf927fd3c9a381f6ddbf181 Mon Sep 17 00:00:00 2001
From: MeiMei <30769358+mei23@users.noreply.github.com>
Date: Sun, 14 Oct 2018 16:56:19 +0900
Subject: [PATCH] =?UTF-8?q?=E9=9D=9EASCII=E3=81=AA=E3=83=89=E3=83=A1?=
 =?UTF-8?q?=E3=82=A4=E3=83=B3=E3=81=B8=E3=81=AE=E3=83=A1=E3=83=B3=E3=82=B7?=
 =?UTF-8?q?=E3=83=A7=E3=83=B3=E3=81=AE=E4=BF=AE=E6=AD=A3=20(#2903)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* punycodeでされたmentionのラベルをunicodeとして表示する

* post-form mentionはpunycodeにする

* mentionの表示はURLもAPI向けもunicodeにする
---
 .../components/misskey-flavored-markdown.ts      |  6 +++---
 .../app/common/views/directives/autocomplete.ts  |  4 ++--
 .../app/desktop/views/components/post-form.vue   |  5 +++--
 .../app/mobile/views/components/post-form.vue    |  5 +++--
 src/mfm/parse/elements/mention.ts                |  4 ++++
 test/mfm.ts                                      | 16 ++++++++++++----
 6 files changed, 27 insertions(+), 13 deletions(-)

diff --git a/src/client/app/common/views/components/misskey-flavored-markdown.ts b/src/client/app/common/views/components/misskey-flavored-markdown.ts
index 8da50395cc..1390e7b284 100644
--- a/src/client/app/common/views/components/misskey-flavored-markdown.ts
+++ b/src/client/app/common/views/components/misskey-flavored-markdown.ts
@@ -116,16 +116,16 @@ export default Vue.component('misskey-flavored-markdown', {
 				case 'mention': {
 					return (createElement as any)('a', {
 						attrs: {
-							href: `${url}/@${getAcct(token)}`,
+							href: `${url}/${token.canonical}`,
 							target: '_blank',
 							dataIsMe: (this as any).i && getAcct((this as any).i) == getAcct(token),
 							style: 'color:var(--mfmMention);'
 						},
 						directives: [{
 							name: 'user-preview',
-							value: token.content
+							value: token.canonical
 						}]
-					}, token.content);
+					}, token.canonical);
 				}
 
 				case 'hashtag': {
diff --git a/src/client/app/common/views/directives/autocomplete.ts b/src/client/app/common/views/directives/autocomplete.ts
index f7f8e9bf16..e2cc64d79f 100644
--- a/src/client/app/common/views/directives/autocomplete.ts
+++ b/src/client/app/common/views/directives/autocomplete.ts
@@ -1,6 +1,6 @@
 import * as getCaretCoordinates from 'textarea-caret';
 import MkAutocomplete from '../components/autocomplete.vue';
-import renderAcct from '../../../../../misc/acct/render';
+import { toASCII } from 'punycode';
 
 export default {
 	bind(el, binding, vn) {
@@ -188,7 +188,7 @@ class Autocomplete {
 			const trimmedBefore = before.substring(0, before.lastIndexOf('@'));
 			const after = source.substr(caret);
 
-			const acct = renderAcct(value);
+			const acct = value.host === null ? value.username : `${value.username}@${toASCII(value.host)}`;
 
 			// 挿入
 			this.text = `${trimmedBefore}@${acct} ${after}`;
diff --git a/src/client/app/desktop/views/components/post-form.vue b/src/client/app/desktop/views/components/post-form.vue
index e25cc33579..5301f19158 100644
--- a/src/client/app/desktop/views/components/post-form.vue
+++ b/src/client/app/desktop/views/components/post-form.vue
@@ -65,6 +65,7 @@ import { host } from '../../../config';
 import { erase, unique } from '../../../../../prelude/array';
 import { length } from 'stringz';
 import parseAcct from '../../../../../misc/acct/parse';
+import { toASCII } from 'punycode';
 
 export default Vue.extend({
 	components: {
@@ -158,14 +159,14 @@ export default Vue.extend({
 		}
 
 		if (this.reply && this.reply.user.host != null) {
-			this.text = `@${this.reply.user.username}@${this.reply.user.host} `;
+			this.text = `@${this.reply.user.username}@${toASCII(this.reply.user.host)} `;
 		}
 
 		if (this.reply && this.reply.text != null) {
 			const ast = parse(this.reply.text);
 
 			ast.filter(t => t.type == 'mention').forEach(x => {
-				const mention = x.host ? `@${x.username}@${x.host}` : `@${x.username}`;
+				const mention = x.host ? `@${x.username}@${toASCII(x.host)}` : `@${x.username}`;
 
 				// 自分は除外
 				if (this.$store.state.i.username == x.username && x.host == null) return;
diff --git a/src/client/app/mobile/views/components/post-form.vue b/src/client/app/mobile/views/components/post-form.vue
index 3de920cf22..e532430d0f 100644
--- a/src/client/app/mobile/views/components/post-form.vue
+++ b/src/client/app/mobile/views/components/post-form.vue
@@ -62,6 +62,7 @@ import { host } from '../../../config';
 import { erase, unique } from '../../../../../prelude/array';
 import { length } from 'stringz';
 import parseAcct from '../../../../../misc/acct/parse';
+import { toASCII } from 'punycode';
 
 export default Vue.extend({
 	components: {
@@ -153,14 +154,14 @@ export default Vue.extend({
 		}
 
 		if (this.reply && this.reply.user.host != null) {
-			this.text = `@${this.reply.user.username}@${this.reply.user.host} `;
+			this.text = `@${this.reply.user.username}@${toASCII(this.reply.user.host)} `;
 		}
 
 		if (this.reply && this.reply.text != null) {
 			const ast = parse(this.reply.text);
 
 			ast.filter(t => t.type == 'mention').forEach(x => {
-				const mention = x.host ? `@${x.username}@${x.host}` : `@${x.username}`;
+				const mention = x.host ? `@${x.username}@${toASCII(x.host)}` : `@${x.username}`;
 
 				// 自分は除外
 				if (this.$store.state.i.username == x.username && x.host == null) return;
diff --git a/src/mfm/parse/elements/mention.ts b/src/mfm/parse/elements/mention.ts
index a95ec00384..ade5954423 100644
--- a/src/mfm/parse/elements/mention.ts
+++ b/src/mfm/parse/elements/mention.ts
@@ -2,10 +2,12 @@
  * Mention
  */
 import parseAcct from '../../../misc/acct/parse';
+import { toUnicode } from 'punycode';
 
 export type TextElementMention = {
 	type: 'mention'
 	content: string
+	canonical: string
 	username: string
 	host: string
 };
@@ -15,9 +17,11 @@ export default function(text: string) {
 	if (!match) return null;
 	const mention = match[0];
 	const { username, host } = parseAcct(mention.substr(1));
+	const canonical = host != null ? `@${username}@${toUnicode(host)}` : mention;
 	return {
 		type: 'mention',
 		content: mention,
+		canonical,
 		username,
 		host
 	} as TextElementMention;
diff --git a/test/mfm.ts b/test/mfm.ts
index dc0947e5e9..f9cb56a003 100644
--- a/test/mfm.ts
+++ b/test/mfm.ts
@@ -8,9 +8,9 @@ describe('Text', () => {
 	it('can be analyzed', () => {
 		const tokens = analyze('@himawari @hima_sub@namori.net お腹ペコい :cat: #yryr');
 		assert.deepEqual([
-			{ type: 'mention', content: '@himawari', username: 'himawari', host: null },
+			{ type: 'mention', content: '@himawari', canonical: '@himawari', username: 'himawari', host: null },
 			{ type: 'text', content: ' '},
-			{ type: 'mention', content: '@hima_sub@namori.net', username: 'hima_sub', host: 'namori.net' },
+			{ type: 'mention', content: '@hima_sub@namori.net', canonical: '@hima_sub@namori.net', username: 'hima_sub', host: 'namori.net' },
 			{ type: 'text', content: ' お腹ペコい ' },
 			{ type: 'emoji', content: ':cat:', emoji: 'cat'},
 			{ type: 'text', content: ' '},
@@ -58,7 +58,7 @@ describe('Text', () => {
 			it('local', () => {
 				const tokens = analyze('@himawari お腹ペコい');
 				assert.deepEqual([
-					{ type: 'mention', content: '@himawari', username: 'himawari', host: null },
+					{ type: 'mention', content: '@himawari', canonical: '@himawari', username: 'himawari', host: null },
 					{ type: 'text', content: ' お腹ペコい' }
 				], tokens);
 			});
@@ -66,7 +66,15 @@ describe('Text', () => {
 			it('remote', () => {
 				const tokens = analyze('@hima_sub@namori.net お腹ペコい');
 				assert.deepEqual([
-					{ type: 'mention', content: '@hima_sub@namori.net', username: 'hima_sub', host: 'namori.net' },
+					{ type: 'mention', content: '@hima_sub@namori.net', canonical: '@hima_sub@namori.net', username: 'hima_sub', host: 'namori.net' },
+					{ type: 'text', content: ' お腹ペコい' }
+				], tokens);
+			});
+
+			it('remote punycode', () => {
+				const tokens = analyze('@hima_sub@xn--q9j5bya.xn--zckzah お腹ペコい');
+				assert.deepEqual([
+					{ type: 'mention', content: '@hima_sub@xn--q9j5bya.xn--zckzah', canonical: '@hima_sub@なもり.テスト', username: 'hima_sub', host: 'xn--q9j5bya.xn--zckzah' },
 					{ type: 'text', content: ' お腹ペコい' }
 				], tokens);
 			});