Merge remote-tracking branch 'misskey-original/develop' into develop
# Conflicts: # package.json # packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts
This commit is contained in:
commit
87d49b5bbf
|
@ -27,10 +27,12 @@
|
||||||
- Enhance: TLの返信表示オプションを記憶するように
|
- Enhance: TLの返信表示オプションを記憶するように
|
||||||
|
|
||||||
### Server
|
### Server
|
||||||
|
- Enhance: タイムライン取得時のパフォーマンスを向上
|
||||||
- Enhance: ストリーミングAPIのパフォーマンスを向上
|
- Enhance: ストリーミングAPIのパフォーマンスを向上
|
||||||
- Fix: users/notesでDBから参照した際にチャンネル投稿のみ取得される問題を修正
|
- Fix: users/notesでDBから参照した際にチャンネル投稿のみ取得される問題を修正
|
||||||
- Fix: コントロールパネルの設定項目が正しく保存できない問題を修正
|
- Fix: コントロールパネルの設定項目が正しく保存できない問題を修正
|
||||||
- Change: nyaizeはAPIレスポンス時ではなく投稿時に一度だけ非可逆的に行われるようになりました
|
- Change: ユーザーのisCatがtrueでも、サーバーではnyaizeが行われなくなりました
|
||||||
|
- isCatな場合、クライアントでnyaize処理を行うことを推奨します
|
||||||
|
|
||||||
## 2023.10.1
|
## 2023.10.1
|
||||||
### General
|
### General
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"version": "2023.10.2-beta.1-prismisskey.1",
|
"version": "2023.10.2-beta.2-prismisskey.1",
|
||||||
"codename": "nasubi",
|
"codename": "nasubi",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
export class NoteReactionAndUserPairCache1697673894459 {
|
||||||
|
name = 'NoteReactionAndUserPairCache1697673894459'
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "note" ADD "reactionAndUserPairCache" character varying(1024) array NOT NULL DEFAULT '{}'`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "reactionAndUserPairCache"`);
|
||||||
|
}
|
||||||
|
}
|
|
@ -227,8 +227,6 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||||
isBot: MiUser['isBot'];
|
isBot: MiUser['isBot'];
|
||||||
isCat: MiUser['isCat'];
|
isCat: MiUser['isCat'];
|
||||||
}, data: Option, silent = false): Promise<MiNote> {
|
}, data: Option, silent = false): Promise<MiNote> {
|
||||||
let patsedText: mfm.MfmNode[] | null = null;
|
|
||||||
|
|
||||||
// チャンネル外にリプライしたら対象のスコープに合わせる
|
// チャンネル外にリプライしたら対象のスコープに合わせる
|
||||||
// (クライアントサイドでやっても良い処理だと思うけどとりあえずサーバーサイドで)
|
// (クライアントサイドでやっても良い処理だと思うけどとりあえずサーバーサイドで)
|
||||||
if (data.reply && data.channel && data.reply.channelId !== data.channel.id) {
|
if (data.reply && data.channel && data.reply.channelId !== data.channel.id) {
|
||||||
|
@ -315,25 +313,6 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||||
data.text = data.text.slice(0, DB_MAX_NOTE_TEXT_LENGTH);
|
data.text = data.text.slice(0, DB_MAX_NOTE_TEXT_LENGTH);
|
||||||
}
|
}
|
||||||
data.text = data.text.trim();
|
data.text = data.text.trim();
|
||||||
|
|
||||||
if (user.isCat) {
|
|
||||||
patsedText = mfm.parse(data.text);
|
|
||||||
function nyaizeNode(node: mfm.MfmNode) {
|
|
||||||
if (node.type === 'quote') return;
|
|
||||||
if (node.type === 'text') {
|
|
||||||
node.props.text = nyaize(node.props.text);
|
|
||||||
}
|
|
||||||
if (node.children) {
|
|
||||||
for (const child of node.children) {
|
|
||||||
nyaizeNode(child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const node of patsedText) {
|
|
||||||
nyaizeNode(node);
|
|
||||||
}
|
|
||||||
data.text = mfm.toString(patsedText);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
data.text = null;
|
data.text = null;
|
||||||
}
|
}
|
||||||
|
@ -344,7 +323,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||||
|
|
||||||
// Parse MFM if needed
|
// Parse MFM if needed
|
||||||
if (!tags || !emojis || !mentionedUsers) {
|
if (!tags || !emojis || !mentionedUsers) {
|
||||||
const tokens = patsedText ?? (data.text ? mfm.parse(data.text)! : []);
|
const tokens = (data.text ? mfm.parse(data.text)! : []);
|
||||||
const cwTokens = data.cw ? mfm.parse(data.cw)! : [];
|
const cwTokens = data.cw ? mfm.parse(data.cw)! : [];
|
||||||
const choiceTokens = data.poll && data.poll.choices
|
const choiceTokens = data.poll && data.poll.choices
|
||||||
? concat(data.poll.choices.map(choice => mfm.parse(choice)!))
|
? concat(data.poll.choices.map(choice => mfm.parse(choice)!))
|
||||||
|
@ -584,7 +563,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pack the note
|
// Pack the note
|
||||||
const noteObj = await this.noteEntityService.pack(note, null, { skipHide: true });
|
const noteObj = await this.noteEntityService.pack(note, null, { skipHide: true, withReactionAndUserPairCache: true });
|
||||||
|
|
||||||
this.globalEventService.publishNotesStream(noteObj);
|
this.globalEventService.publishNotesStream(noteObj);
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,7 @@ import { RoleService } from '@/core/RoleService.js';
|
||||||
import { FeaturedService } from '@/core/FeaturedService.js';
|
import { FeaturedService } from '@/core/FeaturedService.js';
|
||||||
|
|
||||||
const FALLBACK = '❤';
|
const FALLBACK = '❤';
|
||||||
|
const PER_NOTE_REACTION_USER_PAIR_CACHE_MAX = 16;
|
||||||
|
|
||||||
const legacies: Record<string, string> = {
|
const legacies: Record<string, string> = {
|
||||||
'like': '👍',
|
'like': '👍',
|
||||||
|
@ -187,6 +188,9 @@ export class ReactionService {
|
||||||
await this.notesRepository.createQueryBuilder().update()
|
await this.notesRepository.createQueryBuilder().update()
|
||||||
.set({
|
.set({
|
||||||
reactions: () => sql,
|
reactions: () => sql,
|
||||||
|
...(note.reactionAndUserPairCache.length < PER_NOTE_REACTION_USER_PAIR_CACHE_MAX ? {
|
||||||
|
reactionAndUserPairCache: () => `array_append("reactionAndUserPairCache", '${user.id}/${reaction}')`,
|
||||||
|
} : {}),
|
||||||
})
|
})
|
||||||
.where('id = :id', { id: note.id })
|
.where('id = :id', { id: note.id })
|
||||||
.execute();
|
.execute();
|
||||||
|
@ -293,6 +297,7 @@ export class ReactionService {
|
||||||
await this.notesRepository.createQueryBuilder().update()
|
await this.notesRepository.createQueryBuilder().update()
|
||||||
.set({
|
.set({
|
||||||
reactions: () => sql,
|
reactions: () => sql,
|
||||||
|
reactionAndUserPairCache: () => `array_remove("reactionAndUserPairCache", '${user.id}/${exist.reaction}')`,
|
||||||
})
|
})
|
||||||
.where('id = :id', { id: note.id })
|
.where('id = :id', { id: note.id })
|
||||||
.execute();
|
.execute();
|
||||||
|
|
|
@ -170,27 +170,37 @@ export class NoteEntityService implements OnModuleInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async populateMyReaction(noteId: MiNote['id'], meId: MiUser['id'], _hint_?: {
|
public async populateMyReaction(note: { id: MiNote['id']; reactions: MiNote['reactions']; reactionAndUserPairCache?: MiNote['reactionAndUserPairCache']; }, meId: MiUser['id'], _hint_?: {
|
||||||
myReactions: Map<MiNote['id'], MiNoteReaction | null>;
|
myReactions: Map<MiNote['id'], string | null>;
|
||||||
}) {
|
}) {
|
||||||
if (_hint_?.myReactions) {
|
if (_hint_?.myReactions) {
|
||||||
const reaction = _hint_.myReactions.get(noteId);
|
const reaction = _hint_.myReactions.get(note.id);
|
||||||
if (reaction) {
|
if (reaction) {
|
||||||
return this.reactionService.convertLegacyReaction(reaction.reaction);
|
return this.reactionService.convertLegacyReaction(reaction);
|
||||||
} else if (reaction === null) {
|
} else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const reactionsCount = Object.values(note.reactions).reduce((a, b) => a + b, 0);
|
||||||
|
if (reactionsCount === 0) return undefined;
|
||||||
|
if (note.reactionAndUserPairCache && reactionsCount <= note.reactionAndUserPairCache.length) {
|
||||||
|
const pair = note.reactionAndUserPairCache.find(p => p.startsWith(meId));
|
||||||
|
if (pair) {
|
||||||
|
return this.reactionService.convertLegacyReaction(pair.split('/')[1]);
|
||||||
|
} else {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
// 実装上抜けがあるだけかもしれないので、「ヒントに含まれてなかったら(=undefinedなら)return」のようにはしない
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// パフォーマンスのためノートが作成されてから2秒以上経っていない場合はリアクションを取得しない
|
// パフォーマンスのためノートが作成されてから2秒以上経っていない場合はリアクションを取得しない
|
||||||
if (this.idService.parse(noteId).date.getTime() + 2000 > Date.now()) {
|
if (this.idService.parse(note.id).date.getTime() + 2000 > Date.now()) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const reaction = await this.noteReactionsRepository.findOneBy({
|
const reaction = await this.noteReactionsRepository.findOneBy({
|
||||||
userId: meId,
|
userId: meId,
|
||||||
noteId: noteId,
|
noteId: note.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (reaction) {
|
if (reaction) {
|
||||||
|
@ -276,8 +286,9 @@ export class NoteEntityService implements OnModuleInit {
|
||||||
options?: {
|
options?: {
|
||||||
detail?: boolean;
|
detail?: boolean;
|
||||||
skipHide?: boolean;
|
skipHide?: boolean;
|
||||||
|
withReactionAndUserPairCache?: boolean;
|
||||||
_hint_?: {
|
_hint_?: {
|
||||||
myReactions: Map<MiNote['id'], MiNoteReaction | null>;
|
myReactions: Map<MiNote['id'], string | null>;
|
||||||
packedFiles: Map<MiNote['fileIds'][number], Packed<'DriveFile'> | null>;
|
packedFiles: Map<MiNote['fileIds'][number], Packed<'DriveFile'> | null>;
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -285,6 +296,7 @@ export class NoteEntityService implements OnModuleInit {
|
||||||
const opts = Object.assign({
|
const opts = Object.assign({
|
||||||
detail: true,
|
detail: true,
|
||||||
skipHide: false,
|
skipHide: false,
|
||||||
|
withReactionAndUserPairCache: false,
|
||||||
}, options);
|
}, options);
|
||||||
|
|
||||||
const meId = me ? me.id : null;
|
const meId = me ? me.id : null;
|
||||||
|
@ -326,6 +338,7 @@ export class NoteEntityService implements OnModuleInit {
|
||||||
repliesCount: note.repliesCount,
|
repliesCount: note.repliesCount,
|
||||||
reactions: this.reactionService.convertLegacyReactions(note.reactions),
|
reactions: this.reactionService.convertLegacyReactions(note.reactions),
|
||||||
reactionEmojis: this.customEmojiService.populateEmojis(reactionEmojiNames, host),
|
reactionEmojis: this.customEmojiService.populateEmojis(reactionEmojiNames, host),
|
||||||
|
reactionAndUserPairCache: opts.withReactionAndUserPairCache ? note.reactionAndUserPairCache : undefined,
|
||||||
emojis: host != null ? this.customEmojiService.populateEmojis(note.emojis, host) : undefined,
|
emojis: host != null ? this.customEmojiService.populateEmojis(note.emojis, host) : undefined,
|
||||||
tags: note.tags.length > 0 ? note.tags : undefined,
|
tags: note.tags.length > 0 ? note.tags : undefined,
|
||||||
fileIds: note.fileIds,
|
fileIds: note.fileIds,
|
||||||
|
@ -348,18 +361,20 @@ export class NoteEntityService implements OnModuleInit {
|
||||||
|
|
||||||
reply: note.replyId ? this.pack(note.reply ?? note.replyId, me, {
|
reply: note.replyId ? this.pack(note.reply ?? note.replyId, me, {
|
||||||
detail: false,
|
detail: false,
|
||||||
|
withReactionAndUserPairCache: opts.withReactionAndUserPairCache,
|
||||||
_hint_: options?._hint_,
|
_hint_: options?._hint_,
|
||||||
}) : undefined,
|
}) : undefined,
|
||||||
|
|
||||||
renote: note.renoteId ? this.pack(note.renote ?? note.renoteId, me, {
|
renote: note.renoteId ? this.pack(note.renote ?? note.renoteId, me, {
|
||||||
detail: true,
|
detail: true,
|
||||||
|
withReactionAndUserPairCache: opts.withReactionAndUserPairCache,
|
||||||
_hint_: options?._hint_,
|
_hint_: options?._hint_,
|
||||||
}) : undefined,
|
}) : undefined,
|
||||||
|
|
||||||
poll: note.hasPoll ? this.populatePoll(note, meId) : undefined,
|
poll: note.hasPoll ? this.populatePoll(note, meId) : undefined,
|
||||||
|
|
||||||
...(meId ? {
|
...(meId && Object.keys(note.reactions).length > 0 ? {
|
||||||
myReaction: this.populateMyReaction(note.id, meId, options?._hint_),
|
myReaction: this.populateMyReaction(note, meId, options?._hint_),
|
||||||
} : {}),
|
} : {}),
|
||||||
} : {}),
|
} : {}),
|
||||||
});
|
});
|
||||||
|
@ -383,19 +398,48 @@ export class NoteEntityService implements OnModuleInit {
|
||||||
if (notes.length === 0) return [];
|
if (notes.length === 0) return [];
|
||||||
|
|
||||||
const meId = me ? me.id : null;
|
const meId = me ? me.id : null;
|
||||||
const myReactionsMap = new Map<MiNote['id'], MiNoteReaction | null>();
|
const myReactionsMap = new Map<MiNote['id'], string | null>();
|
||||||
if (meId) {
|
if (meId) {
|
||||||
const renoteIds = notes.filter(n => n.renoteId != null).map(n => n.renoteId!);
|
const idsNeedFetchMyReaction = new Set<MiNote['id']>();
|
||||||
|
|
||||||
// パフォーマンスのためノートが作成されてから2秒以上経っていない場合はリアクションを取得しない
|
// パフォーマンスのためノートが作成されてから2秒以上経っていない場合はリアクションを取得しない
|
||||||
const oldId = this.idService.gen(Date.now() - 2000);
|
const oldId = this.idService.gen(Date.now() - 2000);
|
||||||
const targets = [...notes.filter(n => n.id < oldId).map(n => n.id), ...renoteIds];
|
|
||||||
const myReactions = await this.noteReactionsRepository.findBy({
|
|
||||||
userId: meId,
|
|
||||||
noteId: In(targets),
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const target of targets) {
|
for (const note of notes) {
|
||||||
myReactionsMap.set(target, myReactions.find(reaction => reaction.noteId === target) ?? null);
|
if (note.renote && (note.text == null && note.fileIds.length === 0)) { // pure renote
|
||||||
|
const reactionsCount = Object.values(note.renote.reactions).reduce((a, b) => a + b, 0);
|
||||||
|
if (reactionsCount === 0) {
|
||||||
|
myReactionsMap.set(note.renote.id, null);
|
||||||
|
} else if (reactionsCount <= note.renote.reactionAndUserPairCache.length) {
|
||||||
|
const pair = note.renote.reactionAndUserPairCache.find(p => p.startsWith(meId));
|
||||||
|
myReactionsMap.set(note.renote.id, pair ? pair.split('/')[1] : null);
|
||||||
|
} else {
|
||||||
|
idsNeedFetchMyReaction.add(note.renote.id);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (note.id < oldId) {
|
||||||
|
const reactionsCount = Object.values(note.reactions).reduce((a, b) => a + b, 0);
|
||||||
|
if (reactionsCount === 0) {
|
||||||
|
myReactionsMap.set(note.id, null);
|
||||||
|
} else if (reactionsCount <= note.reactionAndUserPairCache.length) {
|
||||||
|
const pair = note.reactionAndUserPairCache.find(p => p.startsWith(meId));
|
||||||
|
myReactionsMap.set(note.id, pair ? pair.split('/')[1] : null);
|
||||||
|
} else {
|
||||||
|
idsNeedFetchMyReaction.add(note.id);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
myReactionsMap.set(note.id, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const myReactions = idsNeedFetchMyReaction.size > 0 ? await this.noteReactionsRepository.findBy({
|
||||||
|
userId: meId,
|
||||||
|
noteId: In(Array.from(idsNeedFetchMyReaction)),
|
||||||
|
}) : [];
|
||||||
|
|
||||||
|
for (const id of idsNeedFetchMyReaction) {
|
||||||
|
myReactionsMap.set(id, myReactions.find(reaction => reaction.noteId === id)?.reaction ?? null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -168,6 +168,11 @@ export class MiNote {
|
||||||
})
|
})
|
||||||
public mentionedRemoteUsers: string;
|
public mentionedRemoteUsers: string;
|
||||||
|
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 1024, array: true, default: '{}',
|
||||||
|
})
|
||||||
|
public reactionAndUserPairCache: string[];
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 128, array: true, default: '{}',
|
length: 128, array: true, default: '{}',
|
||||||
})
|
})
|
||||||
|
|
|
@ -179,6 +179,14 @@ export const packedNoteSchema = {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
optional: true, nullable: false,
|
optional: true, nullable: false,
|
||||||
},
|
},
|
||||||
|
reactionAndUserPairCache: {
|
||||||
|
type: 'array',
|
||||||
|
optional: true, nullable: false,
|
||||||
|
items: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
myReaction: {
|
myReaction: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
|
|
|
@ -73,7 +73,7 @@ export class ServerService implements OnApplicationShutdown {
|
||||||
public async launch(): Promise<void> {
|
public async launch(): Promise<void> {
|
||||||
const fastify = Fastify({
|
const fastify = Fastify({
|
||||||
trustProxy: true,
|
trustProxy: true,
|
||||||
logger: !['production', 'test'].includes(process.env.NODE_ENV ?? ''),
|
logger: false,
|
||||||
});
|
});
|
||||||
this.#fastify = fastify;
|
this.#fastify = fastify;
|
||||||
|
|
||||||
|
|
|
@ -123,6 +123,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
noteIds.sort((a, b) => a > b ? -1 : 1);
|
noteIds.sort((a, b) => a > b ? -1 : 1);
|
||||||
noteIds = noteIds.slice(0, ps.limit);
|
noteIds = noteIds.slice(0, ps.limit);
|
||||||
|
|
||||||
|
shouldFallbackToDb = shouldFallbackToDb || (noteIds.length === 0);
|
||||||
|
|
||||||
if (!shouldFallbackToDb) {
|
if (!shouldFallbackToDb) {
|
||||||
const query = this.notesRepository.createQueryBuilder('note')
|
const query = this.notesRepository.createQueryBuilder('note')
|
||||||
.where('note.id IN (:...noteIds)', { noteIds: noteIds })
|
.where('note.id IN (:...noteIds)', { noteIds: noteIds })
|
||||||
|
|
|
@ -46,8 +46,10 @@ class ChannelChannel extends Channel {
|
||||||
if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return;
|
if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return;
|
||||||
|
|
||||||
if (this.user && note.renoteId && !note.text) {
|
if (this.user && note.renoteId && !note.text) {
|
||||||
const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renoteId, this.user.id);
|
if (note.renote && Object.keys(note.renote.reactions).length > 0) {
|
||||||
note.renote!.myReaction = myRenoteReaction;
|
const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id);
|
||||||
|
note.renote.myReaction = myRenoteReaction;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.connection.cacheNote(note);
|
this.connection.cacheNote(note);
|
||||||
|
|
|
@ -88,8 +88,10 @@ class GlobalTimelineChannel extends Channel {
|
||||||
if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return;
|
if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return;
|
||||||
|
|
||||||
if (this.user && note.renoteId && !note.text) {
|
if (this.user && note.renoteId && !note.text) {
|
||||||
const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renoteId, this.user.id);
|
if (note.renote && Object.keys(note.renote.reactions).length > 0) {
|
||||||
note.renote!.myReaction = myRenoteReaction;
|
const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id);
|
||||||
|
note.renote.myReaction = myRenoteReaction;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.connection.cacheNote(note);
|
this.connection.cacheNote(note);
|
||||||
|
|
|
@ -51,8 +51,10 @@ class HashtagChannel extends Channel {
|
||||||
if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return;
|
if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return;
|
||||||
|
|
||||||
if (this.user && note.renoteId && !note.text) {
|
if (this.user && note.renoteId && !note.text) {
|
||||||
const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renoteId, this.user.id);
|
if (note.renote && Object.keys(note.renote.reactions).length > 0) {
|
||||||
note.renote!.myReaction = myRenoteReaction;
|
const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id);
|
||||||
|
note.renote.myReaction = myRenoteReaction;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.connection.cacheNote(note);
|
this.connection.cacheNote(note);
|
||||||
|
|
|
@ -77,8 +77,10 @@ class HomeTimelineChannel extends Channel {
|
||||||
if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return;
|
if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return;
|
||||||
|
|
||||||
if (this.user && note.renoteId && !note.text) {
|
if (this.user && note.renoteId && !note.text) {
|
||||||
const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renoteId, this.user.id);
|
if (note.renote && Object.keys(note.renote.reactions).length > 0) {
|
||||||
note.renote!.myReaction = myRenoteReaction;
|
const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id);
|
||||||
|
note.renote.myReaction = myRenoteReaction;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.connection.cacheNote(note);
|
this.connection.cacheNote(note);
|
||||||
|
|
|
@ -89,8 +89,11 @@ class HybridTimelineChannel extends Channel {
|
||||||
if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return;
|
if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return;
|
||||||
|
|
||||||
if (this.user && note.renoteId && !note.text) {
|
if (this.user && note.renoteId && !note.text) {
|
||||||
const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renoteId, this.user.id);
|
if (note.renote && Object.keys(note.renote.reactions).length > 0) {
|
||||||
note.renote!.myReaction = myRenoteReaction;
|
console.log(note.renote.reactionAndUserPairCache);
|
||||||
|
const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id);
|
||||||
|
note.renote.myReaction = myRenoteReaction;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.connection.cacheNote(note);
|
this.connection.cacheNote(note);
|
||||||
|
|
|
@ -85,8 +85,10 @@ class LocalTimelineChannel extends Channel {
|
||||||
if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return;
|
if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return;
|
||||||
|
|
||||||
if (this.user && note.renoteId && !note.text) {
|
if (this.user && note.renoteId && !note.text) {
|
||||||
const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renoteId, this.user.id);
|
if (note.renote && Object.keys(note.renote.reactions).length > 0) {
|
||||||
note.renote!.myReaction = myRenoteReaction;
|
const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id);
|
||||||
|
note.renote.myReaction = myRenoteReaction;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.connection.cacheNote(note);
|
this.connection.cacheNote(note);
|
||||||
|
|
|
@ -103,8 +103,10 @@ class UserListChannel extends Channel {
|
||||||
if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return;
|
if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return;
|
||||||
|
|
||||||
if (this.user && note.renoteId && !note.text) {
|
if (this.user && note.renoteId && !note.text) {
|
||||||
const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renoteId, this.user.id);
|
if (note.renote && Object.keys(note.renote.reactions).length > 0) {
|
||||||
note.renote!.myReaction = myRenoteReaction;
|
const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id);
|
||||||
|
note.renote.myReaction = myRenoteReaction;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.connection.cacheNote(note);
|
this.connection.cacheNote(note);
|
||||||
|
|
|
@ -15,6 +15,7 @@ import { host } from '@/config';
|
||||||
import { defaultStore } from '@/store';
|
import { defaultStore } from '@/store';
|
||||||
import { mixEmoji } from '@/scripts/emojiKitchen/emojiMixer';
|
import { mixEmoji } from '@/scripts/emojiKitchen/emojiMixer';
|
||||||
import MkRuby from "@/components/global/MkRuby.vue";
|
import MkRuby from "@/components/global/MkRuby.vue";
|
||||||
|
import { nyaize } from '@/scripts/nyaize.js';
|
||||||
|
|
||||||
const QUOTE_STYLE = `
|
const QUOTE_STYLE = `
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -85,10 +86,13 @@ export default function(props: {
|
||||||
* @param ast MFM AST
|
* @param ast MFM AST
|
||||||
* @param scale How times large the text is
|
* @param scale How times large the text is
|
||||||
*/
|
*/
|
||||||
const genEl = (ast: mfm.MfmNode[], scale: number) => ast.map((token): VNode | string | (VNode | string)[] => {
|
const genEl = (ast: mfm.MfmNode[], scale: number, disableNyaize = false) => ast.map((token): VNode | string | (VNode | string)[] => {
|
||||||
switch (token.type) {
|
switch (token.type) {
|
||||||
case 'text': {
|
case 'text': {
|
||||||
const text = token.props.text.replace(/(\r\n|\n|\r)/g, '\n');
|
let text = token.props.text.replace(/(\r\n|\n|\r)/g, '\n');
|
||||||
|
if (!disableNyaize && props.author.isCat) {
|
||||||
|
text = nyaize(text);
|
||||||
|
}
|
||||||
|
|
||||||
if (!props.plain) {
|
if (!props.plain) {
|
||||||
const res: (VNode | string)[] = [];
|
const res: (VNode | string)[] = [];
|
||||||
|
@ -426,7 +430,7 @@ export default function(props: {
|
||||||
key: Math.random(),
|
key: Math.random(),
|
||||||
url: token.props.url,
|
url: token.props.url,
|
||||||
rel: 'nofollow noopener',
|
rel: 'nofollow noopener',
|
||||||
}, genEl(token.children, scale))];
|
}, genEl(token.children, scale, true))];
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'mention': {
|
case 'mention': {
|
||||||
|
@ -464,11 +468,11 @@ export default function(props: {
|
||||||
if (!props.nowrap) {
|
if (!props.nowrap) {
|
||||||
return [h('div', {
|
return [h('div', {
|
||||||
style: QUOTE_STYLE,
|
style: QUOTE_STYLE,
|
||||||
}, genEl(token.children, scale))];
|
}, genEl(token.children, scale, true))];
|
||||||
} else {
|
} else {
|
||||||
return [h('span', {
|
return [h('span', {
|
||||||
style: QUOTE_STYLE,
|
style: QUOTE_STYLE,
|
||||||
}, genEl(token.children, scale))];
|
}, genEl(token.children, scale, true))];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -523,7 +527,7 @@ export default function(props: {
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'plain': {
|
case 'plain': {
|
||||||
return [h('span', genEl(token.children, scale))];
|
return [h('span', genEl(token.children, scale, true))];
|
||||||
}
|
}
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function nyaize(text: string): string {
|
||||||
|
return text
|
||||||
|
// ja-JP
|
||||||
|
.replaceAll('な', 'にゃ').replaceAll('ナ', 'ニャ').replaceAll('ナ', 'ニャ')
|
||||||
|
// en-US
|
||||||
|
.replace(/(?<=n)a/gi, x => x === 'A' ? 'YA' : 'ya')
|
||||||
|
.replace(/(?<=morn)ing/gi, x => x === 'ING' ? 'YAN' : 'yan')
|
||||||
|
.replace(/(?<=every)one/gi, x => x === 'ONE' ? 'NYAN' : 'nyan')
|
||||||
|
// ko-KR
|
||||||
|
.replace(/[나-낳]/g, match => String.fromCharCode(
|
||||||
|
match.charCodeAt(0)! + '냐'.charCodeAt(0) - '나'.charCodeAt(0),
|
||||||
|
))
|
||||||
|
.replace(/(다$)|(다(?=\.))|(다(?= ))|(다(?=!))|(다(?=\?))/gm, '다냥')
|
||||||
|
.replace(/(야(?=\?))|(야$)|(야(?= ))/gm, '냥');
|
||||||
|
}
|
|
@ -2978,6 +2978,8 @@ type UserLite = {
|
||||||
faviconUrl: Instance['faviconUrl'];
|
faviconUrl: Instance['faviconUrl'];
|
||||||
themeColor: Instance['themeColor'];
|
themeColor: Instance['themeColor'];
|
||||||
};
|
};
|
||||||
|
isCat?: boolean;
|
||||||
|
isBot?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
|
@ -2988,8 +2990,8 @@ type UserSorting = '+follower' | '-follower' | '+createdAt' | '-createdAt' | '+u
|
||||||
// src/api.types.ts:16:32 - (ae-forgotten-export) The symbol "TODO" needs to be exported by the entry point index.d.ts
|
// src/api.types.ts:16:32 - (ae-forgotten-export) The symbol "TODO" needs to be exported by the entry point index.d.ts
|
||||||
// src/api.types.ts:18:25 - (ae-forgotten-export) The symbol "NoParams" needs to be exported by the entry point index.d.ts
|
// src/api.types.ts:18:25 - (ae-forgotten-export) The symbol "NoParams" needs to be exported by the entry point index.d.ts
|
||||||
// src/api.types.ts:633:18 - (ae-forgotten-export) The symbol "ShowUserReq" needs to be exported by the entry point index.d.ts
|
// src/api.types.ts:633:18 - (ae-forgotten-export) The symbol "ShowUserReq" needs to be exported by the entry point index.d.ts
|
||||||
// src/entities.ts:107:2 - (ae-forgotten-export) The symbol "notificationTypes_2" needs to be exported by the entry point index.d.ts
|
// src/entities.ts:109:2 - (ae-forgotten-export) The symbol "notificationTypes_2" needs to be exported by the entry point index.d.ts
|
||||||
// src/entities.ts:603:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts
|
// src/entities.ts:605:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts
|
||||||
// src/streaming.types.ts:33:4 - (ae-forgotten-export) The symbol "FIXME" needs to be exported by the entry point index.d.ts
|
// src/streaming.types.ts:33:4 - (ae-forgotten-export) The symbol "FIXME" needs to be exported by the entry point index.d.ts
|
||||||
|
|
||||||
// (No @packageDocumentation comment for this package)
|
// (No @packageDocumentation comment for this package)
|
||||||
|
|
|
@ -28,6 +28,8 @@ export type UserLite = {
|
||||||
faviconUrl: Instance['faviconUrl'];
|
faviconUrl: Instance['faviconUrl'];
|
||||||
themeColor: Instance['themeColor'];
|
themeColor: Instance['themeColor'];
|
||||||
};
|
};
|
||||||
|
isCat?: boolean;
|
||||||
|
isBot?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type UserDetailed = UserLite & {
|
export type UserDetailed = UserLite & {
|
||||||
|
|
Loading…
Reference in New Issue