diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2023-01-26 11:40:46 +0900 |
|---|---|---|
| committer | syuilo <Syuilotan@yahoo.co.jp> | 2023-01-26 11:40:46 +0900 |
| commit | 34f5d81d1fa82abc985fafc1d83f1c8e7c48703c (patch) | |
| tree | ea24fd52c46127da9c5ea5031cc706e7ff5e2c3b | |
| parent | Merge branch 'develop' (diff) | |
| parent | 13.2.3 (diff) | |
| download | misskey-34f5d81d1fa82abc985fafc1d83f1c8e7c48703c.tar.gz misskey-34f5d81d1fa82abc985fafc1d83f1c8e7c48703c.tar.bz2 misskey-34f5d81d1fa82abc985fafc1d83f1c8e7c48703c.zip | |
Merge branch 'develop'
32 files changed, 257 insertions, 142 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 8dd21ce6d1..b0dbb6e240 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,13 @@ You should also include the user name that made the change. --> +## 13.2.3 (2023/01/26) +### Improvements +- カスタム絵文字の更新をリアルタイムで反映するように + +### Bugfixes +- turnstile-failed: missing-input-secret + ## 13.2.2 (2023/01/25) ### Improvements - サーバーのパフォーマンスを改善 diff --git a/locales/uk-UA.yml b/locales/uk-UA.yml index a77bfa4f8d..a852339969 100644 --- a/locales/uk-UA.yml +++ b/locales/uk-UA.yml @@ -956,9 +956,11 @@ _achievements: _login3: title: "Новачок I" description: "3 дні користування загально" + flavor: "Відсьогодні називайте мене \"Місскіст\"" _login7: title: "Новачок II" description: "7 днів користування загально" + flavor: "Ви звикли до цього?" _login15: title: "Новачок III" description: "15 днів користування загально" @@ -971,6 +973,7 @@ _achievements: _login100: title: "Міскієць III" description: "100 днів користування загально" + flavor: "Цей юзер лютий місскіст" _login200: title: "Завсідник I" description: "200 днів користування загально" @@ -983,6 +986,7 @@ _achievements: _login500: title: "Ветеран I" description: "500 днів користування загально" + flavor: "Meine Kameraden, ich liebe sie, die Notizen." _login600: title: "Ветеран II" description: "600 днів користування загально" @@ -990,13 +994,25 @@ _achievements: title: "Ветеран III" description: "700 днів користування загально" _login800: + title: "Майстер нотаток I" description: "800 днів користування загально" _login900: + title: "Майстер нотаток II" description: "900 днів користування загально" _login1000: + title: "Майстер нотаток III" description: "1000 днів користування загально" flavor: "Дякуємо, що користуєтеся Misskey!" + _myNoteFavorited1: + title: "У пошуках зірок" + _markedAsCat: + flavor: "Я дам тобі ім'я пізніше" + _following1: + title: "Перша підписка" + _following10: + title: "Продовжуй, продовжуй" _following50: + title: "Багато друзів" description: "Кількість підписок сягнула 50" _following100: title: "100 друзів" @@ -1013,6 +1029,7 @@ _achievements: _followers50: description: "Кількість підписників досягла 50" _followers100: + title: "Популярна особа" description: "Кількість підписників досягла 100" _followers300: description: "Кількість підписників досягла 300" @@ -1021,11 +1038,17 @@ _achievements: _followers1000: title: "Інфлюенсер" description: "Кількість підписників досягла 1000" + _passedSinceAccountCreated1: + title: "Перша річниця" + _passedSinceAccountCreated2: + title: "Друга річниця" _passedSinceAccountCreated3: + title: "Третя річниця" description: "Минуло 3 роки з моменту створення акаунта" _loggedInOnBirthday: title: "З Днем народження!" _brainDiver: + title: "Brain Diver" flavor: "Misskey-Misskey La-Tu-Ma" _role: priority: "Пріоритет" diff --git a/package.json b/package.json index 800ca39efa..c5a556aead 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "13.2.2", + "version": "13.2.3", "codename": "nasubi", "repository": { "type": "git", diff --git a/packages/backend/src/core/CaptchaService.ts b/packages/backend/src/core/CaptchaService.ts index c8428a26b0..7aaa1b833f 100644 --- a/packages/backend/src/core/CaptchaService.ts +++ b/packages/backend/src/core/CaptchaService.ts @@ -23,9 +23,9 @@ export class CaptchaService { const res = await this.httpRequestService.send(url, { method: 'POST', - body: JSON.stringify(params), + body: params.toString(), headers: { - 'Content-Type': 'application/json', + 'Content-Type': 'application/x-www-form-urlencoded', }, }, { throwErrorWhenResponseNotOk: false }); diff --git a/packages/backend/src/core/CustomEmojiService.ts b/packages/backend/src/core/CustomEmojiService.ts index 18b4067f61..1f0b214159 100644 --- a/packages/backend/src/core/CustomEmojiService.ts +++ b/packages/backend/src/core/CustomEmojiService.ts @@ -2,6 +2,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { DataSource, In, IsNull } from 'typeorm'; import { DI } from '@/di-symbols.js'; import { IdService } from '@/core/IdService.js'; +import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; import type { DriveFile } from '@/models/entities/DriveFile.js'; import type { Emoji } from '@/models/entities/Emoji.js'; import type { EmojisRepository } from '@/models/index.js'; @@ -17,6 +19,8 @@ export class CustomEmojiService { private emojisRepository: EmojisRepository, private idService: IdService, + private emojiEntityService: EmojiEntityService, + private globalEventService: GlobalEventService, ) { } @@ -42,6 +46,10 @@ export class CustomEmojiService { await this.db.queryResultCache!.remove(['meta_emojis']); + this.globalEventService.publishBroadcastStream('emojiAdded', { + emoji: await this.emojiEntityService.pack(emoji.id), + }); + return emoji; } } diff --git a/packages/backend/src/core/entities/EmojiEntityService.ts b/packages/backend/src/core/entities/EmojiEntityService.ts index 611552d89e..7d248e6342 100644 --- a/packages/backend/src/core/entities/EmojiEntityService.ts +++ b/packages/backend/src/core/entities/EmojiEntityService.ts @@ -22,8 +22,10 @@ export class EmojiEntityService { @bindThis public async pack( src: Emoji['id'] | Emoji, - opts: { omitHost?: boolean; omitId?: boolean; withUrl?: boolean; } = {}, + opts: { omitHost?: boolean; omitId?: boolean; withUrl?: boolean; } = { omitHost: true, omitId: true, withUrl: true }, ): Promise<Packed<'Emoji'>> { + opts = { omitHost: true, omitId: true, withUrl: true, ...opts } + const emoji = typeof src === 'object' ? src : await this.emojisRepository.findOneByOrFail({ id: src }); return { diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index 34b523e143..f532b5bf6e 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -496,10 +496,10 @@ export class UserEntityService implements OnModuleInit { showTimelineReplies: user.showTimelineReplies ?? falsy, achievements: profile!.achievements, loggedInDays: profile!.loggedInDates.length, + policies: this.roleService.getUserPolicies(user.id), } : {}), ...(opts.includeSecrets ? { - policies: this.roleService.getUserPolicies(user.id), email: profile!.email, emailVerified: profile!.emailVerified, securityKeysList: profile!.twoFactorEnabled diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts index 9b6c774f0c..c683cd24c1 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts @@ -3,6 +3,8 @@ import { DataSource, In } from 'typeorm'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { EmojisRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; +import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; export const meta = { tags: ['admin'], @@ -35,6 +37,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { @Inject(DI.emojisRepository) private emojisRepository: EmojisRepository, + + private emojiEntityService: EmojiEntityService, + private globalEventService: GlobalEventService, ) { super(meta, paramDef, async (ps, me) => { const emojis = await this.emojisRepository.findBy({ @@ -49,6 +54,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { } await this.db.queryResultCache!.remove(['meta_emojis']); + + this.globalEventService.publishBroadcastStream('emojiUpdated', { + emojis: await this.emojiEntityService.packMany(ps.ids), + }); }); } } diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts index abca1d169d..1bb05c15c2 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts @@ -2,12 +2,10 @@ import { Inject, Injectable } from '@nestjs/common'; import rndstr from 'rndstr'; import { DataSource } from 'typeorm'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { DriveFilesRepository, EmojisRepository } from '@/models/index.js'; -import { IdService } from '@/core/IdService.js'; +import type { DriveFilesRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; -import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { CustomEmojiService } from '@/core/CustomEmojiService.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; -import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -39,43 +37,26 @@ export const paramDef = { @Injectable() export default class extends Endpoint<typeof meta, typeof paramDef> { constructor( - @Inject(DI.db) - private db: DataSource, - @Inject(DI.driveFilesRepository) private driveFilesRepository: DriveFilesRepository, - @Inject(DI.emojisRepository) - private emojisRepository: EmojisRepository, + private customEmojiService: CustomEmojiService, - private emojiEntityService: EmojiEntityService, - private idService: IdService, - private globalEventService: GlobalEventService, private moderationLogService: ModerationLogService, ) { super(meta, paramDef, async (ps, me) => { - const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId }); + const driveFile = await this.driveFilesRepository.findOneBy({ id: ps.fileId }); - if (file == null) throw new ApiError(meta.errors.noSuchFile); + if (driveFile == null) throw new ApiError(meta.errors.noSuchFile); - const name = file.name.split('.')[0].match(/^[a-z0-9_]+$/) ? file.name.split('.')[0] : `_${rndstr('a-z0-9', 8)}_`; + const name = driveFile.name.split('.')[0].match(/^[a-z0-9_]+$/) ? driveFile.name.split('.')[0] : `_${rndstr('a-z0-9', 8)}_`; - const emoji = await this.emojisRepository.insert({ - id: this.idService.genId(), - updatedAt: new Date(), - name: name, + const emoji = await this.customEmojiService.add({ + driveFile, + name, category: null, - host: null, aliases: [], - originalUrl: file.url, - publicUrl: file.webpublicUrl ?? file.url, - type: file.webpublicType ?? file.type, - }).then(x => this.emojisRepository.findOneByOrFail(x.identifiers[0])); - - await this.db.queryResultCache!.remove(['meta_emojis']); - - this.globalEventService.publishBroadcastStream('emojiAdded', { - emoji: await this.emojiEntityService.pack(emoji.id), + host: null, }); this.moderationLogService.insertModerationLog(me, 'addEmoji', { diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts index ae45105b28..0c337237d3 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts @@ -4,6 +4,8 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import type { EmojisRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; +import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; export const meta = { tags: ['admin'], @@ -35,6 +37,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { private emojisRepository: EmojisRepository, private moderationLogService: ModerationLogService, + private emojiEntityService: EmojiEntityService, + private globalEventService: GlobalEventService, ) { super(meta, paramDef, async (ps, me) => { const emojis = await this.emojisRepository.findBy({ @@ -43,13 +47,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { for (const emoji of emojis) { await this.emojisRepository.delete(emoji.id); - await this.db.queryResultCache!.remove(['meta_emojis']); - this.moderationLogService.insertModerationLog(me, 'deleteEmoji', { emoji: emoji, }); } + + this.globalEventService.publishBroadcastStream('emojiDeleted', { + emojis: await this.emojiEntityService.packMany(emojis), + }); }); } } diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts b/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts index e237d87d34..c1a60a2773 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts @@ -5,6 +5,8 @@ import type { EmojisRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; import { ApiError } from '../../../error.js'; +import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; export const meta = { tags: ['admin'], @@ -42,6 +44,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { private emojisRepository: EmojisRepository, private moderationLogService: ModerationLogService, + private emojiEntityService: EmojiEntityService, + private globalEventService: GlobalEventService, ) { super(meta, paramDef, async (ps, me) => { const emoji = await this.emojisRepository.findOneBy({ id: ps.id }); @@ -52,6 +56,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { await this.db.queryResultCache!.remove(['meta_emojis']); + this.globalEventService.publishBroadcastStream('emojiDeleted', { + emojis: [ await this.emojiEntityService.pack(emoji) ], + }); + this.moderationLogService.insertModerationLog(me, 'deleteEmoji', { emoji: emoji, }); diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts b/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts index d9ce97194a..8e0ea2e117 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts @@ -101,7 +101,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { .take(ps.limit) .getMany(); - return this.emojiEntityService.packMany(emojis); + return this.emojiEntityService.packMany(emojis, { omitHost: false, omitId: false, withUrl: false }); }); } } diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts index 1a6096f36f..1b1931f8e6 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts @@ -98,7 +98,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { emojis = await q.take(ps.limit).getMany(); } - return this.emojiEntityService.packMany(emojis); + return this.emojiEntityService.packMany(emojis, { omitHost: false, omitId: false, withUrl: false }); }); } } diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts index 5fc9e024bf..065965f64a 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts @@ -3,6 +3,8 @@ import { DataSource, In } from 'typeorm'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { EmojisRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; +import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; export const meta = { tags: ['admin'], @@ -35,6 +37,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { @Inject(DI.emojisRepository) private emojisRepository: EmojisRepository, + + private emojiEntityService: EmojiEntityService, + private globalEventService: GlobalEventService, ) { super(meta, paramDef, async (ps, me) => { const emojis = await this.emojisRepository.findBy({ @@ -49,6 +54,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { } await this.db.queryResultCache!.remove(['meta_emojis']); + + this.globalEventService.publishBroadcastStream('emojiUpdated', { + emojis: await this.emojiEntityService.packMany(ps.ids), + }); }); } } diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts index 8b5ba8fbf4..51c0f329ac 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts @@ -3,6 +3,8 @@ import { DataSource, In } from 'typeorm'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { EmojisRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; +import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; export const meta = { tags: ['admin'], @@ -35,6 +37,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { @Inject(DI.emojisRepository) private emojisRepository: EmojisRepository, + + private emojiEntityService: EmojiEntityService, + private globalEventService: GlobalEventService, ) { super(meta, paramDef, async (ps, me) => { await this.emojisRepository.update({ @@ -45,6 +50,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { }); await this.db.queryResultCache!.remove(['meta_emojis']); + + this.globalEventService.publishBroadcastStream('emojiUpdated', { + emojis: await this.emojiEntityService.packMany(ps.ids), + }); }); } } diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts index 827b5ace7a..3329cab7b9 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts @@ -3,6 +3,8 @@ import { DataSource, In } from 'typeorm'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { EmojisRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; +import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; export const meta = { tags: ['admin'], @@ -37,6 +39,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { @Inject(DI.emojisRepository) private emojisRepository: EmojisRepository, + + private emojiEntityService: EmojiEntityService, + private globalEventService: GlobalEventService, ) { super(meta, paramDef, async (ps, me) => { await this.emojisRepository.update({ @@ -47,6 +52,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { }); await this.db.queryResultCache!.remove(['meta_emojis']); + + this.globalEventService.publishBroadcastStream('emojiUpdated', { + emojis: await this.emojiEntityService.packMany(ps.ids), + }); }); } } diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts index fb0ef12878..22bedc7100 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts @@ -4,6 +4,8 @@ import { Endpoint } from '@/server/api/endpoint-base.js'; import type { EmojisRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; +import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; export const meta = { tags: ['admin'], @@ -48,6 +50,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { @Inject(DI.emojisRepository) private emojisRepository: EmojisRepository, + + private emojiEntityService: EmojiEntityService, + private globalEventService: GlobalEventService, ) { super(meta, paramDef, async (ps, me) => { const emoji = await this.emojisRepository.findOneBy({ id: ps.id }); @@ -62,6 +67,22 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { }); await this.db.queryResultCache!.remove(['meta_emojis']); + + const updated = await this.emojiEntityService.pack(emoji.id); + + if (emoji.name === ps.name) { + this.globalEventService.publishBroadcastStream('emojiUpdated', { + emojis: [ updated ], + }); + } else { + this.globalEventService.publishBroadcastStream('emojiDeleted', { + emojis: [ await this.emojiEntityService.pack(emoji) ], + }); + + this.globalEventService.publishBroadcastStream('emojiAdded', { + emoji: updated, + }); + } }); } } diff --git a/packages/backend/src/server/api/endpoints/emojis.ts b/packages/backend/src/server/api/endpoints/emojis.ts index db1eddc80a..77854afb33 100644 --- a/packages/backend/src/server/api/endpoints/emojis.ts +++ b/packages/backend/src/server/api/endpoints/emojis.ts @@ -10,6 +10,8 @@ export const meta = { tags: ['meta'], requireCredential: false, + allowGet: true, + cacheSec: 3600, res: { type: 'object', diff --git a/packages/backend/src/server/api/stream/types.ts b/packages/backend/src/server/api/stream/types.ts index ef65b27ddc..fc145cf0ee 100644 --- a/packages/backend/src/server/api/stream/types.ts +++ b/packages/backend/src/server/api/stream/types.ts @@ -49,6 +49,16 @@ export interface BroadcastTypes { emojiAdded: { emoji: Packed<'Emoji'>; }; + emojiUpdated: { + emojis: Packed<'Emoji'>[]; + }; + emojiDeleted: { + emojis: { + id?: string; + name: string; + [other: string]: any; + }[]; + }; } export interface UserStreamTypes { diff --git a/packages/backend/src/server/web/boot.js b/packages/backend/src/server/web/boot.js index e635959fcf..c6cb25e43a 100644 --- a/packages/backend/src/server/web/boot.js +++ b/packages/backend/src/server/web/boot.js @@ -154,7 +154,7 @@ <path d="M5 19h14a2 2 0 0 0 1.84 -2.75l-7.1 -12.25a2 2 0 0 0 -3.5 0l-7.1 12.25a2 2 0 0 0 1.75 2.75"></path> </svg> <h1>An error has occurred!</h1> - <button class="button-big" onclick="location.reload(true);"> + <button class="button-big" onclick="location.reload();"> <span class="button-label-big">Refresh</span> </button> <p class="dont-worry">Don't worry, it's (probably) not your fault.</p> diff --git a/packages/frontend/src/components/MkAutocomplete.vue b/packages/frontend/src/components/MkAutocomplete.vue index 702fba9796..2cb3aeb3d8 100644 --- a/packages/frontend/src/components/MkAutocomplete.vue +++ b/packages/frontend/src/components/MkAutocomplete.vue @@ -33,7 +33,7 @@ </template> <script lang="ts"> -import { markRaw, ref, shallowRef, onUpdated, onMounted, onBeforeUnmount, nextTick, watch } from 'vue'; +import { markRaw, ref, shallowRef, computed, onUpdated, onMounted, onBeforeUnmount, nextTick, watch } from 'vue'; import sanitizeHtml from 'sanitize-html'; import contains from '@/scripts/contains'; import { char2twemojiFilePath, char2fluentEmojiFilePath } from '@/scripts/emoji-base'; @@ -61,59 +61,62 @@ type EmojiDef = { const lib = emojilist.filter(x => x.category !== 'flags'); -const char2path = defaultStore.state.emojiStyle === 'twemoji' ? char2twemojiFilePath : char2fluentEmojiFilePath; +const emojiDb = computed(() => { + //#region Unicode Emoji + const char2path = defaultStore.reactiveState.emojiStyle.value === 'twemoji' ? char2twemojiFilePath : char2fluentEmojiFilePath; -const emjdb: EmojiDef[] = lib.map(x => ({ - emoji: x.char, - name: x.name, - url: char2path(x.char), -})); + const unicodeEmojiDB: EmojiDef[] = lib.map(x => ({ + emoji: x.char, + name: x.name, + url: char2path(x.char), + })); -for (const x of lib) { - if (x.keywords) { - for (const k of x.keywords) { - emjdb.push({ - emoji: x.char, - name: k, - aliasOf: x.name, - url: char2path(x.char), - }); + for (const x of lib) { + if (x.keywords) { + for (const k of x.keywords) { + unicodeEmojiDB.push({ + emoji: x.char, + name: k, + aliasOf: x.name, + url: char2path(x.char), + }); + } } } -} -emjdb.sort((a, b) => a.name.length - b.name.length); + unicodeEmojiDB.sort((a, b) => a.name.length - b.name.length); + //#endregion -//#region Construct Emoji DB -const emojiDefinitions: EmojiDef[] = []; + //#region Custom Emoji + const customEmojiDB: EmojiDef[] = []; -for (const x of customEmojis) { - emojiDefinitions.push({ - name: x.name, - emoji: `:${x.name}:`, - isCustomEmoji: true, - }); + for (const x of customEmojis.value) { + customEmojiDB.push({ + name: x.name, + emoji: `:${x.name}:`, + isCustomEmoji: true, + }); - if (x.aliases) { - for (const alias of x.aliases) { - emojiDefinitions.push({ - name: alias, - aliasOf: x.name, - emoji: `:${x.name}:`, - isCustomEmoji: true, - }); + if (x.aliases) { + for (const alias of x.aliases) { + customEmojiDB.push({ + name: alias, + aliasOf: x.name, + emoji: `:${x.name}:`, + isCustomEmoji: true, + }); + } } } -} -emojiDefinitions.sort((a, b) => a.name.length - b.name.length); + customEmojiDB.sort((a, b) => a.name.length - b.name.length); + //#endregion -const emojiDb = markRaw(emojiDefinitions.concat(emjdb)); -//#endregion + return markRaw([ ...customEmojiDB, ...unicodeEmojiDB ]); +}); export default { emojiDb, - emojiDefinitions, emojilist, }; </script> @@ -230,27 +233,27 @@ function exec() { } else if (props.type === 'emoji') { if (!props.q || props.q === '') { // 最近使った絵文字をサジェスト - emojis.value = defaultStore.state.recentlyUsedEmojis.map(emoji => emojiDb.find(dbEmoji => dbEmoji.emoji === emoji)).filter(x => x) as EmojiDef[]; + emojis.value = defaultStore.state.recentlyUsedEmojis.map(emoji => emojiDb.value.find(dbEmoji => dbEmoji.emoji === emoji)).filter(x => x) as EmojiDef[]; return; } const matched: EmojiDef[] = []; const max = 30; - emojiDb.some(x => { + emojiDb.value.some(x => { if (x.name.startsWith(props.q ?? '') && !x.aliasOf && !matched.some(y => y.emoji === x.emoji)) matched.push(x); return matched.length === max; }); if (matched.length < max) { - emojiDb.some(x => { + emojiDb.value.some(x => { if (x.name.startsWith(props.q ?? '') && !matched.some(y => y.emoji === x.emoji)) matched.push(x); return matched.length === max; }); } if (matched.length < max) { - emojiDb.some(x => { + emojiDb.value.some(x => { if (x.name.includes(props.q ?? '') && !matched.some(y => y.emoji === x.emoji)) matched.push(x); return matched.length === max; }); diff --git a/packages/frontend/src/components/MkEmojiPicker.section.vue b/packages/frontend/src/components/MkEmojiPicker.section.vue index 8b0b7cf29a..acced44793 100644 --- a/packages/frontend/src/components/MkEmojiPicker.section.vue +++ b/packages/frontend/src/components/MkEmojiPicker.section.vue @@ -18,10 +18,10 @@ </template> <script lang="ts" setup> -import { ref } from 'vue'; +import { ref, computed, Ref } from 'vue'; const props = defineProps<{ - emojis: string[]; + emojis: string[] | Ref<string[]>; initialShown?: boolean; }>(); @@ -29,5 +29,7 @@ const emit = defineEmits<{ (ev: 'chosen', v: string, event: MouseEvent): void; }>(); +const emojis = computed(() => Array.isArray(props.emojis) ? props.emojis : props.emojis.value); + const shown = ref(!!props.initialShown); </script> diff --git a/packages/frontend/src/components/MkEmojiPicker.vue b/packages/frontend/src/components/MkEmojiPicker.vue index 9c6d62ce8b..f64cc6e9aa 100644 --- a/packages/frontend/src/components/MkEmojiPicker.vue +++ b/packages/frontend/src/components/MkEmojiPicker.vue @@ -60,7 +60,15 @@ </div> <div v-once class="group"> <header class="_acrylic">{{ i18n.ts.customEmojis }}</header> - <XSection v-for="category in customEmojiCategories" :key="'custom:' + category" :initial-shown="false" :emojis="customEmojis.filter(e => e.category === category).map(e => ':' + e.name + ':')" @chosen="chosen">{{ category || i18n.ts.other }}</XSection> + <XSection + v-for="category in customEmojiCategories" + :key="`custom:${category}`" + :initial-shown="false" + :emojis="computed(() => customEmojis.filter(e => category === null ? (e.category === 'null' || !e.category) : e.category === category).map(e => `:${e.name}:`))" + @chosen="chosen" + > + {{ category || i18n.ts.other }} + </XSection> </div> <div v-once class="group"> <header class="_acrylic">{{ i18n.ts.emoji }}</header> @@ -88,7 +96,7 @@ import { deviceKind } from '@/scripts/device-kind'; import { instance } from '@/instance'; import { i18n } from '@/i18n'; import { defaultStore } from '@/store'; -import { getCustomEmojiCategories, customEmojis } from '@/custom-emojis'; +import { customEmojiCategories, customEmojis } from '@/custom-emojis'; const props = withDefaults(defineProps<{ showPinned?: boolean; @@ -104,7 +112,6 @@ const emit = defineEmits<{ (ev: 'chosen', v: string): void; }>(); -const customEmojiCategories = getCustomEmojiCategories(); const searchEl = shallowRef<HTMLInputElement>(); const emojisEl = shallowRef<HTMLDivElement>(); @@ -138,7 +145,7 @@ watch(q, () => { const searchCustom = () => { const max = 8; - const emojis = customEmojis; + const emojis = customEmojis.value; const matches = new Set<Misskey.entities.CustomEmoji>(); const exactMatch = emojis.find(emoji => emoji.name === newQ); @@ -323,7 +330,7 @@ function done(query?: string): boolean | void { if (query == null || typeof query !== 'string') return; const q2 = query.replace(/:/g, ''); - const exactMatchCustom = customEmojis.find(emoji => emoji.name === q2); + const exactMatchCustom = customEmojis.value.find(emoji => emoji.name === q2); if (exactMatchCustom) { chosen(exactMatchCustom); return true; diff --git a/packages/frontend/src/components/MkMenu.vue b/packages/frontend/src/components/MkMenu.vue index 94dabcac90..eee77a9475 100644 --- a/packages/frontend/src/components/MkMenu.vue +++ b/packages/frontend/src/components/MkMenu.vue @@ -335,8 +335,7 @@ onBeforeUnmount(() => { } .icon { - margin-right: 5px; - width: 20px; + margin-right: 8px; } .caret { diff --git a/packages/frontend/src/components/MkSuperMenu.vue b/packages/frontend/src/components/MkSuperMenu.vue index bb2a789b3f..5d33ad0ad3 100644 --- a/packages/frontend/src/components/MkSuperMenu.vue +++ b/packages/frontend/src/components/MkSuperMenu.vue @@ -6,15 +6,15 @@ <div class="items"> <template v-for="(item, i) in group.items"> <a v-if="item.type === 'a'" :href="item.href" :target="item.target" :tabindex="i" class="_button item" :class="{ danger: item.danger, active: item.active }"> - <i v-if="item.icon" class="icon ti-fw" :class="item.icon"></i> + <span v-if="item.icon" class="icon"><i :class="item.icon" class="ti-fw"></i></span> <span class="text">{{ item.text }}</span> </a> <button v-else-if="item.type === 'button'" :tabindex="i" class="_button item" :class="{ danger: item.danger, active: item.active }" :disabled="item.active" @click="ev => item.action(ev)"> - <i v-if="item.icon" class="icon ti-fw" :class="item.icon"></i> + <span v-if="item.icon" class="icon"><i :class="item.icon" class="ti-fw"></i></span> <span class="text">{{ item.text }}</span> </button> <MkA v-else :to="item.to" :tabindex="i" class="_button item" :class="{ danger: item.danger, active: item.active }"> - <i v-if="item.icon" class="icon ti-fw" :class="item.icon"></i> + <span v-if="item.icon" class="icon"><i :class="item.icon" class="ti-fw"></i></span> <span class="text">{{ item.text }}</span> </MkA> </template> diff --git a/packages/frontend/src/components/global/MkEmoji.vue b/packages/frontend/src/components/global/MkEmoji.vue index 93f50da20e..b554d5e47c 100644 --- a/packages/frontend/src/components/global/MkEmoji.vue +++ b/packages/frontend/src/components/global/MkEmoji.vue @@ -1,6 +1,6 @@ <template> <span v-if="isCustom && errored">:{{ customEmojiName }}:</span> -<img v-else-if="isCustom" :class="[$style.root, $style.custom, { [$style.normal]: normal, [$style.noStyle]: noStyle }]" :src="url" :alt="alt" :title="alt" decoding="async" @error="errored = true"/> +<img v-else-if="isCustom" :class="[$style.root, $style.custom, { [$style.normal]: normal, [$style.noStyle]: noStyle }]" :src="url" :alt="alt" :title="alt" decoding="async" @error="errored = true" @load="errored = false"/> <img v-else-if="char && !useOsNativeEmojis" :class="$style.root" :src="url" :alt="alt" decoding="async" @pointerenter="computeTitle"/> <span v-else-if="char && useOsNativeEmojis" :alt="alt" @pointerenter="computeTitle">{{ char }}</span> <span v-else>{{ emoji }}</span> @@ -25,29 +25,29 @@ const props = defineProps<{ const char2path = defaultStore.state.emojiStyle === 'twemoji' ? char2twemojiFilePath : char2fluentEmojiFilePath; const isCustom = computed(() => props.emoji.startsWith(':')); -const customEmojiName = props.emoji.substr(1, props.emoji.length - 2).replace('@.', ''); +const customEmojiName = computed(() => props.emoji.substr(1, props.emoji.length - 2).replace('@.', '')); const char = computed(() => isCustom.value ? undefined : props.emoji); const useOsNativeEmojis = computed(() => defaultStore.state.emojiStyle === 'native' && !props.isReaction); const url = computed(() => { if (char.value) { return char2path(char.value); - } else if (props.host == null && !customEmojiName.includes('@')) { - const found = customEmojis.find(x => x.name === customEmojiName); - return found ? found.url : null; + } else if (props.host == null && !customEmojiName.value.includes('@')) { + const found = customEmojis.value.find(x => x.name === customEmojiName.value); + return found ? defaultStore.state.disableShowingAnimatedImages ? getStaticImageUrl(found.url) : found.url : null; } else { - const rawUrl = props.host ? `/emoji/${customEmojiName}@${props.host}.webp` : `/emoji/${customEmojiName}.webp`; + const rawUrl = props.host ? `/emoji/${customEmojiName.value}@${props.host}.webp` : `/emoji/${customEmojiName.value}.webp`; return defaultStore.state.disableShowingAnimatedImages ? getStaticImageUrl(rawUrl) : rawUrl; } }); -const alt = computed(() => isCustom.value ? `:${customEmojiName}:` : char.value); +const alt = computed(() => isCustom.value ? `:${customEmojiName.value}:` : char.value); let errored = $ref(isCustom.value && url.value == null); // Searching from an array with 2000 items for every emoji felt like too energy-consuming, so I decided to do it lazily on pointerenter function computeTitle(event: PointerEvent): void { const title = isCustom.value - ? `:${customEmojiName}:` + ? `:${customEmojiName.value}:` : (getEmojiName(char.value as string) ?? char.value as string); (event.target as HTMLElement).title = title; } diff --git a/packages/frontend/src/custom-emojis.ts b/packages/frontend/src/custom-emojis.ts index a7ac4e03ca..0ba7cab5e2 100644 --- a/packages/frontend/src/custom-emojis.ts +++ b/packages/frontend/src/custom-emojis.ts @@ -1,45 +1,51 @@ -import { api } from './os'; +import { shallowRef, computed, markRaw } from 'vue'; +import * as Misskey from 'misskey-js'; +import { apiGet } from './os'; import { miLocalStorage } from './local-storage'; +import { stream } from '@/stream'; const storageCache = miLocalStorage.getItem('emojis'); -export let customEmojis: { - name: string; - aliases: string[]; - category: string; - url: string; -}[] = storageCache ? JSON.parse(storageCache) : []; +export const customEmojis = shallowRef<Misskey.entities.CustomEmoji[]>(storageCache ? JSON.parse(storageCache) : []); +export const customEmojiCategories = computed<[ ...string[], null ]>(() => { + const categories = new Set<string>(); + for (const emoji of customEmojis.value) { + if (emoji.category && emoji.category !== 'null') { + categories.add(emoji.category); + } + } + return markRaw([...Array.from(categories), null]); +}); + +stream.on('emojiAdded', emojiData => { + customEmojis.value = [emojiData.emoji, ...customEmojis.value]; +}); + +stream.on('emojiUpdated', emojiData => { + customEmojis.value = customEmojis.value.map(item => emojiData.emojis.find(search => search.name === item.name) as Misskey.entities.CustomEmoji ?? item); +}); + +stream.on('emojiDeleted', emojiData => { + customEmojis.value = customEmojis.value.filter(item => !emojiData.emojis.some(search => search.name === item.name)); +}); export async function fetchCustomEmojis() { const now = Date.now(); const lastFetchedAt = miLocalStorage.getItem('lastEmojisFetchedAt'); - if (lastFetchedAt && (now - parseInt(lastFetchedAt)) < 1000 * 60 * 60 * 24) return; + if (lastFetchedAt && (now - parseInt(lastFetchedAt)) < 1000 * 60 * 60) return; - const res = await api('emojis', {}); + const res = await apiGet('emojis', {}); - customEmojis = res.emojis; - miLocalStorage.setItem('emojis', JSON.stringify(customEmojis)); + customEmojis.value = res.emojis; + miLocalStorage.setItem('emojis', JSON.stringify(res.emojis)); miLocalStorage.setItem('lastEmojisFetchedAt', now.toString()); } -let cachedCategories; -export function getCustomEmojiCategories() { - if (cachedCategories) return cachedCategories; - - const categories = new Set(); - for (const emoji of customEmojis) { - categories.add(emoji.category); - } - const res = Array.from(categories); - cachedCategories = res; - return res; -} - let cachedTags; export function getCustomEmojiTags() { if (cachedTags) return cachedTags; const tags = new Set(); - for (const emoji of customEmojis) { + for (const emoji of customEmojis.value) { for (const tag of emoji.aliases) { tags.add(tag); } diff --git a/packages/frontend/src/init.ts b/packages/frontend/src/init.ts index 36897545e2..2432b5f6f9 100644 --- a/packages/frontend/src/init.ts +++ b/packages/frontend/src/init.ts @@ -338,11 +338,6 @@ import { fetchCustomEmojis } from './custom-emojis'; } }); - stream.on('emojiAdded', emojiData => { - // TODO - //store.commit('instance/set', ); - }); - for (const plugin of ColdDeviceStorage.get('plugins').filter(p => p.active)) { import('./plugin').then(({ install }) => { install(plugin); diff --git a/packages/frontend/src/pages/about.emojis.vue b/packages/frontend/src/pages/about.emojis.vue index c0145a5035..d964e48b31 100644 --- a/packages/frontend/src/pages/about.emojis.vue +++ b/packages/frontend/src/pages/about.emojis.vue @@ -39,13 +39,13 @@ import MkSelect from '@/components/MkSelect.vue'; import MkFoldableSection from '@/components/MkFoldableSection.vue'; import MkTab from '@/components/MkTab.vue'; import * as os from '@/os'; -import { customEmojis, getCustomEmojiCategories, getCustomEmojiTags } from '@/custom-emojis'; +import { customEmojis, customEmojiCategories, getCustomEmojiTags } from '@/custom-emojis'; import { i18n } from '@/i18n'; +import * as Misskey from 'misskey-js'; -const customEmojiCategories = getCustomEmojiCategories(); const customEmojiTags = getCustomEmojiTags(); let q = $ref(''); -let searchEmojis = $ref(null); +let searchEmojis = $ref<Misskey.entities.CustomEmoji[]>(null); let selectedTags = $ref(new Set()); function search() { @@ -55,9 +55,9 @@ function search() { } if (selectedTags.size === 0) { - searchEmojis = customEmojis.filter(emoji => emoji.name.includes(q) || emoji.aliases.includes(q)); + searchEmojis = customEmojis.value.filter(emoji => emoji.name.includes(q) || emoji.aliases.includes(q)); } else { - searchEmojis = customEmojis.filter(emoji => (emoji.name.includes(q) || emoji.aliases.includes(q)) && [...selectedTags].every(t => emoji.aliases.includes(t))); + searchEmojis = customEmojis.value.filter(emoji => (emoji.name.includes(q) || emoji.aliases.includes(q)) && [...selectedTags].every(t => emoji.aliases.includes(t))); } } diff --git a/packages/frontend/src/pages/emoji-edit-dialog.vue b/packages/frontend/src/pages/emoji-edit-dialog.vue index b2880b60b1..4d84ed7f16 100644 --- a/packages/frontend/src/pages/emoji-edit-dialog.vue +++ b/packages/frontend/src/pages/emoji-edit-dialog.vue @@ -15,7 +15,7 @@ <MkInput v-model="name"> <template #label>{{ i18n.ts.name }}</template> </MkInput> - <MkInput v-model="category" :datalist="categories"> + <MkInput v-model="category" :datalist="customEmojiCategories"> <template #label>{{ i18n.ts.category }}</template> </MkInput> <MkInput v-model="aliases"> @@ -36,7 +36,7 @@ import MkInput from '@/components/MkInput.vue'; import * as os from '@/os'; import { unique } from '@/scripts/array'; import { i18n } from '@/i18n'; -import { getCustomEmojiCategories } from '@/custom-emojis'; +import { customEmojiCategories } from '@/custom-emojis'; const props = defineProps<{ emoji: any, @@ -46,7 +46,6 @@ let dialog = $ref(null); let name: string = $ref(props.emoji.name); let category: string = $ref(props.emoji.category); let aliases: string = $ref(props.emoji.aliases.join(' ')); -const categories = getCustomEmojiCategories(); const emit = defineEmits<{ (ev: 'done', v: { deleted?: boolean, updated?: any }): void, diff --git a/packages/frontend/src/pages/mfm-cheat-sheet.vue b/packages/frontend/src/pages/mfm-cheat-sheet.vue index b3932ff7ce..73a5716236 100644 --- a/packages/frontend/src/pages/mfm-cheat-sheet.vue +++ b/packages/frontend/src/pages/mfm-cheat-sheet.vue @@ -313,7 +313,7 @@ let preview_mention = $ref('@example'); let preview_hashtag = $ref('#test'); let preview_url = $ref('https://example.com'); let preview_link = $ref(`[${i18n.ts._mfm.dummy}](https://example.com)`); -let preview_emoji = $ref(customEmojis.length ? `:${customEmojis[0].name}:` : ':emojiname:'); +let preview_emoji = $ref(customEmojis.value.length ? `:${customEmojis.value[0].name}:` : ':emojiname:'); let preview_bold = $ref(`**${i18n.ts._mfm.dummy}**`); let preview_small = $ref(`<small>${i18n.ts._mfm.dummy}</small>`); let preview_center = $ref(`<center>${i18n.ts._mfm.dummy}</center>`); diff --git a/packages/frontend/src/scripts/aiscript/api.ts b/packages/frontend/src/scripts/aiscript/api.ts index 29736ac60f..12f00bd32b 100644 --- a/packages/frontend/src/scripts/aiscript/api.ts +++ b/packages/frontend/src/scripts/aiscript/api.ts @@ -10,7 +10,7 @@ export function createAiScriptEnv(opts) { USER_ID: $i ? values.STR($i.id) : values.NULL, USER_NAME: $i ? values.STR($i.name) : values.NULL, USER_USERNAME: $i ? values.STR($i.username) : values.NULL, - CUSTOM_EMOJIS: utils.jsToVal(customEmojis), + CUSTOM_EMOJIS: utils.jsToVal(customEmojis.value), 'Mk:dialog': values.FN_NATIVE(async ([title, text, type]) => { await os.alert({ type: type ? type.value : 'info', |