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); });