Compare commits
18 Commits
Author | SHA1 | Date | |
---|---|---|---|
d8620187ec | |||
520849d070 | |||
b6a028a8ed | |||
af5839bb59 | |||
a53e0d9f73 | |||
49921f2dcf | |||
6b947c2139 | |||
98acf919f1 | |||
c9c2853150 | |||
2bc708f8e6 | |||
874b8fc3c2 | |||
7d6aac3431 | |||
e2fc7decad | |||
21bed71f5e | |||
3b974428fc | |||
580191fb17 | |||
be0cb88b6c | |||
95c4e4497e |
16
CHANGELOG.md
16
CHANGELOG.md
@ -1,6 +1,22 @@
|
|||||||
ChangeLog
|
ChangeLog
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
10.66.2
|
||||||
|
-------
|
||||||
|
* i18nの修正
|
||||||
|
* ドライブのファイル一覧取得APIでファイルサイズによるソートが機能していなかった問題を修正
|
||||||
|
* リモートユーザーの更新時に、各ピン留め投稿の取得失敗は無視するように
|
||||||
|
* リモートMisskeyユーザーの情報が登録/更新出来なくなっていたのを修正
|
||||||
|
* メンションのリンク先URLに余計な@がプリフィクスされていたのを修正
|
||||||
|
* ダイレクトでリプライする際、リプライ先のユーザーは自動的に公開先として追加するように
|
||||||
|
* ダイレクトでメンションでもユーザーを指定できるように
|
||||||
|
|
||||||
|
10.66.1
|
||||||
|
-------
|
||||||
|
* ActivityPubのsharedInboxに関して修正
|
||||||
|
* MFMでのカッコの判定を改善
|
||||||
|
* バグ修正
|
||||||
|
|
||||||
10.66.0
|
10.66.0
|
||||||
-------
|
-------
|
||||||
* ユーザーごとのRSSフィードを提供するように
|
* ユーザーごとのRSSフィードを提供するように
|
||||||
|
@ -805,6 +805,7 @@ desktop/views/components/settings.vue:
|
|||||||
update-settings: "Advanced settings"
|
update-settings: "Advanced settings"
|
||||||
prevent-update: "Postpone updates (not recommended)"
|
prevent-update: "Postpone updates (not recommended)"
|
||||||
prevent-update-desc: "Even if you turn this setting on, updates may apply. This setting is enabled only for this device."
|
prevent-update-desc: "Even if you turn this setting on, updates may apply. This setting is enabled only for this device."
|
||||||
|
mark-as-read-all-unread-notes: "Mark all posts as read"
|
||||||
no-updates: "No updates available"
|
no-updates: "No updates available"
|
||||||
no-updates-desc: "Your Misskey is up to date."
|
no-updates-desc: "Your Misskey is up to date."
|
||||||
update-available: "A new version is available"
|
update-available: "A new version is available"
|
||||||
@ -1430,7 +1431,6 @@ mobile/views/pages/settings.vue:
|
|||||||
signout: "Sign out"
|
signout: "Sign out"
|
||||||
sound: "Sounds"
|
sound: "Sounds"
|
||||||
enable-sounds: "Enable sounds"
|
enable-sounds: "Enable sounds"
|
||||||
mark-as-read-all-unread-notes: "Mark all posts as read"
|
|
||||||
password: "Password"
|
password: "Password"
|
||||||
mobile/views/pages/user.vue:
|
mobile/views/pages/user.vue:
|
||||||
follows-you: "Follows you"
|
follows-you: "Follows you"
|
||||||
|
@ -838,6 +838,7 @@ desktop/views/components/settings.vue:
|
|||||||
2fa: "二段階認証"
|
2fa: "二段階認証"
|
||||||
other: "その他"
|
other: "その他"
|
||||||
license: "ライセンス"
|
license: "ライセンス"
|
||||||
|
mark-as-read-all-unread-notes: "すべての投稿を既読にする"
|
||||||
theme: "テーマ"
|
theme: "テーマ"
|
||||||
|
|
||||||
behaviour: "動作"
|
behaviour: "動作"
|
||||||
@ -1641,7 +1642,6 @@ mobile/views/pages/settings.vue:
|
|||||||
signout: "サインアウト"
|
signout: "サインアウト"
|
||||||
sound: "サウンド"
|
sound: "サウンド"
|
||||||
enable-sounds: "サウンドを有効にする"
|
enable-sounds: "サウンドを有効にする"
|
||||||
mark-as-read-all-unread-notes: "すべての投稿を既読にする"
|
|
||||||
password: "パスワード"
|
password: "パスワード"
|
||||||
|
|
||||||
mobile/views/pages/user.vue:
|
mobile/views/pages/user.vue:
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"author": "syuilo <i@syuilo.com>",
|
"author": "syuilo <i@syuilo.com>",
|
||||||
"version": "10.66.0",
|
"version": "10.66.2",
|
||||||
"clientVersion": "2.0.12855",
|
"clientVersion": "2.0.12873",
|
||||||
"codename": "nighthike",
|
"codename": "nighthike",
|
||||||
"main": "./built/index.js",
|
"main": "./built/index.js",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<router-link class="ldlomzub" :to="`/@${ canonical }`" v-user-preview="canonical">
|
<router-link class="ldlomzub" :to="`/${ canonical }`" v-user-preview="canonical">
|
||||||
<span class="me" v-if="isMe">{{ $t('@.you') }}</span>
|
<span class="me" v-if="isMe">{{ $t('@.you') }}</span>
|
||||||
<span class="main">
|
<span class="main">
|
||||||
<span class="username">@{{ username }}</span>
|
<span class="username">@{{ username }}</span>
|
||||||
|
@ -85,7 +85,7 @@ export default Vue.extend({
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (items[0].kind == 'file') {
|
if (items[0].kind == 'file') {
|
||||||
alert('%i18n:only-one-file-attached%');
|
alert(this.$t('only-one-file-attached'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -107,7 +107,7 @@ export default Vue.extend({
|
|||||||
return;
|
return;
|
||||||
} else if (e.dataTransfer.files.length > 1) {
|
} else if (e.dataTransfer.files.length > 1) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
alert('%i18n:only-one-file-attached%');
|
alert(this.$t('only-one-file-attached'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -207,9 +207,8 @@ export default Vue.extend({
|
|||||||
this.visibility = this.reply.visibility;
|
this.visibility = this.reply.visibility;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ダイレクトへのリプライはリプライ先ユーザーを初期設定
|
if (this.reply) {
|
||||||
if (this.reply && this.reply.visibility === 'specified') {
|
this.$root.api('users/show', { userId: this.reply.userId }).then(user => {
|
||||||
this.$root.api('users/show', { userId: this.reply.userId }).then(user => {
|
|
||||||
this.visibleUsers.push(user);
|
this.visibleUsers.push(user);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -236,7 +235,7 @@ export default Vue.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
trimmedLength(text: string) {
|
trimmedLength(text: string) {
|
||||||
return length(text.trim());
|
return length(text.trim());
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<header :class="$style.header">
|
<header :class="$style.header">
|
||||||
<h1>#{{ $route.params.tag }}</h1>
|
<h1>#{{ $route.params.tag }}</h1>
|
||||||
</header>
|
</header>
|
||||||
<p :class="$style.empty" v-if="!fetching && empty"><fa icon="search"/> {{ $t('no-posts-found', { q }) }}</p>
|
<p :class="$style.empty" v-if="!fetching && empty"><fa icon="search"/> {{ $t('no-posts-found', { q: $route.params.tag }) }}</p>
|
||||||
<mk-notes ref="timeline" :class="$style.notes" :more="existMore ? more : null"/>
|
<mk-notes ref="timeline" :class="$style.notes" :more="existMore ? more : null"/>
|
||||||
</mk-ui>
|
</mk-ui>
|
||||||
</template>
|
</template>
|
||||||
|
@ -197,9 +197,8 @@ export default Vue.extend({
|
|||||||
this.visibility = this.reply.visibility;
|
this.visibility = this.reply.visibility;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ダイレクトへのリプライはリプライ先ユーザーを初期設定
|
if (this.reply) {
|
||||||
if (this.reply && this.reply.visibility === 'specified') {
|
this.$root.api('users/show', { userId: this.reply.userId }).then(user => {
|
||||||
this.$root.api('users/show', { userId: this.reply.userId }).then(user => {
|
|
||||||
this.visibleUsers.push(user);
|
this.visibleUsers.push(user);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<span slot="header"><span style="margin-right:4px;"><fa icon="hashtag"/></span>{{ $route.params.tag }}</span>
|
<span slot="header"><span style="margin-right:4px;"><fa icon="hashtag"/></span>{{ $route.params.tag }}</span>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
<p v-if="!fetching && empty"><fa icon="search"/> {{ $t('no-posts-found', { q }) }}</p>
|
<p v-if="!fetching && empty"><fa icon="search"/> {{ $t('no-posts-found', { q: $route.params.tag }) }}</p>
|
||||||
<mk-notes ref="timeline" :more="existMore ? more : null"/>
|
<mk-notes ref="timeline" :more="existMore ? more : null"/>
|
||||||
</main>
|
</main>
|
||||||
</mk-ui>
|
</mk-ui>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import * as P from 'parsimmon';
|
import * as P from 'parsimmon';
|
||||||
import parseAcct from '../misc/acct/parse';
|
import parseAcct from '../misc/acct/parse';
|
||||||
import { toUnicode } from 'punycode';
|
import { toUnicode } from 'punycode';
|
||||||
import { takeWhile } from '../prelude/array';
|
import { takeWhile, cumulativeSum } from '../prelude/array';
|
||||||
import { Tree } from '../prelude/tree';
|
import { Tree } from '../prelude/tree';
|
||||||
import * as T from '../prelude/tree';
|
import * as T from '../prelude/tree';
|
||||||
|
|
||||||
@ -42,30 +42,18 @@ export function createTree(type: string, children: MfmForest, props: any): MfmTr
|
|||||||
return T.createTree({ type, props }, children);
|
return T.createTree({ type, props }, children);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTrailingPosition(x: string): number {
|
export function removeOrphanedBrackets(s: string): string {
|
||||||
const brackets = [
|
const openBrackets = ['(', '「'];
|
||||||
['(', ')'],
|
const closeBrackets = [')', '」'];
|
||||||
['「', '」'],
|
const xs = cumulativeSum(s.split('').map(c => {
|
||||||
];
|
if (openBrackets.includes(c)) return 1;
|
||||||
const pendingBrackets = [] as any;
|
if (closeBrackets.includes(c)) return -1;
|
||||||
const end = x.split('').findIndex(char => {
|
return 0;
|
||||||
const closeMatch = brackets.map(x => x[1]).indexOf(char);
|
}));
|
||||||
const openMatch = brackets.map(x => x[0]).indexOf(char);
|
const firstOrphanedCloseBracket = xs.findIndex(x => x < 0);
|
||||||
if (closeMatch != -1) {
|
if (firstOrphanedCloseBracket !== -1) return s.substr(0, firstOrphanedCloseBracket);
|
||||||
if (pendingBrackets[closeMatch] > 0) {
|
const lastMatched = xs.lastIndexOf(0);
|
||||||
pendingBrackets[closeMatch]--;
|
return s.substr(0, lastMatched + 1);
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} else if (openMatch != -1) {
|
|
||||||
pendingBrackets[openMatch] = (pendingBrackets[openMatch] || 0) + 1;
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return end > 0 ? end : x.length;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const newline = P((input, i) => {
|
const newline = P((input, i) => {
|
||||||
@ -220,7 +208,7 @@ const mfm = P.createLanguage({
|
|||||||
const match = text.match(/^#([^\s\.,!\?#]+)/i);
|
const match = text.match(/^#([^\s\.,!\?#]+)/i);
|
||||||
if (!match) return P.makeFailure(i, 'not a hashtag');
|
if (!match) return P.makeFailure(i, 'not a hashtag');
|
||||||
let hashtag = match[1];
|
let hashtag = match[1];
|
||||||
hashtag = hashtag.substr(0, getTrailingPosition(hashtag));
|
hashtag = removeOrphanedBrackets(hashtag);
|
||||||
if (hashtag.match(/^[0-9]+$/)) return P.makeFailure(i, 'not a hashtag');
|
if (hashtag.match(/^[0-9]+$/)) return P.makeFailure(i, 'not a hashtag');
|
||||||
if (input[i - 1] != null && input[i - 1].match(/[a-z0-9]/i)) return P.makeFailure(i, 'not a hashtag');
|
if (input[i - 1] != null && input[i - 1].match(/[a-z0-9]/i)) return P.makeFailure(i, 'not a hashtag');
|
||||||
if (hashtag.length > 50) return P.makeFailure(i, 'not a hashtag');
|
if (hashtag.length > 50) return P.makeFailure(i, 'not a hashtag');
|
||||||
@ -390,7 +378,7 @@ const mfm = P.createLanguage({
|
|||||||
const match = text.match(/^https?:\/\/[\w\/:%#@\$&\?!\(\)\[\]~\.,=\+\-]+/);
|
const match = text.match(/^https?:\/\/[\w\/:%#@\$&\?!\(\)\[\]~\.,=\+\-]+/);
|
||||||
if (!match) return P.makeFailure(i, 'not a url');
|
if (!match) return P.makeFailure(i, 'not a url');
|
||||||
let url = match[0];
|
let url = match[0];
|
||||||
url = url.substr(0, getTrailingPosition(url));
|
url = removeOrphanedBrackets(url);
|
||||||
if (url.endsWith('.')) url = url.substr(0, url.lastIndexOf('.'));
|
if (url.endsWith('.')) url = url.substr(0, url.lastIndexOf('.'));
|
||||||
if (url.endsWith(',')) url = url.substr(0, url.lastIndexOf(','));
|
if (url.endsWith(',')) url = url.substr(0, url.lastIndexOf(','));
|
||||||
return P.makeSuccess(i + url.length, url);
|
return P.makeSuccess(i + url.length, url);
|
||||||
|
@ -109,3 +109,9 @@ export function takeWhile<T>(f: Predicate<T>, xs: T[]): T[] {
|
|||||||
}
|
}
|
||||||
return ys;
|
return ys;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function cumulativeSum(xs: number[]): number[] {
|
||||||
|
const ys = Array.from(xs); // deep copy
|
||||||
|
for (let i = 1; i < ys.length; i++) ys[i] += ys[i - 1];
|
||||||
|
return ys;
|
||||||
|
}
|
||||||
|
@ -18,6 +18,7 @@ import Instance from '../../../models/instance';
|
|||||||
import getDriveFileUrl from '../../../misc/get-drive-file-url';
|
import getDriveFileUrl from '../../../misc/get-drive-file-url';
|
||||||
import { IEmoji } from '../../../models/emoji';
|
import { IEmoji } from '../../../models/emoji';
|
||||||
import { ITag } from './tag';
|
import { ITag } from './tag';
|
||||||
|
import Following from '../../../models/following';
|
||||||
|
|
||||||
const log = debug('misskey:activitypub');
|
const log = debug('misskey:activitypub');
|
||||||
|
|
||||||
@ -164,7 +165,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<IU
|
|||||||
publicKeyPem: person.publicKey.publicKeyPem
|
publicKeyPem: person.publicKey.publicKeyPem
|
||||||
},
|
},
|
||||||
inbox: person.inbox,
|
inbox: person.inbox,
|
||||||
sharedInbox: person.sharedInbox,
|
sharedInbox: person.sharedInbox || (person.endpoints ? person.endpoints.sharedInbox : undefined),
|
||||||
featured: person.featured,
|
featured: person.featured,
|
||||||
endpoints: person.endpoints,
|
endpoints: person.endpoints,
|
||||||
uri: person.id,
|
uri: person.id,
|
||||||
@ -340,7 +341,7 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje
|
|||||||
$set: {
|
$set: {
|
||||||
lastFetchedAt: new Date(),
|
lastFetchedAt: new Date(),
|
||||||
inbox: person.inbox,
|
inbox: person.inbox,
|
||||||
sharedInbox: person.sharedInbox,
|
sharedInbox: person.sharedInbox || (person.endpoints ? person.endpoints.sharedInbox : undefined),
|
||||||
featured: person.featured,
|
featured: person.featured,
|
||||||
avatarId: avatar ? avatar._id : null,
|
avatarId: avatar ? avatar._id : null,
|
||||||
bannerId: banner ? banner._id : null,
|
bannerId: banner ? banner._id : null,
|
||||||
@ -368,6 +369,15 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 該当ユーザーが既にフォロワーになっていた場合はFollowingもアップデートする
|
||||||
|
await Following.update({
|
||||||
|
followerId: exist._id
|
||||||
|
}, {
|
||||||
|
$set: {
|
||||||
|
'_follower.sharedInbox': person.sharedInbox || (person.endpoints ? person.endpoints.sharedInbox : undefined)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
await updateFeatured(exist._id).catch(err => console.log(err));
|
await updateFeatured(exist._id).catch(err => console.log(err));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -431,7 +441,7 @@ export async function updateFeatured(userId: mongo.ObjectID) {
|
|||||||
|
|
||||||
await User.update({ _id: user._id }, {
|
await User.update({ _id: user._id }, {
|
||||||
$set: {
|
$set: {
|
||||||
pinnedNoteIds: featuredNotes.map(note => note._id)
|
pinnedNoteIds: featuredNotes.filter(note => note != null).map(note => note._id)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -63,6 +63,7 @@ export default async (user: ILocalUser) => {
|
|||||||
following: `${id}/following`,
|
following: `${id}/following`,
|
||||||
featured: `${id}/collections/featured`,
|
featured: `${id}/collections/featured`,
|
||||||
sharedInbox: `${config.url}/inbox`,
|
sharedInbox: `${config.url}/inbox`,
|
||||||
|
endpoints: { sharedInbox: `${config.url}/inbox` },
|
||||||
url: `${config.url}/@${user.username}`,
|
url: `${config.url}/@${user.username}`,
|
||||||
preferredUsername: user.username,
|
preferredUsername: user.username,
|
||||||
name: user.name,
|
name: user.name,
|
||||||
|
@ -56,7 +56,7 @@ export interface IPerson extends IObject {
|
|||||||
following: any;
|
following: any;
|
||||||
featured?: any;
|
featured?: any;
|
||||||
outbox: any;
|
outbox: any;
|
||||||
endpoints: string[];
|
endpoints: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const isCollection = (object: IObject): object is ICollection =>
|
export const isCollection = (object: IObject): object is ICollection =>
|
||||||
|
@ -52,9 +52,9 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => {
|
|||||||
_sort = {
|
_sort = {
|
||||||
length: -1
|
length: -1
|
||||||
};
|
};
|
||||||
} else if (ps.sort == '+size') {
|
} else if (ps.sort == '-size') {
|
||||||
_sort = {
|
_sort = {
|
||||||
length: -1
|
length: 1
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -42,7 +42,7 @@ export const meta = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
visibleUserIds: {
|
visibleUserIds: {
|
||||||
validator: $.arr($.type(ID)).optional.unique().min(1),
|
validator: $.arr($.type(ID)).optional.unique().min(0),
|
||||||
transform: transformMany,
|
transform: transformMany,
|
||||||
desc: {
|
desc: {
|
||||||
'ja-JP': '(投稿の公開範囲が specified の場合)投稿を閲覧できるユーザー'
|
'ja-JP': '(投稿の公開範囲が specified の場合)投稿を閲覧できるユーザー'
|
||||||
@ -82,6 +82,30 @@ export const meta = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
noExtractMentions: {
|
||||||
|
validator: $.bool.optional,
|
||||||
|
default: false,
|
||||||
|
desc: {
|
||||||
|
'ja-JP': '本文からメンションを展開しないか否か。'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
noExtractHashtags: {
|
||||||
|
validator: $.bool.optional,
|
||||||
|
default: false,
|
||||||
|
desc: {
|
||||||
|
'ja-JP': '本文からハッシュタグを展開しないか否か。'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
noExtractEmojis: {
|
||||||
|
validator: $.bool.optional,
|
||||||
|
default: false,
|
||||||
|
desc: {
|
||||||
|
'ja-JP': '本文からカスタム絵文字を展開しないか否か。'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
geo: {
|
geo: {
|
||||||
validator: $.obj({
|
validator: $.obj({
|
||||||
coordinates: $.arr().length(2)
|
coordinates: $.arr().length(2)
|
||||||
@ -237,6 +261,9 @@ export default define(meta, (ps, user, app) => new Promise(async (res, rej) => {
|
|||||||
localOnly: ps.localOnly,
|
localOnly: ps.localOnly,
|
||||||
visibility: ps.visibility,
|
visibility: ps.visibility,
|
||||||
visibleUsers,
|
visibleUsers,
|
||||||
|
apMentions: ps.noExtractMentions ? [] : undefined,
|
||||||
|
apHashtags: ps.noExtractHashtags ? [] : undefined,
|
||||||
|
apEmojis: ps.noExtractEmojis ? [] : undefined,
|
||||||
geo: ps.geo
|
geo: ps.geo
|
||||||
})
|
})
|
||||||
.then(note => pack(note, user))
|
.then(note => pack(note, user))
|
||||||
|
@ -182,6 +182,17 @@ export default async (user: IUser, data: Option, silent = false) => new Promise<
|
|||||||
mentionedUsers.push(u);
|
mentionedUsers.push(u);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const u of mentionedUsers) {
|
||||||
|
if (!data.visibleUsers.some(x => x._id.equals(u._id))) {
|
||||||
|
data.visibleUsers.push(u);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ダイレクト投稿でユーザーが指定されていなかったらreject
|
||||||
|
if (data.visibleUsers.length === 0) {
|
||||||
|
return rej('Target user is not specified');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const note = await insertNote(user, data, tags, emojis, mentionedUsers);
|
const note = await insertNote(user, data, tags, emojis, mentionedUsers);
|
||||||
|
95
test/mfm.ts
95
test/mfm.ts
@ -6,7 +6,7 @@ import * as assert from 'assert';
|
|||||||
|
|
||||||
import analyze from '../src/mfm/parse';
|
import analyze from '../src/mfm/parse';
|
||||||
import toHtml from '../src/mfm/html';
|
import toHtml from '../src/mfm/html';
|
||||||
import { createTree as tree, createLeaf as leaf, MfmTree } from '../src/mfm/parser';
|
import { createTree as tree, createLeaf as leaf, MfmTree, removeOrphanedBrackets } from '../src/mfm/parser';
|
||||||
|
|
||||||
function text(text: string): MfmTree {
|
function text(text: string): MfmTree {
|
||||||
return leaf('text', { text });
|
return leaf('text', { text });
|
||||||
@ -49,6 +49,99 @@ describe('createTree', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('removeOrphanedBrackets', () => {
|
||||||
|
it('single (contained)', () => {
|
||||||
|
const input = '(foo)';
|
||||||
|
const expected = '(foo)';
|
||||||
|
const actual = removeOrphanedBrackets(input);
|
||||||
|
assert.deepStrictEqual(actual, expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('single (head)', () => {
|
||||||
|
const input = '(foo)bar';
|
||||||
|
const expected = '(foo)bar';
|
||||||
|
const actual = removeOrphanedBrackets(input);
|
||||||
|
assert.deepStrictEqual(actual, expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('single (tail)', () => {
|
||||||
|
const input = 'foo(bar)';
|
||||||
|
const expected = 'foo(bar)';
|
||||||
|
const actual = removeOrphanedBrackets(input);
|
||||||
|
assert.deepStrictEqual(actual, expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('a', () => {
|
||||||
|
const input = '(foo';
|
||||||
|
const expected = '';
|
||||||
|
const actual = removeOrphanedBrackets(input);
|
||||||
|
assert.deepStrictEqual(actual, expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('b', () => {
|
||||||
|
const input = ')foo';
|
||||||
|
const expected = '';
|
||||||
|
const actual = removeOrphanedBrackets(input);
|
||||||
|
assert.deepStrictEqual(actual, expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('nested', () => {
|
||||||
|
const input = 'foo(「(bar)」)';
|
||||||
|
const expected = 'foo(「(bar)」)';
|
||||||
|
const actual = removeOrphanedBrackets(input);
|
||||||
|
assert.deepStrictEqual(actual, expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('no brackets', () => {
|
||||||
|
const input = 'foo';
|
||||||
|
const expected = 'foo';
|
||||||
|
const actual = removeOrphanedBrackets(input);
|
||||||
|
assert.deepStrictEqual(actual, expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('with foreign bracket (single)', () => {
|
||||||
|
const input = 'foo(bar))';
|
||||||
|
const expected = 'foo(bar)';
|
||||||
|
const actual = removeOrphanedBrackets(input);
|
||||||
|
assert.deepStrictEqual(actual, expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('with foreign bracket (open)', () => {
|
||||||
|
const input = 'foo(bar';
|
||||||
|
const expected = 'foo';
|
||||||
|
const actual = removeOrphanedBrackets(input);
|
||||||
|
assert.deepStrictEqual(actual, expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('with foreign bracket (close)', () => {
|
||||||
|
const input = 'foo)bar';
|
||||||
|
const expected = 'foo';
|
||||||
|
const actual = removeOrphanedBrackets(input);
|
||||||
|
assert.deepStrictEqual(actual, expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('with foreign bracket (close and open)', () => {
|
||||||
|
const input = 'foo)(bar';
|
||||||
|
const expected = 'foo';
|
||||||
|
const actual = removeOrphanedBrackets(input);
|
||||||
|
assert.deepStrictEqual(actual, expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('various bracket type', () => {
|
||||||
|
const input = 'foo「(bar)」(';
|
||||||
|
const expected = 'foo「(bar)」';
|
||||||
|
const actual = removeOrphanedBrackets(input);
|
||||||
|
assert.deepStrictEqual(actual, expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('intersected', () => {
|
||||||
|
const input = 'foo(「)」';
|
||||||
|
const expected = 'foo(「)」';
|
||||||
|
const actual = removeOrphanedBrackets(input);
|
||||||
|
assert.deepStrictEqual(actual, expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('MFM', () => {
|
describe('MFM', () => {
|
||||||
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');
|
||||||
|
Reference in New Issue
Block a user