From ebe8179c9eaacda11c72a7216dd47ee62bcd7793 Mon Sep 17 00:00:00 2001 From: dakkar Date: Sun, 3 Mar 2024 11:23:30 +0000 Subject: [PATCH 01/29] save and restore UI language together with other prefs - fixes #443 --- .../frontend/src/pages/settings/preferences-backups.vue | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/frontend/src/pages/settings/preferences-backups.vue b/packages/frontend/src/pages/settings/preferences-backups.vue index f180e0b72c..07f5b05a60 100644 --- a/packages/frontend/src/pages/settings/preferences-backups.vue +++ b/packages/frontend/src/pages/settings/preferences-backups.vue @@ -139,6 +139,7 @@ type Profile = { hot: Record; cold: Record; fontSize: string | null; + lang: string | null; cornerRadius: string | null; useSystemFont: 't' | null; wallpaper: string | null; @@ -197,6 +198,7 @@ function getSettings(): Profile['settings'] { hot, cold, fontSize: miLocalStorage.getItem('fontSize'), + lang: miLocalStorage.getItem('lang'), cornerRadius: miLocalStorage.getItem('cornerRadius'), useSystemFont: miLocalStorage.getItem('useSystemFont') as 't' | null, wallpaper: miLocalStorage.getItem('wallpaper'), @@ -312,6 +314,13 @@ async function applyProfile(id: string): Promise { miLocalStorage.removeItem('fontSize'); } + // lang + if (settings.lang) { + miLocalStorage.setItem('lang', settings.lang); + } else { + miLocalStorage.removeItem('lang'); + } + // cornerRadius if (settings.cornerRadius) { miLocalStorage.setItem('cornerRadius', settings.cornerRadius); From 354cb2a6754b55fd3ad01388a4a17d3a76d4a09b Mon Sep 17 00:00:00 2001 From: dakkar Date: Sat, 9 Mar 2024 12:17:48 +0000 Subject: [PATCH 02/29] handle non-ASCII emoji names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * use the more inclusive regexp for validating emoji names * always normalize emoji names, aliases, categories the latter point is necessary to allow matching, for example, `ä` against `a`+combining diaeresis this will also need to bump the version of `sfm-js` once we merge https://activitypub.software/TransFem-org/sfm-js/-/merge_requests/2 --- .../ExportCustomEmojisProcessorService.ts | 2 +- .../ImportCustomEmojisProcessorService.ts | 13 +++++++------ .../endpoints/admin/emoji/add-aliases-bulk.ts | 2 +- .../src/server/api/endpoints/admin/emoji/add.ts | 11 ++++++----- .../server/api/endpoints/admin/emoji/copy.ts | 9 +++++---- .../api/endpoints/admin/emoji/list-remote.ts | 2 +- .../server/api/endpoints/admin/emoji/list.ts | 11 ++++++----- .../admin/emoji/remove-aliases-bulk.ts | 2 +- .../endpoints/admin/emoji/set-aliases-bulk.ts | 2 +- .../endpoints/admin/emoji/set-category-bulk.ts | 2 +- .../server/api/endpoints/admin/emoji/update.ts | 17 +++++++++-------- .../frontend/src/components/MkAutocomplete.vue | 2 +- .../frontend/src/components/MkEmojiPicker.vue | 2 +- .../frontend/src/pages/emoji-edit-dialog.vue | 2 +- packages/frontend/src/scripts/autocomplete.ts | 4 ++-- 15 files changed, 44 insertions(+), 39 deletions(-) diff --git a/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts b/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts index e4eb4791bd..45087927a5 100644 --- a/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts @@ -85,7 +85,7 @@ export class ExportCustomEmojisProcessorService { }); for (const emoji of customEmojis) { - if (!/^[a-zA-Z0-9_]+$/.test(emoji.name)) { + if (!/^[\p{Letter}\p{Number}\p{Mark}_+-]+$/u.test(emoji.name)) { this.logger.error(`invalid emoji name: ${emoji.name}`); continue; } diff --git a/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts b/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts index 171809d25c..04ad74ee01 100644 --- a/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts @@ -79,13 +79,14 @@ export class ImportCustomEmojisProcessorService { continue; } const emojiInfo = record.emoji; - if (!/^[a-zA-Z0-9_]+$/.test(emojiInfo.name)) { - this.logger.error(`invalid emojiname: ${emojiInfo.name}`); + const nameNfc = emojiInfo.name.normalize('NFC'); + if (!/^[\p{Letter}\p{Number}\p{Mark}_+-]+$/u.test(nameNfc)) { + this.logger.error(`invalid emojiname: ${nameNfc}`); continue; } const emojiPath = outputPath + '/' + record.fileName; await this.emojisRepository.delete({ - name: emojiInfo.name, + name: nameNfc, }); const driveFile = await this.driveService.addFile({ user: null, @@ -94,10 +95,10 @@ export class ImportCustomEmojisProcessorService { force: true, }); await this.customEmojiService.add({ - name: emojiInfo.name, - category: emojiInfo.category, + name: nameNfc, + category: emojiInfo.category?.normalize('NFC'), host: null, - aliases: emojiInfo.aliases, + aliases: emojiInfo.aliases?.map((a: string) => a.normalize('NFC')), driveFile, license: emojiInfo.license, isSensitive: emojiInfo.isSensitive, 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 a30a080e59..f4fc79bdb3 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 @@ -34,7 +34,7 @@ export default class extends Endpoint { // eslint- private customEmojiService: CustomEmojiService, ) { super(meta, paramDef, async (ps, me) => { - await this.customEmojiService.addAliasesBulk(ps.ids, ps.aliases); + await this.customEmojiService.addAliasesBulk(ps.ids, ps.aliases.map(a => a.normalize('NFC'))); }); } } 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 767e517b80..b45a3c7156 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts @@ -40,7 +40,7 @@ export const meta = { export const paramDef = { type: 'object', properties: { - name: { type: 'string', pattern: '^[a-zA-Z0-9_]+$' }, + name: { type: 'string', pattern: '^[\\p{Letter}\\p{Number}\\p{Mark}_+-]+$' }, fileId: { type: 'string', format: 'misskey:id' }, category: { type: 'string', @@ -73,18 +73,19 @@ export default class extends Endpoint { // eslint- private emojiEntityService: EmojiEntityService, ) { super(meta, paramDef, async (ps, me) => { + const nameNfc = ps.name.normalize('NFC'); const driveFile = await this.driveFilesRepository.findOneBy({ id: ps.fileId }); if (driveFile == null) throw new ApiError(meta.errors.noSuchFile); - const isDuplicate = await this.customEmojiService.checkDuplicate(ps.name); + const isDuplicate = await this.customEmojiService.checkDuplicate(nameNfc); if (isDuplicate) throw new ApiError(meta.errors.duplicateName); if (driveFile.user !== null) await this.driveFilesRepository.update(driveFile.id, { user: null }); const emoji = await this.customEmojiService.add({ driveFile, - name: ps.name, - category: ps.category ?? null, - aliases: ps.aliases ?? [], + name: nameNfc, + category: ps.category?.normalize('NFC') ?? null, + aliases: ps.aliases?.map(a => a.normalize('NFC')) ?? [], host: null, license: ps.license ?? null, isSensitive: ps.isSensitive ?? false, diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts b/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts index 29af7598ed..f968813197 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts @@ -82,15 +82,16 @@ export default class extends Endpoint { // eslint- throw new ApiError(); } + const nameNfc = emoji.name.normalize('NFC'); // Duplication Check - const isDuplicate = await this.customEmojiService.checkDuplicate(emoji.name); + const isDuplicate = await this.customEmojiService.checkDuplicate(nameNfc); if (isDuplicate) throw new ApiError(meta.errors.duplicateName); const addedEmoji = await this.customEmojiService.add({ driveFile, - name: emoji.name, - category: emoji.category, - aliases: emoji.aliases, + name: nameNfc, + category: emoji.category?.normalize('NFC'), + aliases: emoji.aliases?.map(a => a.normalize('NFC')), host: null, license: emoji.license, isSensitive: emoji.isSensitive, 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 e423f440d0..1182918ea2 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 @@ -98,7 +98,7 @@ export default class extends Endpoint { // eslint- } if (ps.query) { - q.andWhere('emoji.name like :query', { query: '%' + sqlLikeEscape(ps.query) + '%' }) + q.andWhere('emoji.name like :query', { query: '%' + sqlLikeEscape(ps.query.normalize('NFC')) + '%' }) .orderBy('length(emoji.name)', 'ASC'); } 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 53810d1d16..5e21111f9f 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts @@ -92,17 +92,18 @@ export default class extends Endpoint { // eslint- //const emojis = await q.limit(ps.limit).getMany(); emojis = await q.orderBy('length(emoji.name)', 'ASC').getMany(); - const queryarry = ps.query.match(/\:([a-z0-9_]*)\:/g); + const queryarry = ps.query.match(/:([\p{Letter}\p{Number}\p{Mark}_+-]*):/ug); if (queryarry) { emojis = emojis.filter(emoji => - queryarry.includes(`:${emoji.name}:`), + queryarry.includes(`:${emoji.name.normalize('NFC')}:`), ); } else { + const queryNfc = ps.query!.normalize('NFC'); emojis = emojis.filter(emoji => - emoji.name.includes(ps.query!) || - emoji.aliases.some(a => a.includes(ps.query!)) || - emoji.category?.includes(ps.query!)); + emoji.name.includes(queryNfc) || + emoji.aliases.some(a => a.includes(queryNfc)) || + emoji.category?.includes(queryNfc)); } emojis.splice(ps.limit + 1); } else { 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 0fa119eabe..e78620eac1 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 @@ -34,7 +34,7 @@ export default class extends Endpoint { // eslint- private customEmojiService: CustomEmojiService, ) { super(meta, paramDef, async (ps, me) => { - await this.customEmojiService.removeAliasesBulk(ps.ids, ps.aliases); + await this.customEmojiService.removeAliasesBulk(ps.ids, ps.aliases.map(a => a.normalize('NFC'))); }); } } 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 d9ee18699c..69fc8e0cb5 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 @@ -34,7 +34,7 @@ export default class extends Endpoint { // eslint- private customEmojiService: CustomEmojiService, ) { super(meta, paramDef, async (ps, me) => { - await this.customEmojiService.setAliasesBulk(ps.ids, ps.aliases); + await this.customEmojiService.setAliasesBulk(ps.ids, ps.aliases.map(a => a.normalize('NFC'))); }); } } 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 dc25df2767..679a9f9c42 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 @@ -36,7 +36,7 @@ export default class extends Endpoint { // eslint- private customEmojiService: CustomEmojiService, ) { super(meta, paramDef, async (ps, me) => { - await this.customEmojiService.setCategoryBulk(ps.ids, ps.category ?? null); + await this.customEmojiService.setCategoryBulk(ps.ids, ps.category?.normalize('NFC') ?? null); }); } } 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 22609a16a3..3caa0f84a3 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts @@ -40,7 +40,7 @@ export const paramDef = { type: 'object', properties: { id: { type: 'string', format: 'misskey:id' }, - name: { type: 'string', pattern: '^[a-zA-Z0-9_]+$' }, + name: { type: 'string', pattern: '^[\\p{Letter}\\p{Number}\\p{Mark}_+-]+$' }, fileId: { type: 'string', format: 'misskey:id' }, category: { type: 'string', @@ -72,6 +72,7 @@ export default class extends Endpoint { // eslint- private customEmojiService: CustomEmojiService, ) { super(meta, paramDef, async (ps, me) => { + const nameNfc = ps.name?.normalize('NFC'); let driveFile; if (ps.fileId) { driveFile = await this.driveFilesRepository.findOneBy({ id: ps.fileId }); @@ -83,22 +84,22 @@ export default class extends Endpoint { // eslint- emojiId = ps.id; const emoji = await this.customEmojiService.getEmojiById(ps.id); if (!emoji) throw new ApiError(meta.errors.noSuchEmoji); - if (ps.name && (ps.name !== emoji.name)) { - const isDuplicate = await this.customEmojiService.checkDuplicate(ps.name); + if (nameNfc && (nameNfc !== emoji.name)) { + const isDuplicate = await this.customEmojiService.checkDuplicate(nameNfc); if (isDuplicate) throw new ApiError(meta.errors.sameNameEmojiExists); } } else { - if (!ps.name) throw new Error('Invalid Params unexpectedly passed. This is a BUG. Please report it to the development team.'); - const emoji = await this.customEmojiService.getEmojiByName(ps.name); + if (!nameNfc) throw new Error('Invalid Params unexpectedly passed. This is a BUG. Please report it to the development team.'); + const emoji = await this.customEmojiService.getEmojiByName(nameNfc); if (!emoji) throw new ApiError(meta.errors.noSuchEmoji); emojiId = emoji.id; } await this.customEmojiService.update(emojiId, { driveFile, - name: ps.name, - category: ps.category, - aliases: ps.aliases, + name: nameNfc, + category: ps.category?.normalize('NFC'), + aliases: ps.aliases?.map(a => a.normalize('NFC')), license: ps.license, isSensitive: ps.isSensitive, localOnly: ps.localOnly, diff --git a/packages/frontend/src/components/MkAutocomplete.vue b/packages/frontend/src/components/MkAutocomplete.vue index 8b665bfacd..b04a1c92e7 100644 --- a/packages/frontend/src/components/MkAutocomplete.vue +++ b/packages/frontend/src/components/MkAutocomplete.vue @@ -238,7 +238,7 @@ function exec() { return; } - emojis.value = searchEmoji(props.q.toLowerCase(), emojiDb.value); + emojis.value = searchEmoji(props.q.normalize('NFC').toLowerCase(), emojiDb.value); } else if (props.type === 'mfmTag') { if (!props.q || props.q === '') { mfmTags.value = MFM_TAGS; diff --git a/packages/frontend/src/components/MkEmojiPicker.vue b/packages/frontend/src/components/MkEmojiPicker.vue index 1219a29d85..53fca97fc0 100644 --- a/packages/frontend/src/components/MkEmojiPicker.vue +++ b/packages/frontend/src/components/MkEmojiPicker.vue @@ -205,7 +205,7 @@ watch(q, () => { return; } - const newQ = q.value.replace(/:/g, '').toLowerCase(); + const newQ = q.value.replace(/:/g, '').normalize('NFC').toLowerCase(); const searchCustom = () => { const max = 100; diff --git a/packages/frontend/src/pages/emoji-edit-dialog.vue b/packages/frontend/src/pages/emoji-edit-dialog.vue index 64960fd063..d03d3d9128 100644 --- a/packages/frontend/src/pages/emoji-edit-dialog.vue +++ b/packages/frontend/src/pages/emoji-edit-dialog.vue @@ -33,7 +33,7 @@ SPDX-License-Identifier: AGPL-3.0-only {{ i18n.ts.selectFile }} - + diff --git a/packages/frontend/src/scripts/autocomplete.ts b/packages/frontend/src/scripts/autocomplete.ts index 9fc8f7843e..d942402ffc 100644 --- a/packages/frontend/src/scripts/autocomplete.ts +++ b/packages/frontend/src/scripts/autocomplete.ts @@ -99,7 +99,7 @@ export class Autocomplete { const isHashtag = hashtagIndex !== -1; const isMfmParam = mfmParamIndex !== -1 && afterLastMfmParam?.includes('.') && !afterLastMfmParam?.includes(' '); const isMfmTag = mfmTagIndex !== -1 && !isMfmParam; - const isEmoji = emojiIndex !== -1 && text.split(/:[a-z0-9_+\-]+:/).pop()!.includes(':'); + const isEmoji = emojiIndex !== -1 && text.split(/:[\p{Letter}\p{Number}\p{Mark}_+-]+:/u).pop()!.includes(':'); let opened = false; @@ -125,7 +125,7 @@ export class Autocomplete { if (isEmoji && !opened && this.onlyType.includes('emoji')) { const emoji = text.substring(emojiIndex + 1); if (!emoji.includes(' ')) { - this.open('emoji', emoji); + this.open('emoji', emoji.normalize('NFC')); opened = true; } } From 56b19ab6bbec024abe23ce73e078d5c3ca534252 Mon Sep 17 00:00:00 2001 From: Marie Date: Sun, 24 Mar 2024 23:41:23 +0000 Subject: [PATCH 03/29] fix: incorrect type for quote property --- packages/backend/src/server/api/mastodon/converters.ts | 2 +- packages/megalodon/src/misskey/api_client.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/backend/src/server/api/mastodon/converters.ts b/packages/backend/src/server/api/mastodon/converters.ts index 20fccec21d..ca6f233b7f 100644 --- a/packages/backend/src/server/api/mastodon/converters.ts +++ b/packages/backend/src/server/api/mastodon/converters.ts @@ -278,7 +278,7 @@ export class MastoConverters { reactions: status.emoji_reactions, emoji_reactions: status.emoji_reactions, bookmarked: false, - quote: isQuote ? await this.convertReblog(status.reblog) : false, + quote: isQuote ? await this.convertReblog(status.reblog) : null, edited_at: note.updatedAt?.toISOString(), }); } diff --git a/packages/megalodon/src/misskey/api_client.ts b/packages/megalodon/src/misskey/api_client.ts index 520928c9fe..02828dcf9a 100644 --- a/packages/megalodon/src/misskey/api_client.ts +++ b/packages/megalodon/src/misskey/api_client.ts @@ -303,7 +303,7 @@ namespace MisskeyAPI { pinned: null, emoji_reactions: typeof n.reactions === 'object' ? mapReactions(n.reactions, n.myReaction) : [], bookmarked: false, - quote: n.renote && n.text ? note(n.renote, n.user.host ? n.user.host : host ? host : null) : false + quote: n.renote && n.text ? note(n.renote, n.user.host ? n.user.host : host ? host : null) : null } } From ece86f756dc0696ca833790aa09b182e1246b4ec Mon Sep 17 00:00:00 2001 From: Marie Date: Sun, 24 Mar 2024 23:46:57 +0000 Subject: [PATCH 04/29] chore: add new type --- packages/megalodon/src/entities/status.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/megalodon/src/entities/status.ts b/packages/megalodon/src/entities/status.ts index da36a04717..bb003f13d3 100644 --- a/packages/megalodon/src/entities/status.ts +++ b/packages/megalodon/src/entities/status.ts @@ -38,7 +38,7 @@ namespace Entity { language: string | null pinned: boolean | null emoji_reactions: Array - quote: Status | boolean + quote: Status | boolean | null bookmarked: boolean } From b6f41a28edccf4d5e08457886ce31827a7810ffc Mon Sep 17 00:00:00 2001 From: dakkar Date: Sun, 7 Apr 2024 16:37:31 +0100 Subject: [PATCH 05/29] pull in sfm-js that supports non-ascii in emoji names --- packages/backend/package.json | 2 +- packages/frontend/package.json | 4 ++-- pnpm-lock.yaml | 12 ++++++------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/backend/package.json b/packages/backend/package.json index 2ddb067afe..d973d527c2 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -88,7 +88,7 @@ "@smithy/node-http-handler": "2.1.10", "@swc/cli": "0.1.63", "@swc/core": "1.3.107", - "@transfem-org/sfm-js": "0.24.4", + "@transfem-org/sfm-js": "0.24.5", "@twemoji/parser": "15.0.0", "accepts": "1.3.8", "ajv": "8.12.0", diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 72b26961c3..d3249ca811 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -21,12 +21,12 @@ "@github/webauthn-json": "2.1.1", "@mcaptcha/vanilla-glue": "0.1.0-alpha-3", "@misskey-dev/browser-image-resizer": "2024.1.0", + "@phosphor-icons/web": "^2.0.3", "@rollup/plugin-json": "6.1.0", "@rollup/plugin-replace": "5.0.5", "@rollup/pluginutils": "5.1.0", - "@transfem-org/sfm-js": "0.24.4", "@syuilo/aiscript": "0.17.0", - "@phosphor-icons/web": "^2.0.3", + "@transfem-org/sfm-js": "0.24.5", "@twemoji/parser": "15.0.0", "@vitejs/plugin-vue": "5.0.4", "@vue/compiler-sfc": "3.4.21", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b54383a88f..eaa60b2a6a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -140,8 +140,8 @@ importers: specifier: 1.3.107 version: 1.3.107 '@transfem-org/sfm-js': - specifier: 0.24.4 - version: 0.24.4 + specifier: 0.24.5 + version: 0.24.5 '@twemoji/parser': specifier: 15.0.0 version: 15.0.0 @@ -709,8 +709,8 @@ importers: specifier: 0.17.0 version: 0.17.0 '@transfem-org/sfm-js': - specifier: 0.24.4 - version: 0.24.4 + specifier: 0.24.5 + version: 0.24.5 '@twemoji/parser': specifier: 15.0.0 version: 15.0.0 @@ -7436,8 +7436,8 @@ packages: resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} dev: false - /@transfem-org/sfm-js@0.24.4: - resolution: {integrity: sha1-0wEXqL5UJseGFO4GGFRrES6NCDk=, tarball: https://activitypub.software/api/v4/projects/2/packages/npm/@transfem-org/sfm-js/-/@transfem-org/sfm-js-0.24.4.tgz} + /@transfem-org/sfm-js@0.24.5: + resolution: {integrity: sha1-c9qJO12lIG+kovDGKjZmK2qPqcw=, tarball: https://activitypub.software/api/v4/projects/2/packages/npm/@transfem-org/sfm-js/-/@transfem-org/sfm-js-0.24.5.tgz} dependencies: '@twemoji/parser': 15.0.0 dev: false From dbfafe25e3230d5efa0406ea7a297af4633ed800 Mon Sep 17 00:00:00 2001 From: dakkar Date: Fri, 19 Apr 2024 13:35:18 +0100 Subject: [PATCH 06/29] rework pagination - fixes #491 previously, when adding items either at the beginnig (e.g. new notes coming in while we're not looking at the top of the timeline) or a the end (e.g. more items arriving from a background fetch) of a paginated view, the resulting list got truncated to `displayLimit`, potentially throwing data away and causing a new fetch. This, coupled with the async nature of scrolling & fetching, could cause weird results. Also, `offset` was always incremented by the size of the fetched results, even if not all of them were displayed, meant that it was possible for offset-based pagination to drop items. Finally, the "queue" of new items (usually, new notes) also got truncated to `displayLimit`, which again could drop items (this effect was usually masked by the first point: when scrolling to the top of the timeline, if the queue's length was equal to `displayLimit`, those notes displaced any existing ones, `unshiftItems` set `more.value=true`, you got scrolled to the top, and notes were fetched again, so you lost your position but at least all notes got shown, eventually) --- .../frontend/src/components/MkPagination.vue | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/frontend/src/components/MkPagination.vue b/packages/frontend/src/components/MkPagination.vue index 62a85389ad..6f6007d432 100644 --- a/packages/frontend/src/components/MkPagination.vue +++ b/packages/frontend/src/components/MkPagination.vue @@ -395,10 +395,10 @@ const prepend = (item: MisskeyEntity): void => { * @param newItems 新しいアイテムの配列 */ function unshiftItems(newItems: MisskeyEntity[]) { - const length = newItems.length + items.value.size; - items.value = new Map([...arrayToEntries(newItems), ...items.value].slice(0, props.displayLimit)); - - if (length >= props.displayLimit) more.value = true; + const prevLength = items.value.size; + items.value = new Map([...arrayToEntries(newItems), ...items.value].slice(0, newItems.length + props.displayLimit)); + // if we truncated, mark that there are more values to fetch + if (items.value.size < prevLength) more.value = true; } /** @@ -406,10 +406,10 @@ function unshiftItems(newItems: MisskeyEntity[]) { * @param oldItems 古いアイテムの配列 */ function concatItems(oldItems: MisskeyEntity[]) { - const length = oldItems.length + items.value.size; - items.value = new Map([...items.value, ...arrayToEntries(oldItems)].slice(0, props.displayLimit)); - - if (length >= props.displayLimit) more.value = true; + const prevLength = items.value.size; + items.value = new Map([...items.value, ...arrayToEntries(oldItems)].slice(0, oldItems.length + props.displayLimit)); + // if we truncated, mark that there are more values to fetch + if (items.value.size < prevLength) more.value = true; } function executeQueue() { @@ -418,7 +418,7 @@ function executeQueue() { } function prependQueue(newItem: MisskeyEntity) { - queue.value = new Map([[newItem.id, newItem], ...queue.value].slice(0, props.displayLimit) as [string, MisskeyEntity][]); + queue.value = new Map([[newItem.id, newItem], ...queue.value] as [string, MisskeyEntity][]); } /* From fa60c1d4bf40be534cecb96d6c50560257318c73 Mon Sep 17 00:00:00 2001 From: dakkar Date: Fri, 19 Apr 2024 14:42:58 +0100 Subject: [PATCH 07/29] make eslint happy also add some types to `chiptune2.ts` --- .../settings/notifications.notification-config.vue | 10 +++++----- packages/frontend/src/scripts/chiptune2.ts | 10 +++++++--- packages/frontend/src/scripts/nyaize.ts | 8 ++++---- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/packages/frontend/src/pages/settings/notifications.notification-config.vue b/packages/frontend/src/pages/settings/notifications.notification-config.vue index 6dde006106..8d78ce7031 100644 --- a/packages/frontend/src/pages/settings/notifications.notification-config.vue +++ b/packages/frontend/src/pages/settings/notifications.notification-config.vue @@ -7,11 +7,11 @@ SPDX-License-Identifier: AGPL-3.0-only
- - - - - + + + + + diff --git a/packages/frontend/src/scripts/chiptune2.ts b/packages/frontend/src/scripts/chiptune2.ts index 3cc34c0040..56afb9b708 100644 --- a/packages/frontend/src/scripts/chiptune2.ts +++ b/packages/frontend/src/scripts/chiptune2.ts @@ -1,4 +1,3 @@ -// @ts-nocheck /* eslint-disable */ const ChiptuneAudioContext = window.AudioContext || window.webkitAudioContext; @@ -6,6 +5,11 @@ const ChiptuneAudioContext = window.AudioContext || window.webkitAudioContext; let libopenmpt let libopenmptLoadPromise +type ChiptuneJsConfig = { + repeatCount: number | null; + context: AudioContext | null; +}; + export function ChiptuneJsConfig (repeatCount?: number, context?: AudioContext) { this.repeatCount = repeatCount; this.context = context; @@ -13,7 +17,7 @@ export function ChiptuneJsConfig (repeatCount?: number, context?: AudioContext) ChiptuneJsConfig.prototype.constructor = ChiptuneJsConfig; -export function ChiptuneJsPlayer (config: object) { +export function ChiptuneJsPlayer (config: ChiptuneJsConfig) { this.config = config; this.audioContext = config.context || new ChiptuneAudioContext(); this.context = this.audioContext.createGain(); @@ -27,7 +31,7 @@ ChiptuneJsPlayer.prototype.initialize = function() { if (libopenmptLoadPromise) return libopenmptLoadPromise; if (libopenmpt) return Promise.resolve(); - libopenmptLoadPromise = new Promise(async (resolve, reject) => { + libopenmptLoadPromise = new Promise(async (resolve, reject) => { try { const { Module } = await import('./libopenmpt/libopenmpt.js'); await new Promise((resolve) => { diff --git a/packages/frontend/src/scripts/nyaize.ts b/packages/frontend/src/scripts/nyaize.ts index 58ed88fed1..5e6fa298d1 100644 --- a/packages/frontend/src/scripts/nyaize.ts +++ b/packages/frontend/src/scripts/nyaize.ts @@ -9,9 +9,9 @@ const koRegex3 = /(야(?=\?))|(야$)|(야(?= ))/gm; function ifAfter(prefix, fn) { const preLen = prefix.length; - const regex = new RegExp(prefix,'i'); - return (x,pos,string) => { - return pos > 0 && string.substring(pos-preLen,pos).match(regex) ? fn(x) : x; + const regex = new RegExp(prefix, 'i'); + return (x, pos, string) => { + return pos > 0 && string.substring(pos - preLen, pos).match(regex) ? fn(x) : x; }; } @@ -25,7 +25,7 @@ export function nyaize(text: string): string { .replace(/one/gi, ifAfter('every', x => x === 'ONE' ? 'NYAN' : 'nyan')) // ko-KR .replace(koRegex1, match => String.fromCharCode( - match.charCodeAt(0)! + '냐'.charCodeAt(0) - '나'.charCodeAt(0), + match.charCodeAt(0) + '냐'.charCodeAt(0) - '나'.charCodeAt(0), )) .replace(koRegex2, '다냥') .replace(koRegex3, '냥'); From dd3d562a1e4ae547ddb0b54d77678abf801f2e26 Mon Sep 17 00:00:00 2001 From: Latte macchiato Date: Fri, 19 Apr 2024 21:58:37 +0000 Subject: [PATCH 08/29] Rework cache clearing to be fault tolerant --- .../CleanRemoteFilesProcessorService.ts | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts b/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts index 917de8b72c..ec75f3ba01 100644 --- a/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts +++ b/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts @@ -33,6 +33,12 @@ export class CleanRemoteFilesProcessorService { let deletedCount = 0; let cursor: MiDriveFile['id'] | null = null; + let errorCount = 0; + + const total = await this.driveFilesRepository.countBy({ + userHost: Not(IsNull()), + isLink: false, + }); while (true) { const files = await this.driveFilesRepository.find({ @@ -41,7 +47,7 @@ export class CleanRemoteFilesProcessorService { isLink: false, ...(cursor ? { id: MoreThan(cursor) } : {}), }, - take: 8, + take: 256, // Adjust the batch size as needed order: { id: 1, }, @@ -54,18 +60,21 @@ export class CleanRemoteFilesProcessorService { cursor = files.at(-1)?.id ?? null; - await Promise.all(files.map(file => this.driveService.deleteFileSync(file, true))); + // Handle deletion in a batch + const results = await Promise.allSettled(files.map(file => this.driveService.deleteFileSync(file, true))); - deletedCount += 8; - - const total = await this.driveFilesRepository.countBy({ - userHost: Not(IsNull()), - isLink: false, + results.forEach((result, index) => { + if (result.status === 'fulfilled') { + deletedCount++; + } else { + this.logger.error(`Failed to delete file ID ${files[index].id}: ${result.reason}`); + errorCount++; + } }); - job.updateProgress(deletedCount / total); + await job.updateProgress((deletedCount / total) * 100); } - this.logger.succ('All cached remote files has been deleted.'); + this.logger.succ(`All cached remote files processed. Total deleted: ${deletedCount}, Failed: ${errorCount}.`); } } From 0b705712ee9ca365c9630e789dd60096eb291a22 Mon Sep 17 00:00:00 2001 From: Sugar Date: Sat, 20 Apr 2024 18:01:29 +0200 Subject: [PATCH 09/29] fix: update pl-PL translation for note from verb to noun --- locales/pl-PL.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/locales/pl-PL.yml b/locales/pl-PL.yml index 3f9fa5f5f1..b7566aaa46 100644 --- a/locales/pl-PL.yml +++ b/locales/pl-PL.yml @@ -73,7 +73,7 @@ exportRequested: "Zażądałeś eksportu. Może to zająć trochę czasu. Po zak importRequested: "Zażądano importu. Może to zająć chwilę." lists: "Listy" noLists: "Nie masz żadnych list" -note: "Utwórz wpis" +note: "Wpis" notes: "Wpisy" following: "Obserwowani" followers: "Obserwujący" @@ -1400,4 +1400,3 @@ _moderationLogTypes: resetPassword: "Zresetuj hasło" _reversi: total: "Łącznie" - From 9545b8209a166f75117a911d8871276644d90651 Mon Sep 17 00:00:00 2001 From: Sugar Date: Sat, 20 Apr 2024 20:49:18 +0200 Subject: [PATCH 10/29] fix: update reaction icons in tutorial to match sharkey icons --- locales/ca-ES.yml | 5 ++--- locales/en-US.yml | 4 ++-- locales/es-ES.yml | 5 ++--- locales/fr-FR.yml | 5 ++--- locales/index.d.ts | 8 ++++---- locales/it-IT.yml | 5 ++--- locales/ja-JP.yml | 4 ++-- locales/ja-KS.yml | 4 ++-- locales/ko-KR.yml | 5 ++--- locales/th-TH.yml | 5 ++--- locales/zh-CN.yml | 5 ++--- locales/zh-TW.yml | 5 ++--- .../src/components/MkTutorialDialog.Note.vue | 15 +++++++++++++-- 13 files changed, 39 insertions(+), 36 deletions(-) diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml index ffdcc9787f..985f658999 100644 --- a/locales/ca-ES.yml +++ b/locales/ca-ES.yml @@ -1264,10 +1264,10 @@ _initialTutorial: _reaction: title: "Què són les Reaccions?" description: "Es poden reaccionar a les Notes amb diferents emoticones. Les reaccions et permeten expressar matisos que hi són més enllà d'un simple m'agrada." - letsTryReacting: "Es poden afegir reaccions fent clic al botó '+'. Prova reaccionant a aquesta nota!" + letsTryReacting: "Es poden afegir reaccions fent clic al botó '{reaction}'. Prova reaccionant a aquesta nota!" reactToContinue: "Afegeix una reacció per continuar." reactNotification: "Rebràs notificacions en temps real quan un usuari reaccioni a les teves notes." - reactDone: "Pots desfer una reacció fent clic al botó '-'." + reactDone: "Pots desfer una reacció fent clic al botó '{undo}'." _timeline: title: "El concepte de les línies de temps" description1: "Misskey mostra diferents línies de temps basades en l'ús (algunes poden no estar disponibles depenent de la política del servidor)" @@ -2255,4 +2255,3 @@ _externalResourceInstaller: title: "Paràmetres no vàlids " _reversi: total: "Total" - diff --git a/locales/en-US.yml b/locales/en-US.yml index 80e45c42d8..fe27a131ba 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -1335,10 +1335,10 @@ _initialTutorial: _reaction: title: "What are Reactions?" description: "Notes can be reacted to with various emojis. Reactions allow you to express nuances that may not be conveyed with just a 'like.'" - letsTryReacting: "Reactions can be added by clicking the '+' button on the note. Try reacting to this sample note!" + letsTryReacting: "Reactions can be added by clicking the '{reaction}' button on the note. Try reacting to this sample note!" reactToContinue: "Add a reaction to proceed." reactNotification: "You'll receive real-time notifications when someone reacts to your note." - reactDone: "You can undo a reaction by pressing the '-' button." + reactDone: "You can undo a reaction by pressing the '{undo}' button." _timeline: title: "The Concept of Timelines" description1: "Sharkey provides multiple timelines based on usage (some may not be available depending on the server's policies)." diff --git a/locales/es-ES.yml b/locales/es-ES.yml index 2288038e64..08fed63f5d 100644 --- a/locales/es-ES.yml +++ b/locales/es-ES.yml @@ -1263,10 +1263,10 @@ _initialTutorial: _reaction: title: "¿Qué son las reacciones?" description: "Se puede reaccionar a las Notas con diferentes emojis. Las reacciones te permiten expresar matices que no se pueden transmitir con un simple 'me gusta'." - letsTryReacting: "Puedes añadir reacciones pulsando en el botón '+' de la nota. ¡Intenta reaccionar a esta nota de ejemplo!" + letsTryReacting: "Puedes añadir reacciones pulsando en el botón '{reaction}' de la nota. ¡Intenta reaccionar a esta nota de ejemplo!" reactToContinue: "Añade una reacción para continuar." reactNotification: "Recibirás notificaciones en tiempo real cuando alguien reaccione a tu nota." - reactDone: "Puedes deshacer una reacción pulsando en el botón '-'." + reactDone: "Puedes deshacer una reacción pulsando en el botón '{undo}'." _timeline: title: "El concepto de Línea de tiempo" description1: "Misskey proporciona múltiples líneas de tiempo basadas en su uso (algunas pueden no estar disponibles dependiendo de las políticas de la instancia)." @@ -2449,4 +2449,3 @@ _reversi: reversi: "Reversi" won: "{name} ha ganado" total: "Total" - diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml index 24243c1f18..6a32f75231 100644 --- a/locales/fr-FR.yml +++ b/locales/fr-FR.yml @@ -1245,10 +1245,10 @@ _initialTutorial: _reaction: title: "Qu'est-ce que les réactions ?" description: "Vous pouvez ajouter des « réactions » aux notes. Les réactions vous permettent d'exprimer à l'aise des nuances qui ne peuvent pas être exprimées par des mentions j'aime." - letsTryReacting: "Des réactions peuvent être ajoutées en cliquant sur le bouton « + » de la note. Essayez d'ajouter une réaction à cet exemple de note !" + letsTryReacting: "Des réactions peuvent être ajoutées en cliquant sur le bouton « {reaction} » de la note. Essayez d'ajouter une réaction à cet exemple de note !" reactToContinue: "Ajoutez une réaction pour procéder." reactNotification: "Vous recevez des notifications en temps réel lorsque quelqu'un réagit à votre note." - reactDone: "Vous pouvez annuler la réaction en cliquant sur le bouton « - » ." + reactDone: "Vous pouvez annuler la réaction en cliquant sur le bouton « {undo} » ." _timeline: title: "Fonctionnement des fils" description1: "Misskey offre plusieurs fils selon l'usage (certains peuvent être désactivés par le serveur)." @@ -2140,4 +2140,3 @@ _dataSaver: description: "Si la notation de mise en évidence du code est utilisée, par exemple dans la MFM, elle ne sera pas chargée tant qu'elle n'aura pas été tapée. La mise en évidence du code nécessite le chargement du fichier de définition de chaque langue à mettre en évidence, mais comme ces fichiers ne sont plus chargés automatiquement, on peut s'attendre à une réduction du trafic de données." _reversi: total: "Total" - diff --git a/locales/index.d.ts b/locales/index.d.ts index e407d2119b..6efd5b33b9 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -5334,9 +5334,9 @@ export interface Locale extends ILocale { */ "description": string; /** - * リアクションは、ノートの「+」ボタンをクリックするとつけられます。試しにこのサンプルのノートにリアクションをつけてみてください! + * リアクションは、ノートの「{reaction}」ボタンをクリックするとつけられます。試しにこのサンプルのノートにリアクションをつけてみてください! */ - "letsTryReacting": string; + "letsTryReacting": ParameterizedString<"reaction">; /** * リアクションをつけると先に進めるようになります。 */ @@ -5346,9 +5346,9 @@ export interface Locale extends ILocale { */ "reactNotification": string; /** - * 「ー」ボタンを押すとリアクションを取り消すことができます。 + * 「{undo}」ボタンを押すとリアクションを取り消すことができます。 */ - "reactDone": string; + "reactDone": ParameterizedString<"undo">; }; "_timeline": { /** diff --git a/locales/it-IT.yml b/locales/it-IT.yml index 7414e083d0..3868eea9e3 100644 --- a/locales/it-IT.yml +++ b/locales/it-IT.yml @@ -1275,10 +1275,10 @@ _initialTutorial: _reaction: title: "Cosa sono le Reazioni?" description: "Puoi reagire alle Note. Le sensazioni che non si riescono a trasmettere con i \"Mi piace\" si possono esprimere facilmente inviando una reazione." - letsTryReacting: "Puoi aggiungere una Reazione cliccando il bottone \"+\" (più) della relativa Nota. Prova ad aggiungerne una a questa Nota di esempio!" + letsTryReacting: "Puoi aggiungere una Reazione cliccando il bottone \"{reaction}\" della relativa Nota. Prova ad aggiungerne una a questa Nota di esempio!" reactToContinue: "Aggiungere la Reazione ti consentirà di procedere col tutorial." reactNotification: "Quando qualcuno reagisce alle tue Note, ricevi una notifica in tempo reale." - reactDone: "Puoi annullare la tua Reazione premendo il bottone \"ー\" (meno)" + reactDone: "Puoi annullare la tua Reazione premendo il bottone \"{undo}\"" _timeline: title: "Come funziona la Timeline" description1: "Misskey fornisce alcune Timeline (sequenze cronologiche di Note). Una di queste potrebbe essere stata disattivata dagli amministratori." @@ -2509,4 +2509,3 @@ _reversi: _offlineScreen: title: "Scollegato. Impossibile connettersi al server" header: "Impossibile connettersi al server" - diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 57f52c64b2..68e4091a88 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1338,10 +1338,10 @@ _initialTutorial: _reaction: title: "リアクションって何?" description: "ノートには「リアクション」をつけることができます。「いいね」では伝わらないニュアンスも、リアクションで簡単・気軽に表現できます。" - letsTryReacting: "リアクションは、ノートの「+」ボタンをクリックするとつけられます。試しにこのサンプルのノートにリアクションをつけてみてください!" + letsTryReacting: "リアクションは、ノートの「{reaction}」ボタンをクリックするとつけられます。試しにこのサンプルのノートにリアクションをつけてみてください!" reactToContinue: "リアクションをつけると先に進めるようになります。" reactNotification: "あなたのノートが誰かにリアクションされると、リアルタイムで通知を受け取ります。" - reactDone: "「ー」ボタンを押すとリアクションを取り消すことができます。" + reactDone: "「{undo}」ボタンを押すとリアクションを取り消すことができます。" _timeline: title: "タイムラインのしくみ" description1: "Sharkeyには、使い方に応じて複数のタイムラインが用意されています(サーバーによってはいずれかが無効になっていることがあります)。" diff --git a/locales/ja-KS.yml b/locales/ja-KS.yml index d4c7eb0918..edae188bb6 100644 --- a/locales/ja-KS.yml +++ b/locales/ja-KS.yml @@ -1265,10 +1265,10 @@ _initialTutorial: _reaction: title: "ツッコミってなんや?" description: "ノートには「ツッコミ」できんねん。「いいね」とか何言っとるかわからんし、簡単に表現できるのはええことやん?" - letsTryReacting: "ノートの「+」ボタンでツッコめるわ。試しに下のノートにツッコんでみ。" + letsTryReacting: "ノートの「{reaction}」ボタンでツッコめるわ。試しに下のノートにツッコんでみ。" reactToContinue: "ツッコんだら進めるようになるで。" reactNotification: "あんたのノートが誰かにツッコまれたら、すぐ通知するで。" - reactDone: "「ー」ボタンでツッコミやめれるで。" + reactDone: "「{undo}」ボタンでツッコミやめれるで。" _timeline: title: "タイムラインのしくみ" description1: "Sharkeyには、いろいろタイムラインがあんで(ただ、サーバーによっては無効化されてるところもあるな)。" diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index 877ae6b217..39a186a7f8 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -1271,10 +1271,10 @@ _initialTutorial: _reaction: title: "'리액션'이 무엇인가요?" description: "노트에 '리액션'을 보낼 수 있습니다. '좋아요'만으로는 충분히 전해지지 않는 감정을, 이모지에 실어서 가볍게 보낼 수 있습니다." - letsTryReacting: "리액션은 노트의 '+' 버튼을 클릭하여 붙일 수 있습니다. 지금 표시되는 샘플 노트에 리액션을 달아 보세요!" + letsTryReacting: "리액션은 노트의 '{reaction}' 버튼을 클릭하여 붙일 수 있습니다. 지금 표시되는 샘플 노트에 리액션을 달아 보세요!" reactToContinue: "다음으로 진행하려면 리액션을 보내세요." reactNotification: "누군가가 나의 노트에 리액션을 보내면 실시간으로 알림을 받게 됩니다." - reactDone: "'-' 버튼을 눌러서 리액션을 취소할 수 있습니다." + reactDone: "'{undo}' 버튼을 눌러서 리액션을 취소할 수 있습니다." _timeline: title: "타임라인에 대하여" description1: "Misskey에는 종류에 따라 여러 가지의 타임라인으로 구성되어 있습니다.(서버에 따라서는 일부 타임라인을 사용할 수 없는 경우가 있습니다)" @@ -2505,4 +2505,3 @@ _reversi: _offlineScreen: title: "오프라인 - 서버에 접속할 수 없습니다" header: "서버에 접속할 수 없습니다" - diff --git a/locales/th-TH.yml b/locales/th-TH.yml index 60c34d84b9..f0ddeab822 100644 --- a/locales/th-TH.yml +++ b/locales/th-TH.yml @@ -1285,10 +1285,10 @@ _initialTutorial: _reaction: title: "รีแอคชั่นคืออะไร?" description: "โน้ตสามารถ“รีแอคชั่น”ด้วยเอโมจิต่างๆ ซึ่งทำให้สามารถแสดงความแตกต่างเล็กๆ น้อยๆ ที่อาจไม่สามารถสื่อออกมาได้ด้วยการแค่การกด “ถูกใจ”" - letsTryReacting: "คุณสามารถเพิ่มรีแอคชั่นได้ด้วยการคลิกปุ่ม “+” บนโน้ต ลองรีแอคชั่นโน้ตตัวอย่างนี้ดูสิ!" + letsTryReacting: "คุณสามารถเพิ่มรีแอคชั่นได้ด้วยการคลิกปุ่ม “{reaction}” บนโน้ต ลองรีแอคชั่นโน้ตตัวอย่างนี้ดูสิ!" reactToContinue: "เพิ่มรีแอคชั่นเพื่อดำเนินการต่อ" reactNotification: "คุณจะได้รับการแจ้งเตือนแบบเรียลไทม์เมื่อมีคนตอบรีแอคชั่นโน้ตของคุณ" - reactDone: "คุณสามารถยกเลิกรีแอคชั่นได้โดยการกดปุ่ม “-”" + reactDone: "คุณสามารถยกเลิกรีแอคชั่นได้โดยการกดปุ่ม “{undo}”" _timeline: title: "แนวคิดเรื่องของไทม์ไลน์" description1: "Misskey มีหลายไทม์ไลน์ขึ้นอยู่กับวิธีการใช้งานของคุณ (บางไทม์ไลน์อาจไม่สามารถใช้ได้ขึ้นอยู่กับนโยบายของเซิร์ฟเวอร์)" @@ -2524,4 +2524,3 @@ _reversi: _offlineScreen: title: "ออฟไลน์ - ไม่สามารถเชื่อมต่อกับเซิร์ฟเวอร์ได้" header: "ไม่สามารถเชื่อมต่อกับเซิร์ฟเวอร์ได้" - diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index 17ad6e7150..ddd8914146 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -1284,10 +1284,10 @@ _initialTutorial: _reaction: title: "什么是回应?" description: "您可以在帖子中添加“回应”。 您可以使用反应轻松地表达点“赞”所无法传达的细微差别。" - letsTryReacting: "回应可以通过点击帖子中的「+」按钮来添加。试着给这个示例帖子添加一个回应!" + letsTryReacting: "回应可以通过点击帖子中的「{reaction}」按钮来添加。试着给这个示例帖子添加一个回应!" reactToContinue: "添加一个回应来继续" reactNotification: "当您的帖子被某人添加了回应时,将实时收到通知。" - reactDone: "通过按下「ー」按钮,可以取消已经添加的回应" + reactDone: "通过按下「{undo}」按钮,可以取消已经添加的回应" _timeline: title: "时间线的运作方式" description1: "Misskey 根据使用方式提供了多个时间线(根据服务器的设定,可能有一些被禁用)。" @@ -2519,4 +2519,3 @@ _reversi: _offlineScreen: title: "离线——无法连接到服务器" header: "无法连接到服务器" - diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml index 5cdecc10ac..f8bdd002b4 100644 --- a/locales/zh-TW.yml +++ b/locales/zh-TW.yml @@ -1285,10 +1285,10 @@ _initialTutorial: _reaction: title: "什麼是反應?" description: "您可以在貼文中添加「反應」。您可以使用反應輕鬆隨意地表達「最愛/大心」所無法傳達的細微差別。" - letsTryReacting: "可以透過點擊貼文上的「+」按鈕來添加反應。請嘗試在此範例貼文添加反應!" + letsTryReacting: "可以透過點擊貼文上的「{reaction}」按鈕來添加反應。請嘗試在此範例貼文添加反應!" reactToContinue: "添加反應以繼續教學課程。" reactNotification: "當有人對您的貼文做出反應時會即時接收到通知。" - reactDone: "按下「-」按鈕可以取消反應。" + reactDone: "按下「{undo}」按鈕可以取消反應。" _timeline: title: "時間軸如何運作" description1: "Misskey根據使用方式提供了多個時間軸(伺服器可能會將部份時間軸停用)。" @@ -2524,4 +2524,3 @@ _reversi: _offlineScreen: title: "離線-無法連接伺服器" header: "無法連接伺服器" - diff --git a/packages/frontend/src/components/MkTutorialDialog.Note.vue b/packages/frontend/src/components/MkTutorialDialog.Note.vue index 5544434b5f..f8139d641e 100644 --- a/packages/frontend/src/components/MkTutorialDialog.Note.vue +++ b/packages/frontend/src/components/MkTutorialDialog.Note.vue @@ -16,9 +16,20 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ i18n.ts._initialTutorial._reaction.description }}
-
{{ i18n.ts._initialTutorial._reaction.letsTryReacting }}
+ + + -
{{ i18n.ts._initialTutorial.wellDone }} {{ i18n.ts._initialTutorial._reaction.reactNotification }}
{{ i18n.ts._initialTutorial._reaction.reactDone }}
+
+ {{ i18n.ts._initialTutorial.wellDone }} {{ i18n.ts._initialTutorial._reaction.reactNotification }}
+ + + +
From 504ff4c2af04d474cdd6d3da5c469d9ca00ce9e1 Mon Sep 17 00:00:00 2001 From: dakkar Date: Sun, 21 Apr 2024 11:17:17 +0100 Subject: [PATCH 11/29] increase page size for most admin lists - #491 Since I can't quite figure out how to prevent `MkPagination` from truncating the lists too eagerly, I'm going to hide the problem by making it truncate _less_. --- packages/frontend/src/components/MkFileListForAdmin.vue | 2 +- packages/frontend/src/components/MkUserList.vue | 2 +- packages/frontend/src/pages/about.federation.vue | 2 +- packages/frontend/src/pages/admin/abuses.vue | 2 +- packages/frontend/src/pages/admin/approvals.vue | 2 +- packages/frontend/src/pages/admin/federation.vue | 2 +- packages/frontend/src/pages/admin/invites.vue | 2 +- packages/frontend/src/pages/admin/modlog.vue | 2 +- packages/frontend/src/pages/admin/roles.role.vue | 2 +- packages/frontend/src/pages/admin/users.vue | 2 +- packages/frontend/src/pages/custom-emojis-manager.vue | 4 ++-- 11 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/frontend/src/components/MkFileListForAdmin.vue b/packages/frontend/src/components/MkFileListForAdmin.vue index f3305e9f54..bab844c27f 100644 --- a/packages/frontend/src/components/MkFileListForAdmin.vue +++ b/packages/frontend/src/components/MkFileListForAdmin.vue @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only