非ASCIIなドメインへのメンションの修正 (#2903)
* punycodeでされたmentionのラベルをunicodeとして表示する * post-form mentionはpunycodeにする * mentionの表示はURLもAPI向けもunicodeにする
This commit is contained in:
parent
be82d845a4
commit
5dce81c0db
6 changed files with 27 additions and 13 deletions
|
@ -116,16 +116,16 @@ export default Vue.component('misskey-flavored-markdown', {
|
||||||
case 'mention': {
|
case 'mention': {
|
||||||
return (createElement as any)('a', {
|
return (createElement as any)('a', {
|
||||||
attrs: {
|
attrs: {
|
||||||
href: `${url}/@${getAcct(token)}`,
|
href: `${url}/${token.canonical}`,
|
||||||
target: '_blank',
|
target: '_blank',
|
||||||
dataIsMe: (this as any).i && getAcct((this as any).i) == getAcct(token),
|
dataIsMe: (this as any).i && getAcct((this as any).i) == getAcct(token),
|
||||||
style: 'color:var(--mfmMention);'
|
style: 'color:var(--mfmMention);'
|
||||||
},
|
},
|
||||||
directives: [{
|
directives: [{
|
||||||
name: 'user-preview',
|
name: 'user-preview',
|
||||||
value: token.content
|
value: token.canonical
|
||||||
}]
|
}]
|
||||||
}, token.content);
|
}, token.canonical);
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'hashtag': {
|
case 'hashtag': {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import * as getCaretCoordinates from 'textarea-caret';
|
import * as getCaretCoordinates from 'textarea-caret';
|
||||||
import MkAutocomplete from '../components/autocomplete.vue';
|
import MkAutocomplete from '../components/autocomplete.vue';
|
||||||
import renderAcct from '../../../../../misc/acct/render';
|
import { toASCII } from 'punycode';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
bind(el, binding, vn) {
|
bind(el, binding, vn) {
|
||||||
|
@ -188,7 +188,7 @@ class Autocomplete {
|
||||||
const trimmedBefore = before.substring(0, before.lastIndexOf('@'));
|
const trimmedBefore = before.substring(0, before.lastIndexOf('@'));
|
||||||
const after = source.substr(caret);
|
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}`;
|
this.text = `${trimmedBefore}@${acct} ${after}`;
|
||||||
|
|
|
@ -65,6 +65,7 @@ import { host } from '../../../config';
|
||||||
import { erase, unique } from '../../../../../prelude/array';
|
import { erase, unique } from '../../../../../prelude/array';
|
||||||
import { length } from 'stringz';
|
import { length } from 'stringz';
|
||||||
import parseAcct from '../../../../../misc/acct/parse';
|
import parseAcct from '../../../../../misc/acct/parse';
|
||||||
|
import { toASCII } from 'punycode';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
components: {
|
components: {
|
||||||
|
@ -158,14 +159,14 @@ export default Vue.extend({
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.reply && this.reply.user.host != null) {
|
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) {
|
if (this.reply && this.reply.text != null) {
|
||||||
const ast = parse(this.reply.text);
|
const ast = parse(this.reply.text);
|
||||||
|
|
||||||
ast.filter(t => t.type == 'mention').forEach(x => {
|
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;
|
if (this.$store.state.i.username == x.username && x.host == null) return;
|
||||||
|
|
|
@ -62,6 +62,7 @@ import { host } from '../../../config';
|
||||||
import { erase, unique } from '../../../../../prelude/array';
|
import { erase, unique } from '../../../../../prelude/array';
|
||||||
import { length } from 'stringz';
|
import { length } from 'stringz';
|
||||||
import parseAcct from '../../../../../misc/acct/parse';
|
import parseAcct from '../../../../../misc/acct/parse';
|
||||||
|
import { toASCII } from 'punycode';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
components: {
|
components: {
|
||||||
|
@ -153,14 +154,14 @@ export default Vue.extend({
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.reply && this.reply.user.host != null) {
|
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) {
|
if (this.reply && this.reply.text != null) {
|
||||||
const ast = parse(this.reply.text);
|
const ast = parse(this.reply.text);
|
||||||
|
|
||||||
ast.filter(t => t.type == 'mention').forEach(x => {
|
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;
|
if (this.$store.state.i.username == x.username && x.host == null) return;
|
||||||
|
|
|
@ -2,10 +2,12 @@
|
||||||
* Mention
|
* Mention
|
||||||
*/
|
*/
|
||||||
import parseAcct from '../../../misc/acct/parse';
|
import parseAcct from '../../../misc/acct/parse';
|
||||||
|
import { toUnicode } from 'punycode';
|
||||||
|
|
||||||
export type TextElementMention = {
|
export type TextElementMention = {
|
||||||
type: 'mention'
|
type: 'mention'
|
||||||
content: string
|
content: string
|
||||||
|
canonical: string
|
||||||
username: string
|
username: string
|
||||||
host: string
|
host: string
|
||||||
};
|
};
|
||||||
|
@ -15,9 +17,11 @@ export default function(text: string) {
|
||||||
if (!match) return null;
|
if (!match) return null;
|
||||||
const mention = match[0];
|
const mention = match[0];
|
||||||
const { username, host } = parseAcct(mention.substr(1));
|
const { username, host } = parseAcct(mention.substr(1));
|
||||||
|
const canonical = host != null ? `@${username}@${toUnicode(host)}` : mention;
|
||||||
return {
|
return {
|
||||||
type: 'mention',
|
type: 'mention',
|
||||||
content: mention,
|
content: mention,
|
||||||
|
canonical,
|
||||||
username,
|
username,
|
||||||
host
|
host
|
||||||
} as TextElementMention;
|
} as TextElementMention;
|
||||||
|
|
16
test/mfm.ts
16
test/mfm.ts
|
@ -8,9 +8,9 @@ describe('Text', () => {
|
||||||
it('can be analyzed', () => {
|
it('can be analyzed', () => {
|
||||||
const tokens = analyze('@himawari @hima_sub@namori.net お腹ペコい :cat: #yryr');
|
const tokens = analyze('@himawari @hima_sub@namori.net お腹ペコい :cat: #yryr');
|
||||||
assert.deepEqual([
|
assert.deepEqual([
|
||||||
{ type: 'mention', content: '@himawari', username: 'himawari', host: null },
|
{ type: 'mention', content: '@himawari', canonical: '@himawari', username: 'himawari', host: null },
|
||||||
{ type: 'text', content: ' '},
|
{ 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: 'text', content: ' お腹ペコい ' },
|
||||||
{ type: 'emoji', content: ':cat:', emoji: 'cat'},
|
{ type: 'emoji', content: ':cat:', emoji: 'cat'},
|
||||||
{ type: 'text', content: ' '},
|
{ type: 'text', content: ' '},
|
||||||
|
@ -58,7 +58,7 @@ describe('Text', () => {
|
||||||
it('local', () => {
|
it('local', () => {
|
||||||
const tokens = analyze('@himawari お腹ペコい');
|
const tokens = analyze('@himawari お腹ペコい');
|
||||||
assert.deepEqual([
|
assert.deepEqual([
|
||||||
{ type: 'mention', content: '@himawari', username: 'himawari', host: null },
|
{ type: 'mention', content: '@himawari', canonical: '@himawari', username: 'himawari', host: null },
|
||||||
{ type: 'text', content: ' お腹ペコい' }
|
{ type: 'text', content: ' お腹ペコい' }
|
||||||
], tokens);
|
], tokens);
|
||||||
});
|
});
|
||||||
|
@ -66,7 +66,15 @@ describe('Text', () => {
|
||||||
it('remote', () => {
|
it('remote', () => {
|
||||||
const tokens = analyze('@hima_sub@namori.net お腹ペコい');
|
const tokens = analyze('@hima_sub@namori.net お腹ペコい');
|
||||||
assert.deepEqual([
|
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: ' お腹ペコい' }
|
{ type: 'text', content: ' お腹ペコい' }
|
||||||
], tokens);
|
], tokens);
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue