From 8608801fee6736b8bef91c54358c53d7b7f95f11 Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih Date: Wed, 31 Jan 2024 17:55:18 +0900 Subject: [PATCH 001/315] =?UTF-8?q?feat(reversi):=20=E3=82=B2=E3=83=BC?= =?UTF-8?q?=E3=83=A0=E4=B8=AD=E3=81=AB=E3=83=AA=E3=82=A2=E3=82=AF=E3=82=B7?= =?UTF-8?q?=E3=83=A7=E3=83=B3=E3=82=92=E6=89=93=E3=81=A6=E3=82=8B=E3=82=88?= =?UTF-8?q?=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backend/src/core/GlobalEventService.ts | 4 + packages/backend/src/core/ReactionService.ts | 2 +- packages/backend/src/core/ReversiService.ts | 41 ++++ .../api/stream/channels/reversi-game.ts | 8 + .../frontend/src/components/MkBalloon.vue | 77 +++++++ .../frontend/src/pages/reversi/game.board.vue | 189 +++++++++++++++++- .../src/pages/reversi/game.emoji-balloon.vue | 88 ++++++++ packages/misskey-js/src/streaming.types.ts | 2 + 8 files changed, 408 insertions(+), 3 deletions(-) create mode 100644 packages/frontend/src/components/MkBalloon.vue create mode 100644 packages/frontend/src/pages/reversi/game.emoji-balloon.vue diff --git a/packages/backend/src/core/GlobalEventService.ts b/packages/backend/src/core/GlobalEventService.ts index 6a72671665..7e96904b4c 100644 --- a/packages/backend/src/core/GlobalEventService.ts +++ b/packages/backend/src/core/GlobalEventService.ts @@ -191,6 +191,10 @@ export interface ReversiGameEventTypes { canceled: { userId: MiUser['id']; }; + reacted: { + userId: MiUser['id']; + reaction: string; + }; } //#endregion diff --git a/packages/backend/src/core/ReactionService.ts b/packages/backend/src/core/ReactionService.ts index 2e8f76fa8a..c205d08802 100644 --- a/packages/backend/src/core/ReactionService.ts +++ b/packages/backend/src/core/ReactionService.ts @@ -64,7 +64,7 @@ type DecodedReaction = { host?: string | null; }; -const isCustomEmojiRegexp = /^:([\w+-]+)(?:@\.)?:$/; +export const isCustomEmojiRegexp = /^:([\w+-]+)(?:@\.)?:$/; const decodeCustomEmojiRegexp = /^:([\w+-]+)(?:@([\w.-]+))?:$/; @Injectable() diff --git a/packages/backend/src/core/ReversiService.ts b/packages/backend/src/core/ReversiService.ts index 186ec6d7b1..9531bbb001 100644 --- a/packages/backend/src/core/ReversiService.ts +++ b/packages/backend/src/core/ReversiService.ts @@ -20,6 +20,8 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { IdService } from '@/core/IdService.js'; import { NotificationService } from '@/core/NotificationService.js'; +import { CustomEmojiService } from '@/core/CustomEmojiService.js'; +import { isCustomEmojiRegexp, ReactionService } from '@/core/ReactionService.js'; import { Serialized } from '@/types.js'; import { ReversiGameEntityService } from './entities/ReversiGameEntityService.js'; import type { OnApplicationShutdown, OnModuleInit } from '@nestjs/common'; @@ -44,6 +46,8 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit { private globalEventService: GlobalEventService, private reversiGameEntityService: ReversiGameEntityService, private idService: IdService, + private customEmojiService: CustomEmojiService, + private reactionService: ReactionService, ) { } @@ -602,6 +606,43 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit { } } + @bindThis + public async sendReaction(gameId: MiReversiGame['id'], user: MiUser, reaction: string) { + const game = await this.get(gameId); + if (game == null) throw new Error('game not found'); + if (!game.isStarted || game.isEnded) return; + if ((game.user1Id !== user.id) && (game.user2Id !== user.id)) return; + + const lastReactedAt = await this.redisClient.get(`reversi:game:lastReactedAt:${game.id}:${user.id}`); + + if (lastReactedAt && (Date.now() - parseInt(lastReactedAt, 10) < 3000)) { + // レートリミット(3秒) + return; + } + + let _reaction = '❤️'; + + const custom = reaction.match(isCustomEmojiRegexp); + + if (custom) { + const name = custom[1]; + + const emoji = (await this.customEmojiService.localEmojisCache.fetch()).get(name); + if (emoji && !emoji.isSensitive) { + _reaction = `:${name}:`; + } + } else { + _reaction = this.reactionService.normalize(reaction); + } + + this.globalEventService.publishReversiGameStream(game.id, 'reacted', { + userId: user.id, + reaction: _reaction, + }); + + this.redisClient.setex(`reversi:game:lastReactedAt:${game.id}:${user.id}`, 60 * 60, Date.now().toString()); + } + @bindThis public dispose(): void { } diff --git a/packages/backend/src/server/api/stream/channels/reversi-game.ts b/packages/backend/src/server/api/stream/channels/reversi-game.ts index fb24a29b75..a2cddc749f 100644 --- a/packages/backend/src/server/api/stream/channels/reversi-game.ts +++ b/packages/backend/src/server/api/stream/channels/reversi-game.ts @@ -42,6 +42,7 @@ class ReversiGameChannel extends Channel { case 'cancel': this.cancelGame(); break; case 'putStone': this.putStone(body.pos, body.id); break; case 'claimTimeIsUp': this.claimTimeIsUp(); break; + case 'reaction': this.sendReaction(body); break; } } @@ -80,6 +81,13 @@ class ReversiGameChannel extends Channel { this.reversiService.checkTimeout(this.gameId!); } + @bindThis + private async sendReaction(reaction: string) { + if (this.user == null) return; + + this.reversiService.sendReaction(this.gameId!, this.user, reaction); + } + @bindThis public dispose() { // Unsubscribe events diff --git a/packages/frontend/src/components/MkBalloon.vue b/packages/frontend/src/components/MkBalloon.vue new file mode 100644 index 0000000000..57df4509a4 --- /dev/null +++ b/packages/frontend/src/components/MkBalloon.vue @@ -0,0 +1,77 @@ + + + + + + + diff --git a/packages/frontend/src/pages/reversi/game.board.vue b/packages/frontend/src/pages/reversi/game.board.vue index eb4dbfdee0..14c71a3ee5 100644 --- a/packages/frontend/src/pages/reversi/game.board.vue +++ b/packages/frontend/src/pages/reversi/game.board.vue @@ -8,9 +8,13 @@ SPDX-License-Identifier: AGPL-3.0-only
({{ i18n.ts._reversi.black }}) - +
+ +
vs - +
+ +
({{ i18n.ts._reversi.white }})
@@ -121,6 +125,19 @@ SPDX-License-Identifier: AGPL-3.0-only
+
+
{{ i18n.ts.reaction }}
+
+ + +
+
+
@@ -145,9 +162,12 @@ SPDX-License-Identifier: AGPL-3.0-only import { computed, onActivated, onDeactivated, onMounted, onUnmounted, ref, shallowRef, triggerRef, watch } from 'vue'; import * as Misskey from 'misskey-js'; import * as Reversi from 'misskey-reversi'; +import type { UnicodeEmojiDef } from '@/scripts/emojilist.js'; import MkButton from '@/components/MkButton.vue'; import MkFolder from '@/components/MkFolder.vue'; import MkSwitch from '@/components/MkSwitch.vue'; +import MkRippleEffect from '@/components/MkRippleEffect.vue'; +import XEmojiBalloon from './game.emoji-balloon.vue'; import { deepClone } from '@/scripts/clone.js'; import { useInterval } from '@/scripts/use-interval.js'; import { signinRequired } from '@/account.js'; @@ -156,6 +176,8 @@ import { misskeyApi } from '@/scripts/misskey-api.js'; import { userPage } from '@/filters/user.js'; import * as sound from '@/scripts/sound.js'; import * as os from '@/os.js'; +import { defaultStore } from '@/store.js'; +import { reactionPicker } from '@/scripts/reaction-picker.js'; import { confetti } from '@/scripts/confetti.js'; const $i = signinRequired(); @@ -183,6 +205,7 @@ const iAmPlayer = computed(() => { return game.value.user1Id === $i.id || game.value.user2Id === $i.id; }); +// true: 黒, false: 白 const myColor = computed(() => { if (!iAmPlayer.value) return null; if (game.value.user1Id === $i.id && game.value.black === 1) return true; @@ -447,9 +470,109 @@ function share() { }); } +const _reactionEmojis = ref(defaultStore.reactiveState.reactions.value); +const reactionEmojis = computed(() => _reactionEmojis.value.slice(0, 10)); + +const blackUserEl = ref(null); +const whiteUserEl = ref(null); + +const canReact = ref(true); +let canReactFallbackTimer: number | null = null; + +function getKey(emoji: string | Misskey.entities.EmojiSimple | UnicodeEmojiDef): string { + return typeof emoji === 'string' ? emoji : 'char' in emoji ? emoji.char : `:${emoji.name}:`; +} + +// 既にでている絵文字をクリックした際の挙動 +function onReactionEmojiClick(emoji: string, ev?: MouseEvent) { + const el = ev && (ev.currentTarget ?? ev.target) as HTMLElement | null | undefined; + if (el) { + const rect = el.getBoundingClientRect(); + const x = rect.left + (el.offsetWidth / 2); + const y = rect.top + (el.offsetHeight / 2); + os.popup(MkRippleEffect, { x, y }, {}, 'end'); + } + + const key = getKey(emoji); + + sendReaction(key); + + if (!_reactionEmojis.value.includes(key)) { + _reactionEmojis.value.unshift(key); + } +} + +// #region リアクションピッカー +const reactButton = ref(null); + +function onReactionPickerClick() { + reactionPicker.show(reactButton.value ?? null, reaction => { + const key = getKey(reaction); + sendReaction(key); + + if (!_reactionEmojis.value.includes(key)) { + _reactionEmojis.value.unshift(key); + } + }); +} +// #endregion + +function sendReaction(emojiKey: string) { + if (!canReact.value) return; + canReact.value = false; + + // リアクション音は受信時のみ + props.connection!.send('reaction', emojiKey); + + // リアクションを無効にする(受信を確認できて3秒後 or 明らかに遅い場合は解除) + if (canReactFallbackTimer != null) { + window.clearTimeout(canReactFallbackTimer); + } + canReactFallbackTimer = window.setTimeout(() => { + if (!canReact.value) { + canReact.value = true; + } + }, 10000); +} + +function onReacted(payload: Parameters['0']) { + console.log('onReacted', payload); + + const { userId, reaction } = payload; + + sound.playMisskeySfx('reaction'); + + const el = (userId === blackUser.value.id) ? blackUserEl.value : whiteUserEl.value; + + if (el) { + const rect = el.getBoundingClientRect(); + const x = rect.right; + const y = rect.bottom; + os.popup(XEmojiBalloon, { + reaction, + tail: 'left', + x, + y, + }, {}, 'end'); + } + + if (userId === $i.id) { + // リアクションが可能になるまでのタイマー + window.setTimeout(() => { + if (canReactFallbackTimer != null) { + window.clearTimeout(canReactFallbackTimer); + } + if (!canReact.value) { + canReact.value = true; + } + }, 3000); + } +} + onMounted(() => { if (props.connection != null) { props.connection.on('log', onStreamLog); + props.connection.on('reacted', onReacted); props.connection.on('ended', onStreamEnded); } }); @@ -457,6 +580,7 @@ onMounted(() => { onActivated(() => { if (props.connection != null) { props.connection.on('log', onStreamLog); + props.connection.on('reacted', onReacted); props.connection.on('ended', onStreamEnded); } }); @@ -464,6 +588,7 @@ onActivated(() => { onDeactivated(() => { if (props.connection != null) { props.connection.off('log', onStreamLog); + props.connection.off('reacted', onReacted); props.connection.off('ended', onStreamEnded); } }); @@ -471,6 +596,7 @@ onDeactivated(() => { onUnmounted(() => { if (props.connection != null) { props.connection.off('log', onStreamLog); + props.connection.off('reacted', onReacted); props.connection.off('ended', onStreamEnded); } }); @@ -630,4 +756,63 @@ $gap: 4px; height: 100%; border-radius: 100%; } + +.reactionLabel { + font-size: 0.85em; + padding: 0 0 8px 0; + user-select: none; + + &:empty { + display: none; + } +} + +.reactionPickerBar { + display: flex; + flex-wrap: wrap; + padding: 12px; + background: var(--bg); + border-radius: calc(var(--radius) / 2); +} + +.emojisItem { + font-size: 24px; + border-radius: 4px; + width: 40px; + height: 40px; + + &.plus { + font-size: 16px; + + &:active { + background: var(--accentedBg); + } + } + + &:focus-visible { + outline: solid 2px var(--focus); + z-index: 1; + } + + &:hover { + background: rgba(0, 0, 0, 0.05); + } + + &:not(:disabled):active { + background: var(--accent); + box-shadow: inset 0 0.15em 0.3em rgba(27, 31, 35, 0.15); + } + + &:disabled { + opacity: 0.7; + } + + > .emoji { + height: 1.25em; + vertical-align: -.25em; + pointer-events: none; + width: 100%; + object-fit: contain; + } +} diff --git a/packages/frontend/src/pages/reversi/game.emoji-balloon.vue b/packages/frontend/src/pages/reversi/game.emoji-balloon.vue new file mode 100644 index 0000000000..8d943f1fdb --- /dev/null +++ b/packages/frontend/src/pages/reversi/game.emoji-balloon.vue @@ -0,0 +1,88 @@ + + + + + diff --git a/packages/misskey-js/src/streaming.types.ts b/packages/misskey-js/src/streaming.types.ts index 0ba5715d68..5657ec326e 100644 --- a/packages/misskey-js/src/streaming.types.ts +++ b/packages/misskey-js/src/streaming.types.ts @@ -208,6 +208,7 @@ export type Channels = { canceled: (payload: { userId: User['id']; }) => void; changeReadyStates: (payload: { user1: boolean; user2: boolean; }) => void; updateSettings: (payload: { userId: User['id']; key: string; value: any; }) => void; + reacted: (payload: { userId: User['id']; reaction: string; }) => void; log: (payload: Record) => void; }; receives: { @@ -222,6 +223,7 @@ export type Channels = { value: any; }; claimTimeIsUp: null | Record; + reaction: string; } } }; From efc272a340be7c1cb62b6a3adeb1ec5784cc9b08 Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih Date: Wed, 31 Jan 2024 17:56:30 +0900 Subject: [PATCH 002/315] run api extractor --- packages/misskey-js/etc/misskey-js.api.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index 860d3629d6..7b45912cb8 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -715,6 +715,10 @@ export type Channels = { key: string; value: any; }) => void; + reacted: (payload: { + userId: User['id']; + reaction: string; + }) => void; log: (payload: Record) => void; }; receives: { @@ -729,6 +733,7 @@ export type Channels = { value: any; }; claimTimeIsUp: null | Record; + reaction: string; }; }; }; From 4c50b0a5dd75b8444c50b20acff2b925cf5622d6 Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih Date: Thu, 1 Feb 2024 07:26:05 +0900 Subject: [PATCH 003/315] =?UTF-8?q?=E3=83=AA=E3=82=A2=E3=82=AF=E3=82=B7?= =?UTF-8?q?=E3=83=A7=E3=83=B3=E3=82=92=E8=A1=A8=E7=A4=BA=E3=81=99=E3=82=8B?= =?UTF-8?q?=E3=81=8B=E3=81=A9=E3=81=86=E3=81=8B=E3=82=92=E9=81=B8=E3=81=B9?= =?UTF-8?q?=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../frontend/src/pages/reversi/game.board.vue | 38 ++++++++++--------- .../src/pages/reversi/game.emoji-balloon.vue | 5 +-- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/packages/frontend/src/pages/reversi/game.board.vue b/packages/frontend/src/pages/reversi/game.board.vue index 14c71a3ee5..4dcc5043f4 100644 --- a/packages/frontend/src/pages/reversi/game.board.vue +++ b/packages/frontend/src/pages/reversi/game.board.vue @@ -142,6 +142,7 @@ SPDX-License-Identifier: AGPL-3.0-only
Show labels + Show reactions useAvatarAsStone
@@ -188,6 +189,7 @@ const props = defineProps<{ }>(); const showBoardLabels = ref(false); +const showReactions = ref(true); const useAvatarAsStone = ref(true); const autoplaying = ref(false); // eslint-disable-next-line vue/no-setup-props-destructure @@ -536,24 +538,24 @@ function sendReaction(emojiKey: string) { } function onReacted(payload: Parameters['0']) { - console.log('onReacted', payload); - const { userId, reaction } = payload; - sound.playMisskeySfx('reaction'); - - const el = (userId === blackUser.value.id) ? blackUserEl.value : whiteUserEl.value; - - if (el) { - const rect = el.getBoundingClientRect(); - const x = rect.right; - const y = rect.bottom; - os.popup(XEmojiBalloon, { - reaction, - tail: 'left', - x, - y, - }, {}, 'end'); + if (showReactions.value || userId === $i.id) { + sound.playMisskeySfx('reaction'); + + const el = (userId === blackUser.value.id) ? blackUserEl.value : whiteUserEl.value; + + if (el) { + const rect = el.getBoundingClientRect(); + const x = rect.right; + const y = rect.bottom; + os.popup(XEmojiBalloon, { + reaction, + tail: 'left', + x, + y, + }, {}, 'end'); + } } if (userId === $i.id) { @@ -789,12 +791,12 @@ $gap: 4px; } } - &:focus-visible { + &:not(:disabled):focus-visible { outline: solid 2px var(--focus); z-index: 1; } - &:hover { + &:not(:disabled):hover { background: rgba(0, 0, 0, 0.05); } diff --git a/packages/frontend/src/pages/reversi/game.emoji-balloon.vue b/packages/frontend/src/pages/reversi/game.emoji-balloon.vue index 8d943f1fdb..fecb760430 100644 --- a/packages/frontend/src/pages/reversi/game.emoji-balloon.vue +++ b/packages/frontend/src/pages/reversi/game.emoji-balloon.vue @@ -49,7 +49,7 @@ onMounted(() => { active.value = false; setTimeout(() => { emit('end'); - }, 1000); + }, 750); }, 3000); }); @@ -60,7 +60,7 @@ onMounted(() => { } .transition_balloon_leaveActive { - transition: all 1s ease; + transition: all .75s ease; } .transition_balloon_enterFrom { @@ -75,7 +75,6 @@ onMounted(() => { .balloonRoot { position: absolute; filter: drop-shadow(0 2px 8px var(--shadow)); - --balloon-radius: 24px; user-select: none; pointer-events: none; } From 46c61602d4be9d4dbf13ea6491e24a71a2f28143 Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih Date: Thu, 1 Feb 2024 07:32:47 +0900 Subject: [PATCH 004/315] fix --- packages/frontend/src/pages/reversi/game.board.vue | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/frontend/src/pages/reversi/game.board.vue b/packages/frontend/src/pages/reversi/game.board.vue index 4dcc5043f4..50910b8649 100644 --- a/packages/frontend/src/pages/reversi/game.board.vue +++ b/packages/frontend/src/pages/reversi/game.board.vue @@ -142,7 +142,7 @@ SPDX-License-Identifier: AGPL-3.0-only
Show labels - Show reactions + Show reaction useAvatarAsStone
@@ -189,7 +189,7 @@ const props = defineProps<{ }>(); const showBoardLabels = ref(false); -const showReactions = ref(true); +const showReaction = ref(true); const useAvatarAsStone = ref(true); const autoplaying = ref(false); // eslint-disable-next-line vue/no-setup-props-destructure @@ -540,7 +540,7 @@ function sendReaction(emojiKey: string) { function onReacted(payload: Parameters['0']) { const { userId, reaction } = payload; - if (showReactions.value || userId === $i.id) { + if (showReaction.value || userId === $i.id) { sound.playMisskeySfx('reaction'); const el = (userId === blackUser.value.id) ? blackUserEl.value : whiteUserEl.value; From 66a7a9e9d1050d5ffeecbd961f96236afed03bd7 Mon Sep 17 00:00:00 2001 From: kakkokari-gtyih Date: Thu, 1 Feb 2024 07:36:32 +0900 Subject: [PATCH 005/315] spdx --- packages/frontend/src/pages/reversi/game.emoji-balloon.vue | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/frontend/src/pages/reversi/game.emoji-balloon.vue b/packages/frontend/src/pages/reversi/game.emoji-balloon.vue index fecb760430..6beebebd4c 100644 --- a/packages/frontend/src/pages/reversi/game.emoji-balloon.vue +++ b/packages/frontend/src/pages/reversi/game.emoji-balloon.vue @@ -1,3 +1,8 @@ + +
- +
- + + + -
+
@@ -126,12 +156,16 @@ SPDX-License-Identifier: AGPL-3.0-only
- + - + + + -
+
@@ -173,17 +207,10 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+ - @@ -195,7 +222,6 @@ import MkSwitch from '@/components/MkSwitch.vue'; import MkInput from '@/components/MkInput.vue'; import MkTextarea from '@/components/MkTextarea.vue'; import MkInfo from '@/components/MkInfo.vue'; -import FormSection from '@/components/form/section.vue'; import FormSplit from '@/components/form/split.vue'; import FormSuspense from '@/components/form/suspense.vue'; import * as os from '@/os.js'; @@ -258,8 +284,8 @@ async function init(): Promise { urlPreviewSummaryProxyUrl.value = meta.urlPreviewSummaryProxyUrl; } -async function save() { - await os.apiWithDialog('admin/update-meta', { +function saveInfo() { + os.apiWithDialog('admin/update-meta', { name: name.value, shortName: shortName.value === '' ? null : shortName.value, description: description.value, @@ -270,22 +296,57 @@ async function save() { inquiryUrl: inquiryUrl.value, repositoryUrl: repositoryUrl.value, impressumUrl: impressumUrl.value, + }).then(() => { + fetchInstance(true); + }); +} + +function save_pinnedUsers() { + os.apiWithDialog('admin/update-meta', { pinnedUsers: pinnedUsers.value.split('\n'), + }).then(() => { + fetchInstance(true); + }); +} + +function saveFiles() { + os.apiWithDialog('admin/update-meta', { cacheRemoteFiles: cacheRemoteFiles.value, cacheRemoteSensitiveFiles: cacheRemoteSensitiveFiles.value, + }).then(() => { + fetchInstance(true); + }); +} + +function saveServiceWorker() { + os.apiWithDialog('admin/update-meta', { enableServiceWorker: enableServiceWorker.value, swPublicKey: swPublicKey.value, swPrivateKey: swPrivateKey.value, + }).then(() => { + fetchInstance(true); + }); +} + +function saveAd() { + os.apiWithDialog('admin/update-meta', { notesPerOneAd: notesPerOneAd.value, + }).then(() => { + fetchInstance(true); + }); +} + +function saveUrlPreview() { + os.apiWithDialog('admin/update-meta', { urlPreviewEnabled: urlPreviewEnabled.value, urlPreviewTimeout: urlPreviewTimeout.value, urlPreviewMaximumContentLength: urlPreviewMaximumContentLength.value, urlPreviewRequireContentLength: urlPreviewRequireContentLength.value, urlPreviewUserAgent: urlPreviewUserAgent.value, urlPreviewSummaryProxyUrl: urlPreviewSummaryProxyUrl.value, + }).then(() => { + fetchInstance(true); }); - - fetchInstance(true); } const headerTabs = computed(() => []); @@ -297,11 +358,6 @@ definePageMetadata(() => ({ diff --git a/packages/frontend/src/components/MkFormFooter.vue b/packages/frontend/src/components/MkFormFooter.vue new file mode 100644 index 0000000000..1e88d59d8e --- /dev/null +++ b/packages/frontend/src/components/MkFormFooter.vue @@ -0,0 +1,49 @@ + + + + + + + diff --git a/packages/frontend/src/pages/admin/performance.vue b/packages/frontend/src/pages/admin/performance.vue index 0f4d94aa4e..57f68a2a26 100644 --- a/packages/frontend/src/pages/admin/performance.vue +++ b/packages/frontend/src/pages/admin/performance.vue @@ -7,103 +7,100 @@ SPDX-License-Identifier: AGPL-3.0-only - -
-
- - - - -
+
+
+ + + + +
+ +
+ + + + +
+ +
+ + + + +
-
- - - +
+ + + + +
+ + + + + + + + +
+ + + -
-
- - - + + + -
-
- - - + + + + + + + + + + + + + + + +
+
+ + + + + + + + +
+ + +
- - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - - - - - - -
- - - - -
-
-
- + +
diff --git a/packages/frontend/src/pages/admin/security.vue b/packages/frontend/src/pages/admin/security.vue index 9bccee89a5..975a4a1265 100644 --- a/packages/frontend/src/pages/admin/security.vue +++ b/packages/frontend/src/pages/admin/security.vue @@ -7,119 +7,115 @@ SPDX-License-Identifier: AGPL-3.0-only - -
- - - - - - - - - - - - - - - - - - - - -
- {{ i18n.ts._sensitiveMediaDetection.description }} - - - - - - - - - - - - - - - - - - - - - - - - - - {{ i18n.ts.save }} -
-
- - - - - - -
- {{ i18n.ts.activeEmailValidationDescription }} - - - - - - - - - - - - - - - - - - - - - - {{ i18n.ts.save }} -
-
- - - - -
- - - - {{ i18n.ts.save }} -
-
- - - - - - -
- - - -
-
-
-
+
+ + + + + + + + + + + +
+ {{ i18n.ts._sensitiveMediaDetection.description }} + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + +
+ {{ i18n.ts.activeEmailValidationDescription }} + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+
+ + + + + + + +
+ + + +
+
+
@@ -131,83 +127,80 @@ import XHeader from './_header_.vue'; import MkFolder from '@/components/MkFolder.vue'; import MkRadios from '@/components/MkRadios.vue'; import MkSwitch from '@/components/MkSwitch.vue'; -import FormSuspense from '@/components/form/suspense.vue'; import MkRange from '@/components/MkRange.vue'; import MkInput from '@/components/MkInput.vue'; -import MkButton from '@/components/MkButton.vue'; import MkTextarea from '@/components/MkTextarea.vue'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { fetchInstance } from '@/instance.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; - -const enableHcaptcha = ref(false); -const enableMcaptcha = ref(false); -const enableRecaptcha = ref(false); -const enableTurnstile = ref(false); -const sensitiveMediaDetection = ref('none'); -const sensitiveMediaDetectionSensitivity = ref(0); -const setSensitiveFlagAutomatically = ref(false); -const enableSensitiveMediaDetectionForVideos = ref(false); -const enableIpLogging = ref(false); -const enableActiveEmailValidation = ref(false); -const enableVerifymailApi = ref(false); -const verifymailAuthKey = ref(null); -const enableTruemailApi = ref(false); -const truemailInstance = ref(null); -const truemailAuthKey = ref(null); -const bannedEmailDomains = ref(''); - -async function init() { - const meta = await misskeyApi('admin/meta'); - enableHcaptcha.value = meta.enableHcaptcha; - enableMcaptcha.value = meta.enableMcaptcha; - enableRecaptcha.value = meta.enableRecaptcha; - enableTurnstile.value = meta.enableTurnstile; - sensitiveMediaDetection.value = meta.sensitiveMediaDetection; - sensitiveMediaDetectionSensitivity.value = - meta.sensitiveMediaDetectionSensitivity === 'veryLow' ? 0 : - meta.sensitiveMediaDetectionSensitivity === 'low' ? 1 : - meta.sensitiveMediaDetectionSensitivity === 'medium' ? 2 : - meta.sensitiveMediaDetectionSensitivity === 'high' ? 3 : - meta.sensitiveMediaDetectionSensitivity === 'veryHigh' ? 4 : 0; - setSensitiveFlagAutomatically.value = meta.setSensitiveFlagAutomatically; - enableSensitiveMediaDetectionForVideos.value = meta.enableSensitiveMediaDetectionForVideos; - enableIpLogging.value = meta.enableIpLogging; - enableActiveEmailValidation.value = meta.enableActiveEmailValidation; - enableVerifymailApi.value = meta.enableVerifymailApi; - verifymailAuthKey.value = meta.verifymailAuthKey; - enableTruemailApi.value = meta.enableTruemailApi; - truemailInstance.value = meta.truemailInstance; - truemailAuthKey.value = meta.truemailAuthKey; - bannedEmailDomains.value = meta.bannedEmailDomains?.join('\n') || ''; -} - -function save() { - os.apiWithDialog('admin/update-meta', { - sensitiveMediaDetection: sensitiveMediaDetection.value, +import { useForm } from '@/scripts/use-form.js'; +import MkFormFooter from '@/components/MkFormFooter.vue'; + +const meta = await misskeyApi('admin/meta'); + +const sensitiveMediaDetectionForm = useForm({ + sensitiveMediaDetection: meta.sensitiveMediaDetection, + sensitiveMediaDetectionSensitivity: meta.sensitiveMediaDetectionSensitivity === 'veryLow' ? 0 : + meta.sensitiveMediaDetectionSensitivity === 'low' ? 1 : + meta.sensitiveMediaDetectionSensitivity === 'medium' ? 2 : + meta.sensitiveMediaDetectionSensitivity === 'high' ? 3 : + meta.sensitiveMediaDetectionSensitivity === 'veryHigh' ? 4 : 0, + setSensitiveFlagAutomatically: meta.setSensitiveFlagAutomatically, + enableSensitiveMediaDetectionForVideos: meta.enableSensitiveMediaDetectionForVideos, +}, async (state) => { + await os.apiWithDialog('admin/update-meta', { + sensitiveMediaDetection: state.sensitiveMediaDetection, sensitiveMediaDetectionSensitivity: - sensitiveMediaDetectionSensitivity.value === 0 ? 'veryLow' : - sensitiveMediaDetectionSensitivity.value === 1 ? 'low' : - sensitiveMediaDetectionSensitivity.value === 2 ? 'medium' : - sensitiveMediaDetectionSensitivity.value === 3 ? 'high' : - sensitiveMediaDetectionSensitivity.value === 4 ? 'veryHigh' : + state.sensitiveMediaDetectionSensitivity === 0 ? 'veryLow' : + state.sensitiveMediaDetectionSensitivity === 1 ? 'low' : + state.sensitiveMediaDetectionSensitivity === 2 ? 'medium' : + state.sensitiveMediaDetectionSensitivity === 3 ? 'high' : + state.sensitiveMediaDetectionSensitivity === 4 ? 'veryHigh' : 0, - setSensitiveFlagAutomatically: setSensitiveFlagAutomatically.value, - enableSensitiveMediaDetectionForVideos: enableSensitiveMediaDetectionForVideos.value, - enableIpLogging: enableIpLogging.value, - enableActiveEmailValidation: enableActiveEmailValidation.value, - enableVerifymailApi: enableVerifymailApi.value, - verifymailAuthKey: verifymailAuthKey.value, - enableTruemailApi: enableTruemailApi.value, - truemailInstance: truemailInstance.value, - truemailAuthKey: truemailAuthKey.value, - bannedEmailDomains: bannedEmailDomains.value.split('\n'), - }).then(() => { - fetchInstance(true); + setSensitiveFlagAutomatically: state.setSensitiveFlagAutomatically, + enableSensitiveMediaDetectionForVideos: state.enableSensitiveMediaDetectionForVideos, + }); + fetchInstance(true); +}); + +const ipLoggingForm = useForm({ + enableIpLogging: meta.enableIpLogging, +}, async (state) => { + await os.apiWithDialog('admin/update-meta', { + enableIpLogging: state.enableIpLogging, + }); + fetchInstance(true); +}); + +const emailValidationForm = useForm({ + enableActiveEmailValidation: meta.enableActiveEmailValidation, + enableVerifymailApi: meta.enableVerifymailApi, + verifymailAuthKey: meta.verifymailAuthKey, + enableTruemailApi: meta.enableTruemailApi, + truemailInstance: meta.truemailInstance, + truemailAuthKey: meta.truemailAuthKey, +}, async (state) => { + await os.apiWithDialog('admin/update-meta', { + enableActiveEmailValidation: state.enableActiveEmailValidation, + enableVerifymailApi: state.enableVerifymailApi, + verifymailAuthKey: state.verifymailAuthKey, + enableTruemailApi: state.enableTruemailApi, + truemailInstance: state.truemailInstance, + truemailAuthKey: state.truemailAuthKey, + }); + fetchInstance(true); +}); + +const bannedEmailDomainsForm = useForm({ + bannedEmailDomains: meta.bannedEmailDomains?.join('\n') || '', +}, async (state) => { + await os.apiWithDialog('admin/update-meta', { + bannedEmailDomains: state.bannedEmailDomains.split('\n'), }); -} + fetchInstance(true); +}); const headerActions = computed(() => []); From 3f0aaaa41efe42776d70490ea213e3c8b194c152 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Mon, 23 Sep 2024 19:49:52 +0900 Subject: [PATCH 130/315] perf(embed): improve embed performance (#14613) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * wip * wip * wip * refactor * refactor --------- Co-authored-by: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com> --- .../src/server/web/ClientServerService.ts | 66 +++++++++++++++++++ .../src/server/web/views/base-embed.pug | 3 + packages/frontend-embed/src/boot.ts | 10 ++- .../src/components/EmNoteDetailed.vue | 4 +- .../frontend-embed/src/components/EmNotes.vue | 6 +- packages/frontend-embed/src/di.ts | 2 + packages/frontend-embed/src/pages/clip.vue | 44 +++++++------ packages/frontend-embed/src/pages/note.vue | 33 +++++----- packages/frontend-embed/src/pages/tag.vue | 7 +- .../src/pages/user-timeline.vue | 51 ++++++++------ packages/frontend-embed/src/server-context.ts | 21 ++++++ packages/frontend-embed/src/ui.vue | 16 +++-- 12 files changed, 190 insertions(+), 73 deletions(-) create mode 100644 packages/frontend-embed/src/server-context.ts diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index 063141273a..5de1f87667 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -785,6 +785,72 @@ export class ClientServerService { //#endregion //#region embed pages + fastify.get<{ Params: { user: string; } }>('/embed/user-timeline/:user', async (request, reply) => { + reply.removeHeader('X-Frame-Options'); + + const user = await this.usersRepository.findOneBy({ + id: request.params.user, + }); + + if (user == null) return; + if (user.host != null) return; + + const _user = await this.userEntityService.pack(user); + + reply.header('Cache-Control', 'public, max-age=3600'); + return await reply.view('base-embed', { + title: this.meta.name ?? 'Misskey', + ...await this.generateCommonPugData(this.meta), + embedCtx: htmlSafeJsonStringify({ + user: _user, + }), + }); + }); + + fastify.get<{ Params: { note: string; } }>('/embed/notes/:note', async (request, reply) => { + reply.removeHeader('X-Frame-Options'); + + const note = await this.notesRepository.findOneBy({ + id: request.params.note, + }); + + if (note == null) return; + if (note.visibility !== 'public') return; + if (note.userHost != null) return; + + const _note = await this.noteEntityService.pack(note, null, { detail: true }); + + reply.header('Cache-Control', 'public, max-age=3600'); + return await reply.view('base-embed', { + title: this.meta.name ?? 'Misskey', + ...await this.generateCommonPugData(this.meta), + embedCtx: htmlSafeJsonStringify({ + note: _note, + }), + }); + }); + + fastify.get<{ Params: { clip: string; } }>('/embed/clips/:clip', async (request, reply) => { + reply.removeHeader('X-Frame-Options'); + + const clip = await this.clipsRepository.findOneBy({ + id: request.params.clip, + }); + + if (clip == null) return; + + const _clip = await this.clipEntityService.pack(clip); + + reply.header('Cache-Control', 'public, max-age=3600'); + return await reply.view('base-embed', { + title: this.meta.name ?? 'Misskey', + ...await this.generateCommonPugData(this.meta), + embedCtx: htmlSafeJsonStringify({ + clip: _clip, + }), + }); + }); + fastify.get('/embed/*', async (request, reply) => { reply.removeHeader('X-Frame-Options'); diff --git a/packages/backend/src/server/web/views/base-embed.pug b/packages/backend/src/server/web/views/base-embed.pug index d773f2676a..2bab20a36c 100644 --- a/packages/backend/src/server/web/views/base-embed.pug +++ b/packages/backend/src/server/web/views/base-embed.pug @@ -43,6 +43,9 @@ html(class='embed') script(type='application/json' id='misskey_meta' data-generated-at=now) != metaJson + script(type='application/json' id='misskey_embedCtx' data-generated-at=now) + != embedCtx + script include ../boot.embed.js diff --git a/packages/frontend-embed/src/boot.ts b/packages/frontend-embed/src/boot.ts index fcea7d32ea..00c7944eb3 100644 --- a/packages/frontend-embed/src/boot.ts +++ b/packages/frontend-embed/src/boot.ts @@ -20,16 +20,19 @@ import { serverMetadata } from '@/server-metadata.js'; import { url } from '@@/js/config.js'; import { parseEmbedParams } from '@@/js/embed-page.js'; import { postMessageToParentWindow, setIframeId } from '@/post-message.js'; +import { serverContext } from '@/server-context.js'; import type { Theme } from '@/theme.js'; console.log('Misskey Embed'); +//#region Embedパラメータの取得・パース const params = new URLSearchParams(location.search); const embedParams = parseEmbedParams(params); - if (_DEV_) console.log(embedParams); +//#endregion +//#region テーマ function parseThemeOrNull(theme: string | null): Theme | null { if (theme == null) return null; try { @@ -65,6 +68,7 @@ if (embedParams.colorMode === 'dark') { } }); } +//#endregion // サイズの制限 document.documentElement.style.maxWidth = '500px'; @@ -89,6 +93,10 @@ const app = createApp( app.provide(DI.mediaProxy, new MediaProxy(serverMetadata, url)); +app.provide(DI.serverMetadata, serverMetadata); + +app.provide(DI.serverContext, serverContext); + app.provide(DI.embedParams, embedParams); // https://github.com/misskey-dev/misskey/pull/8575#issuecomment-1114239210 diff --git a/packages/frontend-embed/src/components/EmNoteDetailed.vue b/packages/frontend-embed/src/components/EmNoteDetailed.vue index 8169f500a9..a233011af7 100644 --- a/packages/frontend-embed/src/components/EmNoteDetailed.vue +++ b/packages/frontend-embed/src/components/EmNoteDetailed.vue @@ -142,8 +142,8 @@ import EmAcct from '@/components/EmAcct.vue'; import { userPage } from '@/utils.js'; import { notePage } from '@/utils.js'; import { i18n } from '@/i18n.js'; +import { DI } from '@/di.js'; import { shouldCollapsed } from '@@/js/collapsed.js'; -import { serverMetadata } from '@/server-metadata.js'; import { url } from '@@/js/config.js'; import EmMfm from '@/components/EmMfm.js'; @@ -151,6 +151,8 @@ const props = defineProps<{ note: Misskey.entities.Note; }>(); +const serverMetadata = inject(DI.serverMetadata)!; + const inChannel = inject('inChannel', null); const note = ref(props.note); diff --git a/packages/frontend-embed/src/components/EmNotes.vue b/packages/frontend-embed/src/components/EmNotes.vue index 6370f4aeae..3418d97f77 100644 --- a/packages/frontend-embed/src/components/EmNotes.vue +++ b/packages/frontend-embed/src/components/EmNotes.vue @@ -20,12 +20,12 @@ SPDX-License-Identifier: AGPL-3.0-only From 1b2b95e199938d30be546c1afa1088eecdc1097c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 24 Sep 2024 01:22:57 +0000 Subject: [PATCH 143/315] Bump version to 2024.9.0-alpha.8 --- package.json | 2 +- packages/misskey-js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 3bfa95a206..63c22cdc5b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "2024.9.0-alpha.7", + "version": "2024.9.0-alpha.8", "codename": "nasubi", "repository": { "type": "git", diff --git a/packages/misskey-js/package.json b/packages/misskey-js/package.json index 6c07ce87da..0f797e8259 100644 --- a/packages/misskey-js/package.json +++ b/packages/misskey-js/package.json @@ -1,7 +1,7 @@ { "type": "module", "name": "misskey-js", - "version": "2024.9.0-alpha.7", + "version": "2024.9.0-alpha.8", "description": "Misskey SDK for JavaScript", "license": "MIT", "main": "./built/index.js", From 4be307f22363ab594984c240a292509bfb6895fa Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Tue, 24 Sep 2024 13:55:35 +0900 Subject: [PATCH 144/315] refactor --- packages/frontend/src/ui/_common_/navbar-for-mobile.vue | 2 +- packages/frontend/src/ui/_common_/navbar.vue | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/frontend/src/ui/_common_/navbar-for-mobile.vue b/packages/frontend/src/ui/_common_/navbar-for-mobile.vue index e80d5fd399..5115d21d56 100644 --- a/packages/frontend/src/ui/_common_/navbar-for-mobile.vue +++ b/packages/frontend/src/ui/_common_/navbar-for-mobile.vue @@ -82,7 +82,7 @@ function more() { diff --git a/idea/README.md b/idea/README.md new file mode 100644 index 0000000000..f64d16800a --- /dev/null +++ b/idea/README.md @@ -0,0 +1 @@ +使われなくなったけど消すのは勿体ない(将来使えるかもしれない)コードを入れておくとこ diff --git a/packages/frontend/src/pages/admin/performance.vue b/packages/frontend/src/pages/admin/performance.vue index 57f68a2a26..7e0a932f82 100644 --- a/packages/frontend/src/pages/admin/performance.vue +++ b/packages/frontend/src/pages/admin/performance.vue @@ -45,7 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only -
+
- - - - +
@@ -110,7 +112,6 @@ import MkSwitch from '@/components/MkSwitch.vue'; import MkFolder from '@/components/MkFolder.vue'; import MkInput from '@/components/MkInput.vue'; import MkLink from '@/components/MkLink.vue'; -import MkButton from '@/components/MkButton.vue'; import { useForm } from '@/scripts/use-form.js'; import MkFormFooter from '@/components/MkFormFooter.vue'; diff --git a/packages/frontend/src/pages/admin/settings.vue b/packages/frontend/src/pages/admin/settings.vue index 537c86cb14..5207f0e38e 100644 --- a/packages/frontend/src/pages/admin/settings.vue +++ b/packages/frontend/src/pages/admin/settings.vue @@ -169,42 +169,44 @@ SPDX-License-Identifier: AGPL-3.0-only - - - - - - - - - +
@@ -230,7 +232,6 @@ SPDX-License-Identifier: AGPL-3.0-only From 8c3be57ab362b8b2a24dad9b42ac0c3762bcb34e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Wed, 25 Sep 2024 16:12:34 +0900 Subject: [PATCH 166/315] =?UTF-8?q?fix(frontend-embed):=20URL=E3=82=A8?= =?UTF-8?q?=E3=83=B3=E3=82=B3=E3=83=BC=E3=83=89=E3=81=95=E3=82=8C=E3=81=9F?= =?UTF-8?q?=E6=96=87=E5=AD=97=E5=88=97=E3=81=8C=E6=AD=A3=E5=B8=B8=E3=81=AB?= =?UTF-8?q?=E8=AA=AD=E3=81=BF=E8=BE=BC=E3=82=81=E3=81=AA=E3=81=84=E5=95=8F?= =?UTF-8?q?=E9=A1=8C=E3=82=92=E4=BF=AE=E6=AD=A3=20(#14630)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(frontend-embed): URLエンコードされた文字列が正常に読み込めない問題を修正 * fix(frontend-embed): bring back missing bits --- packages/frontend-embed/src/pages/user-timeline.vue | 13 ++++++++++++- packages/frontend-embed/src/ui.vue | 10 +++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/packages/frontend-embed/src/pages/user-timeline.vue b/packages/frontend-embed/src/pages/user-timeline.vue index 2d5dbb687b..85e6f52d50 100644 --- a/packages/frontend-embed/src/pages/user-timeline.vue +++ b/packages/frontend-embed/src/pages/user-timeline.vue @@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only -
+
+ + diff --git a/packages/frontend/src/pages/settings/general.vue b/packages/frontend/src/pages/settings/general.vue index 8561024879..e5bd72a7a7 100644 --- a/packages/frontend/src/pages/settings/general.vue +++ b/packages/frontend/src/pages/settings/general.vue @@ -38,250 +38,6 @@ SPDX-License-Identifier: AGPL-3.0-only
- - - -
-
- - - - - - - - CherryPick - - {{ i18n.ts.collapseLongNoteContent }} CherryPick - {{ i18n.ts.collapseDefault }} CherryPick - {{ i18n.ts.showNoteActionsOnlyHover }} - {{ i18n.ts.showClipButtonInNoteFooter }} - {{ i18n.ts.showTranslateButtonInNote }} CherryPick - {{ i18n.ts.enableAdvancedMfm }} - {{ i18n.ts.enableAnimatedMfm }} -
-
- -
-
- -
-
- {{ i18n.ts.enableQuickAddMfmFunction }} - {{ i18n.ts.showReactionsCount }} - {{ i18n.ts.showGapBetweenNotesInTimeline }} - {{ i18n.ts.loadRawImages }} - - - - - - - {{ i18n.ts.limitWidthOfReaction }} - {{ i18n.ts.hideAvatarsInNote }} CherryPick - {{ i18n.ts.enableAbsoluteTime }} CherryPick - {{ i18n.ts.enableMarkByDate }} CherryPick - {{ i18n.ts.showSubNoteFooterButton }} CherryPick - {{ i18n.ts.infoButtonForNoteActions }} CherryPick - {{ i18n.ts.showReplyInNotification }} CherryPick - {{ i18n.ts.renoteQuoteButtonSeparation }} CherryPick - {{ i18n.ts.showRenoteVisibilitySelector }} CherryPick - - - - - - - - {{ i18n.ts.showFixedPostFormInReplies }} CherryPick - {{ i18n.ts.allMediaNoteCollapse }} CherryPick - {{ i18n.ts.alwaysShowCw }} CherryPick - {{ i18n.ts.showReplyTargetNoteInSemiTransparent }} CherryPick -
- -
-
{{ i18n.ts.noteFooterButton }} CherryPick
- - {{ i18n.ts.reply }} - {{ i18n.ts.renote }} - {{ i18n.ts.like }} - {{ i18n.ts.doReaction }} - {{ i18n.ts.quote }} - {{ i18n.ts.more }} -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - - -
- {{ i18n.ts.useGroupedNotifications }} - - - - - - - - - - - - - - - - {{ i18n.ts._notification.checkNotificationBehavior }} -
-
- - - - -
-
- {{ i18n.ts.reduceUiAnimation }} - {{ i18n.ts.useBlurEffect }} - {{ i18n.ts.useBlurEffectForModal }} - {{ i18n.ts.removeModalBgColorForBlur }} CherryPick - {{ i18n.ts.disableShowingAnimatedImages }} - - - - - - - {{ i18n.ts.highlightSensitiveMedia }} - {{ i18n.ts.squareAvatars }} - {{ i18n.ts.showAvatarDecorations }} - {{ i18n.ts.useSystemFont }} - {{ i18n.ts.forceShowAds }} - {{ i18n.ts.seasonalScreenEffect }} - {{ i18n.ts.useNativeUIForVideoAudioPlayer }} - {{ i18n.ts.showUnreadNotificationsCount }} - - {{ i18n.ts.filesGridLayoutInUserPage }} CherryPick - - -
- - - - - - - - -
- - - - - - -
-
- - - -
-
{{ i18n.ts.fontSize }} CherryPick
-
-
{{ i18n.ts._mfm.dummy }}
-
{{ i18n.ts._mfm.dummy }}
-
{{ i18n.ts._mfm.dummy }}
-
{{ i18n.ts._mfm.dummy }}
-
{{ i18n.ts._mfm.dummy }}
-
{{ i18n.ts._mfm.dummy }}
-
{{ i18n.ts._mfm.dummy }}
-
{{ i18n.ts._mfm.dummy }}
-
{{ i18n.ts._mfm.dummy }}
-
{{ i18n.ts._mfm.dummy }}
-
{{ i18n.ts._mfm.dummy }}
-
{{ i18n.ts._mfm.dummy }}
-
{{ i18n.ts._mfm.dummy }}
-
{{ i18n.ts._mfm.dummy }}
-
{{ i18n.ts._mfm.dummy }}
-
{{ i18n.ts._mfm.dummy }}
-
{{ i18n.ts._mfm.dummy }}
-
{{ i18n.ts._mfm.dummy }}
-
{{ i18n.ts._mfm.dummy }}
-
-
-
Aa
- - -
Aa
-
- {{ i18n.ts.reloadToApplySetting2 }} - {{ i18n.ts.useBoldFont }} -
-
-
- @@ -385,8 +141,7 @@ SPDX-License-Identifier: AGPL-3.0-only - - diff --git a/packages/frontend/src/pages/settings/index.vue b/packages/frontend/src/pages/settings/index.vue index 43e9c6b0af..57d52416ae 100644 --- a/packages/frontend/src/pages/settings/index.vue +++ b/packages/frontend/src/pages/settings/index.vue @@ -105,6 +105,11 @@ const menuDef = computed(() => [{ text: i18n.ts.general, to: '/settings/general', active: currentPage.value?.route.name === 'general', + }, { + icon: 'ti ti-brush', + text: i18n.ts.appearance, + to: '/settings/appearance', + active: currentPage.value?.route.name === 'appearance', }, { icon: 'ti ti-palette', text: i18n.ts.theme, diff --git a/packages/frontend/src/router/definition.ts b/packages/frontend/src/router/definition.ts index c4b14d3afb..e7d04d4669 100644 --- a/packages/frontend/src/router/definition.ts +++ b/packages/frontend/src/router/definition.ts @@ -191,6 +191,10 @@ const routes: RouteDef[] = [{ path: '/cherrypick', name: 'cherrypick', component: page(() => import('@/pages/settings/cherrypick.vue')), + }, { + path: '/appearance', + name: 'appearance', + component: page(() => import('@/pages/settings/appearance.vue')), }, { path: '/', component: page(() => import('@/pages/_empty_.vue')), From f22d66c27251a4b3252d203fe2ab97b6a2055c5d Mon Sep 17 00:00:00 2001 From: NoriDev Date: Wed, 2 Oct 2024 22:06:11 +0900 Subject: [PATCH 234/315] Update ko-KR.yml --- locales/ko-KR.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index 75c8234d5e..14563f9272 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -1354,7 +1354,7 @@ overwriteContentConfirm: "현재 내용을 덮어쓰기 하게 돼요. 그래도 seasonalScreenEffect: "계절에 따른 화면 연출" decorate: "장식하기" addMfmFunction: "장식 추가" -enableQuickAddMfmFunction: "고급 MFM 선택기 표시하기" +enableQuickAddMfmFunction: "고급 MFM 선택기 표시" bubbleGame: "버블 게임" sfx: "효과음" soundWillBePlayed: "사운드가 재생돼요" From 5d29b05a284846edbe07cd7a0ec2c125f1dde2ae Mon Sep 17 00:00:00 2001 From: NoriDev Date: Wed, 2 Oct 2024 22:07:43 +0900 Subject: [PATCH 235/315] =?UTF-8?q?=F0=9F=8E=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/components/MkPostForm.vue | 2 +- packages/frontend/src/components/MkPostFormSimple.vue | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue index 48af1d7515..faf0ab6620 100644 --- a/packages/frontend/src/components/MkPostForm.vue +++ b/packages/frontend/src/components/MkPostForm.vue @@ -1300,7 +1300,7 @@ defineExpose({ //#endregion .preview { - padding: 16px 20px 0 20px; + padding: 16px 20px; // min-height: 75px; max-height: 150px; overflow: auto; diff --git a/packages/frontend/src/components/MkPostFormSimple.vue b/packages/frontend/src/components/MkPostFormSimple.vue index 0fef5023b1..7f05f6849f 100644 --- a/packages/frontend/src/components/MkPostFormSimple.vue +++ b/packages/frontend/src/components/MkPostFormSimple.vue @@ -1343,7 +1343,7 @@ defineExpose({ //#endregion .preview { - padding: 16px 20px 0 20px; + padding: 16px 20px; // min-height: 75px; max-height: 150px; overflow: auto; From 497e0564afbd409d2f518c8cdf47ac7efd0c2818 Mon Sep 17 00:00:00 2001 From: NoriDev Date: Wed, 2 Oct 2024 23:32:17 +0900 Subject: [PATCH 236/315] =?UTF-8?q?enhance(frontend):=20=EB=85=B8=ED=8A=B8?= =?UTF-8?q?=EC=97=90=202=EA=B0=9C=20=EC=9D=B4=EC=83=81=EC=9D=98=20?= =?UTF-8?q?=EB=AF=B8=EB=94=94=EC=96=B4=EA=B0=80=20=EC=9E=88=EB=8A=94=20?= =?UTF-8?q?=EA=B2=BD=EC=9A=B0=20=EC=95=84=EC=9D=B4=EC=BD=98=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=ED=91=9C=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pages/user/index.timeline.files.files.vue | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/packages/frontend/src/pages/user/index.timeline.files.files.vue b/packages/frontend/src/pages/user/index.timeline.files.files.vue index 5b6b804686..7a90be342c 100644 --- a/packages/frontend/src/pages/user/index.timeline.files.files.vue +++ b/packages/frontend/src/pages/user/index.timeline.files.files.vue @@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only --> @@ -81,6 +84,10 @@ onUnmounted(() => { diff --git a/packages/frontend/src/widgets/index.ts b/packages/frontend/src/widgets/index.ts index e269fcf9eb..9c533feecf 100644 --- a/packages/frontend/src/widgets/index.ts +++ b/packages/frontend/src/widgets/index.ts @@ -34,6 +34,7 @@ export default function(app: App) { app.component('WidgetUserList', defineAsyncComponent(() => import('./WidgetUserList.vue'))); app.component('WidgetClicker', defineAsyncComponent(() => import('./WidgetClicker.vue'))); app.component('WidgetBirthdayFollowings', defineAsyncComponent(() => import('./WidgetBirthdayFollowings.vue'))); + app.component('WidgetSearch', defineAsyncComponent(() => import('./WidgetSearch.vue'))); } export const widgets = [ @@ -65,4 +66,5 @@ export const widgets = [ 'userList', 'clicker', 'birthdayFollowings', + 'search', ]; From 1a5f3d963bf5295dbb62bad3dbb89cc16411df3f Mon Sep 17 00:00:00 2001 From: NoriDev Date: Thu, 3 Oct 2024 08:51:59 +0900 Subject: [PATCH 242/315] Update CHANGELOG_CHERRYPICK.md --- CHANGELOG_CHERRYPICK.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG_CHERRYPICK.md b/CHANGELOG_CHERRYPICK.md index a3896b5100..59cea6ce3e 100644 --- a/CHANGELOG_CHERRYPICK.md +++ b/CHANGELOG_CHERRYPICK.md @@ -28,9 +28,11 @@ Misskey의 전체 변경 사항을 확인하려면, [CHANGELOG.md#2024xx](CHANGE 기반 Misskey 버전: 2024.x.x
Misskey의 전체 변경 사항을 확인하려면, [CHANGELOG.md#2024xx](CHANGELOG.md#2024xx) 문서를 참고하십시오. -### General -- Change: Enable condensed line의 기본값을 꺼짐으로 설정함 +## NOTE +- Change: Enable condensed line의 기본값이 꺼짐으로 변경됨 - 디자인이 상대적으로 이상하게 보일 수 있으며, 실험실 기능이므로 이 기능이 변경하는 부분을 확실히 알고 있는 사용자만 활성화할 것을 권장합니다. + +### General - Feat: 위젯 영역을 숨길 수 있음 - 기존 Friendly UI 한정 기능이 다른 UI에서도 사용할 수 있도록 확대됨 - Feat: '내용 숨기기'로 설정한 내용을 항상 보이게 설정할 수 있음 (kokonect-link/cherrypick#495) From 7702ff29c1c8d5784b0457e3f6948013b02dd88d Mon Sep 17 00:00:00 2001 From: NoriDev Date: Thu, 3 Oct 2024 10:31:03 +0900 Subject: [PATCH 243/315] =?UTF-8?q?feat:=20=EC=BA=A1=EC=85=98=20=EB=AF=B8?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EC=95=88=EB=82=B4=20=ED=91=9C=EC=8B=9C=20?= =?UTF-8?q?(1673beta/cherrypick#142)=20=20=20-=20=EB=85=B8=ED=8A=B8?= =?UTF-8?q?=EB=A5=BC=20=EA=B2=8C=EC=8B=9C=ED=95=98=EA=B8=B0=20=EC=A0=84?= =?UTF-8?q?=EC=97=90=20=EC=B2=A8=EB=B6=80=ED=95=9C=20=ED=8C=8C=EC=9D=BC?= =?UTF-8?q?=EC=97=90=20=EC=BA=A1=EC=85=98=EC=9D=B4=20=EC=97=86=EC=9C=BC?= =?UTF-8?q?=EB=A9=B4=20=EA=B2=BD=EA=B3=A0=EB=A5=BC=20=ED=91=9C=EC=8B=9C?= =?UTF-8?q?=ED=95=A9=EB=8B=88=EB=8B=A4.=20=20=20-=20=EC=9D=B4=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=EC=9C=BC=EB=A1=9C=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EB=B7=B0=EC=96=B4=EC=9D=98=20=ED=8C=8C=EC=9D=BC=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=20=EC=98=81=EC=97=AD=EC=97=90=EB=8A=94=20=EB=8D=94=20?= =?UTF-8?q?=EC=9D=B4=EC=83=81=20=EC=BA=A1=EC=85=98=EC=9D=B4=20=EC=95=84?= =?UTF-8?q?=EB=8B=8C=20=EC=8B=A4=EC=A0=9C=20=ED=8C=8C=EC=9D=BC=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=EC=9D=B4=20=ED=91=9C=EC=8B=9C=EB=90=A9=EB=8B=88?= =?UTF-8?q?=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG_CHERRYPICK.md | 9 +++++ locales/en-US.yml | 9 ++++- locales/index.d.ts | 18 +++++++++ locales/ja-JP.yml | 6 +++ locales/ko-KR.yml | 5 +++ .../src/components/MkCropperDialog.vue | 3 ++ .../src/components/MkDriveFileThumbnail.vue | 3 +- .../src/components/MkImgWithBlurhash.vue | 20 ++++++++++ .../frontend/src/components/MkMediaImage.vue | 11 +++--- .../frontend/src/components/MkMediaList.vue | 37 ++++++++++++++++++- .../frontend/src/components/MkPostForm.vue | 13 ++++++- .../src/components/MkPostFormAttaches.vue | 2 +- .../src/components/MkPostFormSimple.vue | 13 ++++++- .../src/pages/settings/appearance.vue | 2 + packages/frontend/src/store.ts | 4 ++ 15 files changed, 142 insertions(+), 13 deletions(-) diff --git a/CHANGELOG_CHERRYPICK.md b/CHANGELOG_CHERRYPICK.md index 59cea6ce3e..e2e7136835 100644 --- a/CHANGELOG_CHERRYPICK.md +++ b/CHANGELOG_CHERRYPICK.md @@ -45,6 +45,9 @@ Misskey의 전체 변경 사항을 확인하려면, [CHANGELOG.md#2024xx](CHANGE - Feat: 답글 대상 노트의 반투명 옵션을 선택할 수 있음 (kokonect-link/cherrypick#495) - Feat: 사용자 페이지의 미디어 탭을 그리드 레이아웃으로 설정할 수 있음 (kokonect-link/cherrypick#494) - Feat: 검색 위젯 (1673beta/cherrypick#125) +- Feat: 캡션 미설정 안내 표시 (1673beta/cherrypick#142) + - 노트를 게시하기 전에 첨부한 파일에 캡션이 없으면 경고를 표시합니다. + - 이 변경으로 이미지 뷰어의 파일 이름 영역에는 더 이상 캡션이 아닌 실제 파일 이름이 표시됩니다. ### Client - Enhance: CherryPick 업데이트 페이지를 제어판 목록에 추가함 @@ -55,7 +58,13 @@ Misskey의 전체 변경 사항을 확인하려면, [CHANGELOG.md#2024xx](CHANGE - Enhance: 설정 페이지 개선 - 일반 설정에 있던 설정 중 디자인과 관련된 설정을 모양으로 옮겼습니다. - Enhance: 외부 사이트로 이동할 때 경고 표시 (MisskeyIO/misskey#558) +- Enhance: 이미지의 확장자를 더욱 정확하게 표시함 + - APNG 형식의 이미지가 GIF로 표시되던 것을 APNG로 표시하도록 변경 +- Enhance: 이미지 뷰어가 파일 이름과 캡션을 동시에 표시하도록 변경 +- Enhance: 노트를 작성할 때 첨부한 파일에 캡션이 존재하는 경우 파일 목록에 아이콘을 표시함 +- Enhance: 미디어 숨기기 버튼의 디자인을 개선함 - Fix: 환경설정 백업 시 일부 설정이 누락되어 백업될 수 있음 +- Fix: 이미지 자르기를 할 때 이미지 크기 전체를 표시하지 못할 수 있음 --- diff --git a/locales/en-US.yml b/locales/en-US.yml index 865c2e2f9d..f839aebbb1 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -1,5 +1,7 @@ --- _lang_: "English" +showNoAltWarning: "Show caption unset warning" +showNoAltWarningDescription: "Display a warning when no alternate text is set in the image" filesGridLayoutInUserPage: "Change the media tab to grid layout" filesGridLayoutInUserPageDescription: "When this function is turned on, the Media tab on your page is shown in album format.\nTurning it off will change to the original note timeline." showReplyTargetNoteInSemiTransparent: "Show reply target note in semi-transparent" @@ -1479,8 +1481,8 @@ _cherrypick: reactableRemoteReaction: "Allow remote custom emoji reactions to react if there is an emoji with the same name on this server." showFollowingMessageInsteadOfButton: "Do not show the follow button in the notification field if you are already following someone" mobileHeaderChange: "Header design change in mobile environment" - renameTheButtonInPostFormToNya: "Change the \"Note\" button on the note-posting form to \"Nyan!\"" - renameTheButtonInPostFormToNyaDescription: "Outside of the note-posting form, they are still as \"Note\"." + renameTheButtonInPostFormToNya: "Change the \"Note\" button on the posting form to \"Nyan!\"" + renameTheButtonInPostFormToNyaDescription: "Outside of the posting form, they are still as \"Note\"." enableWidgetsArea: "Enable the widgets area" disableWidgetsArea: "Disable the widgets area" friendlyUiEnableNotificationsArea: "Enable the notifications area" @@ -3016,3 +3018,6 @@ _externalNavigationWarning: title: "Navigate to an external site" description: "Leave {host} and go to an external site" trustThisDomain: "Trust this domain on this device in the future" +_altWarning: + noAltWarning: "No alternate text is configured in the file." + noAltWarningDescription: "You can change this setting in \"Settings - Appearance\"." diff --git a/locales/index.d.ts b/locales/index.d.ts index 898113106b..1692fc7142 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -13,6 +13,14 @@ export interface Locale extends ILocale { * 日本語 */ "_lang_": string; + /** + * キャプション未設定案内を表示 + */ + "showNoAltWarning": string; + /** + * 画像に代替テキストが設定されていない場合に警告を表示する + */ + "showNoAltWarningDescription": string; /** * メディアタブをグリッドレイアウトに変更 */ @@ -11794,6 +11802,16 @@ export interface Locale extends ILocale { */ "trustThisDomain": string; }; + "_altWarning": { + /** + * ファイルに代替テキストが設定されていません。 + */ + "noAltWarning": string; + /** + * この設定は「設定 - アピアランス」で変更できます。 + */ + "noAltWarningDescription": string; + }; } declare const locales: { [lang: string]: Locale; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 53942940ed..cc0c9072d3 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1,5 +1,7 @@ _lang_: "日本語" +showNoAltWarning: "キャプション未設定案内を表示" +showNoAltWarningDescription: "画像に代替テキストが設定されていない場合に警告を表示する" filesGridLayoutInUserPage: "メディアタブをグリッドレイアウトに変更" filesGridLayoutInUserPageDescription: "この設定をオンにすると、ユーザーページのメディアタブがアルバム形式で表示されます。\nオフにすると、元のノートのタイムラインに変更されます。" showReplyTargetNoteInSemiTransparent: "返信対象ノートを半透明に表示" @@ -3140,3 +3142,7 @@ _externalNavigationWarning: title: "外部サイトに移動します" description: "{host}を離れて外部サイトに移動します" trustThisDomain: "このデバイスで今後このドメインを信頼する" + +_altWarning: + noAltWarning: "ファイルに代替テキストが設定されていません。" + noAltWarningDescription: "この設定は「設定 - アピアランス」で変更できます。" diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index bb71c1379c..044c327a11 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -1,5 +1,7 @@ --- _lang_: "한국어" +showNoAltWarning: "캡션 미설정 안내 표시" +showNoAltWarningDescription: "이미지에 캡션이 설정되어 있지 않으면 경고를 표시해요" filesGridLayoutInUserPage: "미디어 탭을 그리드 레이아웃으로 변경" filesGridLayoutInUserPageDescription: "이 설정을 켜면 사용자 페이지의 미디어 탭이 앨범 형식으로 보여져요.\n끄면 원래의 노트 타임라인으로 변경돼요." showReplyTargetNoteInSemiTransparent: "답글 대상 노트를 반투명하게 표시" @@ -3047,3 +3049,6 @@ _externalNavigationWarning: title: "외부 사이트로 이동할까요?" description: "{host}을(를) 떠나 외부 사이트로 이동하려고 해요" trustThisDomain: "이 장치에서 앞으로 이 도메인을 신뢰할게요" +_altWarning: + noAltWarning: "파일에 캡션이 설정되어 있지 않아요." + noAltWarningDescription: "이 설정은 [설정 - 모양]에서 변경할 수 있어요." diff --git a/packages/frontend/src/components/MkCropperDialog.vue b/packages/frontend/src/components/MkCropperDialog.vue index 4f490510c1..8c74fa1735 100644 --- a/packages/frontend/src/components/MkCropperDialog.vue +++ b/packages/frontend/src/components/MkCropperDialog.vue @@ -113,6 +113,7 @@ const onImageLoad = () => { loading.value = false; if (cropper) { + cropper.getCropperCanvas(); cropper.getCropperImage()!.$center('contain'); cropper.getCropperSelection()!.$center(); } @@ -159,6 +160,7 @@ onMounted(() => { width: var(--vw); height: var(--vh); position: relative; + object-fit: contain; > .loading { position: absolute; @@ -183,6 +185,7 @@ onMounted(() => { > ::v-deep(cropper-canvas) { width: 100%; height: 100%; + object-fit: contain; > cropper-selection > cropper-handle[action="move"] { background: transparent; diff --git a/packages/frontend/src/components/MkDriveFileThumbnail.vue b/packages/frontend/src/components/MkDriveFileThumbnail.vue index d20e660788..20475e69e8 100644 --- a/packages/frontend/src/components/MkDriveFileThumbnail.vue +++ b/packages/frontend/src/components/MkDriveFileThumbnail.vue @@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only { [$style.sensitiveHighlight]: highlightWhenSensitive && file.isSensitive }, ]" > - + @@ -34,6 +34,7 @@ const props = defineProps<{ file: Misskey.entities.DriveFile; fit: 'cover' | 'contain'; highlightWhenSensitive?: boolean; + showAltIndicator?: boolean; }>(); const is = computed(() => { diff --git a/packages/frontend/src/components/MkImgWithBlurhash.vue b/packages/frontend/src/components/MkImgWithBlurhash.vue index 837a9e17ae..a1794fab13 100644 --- a/packages/frontend/src/components/MkImgWithBlurhash.vue +++ b/packages/frontend/src/components/MkImgWithBlurhash.vue @@ -16,6 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only > +
@@ -82,6 +83,7 @@ const props = withDefaults(defineProps<{ forceBlurhash?: boolean; onlyAvgColor?: boolean; // 軽量化のためにBlurhashを使わずに平均色だけを描画 noDrag?: boolean; + showAltIndicator?: boolean; }>(), { transition: null, src: null, @@ -93,6 +95,7 @@ const props = withDefaults(defineProps<{ forceBlurhash: false, onlyAvgColor: false, noDrag: false, + showAltIndicator: false, }); const viewId = uuid(); @@ -269,4 +272,21 @@ onUnmounted(() => { -webkit-user-drag: none; } } + +.altIndicator { + display: flex; + gap: 4px; + position: absolute; + border-radius: 8px; + overflow: hidden; + top: 2.5px; + right: 2px; + background-color: var(--bg); + -webkit-backdrop-filter: var(--blur, blur(15px)); + backdrop-filter: var(--blur, blur(15px)); + color: var(--accent); + font-size: 1em; + padding: 2px 4px; + text-align: center; +} diff --git a/packages/frontend/src/components/MkMediaImage.vue b/packages/frontend/src/components/MkMediaImage.vue index bae422cd1c..43456cae1d 100644 --- a/packages/frontend/src/components/MkMediaImage.vue +++ b/packages/frontend/src/components/MkMediaImage.vue @@ -23,7 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only :forceBlurhash="hide" :cover="hide || cover" :alt="image.comment || image.name" - :title="image.comment || image.name" + :title="image.name" :width="image.properties.width" :height="image.properties.height" :style="hide ? 'filter: brightness(0.7);' : null" @@ -44,7 +44,8 @@ SPDX-License-Identifier: AGPL-3.0-only @@ -61,6 +79,8 @@ import MkRadios from '@/components/MkRadios.vue'; import MkButton from '@/components/MkButton.vue'; import FormSlot from '@/components/form/slot.vue'; import MkContainer from '@/components/MkContainer.vue'; +import MkSwitch from '@/components/MkSwitch.vue'; +import FormSection from '@/components/form/section.vue'; import * as os from '@/os.js'; import { navbarItemDef } from '@/navbar.js'; import { defaultStore } from '@/store.js'; @@ -78,6 +98,15 @@ const items = ref(defaultStore.state.menu.map(x => ({ const menuDisplay = computed(defaultStore.makeGetterSetter('menuDisplay')); const bannerDisplay = computed(defaultStore.makeGetterSetter('bannerDisplay')); +const showMenuButtonInNavbar = computed(defaultStore.makeGetterSetter('showMenuButtonInNavbar')); +const showHomeButtonInNavbar = computed(defaultStore.makeGetterSetter('showHomeButtonInNavbar')); +const showExploreButtonInNavbar = computed(defaultStore.makeGetterSetter('showExploreButtonInNavbar')); +const showSearchButtonInNavbar = computed(defaultStore.makeGetterSetter('showSearchButtonInNavbar')); +const showNotificationButtonInNavbar = computed(defaultStore.makeGetterSetter('showNotificationButtonInNavbar')); +const showMessageButtonInNavbar = computed(defaultStore.makeGetterSetter('showMessageButtonInNavbar')); +const showWidgetButtonInNavbar = computed(defaultStore.makeGetterSetter('showWidgetButtonInNavbar')); +const showPostButtonInNavbar = computed(defaultStore.makeGetterSetter('showPostButtonInNavbar')); + async function addItem(ev: MouseEvent) { const menu = Object.keys(navbarItemDef).filter(k => !defaultStore.state.menu.includes(k)); os.popupMenu([ @@ -122,6 +151,15 @@ function reset() { })); } +function resetButtomNavbar() { + defaultStore.set('showHomeButtonInNavbar', true); + defaultStore.set('showExploreButtonInNavbar', true); + defaultStore.set('showSearchButtonInNavbar', false); + defaultStore.set('showNotificationButtonInNavbar', true); + defaultStore.set('showMessageButtonInNavbar', true); + defaultStore.set('showWidgetButtonInNavbar', true); +} + watch([menuDisplay, bannerDisplay], async () => { await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true }); }); diff --git a/packages/frontend/src/pages/settings/preferences-backups.vue b/packages/frontend/src/pages/settings/preferences-backups.vue index 6ac6ed95c8..6f99463411 100644 --- a/packages/frontend/src/pages/settings/preferences-backups.vue +++ b/packages/frontend/src/pages/settings/preferences-backups.vue @@ -134,6 +134,14 @@ const defaultStoreSaveKeys: (keyof typeof defaultStore['state'])[] = [ 'enableAbsoluteTime', 'enableMarkByDate', 'showSubNoteFooterButton', + 'showMenuButtonInNavbar', + 'showHomeButtonInNavbar', + 'showExploreButtonInNavbar', + 'showSearchButtonInNavbar', + 'showNotificationButtonInNavbar', + 'showMessageButtonInNavbar', + 'showWidgetButtonInNavbar', + 'showPostButtonInNavbar', 'enableHomeTimeline', 'enableLocalTimeline', 'enableSocialTimeline', diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts index 33b0118e45..0ef82ff31c 100644 --- a/packages/frontend/src/store.ts +++ b/packages/frontend/src/store.ts @@ -655,6 +655,40 @@ export const defaultStore = markRaw(new Storage('base', { default: true, }, + // - Settings/Navigation bar + showMenuButtonInNavbar: { + where: 'device', + default: true, + }, + showHomeButtonInNavbar: { + where: 'device', + default: true, + }, + showExploreButtonInNavbar: { + where: 'device', + default: true, + }, + showSearchButtonInNavbar: { + where: 'device', + default: false, + }, + showNotificationButtonInNavbar: { + where: 'device', + default: true, + }, + showMessageButtonInNavbar: { + where: 'device', + default: true, + }, + showWidgetButtonInNavbar: { + where: 'device', + default: true, + }, + showPostButtonInNavbar: { + where: 'device', + default: true, + }, + // - Settings/Timeline enableHomeTimeline: { where: 'device', diff --git a/packages/frontend/src/ui/deck.vue b/packages/frontend/src/ui/deck.vue index a143bef3ae..1c4d669610 100644 --- a/packages/frontend/src/ui/deck.vue +++ b/packages/frontend/src/ui/deck.vue @@ -50,16 +50,19 @@ SPDX-License-Identifier: AGPL-3.0-only
- - - + + + + - + +
- - - - + + + - - + +
diff --git a/packages/frontend/src/ui/universal.vue b/packages/frontend/src/ui/universal.vue index b97f8412dc..7e17fcfdaf 100644 --- a/packages/frontend/src/ui/universal.vue +++ b/packages/frontend/src/ui/universal.vue @@ -25,17 +25,20 @@ SPDX-License-Identifier: AGPL-3.0-only
- - - + + + + - - + + +
Date: Sat, 5 Oct 2024 19:06:39 +0900 Subject: [PATCH 274/315] Update CHANGELOG_CHERRYPICK.md --- CHANGELOG_CHERRYPICK.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG_CHERRYPICK.md b/CHANGELOG_CHERRYPICK.md index 253aab5db6..cd2737f4ae 100644 --- a/CHANGELOG_CHERRYPICK.md +++ b/CHANGELOG_CHERRYPICK.md @@ -35,12 +35,12 @@ Misskey의 전체 변경 사항을 확인하려면, [CHANGELOG.md#2024xx](CHANGE ### General - Feat: 위젯 영역을 숨길 수 있음 - 기존 Friendly UI 한정 기능이 다른 UI에서도 사용할 수 있도록 확대됨 -- Feat: '내용 숨기기'로 설정한 내용을 항상 보이게 설정할 수 있음 (kokonect-link/cherrypick#495) +- Feat: `내용 숨기기`로 설정한 내용을 항상 보이게 설정할 수 있음 (kokonect-link/cherrypick#495) - Feat: 내용이 긴 노트의 간략화 여부를 선택할 수 있음 (kokonect-link/cherrypick#495) - Feat: 답글로 작성된 노트를 간략화하여 표시할 수 있음 (kokonect-link/cherrypick#495) - 리액션한 노트는 옵션 활성화 유무와 상관없이 항상 표시됩니다. - Feat: 사용자 메뉴에서 원격 서버를 관리할 수 있음 (kokonect-link/cherrypick#502) - - 서버 차단, 서버 사일런스, 서버 미디어 사일런스 + - `서버 차단`, `서버 사일런스`, `서버 미디어 사일런스` - Feat: 노트 동작 버튼을 개인화할 수 있음 (kokonect-link/cherrypick#501) - Feat: 답글 대상 노트의 반투명 옵션을 선택할 수 있음 (kokonect-link/cherrypick#495) - Feat: 사용자 페이지의 미디어 탭을 그리드 레이아웃으로 설정할 수 있음 (kokonect-link/cherrypick#494) @@ -78,7 +78,7 @@ Misskey의 전체 변경 사항을 확인하려면, [CHANGELOG.md#2024xx](CHANGE - Enhance: 의도하지 않게 `검색`으로 검색할 수 있었던 부분을 개선함 - 이제 노트에서 검색 블록을 활성화 하려면 `[검색]`으로만 사용할 수 있습니다. - Enhance: 설정 페이지의 하단 여백 디자인을 조정함 -- Enhance: 노트 작성 시 '본문 미리보기'를 활성화한 경우, 본문에 내용이 있을 때만 표시되도록 변경함 +- Enhance: 노트 작성 시 `본문 미리보기`를 활성화한 경우, 본문에 내용이 있을 때만 표시되도록 변경함 - Fix: 환경설정 백업 시 일부 설정이 누락되어 백업될 수 있음 - Fix: 이미지 자르기를 할 때 이미지 크기 전체를 표시하지 못할 수 있음 - Fix: 페이지에서 페이지 생성 버튼이 본문에 중복으로 표시됨 From 68d5487df27b718f673186031e62914e5c0fc966 Mon Sep 17 00:00:00 2001 From: Mar0xy Date: Fri, 22 Sep 2023 00:21:57 +0200 Subject: [PATCH 275/315] upd: swap bcrypt to argon2 --- packages/backend/package.json | 1 + .../src/core/CreateSystemUserService.ts | 7 ++-- packages/backend/src/core/SignupService.ts | 7 ++-- .../src/server/api/SigninApiService.ts | 5 +-- .../src/server/api/SignupApiService.ts | 7 ++-- .../api/endpoints/admin/reset-password.ts | 5 +-- .../server/api/endpoints/i/2fa/key-done.ts | 5 +-- .../api/endpoints/i/2fa/register-key.ts | 5 +-- .../server/api/endpoints/i/2fa/register.ts | 5 +-- .../server/api/endpoints/i/2fa/remove-key.ts | 5 +-- .../server/api/endpoints/i/2fa/unregister.ts | 5 +-- .../server/api/endpoints/i/2fa/update-key.ts | 2 +- .../server/api/endpoints/i/change-password.ts | 9 ++--- .../server/api/endpoints/i/delete-account.ts | 5 +-- .../api/endpoints/i/regenerate-token.ts | 5 +-- .../server/api/endpoints/i/update-email.ts | 5 +-- .../server/api/endpoints/reset-password.ts | 7 ++-- pnpm-lock.yaml | 33 +++++++++++++++++-- 18 files changed, 84 insertions(+), 39 deletions(-) diff --git a/packages/backend/package.json b/packages/backend/package.json index 9ae88a8a97..1b4f04f20b 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -101,6 +101,7 @@ "accepts": "1.3.8", "ajv": "8.17.1", "archiver": "7.0.1", + "argon2": "^0.40.1", "async-mutex": "0.5.0", "bcryptjs": "2.4.3", "blurhash": "2.0.5", diff --git a/packages/backend/src/core/CreateSystemUserService.ts b/packages/backend/src/core/CreateSystemUserService.ts index 6c5b0f6a36..7de67dade1 100644 --- a/packages/backend/src/core/CreateSystemUserService.ts +++ b/packages/backend/src/core/CreateSystemUserService.ts @@ -5,7 +5,8 @@ import { randomUUID } from 'node:crypto'; import { Inject, Injectable } from '@nestjs/common'; -import bcrypt from 'bcryptjs'; +import * as argon2 from 'argon2'; +//import bcrypt from 'bcryptjs'; import { IsNull, DataSource } from 'typeorm'; import { genRsaKeyPair } from '@/misc/gen-key-pair.js'; import { MiUser } from '@/models/User.js'; @@ -32,8 +33,8 @@ export class CreateSystemUserService { const password = randomUUID(); // Generate hash of password - const salt = await bcrypt.genSalt(8); - const hash = await bcrypt.hash(password, salt); + //const salt = await bcrypt.genSalt(8); + const hash = await argon2.hash(password); // Generate secret const secret = generateNativeUserToken(); diff --git a/packages/backend/src/core/SignupService.ts b/packages/backend/src/core/SignupService.ts index cc8a3d6461..5374ec09f6 100644 --- a/packages/backend/src/core/SignupService.ts +++ b/packages/backend/src/core/SignupService.ts @@ -5,7 +5,8 @@ import { generateKeyPair } from 'node:crypto'; import { Inject, Injectable } from '@nestjs/common'; -import bcrypt from 'bcryptjs'; +//import bcrypt from 'bcryptjs'; +import * as argon2 from 'argon2'; import { DataSource, IsNull } from 'typeorm'; import { DI } from '@/di-symbols.js'; import type { MiMeta, UsedUsernamesRepository, UsersRepository } from '@/models/_.js'; @@ -69,8 +70,8 @@ export class SignupService { } // Generate hash of password - const salt = await bcrypt.genSalt(8); - hash = await bcrypt.hash(password, salt); + //const salt = await bcrypt.genSalt(8); + hash = await argon2.hash(password); } // Generate secret diff --git a/packages/backend/src/server/api/SigninApiService.ts b/packages/backend/src/server/api/SigninApiService.ts index edac9b3beb..2a21c73025 100644 --- a/packages/backend/src/server/api/SigninApiService.ts +++ b/packages/backend/src/server/api/SigninApiService.ts @@ -4,7 +4,8 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import bcrypt from 'bcryptjs'; +//import bcrypt from 'bcryptjs'; +import * as argon2 from 'argon2'; import * as OTPAuth from 'otpauth'; import { IsNull } from 'typeorm'; import { DI } from '@/di-symbols.js'; @@ -123,7 +124,7 @@ export class SigninApiService { const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id }); // Compare password - const same = await bcrypt.compare(password, profile.password!); + const same = await argon2.verify(profile.password!, password); const fail = async (status?: number, failure?: { id: string }) => { // Append signin history diff --git a/packages/backend/src/server/api/SignupApiService.ts b/packages/backend/src/server/api/SignupApiService.ts index c499638018..f99e2761a3 100644 --- a/packages/backend/src/server/api/SignupApiService.ts +++ b/packages/backend/src/server/api/SignupApiService.ts @@ -4,7 +4,8 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import bcrypt from 'bcryptjs'; +//import bcrypt from 'bcryptjs'; +import * as argon2 from 'argon2'; import { IsNull } from 'typeorm'; import { DI } from '@/di-symbols.js'; import type { RegistrationTicketsRepository, UsedUsernamesRepository, UserPendingsRepository, UserProfilesRepository, UsersRepository, MiRegistrationTicket, MiMeta } from '@/models/_.js'; @@ -179,8 +180,8 @@ export class SignupApiService { const code = secureRndstr(16, { chars: L_CHARS }); // Generate hash of password - const salt = await bcrypt.genSalt(8); - const hash = await bcrypt.hash(password, salt); + //const salt = await bcrypt.genSalt(8); + const hash = await argon2.hash(password); const pendingUser = await this.userPendingsRepository.insertOne({ id: this.idService.gen(), diff --git a/packages/backend/src/server/api/endpoints/admin/reset-password.ts b/packages/backend/src/server/api/endpoints/admin/reset-password.ts index 53db096c1d..828dbae712 100644 --- a/packages/backend/src/server/api/endpoints/admin/reset-password.ts +++ b/packages/backend/src/server/api/endpoints/admin/reset-password.ts @@ -4,7 +4,8 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import bcrypt from 'bcryptjs'; +//import bcrypt from 'bcryptjs'; +import * as argon2 from 'argon2'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { UsersRepository, UserProfilesRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; @@ -65,7 +66,7 @@ export default class extends Endpoint { // eslint- const passwd = secureRndstr(8); // Generate hash of password - const hash = bcrypt.hashSync(passwd); + const hash = await argon2.hash(passwd); await this.userProfilesRepository.update({ userId: user.id, diff --git a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts index 2ec2c5ca21..d87df588bf 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts @@ -3,7 +3,8 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import bcrypt from 'bcryptjs'; +//import bcrypt from 'bcryptjs'; +import * as argon2 from 'argon2'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; @@ -85,7 +86,7 @@ export default class extends Endpoint { } } - const passwordMatched = await bcrypt.compare(ps.password, profile.password ?? ''); + const passwordMatched = await argon2.verify(profile.password ?? '', ps.password); if (!passwordMatched) { throw new ApiError(meta.errors.incorrectPassword); } diff --git a/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts index 74903e2729..5621ff3dc1 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts @@ -3,7 +3,8 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import bcrypt from 'bcryptjs'; +//import bcrypt from 'bcryptjs'; +import * as argon2 from 'argon2'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { UserProfilesRepository } from '@/models/_.js'; @@ -216,7 +217,7 @@ export default class extends Endpoint { } } - const passwordMatched = await bcrypt.compare(ps.password, profile.password ?? ''); + const passwordMatched = await argon2.verify(profile.password ?? '', ps.password); if (!passwordMatched) { throw new ApiError(meta.errors.incorrectPassword); } diff --git a/packages/backend/src/server/api/endpoints/i/2fa/register.ts b/packages/backend/src/server/api/endpoints/i/2fa/register.ts index a54c598213..7283159f87 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/register.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/register.ts @@ -3,7 +3,8 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import bcrypt from 'bcryptjs'; +//import bcrypt from 'bcryptjs'; +import * as argon2 from 'argon2'; import * as OTPAuth from 'otpauth'; import * as QRCode from 'qrcode'; import { Inject, Injectable } from '@nestjs/common'; @@ -77,7 +78,7 @@ export default class extends Endpoint { // eslint- } } - const passwordMatched = await bcrypt.compare(ps.password, profile.password ?? ''); + const passwordMatched = await argon2.verify(profile.password ?? '', ps.password); if (!passwordMatched) { throw new ApiError(meta.errors.incorrectPassword); } diff --git a/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts index c350136eae..982cb7aee5 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts @@ -3,7 +3,8 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import bcrypt from 'bcryptjs'; +//import bcrypt from 'bcryptjs'; +import * as argon2 from 'argon2'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { UserProfilesRepository, UserSecurityKeysRepository } from '@/models/_.js'; @@ -66,7 +67,7 @@ export default class extends Endpoint { // eslint- } } - const passwordMatched = await bcrypt.compare(ps.password, profile.password ?? ''); + const passwordMatched = await argon2.verify(profile.password ?? '', ps.password); if (!passwordMatched) { throw new ApiError(meta.errors.incorrectPassword); } diff --git a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts index b5a53cc889..8da331505b 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts @@ -3,7 +3,8 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import bcrypt from 'bcryptjs'; +//import bcrypt from 'bcryptjs'; +import * as argon2 from 'argon2'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; @@ -62,7 +63,7 @@ export default class extends Endpoint { // eslint- } } - const passwordMatched = await bcrypt.compare(ps.password, profile.password ?? ''); + const passwordMatched = await argon2.verify(profile.password ?? '', ps.password); if (!passwordMatched) { throw new ApiError(meta.errors.incorrectPassword); } diff --git a/packages/backend/src/server/api/endpoints/i/2fa/update-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/update-key.ts index cfa07cc8d7..deb56a3ac4 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/update-key.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/update-key.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import bcrypt from 'bcryptjs'; +//import bcrypt from 'bcryptjs'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { UserSecurityKeysRepository } from '@/models/_.js'; diff --git a/packages/backend/src/server/api/endpoints/i/change-password.ts b/packages/backend/src/server/api/endpoints/i/change-password.ts index bb78d47149..6aedde717c 100644 --- a/packages/backend/src/server/api/endpoints/i/change-password.ts +++ b/packages/backend/src/server/api/endpoints/i/change-password.ts @@ -3,7 +3,8 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import bcrypt from 'bcryptjs'; +//import bcrypt from 'bcryptjs'; +import * as argon2 from 'argon2'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { UserProfilesRepository } from '@/models/_.js'; @@ -50,15 +51,15 @@ export default class extends Endpoint { // eslint- } } - const passwordMatched = await bcrypt.compare(ps.currentPassword, profile.password!); + const passwordMatched = await argon2.verify(profile.password!, ps.currentPassword); if (!passwordMatched) { throw new Error('incorrect password'); } // Generate hash of password - const salt = await bcrypt.genSalt(8); - const hash = await bcrypt.hash(ps.newPassword, salt); + //const salt = await bcrypt.genSalt(8); + const hash = await argon2.hash(ps.newPassword); await this.userProfilesRepository.update(me.id, { password: hash, diff --git a/packages/backend/src/server/api/endpoints/i/delete-account.ts b/packages/backend/src/server/api/endpoints/i/delete-account.ts index bfa0b4605d..af4d601ad6 100644 --- a/packages/backend/src/server/api/endpoints/i/delete-account.ts +++ b/packages/backend/src/server/api/endpoints/i/delete-account.ts @@ -3,7 +3,8 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import bcrypt from 'bcryptjs'; +//import bcrypt from 'bcryptjs'; +import * as argon2 from 'argon2'; import { Inject, Injectable } from '@nestjs/common'; import type { UsersRepository, UserProfilesRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; @@ -59,7 +60,7 @@ export default class extends Endpoint { // eslint- return; } - const passwordMatched = await bcrypt.compare(ps.password, profile.password!); + const passwordMatched = await argon2.verify(profile.password!, ps.password); if (!passwordMatched) { throw new Error('incorrect password'); } diff --git a/packages/backend/src/server/api/endpoints/i/regenerate-token.ts b/packages/backend/src/server/api/endpoints/i/regenerate-token.ts index 78f3cce9ad..e1cdfdc185 100644 --- a/packages/backend/src/server/api/endpoints/i/regenerate-token.ts +++ b/packages/backend/src/server/api/endpoints/i/regenerate-token.ts @@ -3,7 +3,8 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import bcrypt from 'bcryptjs'; +//import bcrypt from 'bcryptjs'; +import * as argon2 from 'argon2'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { UsersRepository, UserProfilesRepository } from '@/models/_.js'; @@ -43,7 +44,7 @@ export default class extends Endpoint { // eslint- const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id }); // Compare password - const same = await bcrypt.compare(ps.password, profile.password!); + const same = await argon2.verify(profile.password!, ps.password); if (!same) { throw new Error('incorrect password'); diff --git a/packages/backend/src/server/api/endpoints/i/update-email.ts b/packages/backend/src/server/api/endpoints/i/update-email.ts index da1faee30d..0be8bfb695 100644 --- a/packages/backend/src/server/api/endpoints/i/update-email.ts +++ b/packages/backend/src/server/api/endpoints/i/update-email.ts @@ -5,7 +5,8 @@ import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; -import bcrypt from 'bcryptjs'; +//import bcrypt from 'bcryptjs'; +import * as argon2 from 'argon2'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { MiMeta, UserProfilesRepository } from '@/models/_.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; @@ -96,7 +97,7 @@ export default class extends Endpoint { // eslint- } } - const passwordMatched = await bcrypt.compare(ps.password, profile.password!); + const passwordMatched = await argon2.verify(profile.password!, ps.password); if (!passwordMatched) { throw new ApiError(meta.errors.incorrectPassword); } diff --git a/packages/backend/src/server/api/endpoints/reset-password.ts b/packages/backend/src/server/api/endpoints/reset-password.ts index 9693892637..1639b57bc5 100644 --- a/packages/backend/src/server/api/endpoints/reset-password.ts +++ b/packages/backend/src/server/api/endpoints/reset-password.ts @@ -3,7 +3,8 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import bcrypt from 'bcryptjs'; +//import bcrypt from 'bcryptjs'; +import * as argon2 from 'argon2'; import { Inject, Injectable } from '@nestjs/common'; import type { UserProfilesRepository, PasswordResetRequestsRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; @@ -53,8 +54,8 @@ export default class extends Endpoint { // eslint- } // Generate hash of password - const salt = await bcrypt.genSalt(8); - const hash = await bcrypt.hash(ps.password, salt); + //const salt = await bcrypt.genSalt(8); + const hash = await argon2.hash(ps.password); await this.userProfilesRepository.update(req.userId, { password: hash, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e8b0cf43dd..c28b31b6c3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -188,6 +188,9 @@ importers: archiver: specifier: 7.0.1 version: 7.0.1 + argon2: + specifier: ^0.40.1 + version: 0.40.3 async-mutex: specifier: 0.5.0 version: 0.5.0 @@ -3558,6 +3561,10 @@ packages: resolution: {integrity: sha512-aGQIwo6/sWtyyqhVK4e1MtxYz4N1X8CNt6SOtCc+Wnczs5S5ONaLHDDR8LYaGn0MgOwvGgXyuZ5sJIfd7iyoUw==} engines: {node: '>=0.10'} + '@phc/format@1.0.0': + resolution: {integrity: sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==} + engines: {node: '>=10'} + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -5434,6 +5441,10 @@ packages: arg@5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + argon2@0.40.3: + resolution: {integrity: sha512-FrSmz4VeM91jwFvvjsQv9GYp6o/kARWoYKjbjDB2U5io1H3e5X67PYGclFDeQff6UXIhUd4aHR3mxCdBbMMuQw==} + engines: {node: '>=16.17.0'} + argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} @@ -7373,12 +7384,10 @@ packages: glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported glob@8.1.0: resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} engines: {node: '>=12'} - deprecated: Glob versions prior to v9 are no longer supported global-dirs@3.0.1: resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==} @@ -9098,6 +9107,10 @@ packages: node-addon-api@3.2.1: resolution: {integrity: sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==} + node-addon-api@8.2.0: + resolution: {integrity: sha512-qnyuI2ROiCkye42n9Tj5aX1ns7rzj6n7zW1XReSnLSL9v/vbLeR6fJq6PU27YU/ICfYw6W7Ouk/N7cysWu/hlw==} + engines: {node: ^18 || ^20 || >= 21} + node-bitmap@0.0.1: resolution: {integrity: sha512-Jx5lPaaLdIaOsj2mVLWMWulXF6GQVdyLvNSxmiYCvZ8Ma2hfKX0POoR2kgKOqz+oFsRreq0yYZjQ2wjE9VNzCA==} engines: {node: '>=v0.6.5'} @@ -9149,6 +9162,10 @@ packages: resolution: {integrity: sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==} hasBin: true + node-gyp-build@4.8.2: + resolution: {integrity: sha512-IRUxE4BVsHWXkV/SFOut4qTlagw2aM8T5/vnTsmrHJvVoKueJHRc/JaFND7QDDc61kLYUJ6qlZM3sqTSyx2dTw==} + hasBin: true + node-gyp@10.2.0: resolution: {integrity: sha512-sp3FonBAaFe4aYTcFdZUn2NYkbP7xroPGYvQmP4Nl5PxamznItBnNCgjrVTKrEfQynInMsJvZrdmqUnysCJ8rw==} engines: {node: ^16.14.0 || >=18.0.0} @@ -14491,6 +14508,8 @@ snapshots: jsprim: 1.4.2 sshpk: 1.17.0 + '@phc/format@1.0.0': {} + '@pkgjs/parseargs@0.11.0': optional: true @@ -16923,6 +16942,12 @@ snapshots: arg@5.0.2: {} + argon2@0.40.3: + dependencies: + '@phc/format': 1.0.0 + node-addon-api: 8.2.0 + node-gyp-build: 4.8.2 + argparse@1.0.10: dependencies: sprintf-js: 1.0.3 @@ -21746,6 +21771,8 @@ snapshots: node-addon-api@3.2.1: optional: true + node-addon-api@8.2.0: {} + node-bitmap@0.0.1: {} node-domexception@1.0.0: {} @@ -21782,6 +21809,8 @@ snapshots: node-gyp-build@4.6.0: optional: true + node-gyp-build@4.8.2: {} + node-gyp@10.2.0: dependencies: env-paths: 2.2.1 From 1da00235abac892026729f91b91d938c8d982b19 Mon Sep 17 00:00:00 2001 From: Mar0xy Date: Wed, 27 Sep 2023 21:46:56 +0200 Subject: [PATCH 276/315] upd: rehash misskey passwords with argon2 on login --- .../backend/src/server/api/SigninApiService.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/backend/src/server/api/SigninApiService.ts b/packages/backend/src/server/api/SigninApiService.ts index 2a21c73025..2da07710de 100644 --- a/packages/backend/src/server/api/SigninApiService.ts +++ b/packages/backend/src/server/api/SigninApiService.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -//import bcrypt from 'bcryptjs'; +import bcrypt from 'bcryptjs'; import * as argon2 from 'argon2'; import * as OTPAuth from 'otpauth'; import { IsNull } from 'typeorm'; @@ -124,7 +124,7 @@ export class SigninApiService { const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id }); // Compare password - const same = await argon2.verify(profile.password!, password); + const same = await argon2.verify(profile.password!, password) || bcrypt.compareSync(password, profile.password!); const fail = async (status?: number, failure?: { id: string }) => { // Append signin history @@ -141,6 +141,12 @@ export class SigninApiService { if (!profile.twoFactorEnabled) { if (same) { + if (profile.password!.startsWith('$2')) { + const newHash = await argon2.hash(password); + this.userProfilesRepository.update(user.id, { + password: newHash + }); + } return this.signinService.signin(request, reply, user); } else { return await fail(403, { @@ -157,6 +163,12 @@ export class SigninApiService { } try { + if (profile.password!.startsWith('$2')) { + const newHash = await argon2.hash(password); + this.userProfilesRepository.update(user.id, { + password: newHash + }); + } await this.userAuthService.twoFactorAuthenticate(profile, token); } catch (e) { return await fail(403, { From 28b92056c2a07f87d02509c446636cf6c46911f5 Mon Sep 17 00:00:00 2001 From: NoriDev Date: Sat, 5 Oct 2024 21:26:51 +0900 Subject: [PATCH 277/315] tweak 36c922fb --- packages/frontend/src/boot/common.ts | 2 ++ packages/frontend/src/pages/settings/navbar.vue | 9 ++++++--- packages/frontend/src/store.ts | 8 +++++--- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/packages/frontend/src/boot/common.ts b/packages/frontend/src/boot/common.ts index cf832b0e18..b1adace208 100644 --- a/packages/frontend/src/boot/common.ts +++ b/packages/frontend/src/boot/common.ts @@ -66,6 +66,8 @@ export async function common(createVue: () => App) { let isClientMigrated = false; const showPushNotificationDialog = miLocalStorage.getItem('showPushNotificationDialog'); + if (miLocalStorage.getItem('ui') === null) miLocalStorage.setItem('ui', 'friendly'); + if (instance.swPublickey && ('PushManager' in window) && $i && $i.token && showPushNotificationDialog == null) { popup(defineAsyncComponent(() => import('@/components/MkPushNotification.vue')), {}, {}, 'closed'); } diff --git a/packages/frontend/src/pages/settings/navbar.vue b/packages/frontend/src/pages/settings/navbar.vue index f982f23faa..ba5f616cce 100644 --- a/packages/frontend/src/pages/settings/navbar.vue +++ b/packages/frontend/src/pages/settings/navbar.vue @@ -87,6 +87,9 @@ import { defaultStore } from '@/store.js'; import { reloadAsk } from '@/scripts/reload-ask.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; +import { miLocalStorage } from '@/local-storage.js'; + +const isFriendly = ref(miLocalStorage.getItem('ui') === 'friendly'); const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default)); @@ -152,11 +155,11 @@ function reset() { } function resetButtomNavbar() { - defaultStore.set('showHomeButtonInNavbar', true); - defaultStore.set('showExploreButtonInNavbar', true); + defaultStore.set('showHomeButtonInNavbar', !isFriendly.value); + defaultStore.set('showExploreButtonInNavbar', isFriendly.value); defaultStore.set('showSearchButtonInNavbar', false); defaultStore.set('showNotificationButtonInNavbar', true); - defaultStore.set('showMessageButtonInNavbar', true); + defaultStore.set('showMessageButtonInNavbar', isFriendly.value); defaultStore.set('showWidgetButtonInNavbar', true); } diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts index 0ef82ff31c..481915217c 100644 --- a/packages/frontend/src/store.ts +++ b/packages/frontend/src/store.ts @@ -62,6 +62,8 @@ export const noteViewInterruptors: NoteViewInterruptor[] = []; export const notePostInterruptors: NotePostInterruptor[] = []; export const pageViewInterruptors: PageViewInterruptor[] = []; +const isFriendly = ref(miLocalStorage.getItem('ui') === 'friendly'); + // TODO: それぞれいちいちwhereとかdefaultというキーを付けなきゃいけないの冗長なのでなんとかする(ただ型定義が面倒になりそう) // あと、現行の定義の仕方なら「whereが何であるかに関わらずキー名の重複不可」という制約を付けられるメリットもあるからそのメリットを引き継ぐ方法も考えないといけない export const defaultStore = markRaw(new Storage('base', { @@ -658,7 +660,7 @@ export const defaultStore = markRaw(new Storage('base', { // - Settings/Navigation bar showMenuButtonInNavbar: { where: 'device', - default: true, + default: !isFriendly.value, }, showHomeButtonInNavbar: { where: 'device', @@ -666,7 +668,7 @@ export const defaultStore = markRaw(new Storage('base', { }, showExploreButtonInNavbar: { where: 'device', - default: true, + default: isFriendly.value, }, showSearchButtonInNavbar: { where: 'device', @@ -678,7 +680,7 @@ export const defaultStore = markRaw(new Storage('base', { }, showMessageButtonInNavbar: { where: 'device', - default: true, + default: isFriendly.value, }, showWidgetButtonInNavbar: { where: 'device', From 0dc322b61d0830cc0c7d3a8745f7d13da32d4594 Mon Sep 17 00:00:00 2001 From: NoriDev Date: Sat, 5 Oct 2024 21:42:37 +0900 Subject: [PATCH 278/315] =?UTF-8?q?enhance(backend):=20=EB=B3=B4=EC=95=88?= =?UTF-8?q?=20=ED=96=A5=EC=83=81=EC=9D=84=20=EC=9C=84=ED=95=B4=20=EB=B9=84?= =?UTF-8?q?=EB=B0=80=EB=B2=88=ED=98=B8=20=ED=95=B4=EC=8B=B1=20=EC=95=8C?= =?UTF-8?q?=EA=B3=A0=EB=A6=AC=EC=A6=98=EC=9D=B4=20`bcrypt`=EC=97=90?= =?UTF-8?q?=EC=84=9C=20`argon2`=EB=A1=9C=20=EB=B3=80=EA=B2=BD=EB=90=A8=20(?= =?UTF-8?q?kokonect-link/cherrypick#511)=20=20=20-=2072=20=EB=B0=94?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8=EB=A5=BC=20=EC=B4=88=EA=B3=BC=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=EB=B9=84=EB=B0=80=EB=B2=88=ED=98=B8=EB=A5=BC=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=ED=95=A0=20=EC=88=98=20=EC=9E=88=EC=8A=B5?= =?UTF-8?q?=EB=8B=88=EB=8B=A4.=20=20=20-=20=EC=9D=B4=EB=A1=9C=EC=8D=A8=20`?= =?UTF-8?q?Sharkey`,=20`FireFish`,=20`IceShrimp`=20=EB=93=B1=EC=9D=98=20?= =?UTF-8?q?=ED=81=B4=EB=9D=BC=EC=9D=B4=EC=96=B8=ED=8A=B8=EC=97=90=EC=84=9C?= =?UTF-8?q?=20`CherryPick`=EC=9C=BC=EB=A1=9C=20=EC=9D=B4=EC=A0=84=ED=95=A0?= =?UTF-8?q?=20=EB=95=8C=20=EC=95=94=ED=98=B8=20=ED=98=B8=ED=99=98=EC=84=B1?= =?UTF-8?q?=EC=9D=B4=20=EB=B3=B4=EC=9E=A5=EB=90=A9=EB=8B=88=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG_CHERRYPICK.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG_CHERRYPICK.md b/CHANGELOG_CHERRYPICK.md index cd2737f4ae..64ab9e9f61 100644 --- a/CHANGELOG_CHERRYPICK.md +++ b/CHANGELOG_CHERRYPICK.md @@ -29,8 +29,11 @@ Misskey의 전체 변경 사항을 확인하려면, [CHANGELOG.md#2024xx](CHANGE Misskey의 전체 변경 사항을 확인하려면, [CHANGELOG.md#2024xx](CHANGELOG.md#2024xx) 문서를 참고하십시오. ## NOTE -- Change: Enable condensed line의 기본값이 꺼짐으로 변경됨 +- Enable condensed line의 기본값이 꺼짐으로 변경됨 - 활성화하면 글자를 표시하기 위한 여유 공간이 좁을 때 디자인이 상대적으로 어색하게 보일 수 있으며, 실험실 기능이므로 이 기능이 변경하는 부분을 확실히 알고 있는 사용자만 활성화할 것을 권장합니다. +- 비밀번호 해싱 알고리즘이 `bcrypt`에서 `argon2`로 변경됨 + - 이 변경으로 이후에 비밀번호를 변경하거나 신규로 가입한 사용자는 `argon2`를 사용하여 비밀번호 해시가 생성됩니다. + - 이전에 가입한 사용자는 비밀번호를 변경하지 않아도 `bcypt`를 사용할 수 있으며 여전히 기존과 동일하게 호환됩니다. ### General - Feat: 위젯 영역을 숨길 수 있음 @@ -85,6 +88,9 @@ Misskey의 전체 변경 사항을 확인하려면, [CHANGELOG.md#2024xx](CHANGE - Fix: 노트 본문의 사용자 멘션 영역을 클릭하면 노트 상세 페이지가 표시됨 ### Server +- Enhance: 보안 향상을 위해 비밀번호 해싱 알고리즘이 `bcrypt`에서 `argon2`로 변경됨 (kokonect-link/cherrypick#511) + - 72 바이트를 초과하는 비밀번호를 사용할 수 있습니다. + - 이로써 `Sharkey`, `FireFish`, `IceShrimp` 등의 클라이언트에서 `CherryPick`으로 이전할 때 암호 호환성이 보장됩니다. - Fix: 이모지를 등록하거나 가져오려고 할 때 오류가 발생할 수 있음 (kokonect-link/cherrypick#487), (kokonect-link/cherrypick#508) - Fix: 사용자 이름에 `.`이 있는 경우 멘션을 할 수 없음 (kokonect-link/cherrypick#509) From 35a58cd44b7781bb8e24c0b43f327cbe4b566ad3 Mon Sep 17 00:00:00 2001 From: NoriDev Date: Sat, 5 Oct 2024 22:33:49 +0900 Subject: [PATCH 279/315] =?UTF-8?q?=F0=9F=8E=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/pages/user/index.timeline.files.files.vue | 2 +- packages/frontend/src/pages/user/index.timeline.files.vue | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/frontend/src/pages/user/index.timeline.files.files.vue b/packages/frontend/src/pages/user/index.timeline.files.files.vue index 049746d6d9..358a25d8ab 100644 --- a/packages/frontend/src/pages/user/index.timeline.files.files.vue +++ b/packages/frontend/src/pages/user/index.timeline.files.files.vue @@ -91,7 +91,7 @@ onUnmounted(() => { .img { display: flex; position: relative; - height: 256px; + height: 224px; border-radius: 3px; overflow: clip; } diff --git a/packages/frontend/src/pages/user/index.timeline.files.vue b/packages/frontend/src/pages/user/index.timeline.files.vue index b6d95263e2..3782a49867 100644 --- a/packages/frontend/src/pages/user/index.timeline.files.vue +++ b/packages/frontend/src/pages/user/index.timeline.files.vue @@ -42,7 +42,7 @@ defineExpose({ diff --git a/packages/frontend/src/pages/reversi/game.board.vue b/packages/frontend/src/pages/reversi/game.board.vue index 612ee50487..04e420e1bf 100644 --- a/packages/frontend/src/pages/reversi/game.board.vue +++ b/packages/frontend/src/pages/reversi/game.board.vue @@ -8,9 +8,13 @@ SPDX-License-Identifier: AGPL-3.0-only
({{ i18n.ts._reversi.black }}) - +
+ +
vs - +
+ +
({{ i18n.ts._reversi.white }})
@@ -125,10 +129,24 @@ SPDX-License-Identifier: AGPL-3.0-only
+
+
{{ i18n.ts.reaction }}
+
+ + +
+
+
{{ i18n.ts._reversi.showBoardLabels }} + {{ i18n.ts._reversi.showReaction }} {{ i18n.ts._reversi.useAvatarAsStone }}
@@ -151,9 +169,12 @@ import * as Misskey from 'cherrypick-js'; import * as Reversi from 'misskey-reversi'; import { useInterval } from '@@/js/use-interval.js'; import { url } from '@@/js/config.js'; +import XEmojiBalloon from './game.emoji-balloon.vue'; +import type { UnicodeEmojiDef } from '@@/js/emojilist.js'; import MkButton from '@/components/MkButton.vue'; import MkFolder from '@/components/MkFolder.vue'; import MkSwitch from '@/components/MkSwitch.vue'; +import MkRippleEffect from '@/components/MkRippleEffect.vue'; import { deepClone } from '@/scripts/clone.js'; import { signinRequired } from '@/account.js'; import { i18n } from '@/i18n.js'; @@ -161,6 +182,7 @@ import { misskeyApi } from '@/scripts/misskey-api.js'; import { userPage } from '@/filters/user.js'; import * as sound from '@/scripts/sound.js'; import * as os from '@/os.js'; +import { reactionPicker } from '@/scripts/reaction-picker.js'; import { confetti } from '@/scripts/confetti.js'; import { defaultStore } from '@/store.js'; import { getStaticImageUrl } from '@/scripts/media-proxy.js'; @@ -173,6 +195,7 @@ const props = defineProps<{ }>(); const showBoardLabels = ref(false); +const showReaction = ref(true); const useAvatarAsStone = ref(true); const autoplaying = ref(false); // eslint-disable-next-line vue/no-setup-props-reactivity-loss @@ -190,6 +213,7 @@ const iAmPlayer = computed(() => { return game.value.user1Id === $i.id || game.value.user2Id === $i.id; }); +// true: 黒, false: 白 const myColor = computed(() => { if (!iAmPlayer.value) return null; if (game.value.user1Id === $i.id && game.value.black === 1) return true; @@ -474,9 +498,111 @@ function resetTimer() { playAnimationTimer = setTimeout(() => playAnimation.value = false, 5000); } +const _reactionEmojis = ref(defaultStore.reactiveState.reactions.value); +const reactionEmojis = computed(() => _reactionEmojis.value.slice(0, 10)); + +const blackUserEl = ref(null); +const whiteUserEl = ref(null); + +const canReact = ref(true); +let canReactFallbackTimer: number | null = null; + +function getKey(emoji: string | Misskey.entities.EmojiSimple | UnicodeEmojiDef): string { + return typeof emoji === 'string' ? emoji : 'char' in emoji ? emoji.char : `:${emoji.name}:`; +} + +// 既にでている絵文字をクリックした際の挙動 +function onReactionEmojiClick(emoji: string, ev?: MouseEvent) { + console.log('emoji click'); + const el = ev && (ev.currentTarget ?? ev.target) as HTMLElement | null | undefined; + if (el) { + const rect = el.getBoundingClientRect(); + const x = rect.left + (el.offsetWidth / 2); + const y = rect.top + (el.offsetHeight / 2); + os.popup(MkRippleEffect, { x, y }, {}, 'end'); + } + + const key = getKey(emoji); + + sendReaction(key); + + if (!_reactionEmojis.value.includes(key)) { + _reactionEmojis.value.unshift(key); + } +} + +// #region リアクションピッカー +const reactButton = ref(null); + +function onReactionPickerClick() { + reactionPicker.show(reactButton.value ?? null, reaction => { + const key = getKey(reaction); + sendReaction(key); + + if (!_reactionEmojis.value.includes(key)) { + _reactionEmojis.value.unshift(key); + } + }); +} +// #endregion + +function sendReaction(emojiKey: string) { + console.log('send emoji'); + if (!canReact.value) return; + canReact.value = false; + + // リアクション音は受信時のみ + props.connection!.send('reaction', emojiKey); + + // リアクションを無効にする(受信を確認できて3秒後 or 明らかに遅い場合は解除) + if (canReactFallbackTimer != null) { + window.clearTimeout(canReactFallbackTimer); + } + canReactFallbackTimer = window.setTimeout(() => { + if (!canReact.value) { + canReact.value = true; + } + }, 10000); +} + +function onReacted(payload: Parameters['0']) { + const { userId, reaction } = payload; + + if (showReaction.value || userId === $i.id) { + sound.playMisskeySfx('reaction'); + + const el = (userId === blackUser.value.id) ? blackUserEl.value : whiteUserEl.value; + + if (el) { + const rect = el.getBoundingClientRect(); + const x = rect.right; + const y = rect.bottom; + os.popup(XEmojiBalloon, { + reaction, + tail: 'left', + x, + y, + }, {}, 'end'); + } + } + + if (userId === $i.id) { + // リアクションが可能になるまでのタイマー + window.setTimeout(() => { + if (canReactFallbackTimer != null) { + window.clearTimeout(canReactFallbackTimer); + } + if (!canReact.value) { + canReact.value = true; + } + }, 3000); + } +} + onMounted(() => { if (props.connection != null) { props.connection.on('log', onStreamLog); + props.connection.on('reacted', onReacted); props.connection.on('ended', onStreamEnded); } @@ -490,6 +616,7 @@ onMounted(() => { onActivated(() => { if (props.connection != null) { props.connection.on('log', onStreamLog); + props.connection.on('reacted', onReacted); props.connection.on('ended', onStreamEnded); } }); @@ -497,6 +624,7 @@ onActivated(() => { onDeactivated(() => { if (props.connection != null) { props.connection.off('log', onStreamLog); + props.connection.off('reacted', onReacted); props.connection.off('ended', onStreamEnded); } }); @@ -504,6 +632,7 @@ onDeactivated(() => { onUnmounted(() => { if (props.connection != null) { props.connection.off('log', onStreamLog); + props.connection.off('reacted', onReacted); props.connection.off('ended', onStreamEnded); } @@ -658,4 +787,63 @@ $gap: 4px; height: 100%; border-radius: 100%; } + +.reactionLabel { + font-size: 0.85em; + padding: 0 0 8px 0; + user-select: none; + + &:empty { + display: none; + } +} + +.reactionPickerBar { + display: flex; + flex-wrap: wrap; + padding: 12px; + background: var(--bg); + border-radius: calc(var(--radius) / 2); +} + +.emojisItem { + font-size: 24px; + border-radius: 4px; + width: 40px; + height: 40px; + + &.plus { + font-size: 16px; + + &:active { + background: var(--accentedBg); + } + } + + &:not(:disabled):focus-visible { + outline: solid 2px var(--focus); + z-index: 1; + } + + &:not(:disabled):hover { + background: rgba(0, 0, 0, 0.05); + } + + &:not(:disabled):active { + background: var(--accent); + box-shadow: inset 0 0.15em 0.3em rgba(27, 31, 35, 0.15); + } + + &:disabled { + opacity: 0.7; + } + + > .emoji { + height: 1.25em; + vertical-align: -.25em; + pointer-events: none; + width: 100%; + object-fit: contain; + } +} diff --git a/packages/frontend/src/pages/reversi/game.emoji-balloon.vue b/packages/frontend/src/pages/reversi/game.emoji-balloon.vue new file mode 100644 index 0000000000..6beebebd4c --- /dev/null +++ b/packages/frontend/src/pages/reversi/game.emoji-balloon.vue @@ -0,0 +1,92 @@ + + + + + + + From 86ab74e4f1b88ea8e78f7c9813bede8d6feaebd3 Mon Sep 17 00:00:00 2001 From: NoriDev Date: Sun, 6 Oct 2024 20:10:55 +0900 Subject: [PATCH 289/315] chore --- packages/frontend-embed/eslint.config.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/frontend-embed/eslint.config.js b/packages/frontend-embed/eslint.config.js index dd8f03dac5..917bd3a82a 100644 --- a/packages/frontend-embed/eslint.config.js +++ b/packages/frontend-embed/eslint.config.js @@ -24,10 +24,11 @@ export default [ require: false, __dirname: false, - // Misskey + // CherryPick _DEV_: false, _LANGS_: false, _VERSION_: false, + _BASEDMISSKEYVERSION_: false, _ENV_: false, _PERF_PREFIX_: false, _DATA_TRANSFER_DRIVE_FILE_: false, From 1a4cee0dc38c60fc8ef975a91e540f5168fcd621 Mon Sep 17 00:00:00 2001 From: NoriDev Date: Sun, 6 Oct 2024 20:40:21 +0900 Subject: [PATCH 290/315] =?UTF-8?q?Lint=EB=A5=BC=20=EC=9C=84=ED=95=9C=20?= =?UTF-8?q?=EC=8B=A0=EA=B7=9C=20=ED=88=B4=20=EC=B6=94=EA=B0=80=20(Biome)?= =?UTF-8?q?=20(1673beta/cherrypick#105)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 + packages/backend/biome.json | 127 +++++++++++++++++++++ packages/backend/package.json | 4 + packages/cherrypick-js/biome.json | 126 +++++++++++++++++++++ packages/cherrypick-js/package.json | 4 + packages/frontend-embed/biome.json | 12 ++ packages/frontend-embed/package.json | 6 +- packages/frontend-shared/biome.json | 12 ++ packages/frontend-shared/package.json | 6 +- packages/frontend/biome.json | 12 ++ packages/frontend/package.json | 6 +- packages/misskey-bubble-game/biome.json | 126 +++++++++++++++++++++ packages/misskey-bubble-game/package.json | 6 +- packages/misskey-reversi/biome.json | 122 ++++++++++++++++++++ packages/misskey-reversi/package.json | 6 +- packages/sw/biome.json | 119 ++++++++++++++++++++ packages/sw/package.json | 6 +- pnpm-lock.yaml | 129 ++++++++++++++++++++-- 18 files changed, 818 insertions(+), 13 deletions(-) create mode 100644 packages/backend/biome.json create mode 100644 packages/cherrypick-js/biome.json create mode 100644 packages/frontend-embed/biome.json create mode 100644 packages/frontend-shared/biome.json create mode 100644 packages/frontend/biome.json create mode 100644 packages/misskey-bubble-game/biome.json create mode 100644 packages/misskey-reversi/biome.json create mode 100644 packages/sw/biome.json diff --git a/package.json b/package.json index f2cccf75b8..5946d3289f 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "watch": "pnpm dev", "dev": "node scripts/dev.mjs", "lint": "pnpm -r lint", + "biome-lint": "pnpm -r biome-lint", "cy:open": "pnpm cypress open --browser --e2e --config-file=cypress.config.ts", "cy:run": "pnpm cypress run", "e2e": "pnpm start-server-and-test start:test http://localhost:61812 cy:run", @@ -68,6 +69,7 @@ "glob": "11.0.0" }, "devDependencies": { + "@biomejs/biome": "1.9.3", "@misskey-dev/eslint-plugin": "2.0.3", "@types/node": "20.14.12", "@typescript-eslint/eslint-plugin": "7.17.0", diff --git a/packages/backend/biome.json b/packages/backend/biome.json new file mode 100644 index 0000000000..ab73fd8682 --- /dev/null +++ b/packages/backend/biome.json @@ -0,0 +1,127 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.8.3/schema.json", + "organizeImports": { "enabled": true }, + "linter": { + "enabled": true, + "rules": { + "recommended": false, + "complexity": { + "noBannedTypes": "error", + "noExtraBooleanCast": "error", + "noMultipleSpacesInRegularExpressionLiterals": "error", + "noUselessCatch": "error", + "noUselessTypeConstraint": "error", + "noWith": "error" + }, + "correctness": { + "noConstAssign": "error", + "noConstantCondition": "warn", + "noEmptyCharacterClassInRegex": "error", + "noEmptyPattern": "warn", + "noGlobalObjectCalls": "error", + "noInnerDeclarations": "off", + "noInvalidConstructorSuper": "error", + "noNewSymbol": "error", + "noNonoctalDecimalEscape": "error", + "noPrecisionLoss": "error", + "noSelfAssign": "error", + "noSetterReturn": "error", + "noSwitchDeclarations": "error", + "noUndeclaredVariables": "error", + "noUnreachable": "error", + "noUnreachableSuper": "error", + "noUnsafeFinally": "error", + "noUnsafeOptionalChaining": "error", + "noUnusedLabels": "error", + "noUnusedVariables": "error", + "useArrayLiterals": "off", + "useIsNan": "error", + "useValidForDirection": "error", + "useYield": "error" + }, + "style": { + "noDefaultExport": "warn", + "noInferrableTypes": "warn", + "noNamespace": "error", + "noNonNullAssertion": "warn", + "noParameterAssign": "warn", + "noRestrictedGlobals": { + "level": "error", + "options": { "deniedGlobals": ["__dirname", "__filename"] } + }, + "noVar": "error", + "useAsConstAssertion": "error" + }, + "suspicious": { + "noAsyncPromiseExecutor": "off", + "noCatchAssign": "error", + "noClassAssign": "error", + "noCompareNegZero": "error", + "noControlCharactersInRegex": "warn", + "noDebugger": "error", + "noDoubleEquals": "error", + "noDuplicateCase": "error", + "noDuplicateClassMembers": "error", + "noDuplicateObjectKeys": "error", + "noDuplicateParameters": "error", + "noEmptyBlockStatements": "off", + "noExplicitAny": "warn", + "noExtraNonNullAssertion": "error", + "noFallthroughSwitchClause": "error", + "noFunctionAssign": "error", + "noGlobalAssign": "error", + "noImportAssign": "error", + "noMisleadingCharacterClass": "error", + "noMisleadingInstantiator": "error", + "noPrototypeBuiltins": "error", + "noRedeclare": "error", + "noShadowRestrictedNames": "error", + "noUnsafeDeclarationMerging": "error", + "noUnsafeNegation": "error", + "useGetterReturn": "error", + "useValidTypeof": "error" + } + }, + "ignore": [ + "**/node_modules", + "built", + "@types/**/*", + "migration" + ] + }, + "overrides": [ + { + "include": ["*.ts", "*.tsx", "*.mts", "*.cts"], + "linter": { + "rules": { + "correctness": { + "noConstAssign": "off", + "noGlobalObjectCalls": "off", + "noInvalidConstructorSuper": "off", + "noInvalidNewBuiltin": "off", + "noNewSymbol": "off", + "noSetterReturn": "off", + "noUndeclaredVariables": "off", + "noUnreachable": "off", + "noUnreachableSuper": "off" + }, + "style": { + "noArguments": "error", + "noVar": "error", + "useConst": "error" + }, + "suspicious": { + "noDuplicateClassMembers": "off", + "noDuplicateObjectKeys": "off", + "noDuplicateParameters": "off", + "noFunctionAssign": "off", + "noImportAssign": "off", + "noRedeclare": "off", + "noUnsafeNegation": "off", + "useGetterReturn": "off" + } + } + } + } + ] +} diff --git a/packages/backend/package.json b/packages/backend/package.json index 1b4f04f20b..3b5dea1e96 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -22,6 +22,9 @@ "typecheck": "tsc --noEmit", "eslint": "eslint --quiet \"src/**/*.ts\"", "lint": "pnpm typecheck && pnpm eslint", + "biome-lint": "pnpm typecheck && pnpm biome lint", + "format": "pnpm biome format", + "format:write": "pnpm biome format --write", "jest": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --forceExit --config jest.config.unit.cjs", "jest:e2e": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --forceExit --config jest.config.e2e.cjs", "jest-and-coverage": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --coverage --forceExit --config jest.config.unit.cjs", @@ -192,6 +195,7 @@ "xev": "3.0.2" }, "devDependencies": { + "@biomejs/biome": "1.9.3", "@jest/globals": "29.7.0", "@nestjs/platform-express": "10.4.3", "@simplewebauthn/types": "10.0.0", diff --git a/packages/cherrypick-js/biome.json b/packages/cherrypick-js/biome.json new file mode 100644 index 0000000000..fae2bf52ce --- /dev/null +++ b/packages/cherrypick-js/biome.json @@ -0,0 +1,126 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.8.3/schema.json", + "organizeImports": { "enabled": true }, + "linter": { + "enabled": true, + "rules": { + "recommended": false, + "complexity": { + "noBannedTypes": "error", + "noExtraBooleanCast": "error", + "noMultipleSpacesInRegularExpressionLiterals": "error", + "noUselessCatch": "error", + "noUselessTypeConstraint": "error", + "noWith": "error" + }, + "correctness": { + "noConstAssign": "error", + "noConstantCondition": "warn", + "noEmptyCharacterClassInRegex": "error", + "noEmptyPattern": "warn", + "noGlobalObjectCalls": "error", + "noInnerDeclarations": "off", + "noInvalidConstructorSuper": "error", + "noNewSymbol": "error", + "noNonoctalDecimalEscape": "error", + "noPrecisionLoss": "error", + "noSelfAssign": "error", + "noSetterReturn": "error", + "noSwitchDeclarations": "error", + "noUndeclaredVariables": "error", + "noUnreachable": "error", + "noUnreachableSuper": "error", + "noUnsafeFinally": "error", + "noUnsafeOptionalChaining": "error", + "noUnusedLabels": "error", + "noUnusedVariables": "error", + "useArrayLiterals": "off", + "useIsNan": "error", + "useValidForDirection": "error", + "useYield": "error" + }, + "style": { + "noDefaultExport": "warn", + "noInferrableTypes": "warn", + "noNamespace": "error", + "noNonNullAssertion": "warn", + "noParameterAssign": "warn", + "noVar": "error", + "useAsConstAssertion": "error" + }, + "suspicious": { + "noAsyncPromiseExecutor": "off", + "noCatchAssign": "error", + "noClassAssign": "error", + "noCompareNegZero": "error", + "noControlCharactersInRegex": "warn", + "noDebugger": "error", + "noDoubleEquals": "error", + "noDuplicateCase": "error", + "noDuplicateClassMembers": "error", + "noDuplicateObjectKeys": "error", + "noDuplicateParameters": "error", + "noEmptyBlockStatements": "off", + "noExplicitAny": "warn", + "noExtraNonNullAssertion": "error", + "noFallthroughSwitchClause": "error", + "noFunctionAssign": "error", + "noGlobalAssign": "error", + "noImportAssign": "error", + "noMisleadingCharacterClass": "error", + "noMisleadingInstantiator": "error", + "noPrototypeBuiltins": "error", + "noRedeclare": "error", + "noShadowRestrictedNames": "error", + "noUnsafeDeclarationMerging": "error", + "noUnsafeNegation": "error", + "useGetterReturn": "error", + "useValidTypeof": "error" + } + }, + "ignore": [ + "**/node_modules", + "built", + "coverage", + "jest.config.ts", + "test", + "test-d", + "generator" + ] + }, + "overrides": [ + { + "include": ["*.ts", "*.tsx"], + "linter": { + "rules": { + "correctness": { + "noConstAssign": "off", + "noGlobalObjectCalls": "off", + "noInvalidConstructorSuper": "off", + "noInvalidNewBuiltin": "off", + "noNewSymbol": "off", + "noSetterReturn": "off", + "noUndeclaredVariables": "off", + "noUnreachable": "off", + "noUnreachableSuper": "off" + }, + "style": { + "noArguments": "error", + "noVar": "error", + "useConst": "error" + }, + "suspicious": { + "noDuplicateClassMembers": "off", + "noDuplicateObjectKeys": "off", + "noDuplicateParameters": "off", + "noFunctionAssign": "off", + "noImportAssign": "off", + "noRedeclare": "off", + "noUnsafeNegation": "off", + "useGetterReturn": "off" + } + } + } + } + ] +} diff --git a/packages/cherrypick-js/package.json b/packages/cherrypick-js/package.json index 6152b1f802..3f1a04eff8 100644 --- a/packages/cherrypick-js/package.json +++ b/packages/cherrypick-js/package.json @@ -26,6 +26,9 @@ "eslint": "eslint './**/*.{js,jsx,ts,tsx}'", "typecheck": "tsc --noEmit", "lint": "pnpm typecheck && pnpm eslint", + "biome-lint": "pnpm typecheck && pnpm biome lint", + "format": "pnpm biome format", + "format:write": "pnpm biome format --write", "jest": "jest --coverage --detectOpenHandles", "test": "pnpm jest && pnpm tsd", "update-autogen-code": "pnpm --filter cherrypick-js-type-generator generate && ncp generator/built/autogen src/autogen" @@ -36,6 +39,7 @@ "directory": "packages/cherrypick-js" }, "devDependencies": { + "@biomejs/biome": "1.9.3", "@microsoft/api-extractor": "7.47.9", "@swc/jest": "0.2.36", "@types/jest": "29.5.13", diff --git a/packages/frontend-embed/biome.json b/packages/frontend-embed/biome.json new file mode 100644 index 0000000000..3867749276 --- /dev/null +++ b/packages/frontend-embed/biome.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.8.3/schema.json", + "organizeImports": { + "enabled": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + } +} diff --git a/packages/frontend-embed/package.json b/packages/frontend-embed/package.json index fe9602a6a1..86fcdf0d81 100644 --- a/packages/frontend-embed/package.json +++ b/packages/frontend-embed/package.json @@ -8,7 +8,10 @@ "build": "vite build", "typecheck": "vue-tsc --noEmit", "eslint": "eslint --quiet \"src/**/*.{ts,vue}\"", - "lint": "pnpm typecheck && pnpm eslint" + "lint": "pnpm typecheck && pnpm eslint", + "biome-lint": "pnpm typecheck && pnpm biome lint", + "format": "pnpm biome format", + "format:write": "pnpm biome format --write" }, "dependencies": { "@discordapp/twemoji": "15.1.0", @@ -39,6 +42,7 @@ "vue": "3.5.10" }, "devDependencies": { + "@biomejs/biome": "1.9.3", "@misskey-dev/summaly": "5.1.0", "@testing-library/vue": "8.1.0", "@types/estree": "1.0.6", diff --git a/packages/frontend-shared/biome.json b/packages/frontend-shared/biome.json new file mode 100644 index 0000000000..3867749276 --- /dev/null +++ b/packages/frontend-shared/biome.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.8.3/schema.json", + "organizeImports": { + "enabled": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + } +} diff --git a/packages/frontend-shared/package.json b/packages/frontend-shared/package.json index f103bba896..1ac222924c 100644 --- a/packages/frontend-shared/package.json +++ b/packages/frontend-shared/package.json @@ -18,9 +18,13 @@ "watch": "nodemon -w package.json -e json --exec \"node ./build.js --watch\"", "eslint": "eslint './**/*.{js,jsx,ts,tsx}'", "typecheck": "tsc --noEmit", - "lint": "pnpm typecheck && pnpm eslint" + "lint": "pnpm typecheck && pnpm eslint", + "biome-lint": "pnpm typecheck && pnpm biome lint", + "format": "pnpm biome format", + "format:write": "pnpm biome format --write" }, "devDependencies": { + "@biomejs/biome": "1.9.3", "@types/node": "20.14.12", "@typescript-eslint/eslint-plugin": "7.17.0", "@typescript-eslint/parser": "7.17.0", diff --git a/packages/frontend/biome.json b/packages/frontend/biome.json new file mode 100644 index 0000000000..3867749276 --- /dev/null +++ b/packages/frontend/biome.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.8.3/schema.json", + "organizeImports": { + "enabled": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + } +} diff --git a/packages/frontend/package.json b/packages/frontend/package.json index f477b6002c..173abe2c93 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -14,7 +14,10 @@ "test-and-coverage": "vitest --run --coverage --globals", "typecheck": "vue-tsc --noEmit", "eslint": "eslint --quiet \"src/**/*.{ts,vue}\"", - "lint": "pnpm typecheck && pnpm eslint" + "lint": "pnpm typecheck && pnpm eslint", + "biome-lint": "pnpm typecheck && pnpm biome lint", + "format": "pnpm biome format", + "format:write": "pnpm biome format --write" }, "dependencies": { "@dice-roller/rpg-dice-roller": "^5.5.0", @@ -84,6 +87,7 @@ "vuedraggable": "next" }, "devDependencies": { + "@biomejs/biome": "1.9.3", "@misskey-dev/summaly": "5.1.0", "@storybook/addon-actions": "8.3.3", "@storybook/addon-essentials": "8.3.3", diff --git a/packages/misskey-bubble-game/biome.json b/packages/misskey-bubble-game/biome.json new file mode 100644 index 0000000000..b2eaad8ca9 --- /dev/null +++ b/packages/misskey-bubble-game/biome.json @@ -0,0 +1,126 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.8.3/schema.json", + "organizeImports": { "enabled": true }, + "linter": { + "enabled": true, + "rules": { + "recommended": false, + "complexity": { + "noBannedTypes": "error", + "noExtraBooleanCast": "error", + "noMultipleSpacesInRegularExpressionLiterals": "error", + "noUselessCatch": "error", + "noUselessTypeConstraint": "error", + "noWith": "error" + }, + "correctness": { + "noConstAssign": "error", + "noConstantCondition": "warn", + "noEmptyCharacterClassInRegex": "error", + "noEmptyPattern": "warn", + "noGlobalObjectCalls": "error", + "noInnerDeclarations": "off", + "noInvalidConstructorSuper": "error", + "noNewSymbol": "error", + "noNonoctalDecimalEscape": "error", + "noPrecisionLoss": "error", + "noSelfAssign": "error", + "noSetterReturn": "error", + "noSwitchDeclarations": "error", + "noUndeclaredVariables": "error", + "noUnreachable": "error", + "noUnreachableSuper": "error", + "noUnsafeFinally": "error", + "noUnsafeOptionalChaining": "error", + "noUnusedLabels": "error", + "noUnusedVariables": "error", + "useArrayLiterals": "off", + "useIsNan": "error", + "useValidForDirection": "error", + "useYield": "error" + }, + "style": { + "noDefaultExport": "warn", + "noInferrableTypes": "warn", + "noNamespace": "error", + "noNonNullAssertion": "warn", + "noParameterAssign": "warn", + "noVar": "error", + "useAsConstAssertion": "error" + }, + "suspicious": { + "noAsyncPromiseExecutor": "off", + "noCatchAssign": "error", + "noClassAssign": "error", + "noCompareNegZero": "error", + "noControlCharactersInRegex": "warn", + "noDebugger": "error", + "noDoubleEquals": "error", + "noDuplicateCase": "error", + "noDuplicateClassMembers": "error", + "noDuplicateObjectKeys": "error", + "noDuplicateParameters": "error", + "noEmptyBlockStatements": "off", + "noExplicitAny": "warn", + "noExtraNonNullAssertion": "error", + "noFallthroughSwitchClause": "error", + "noFunctionAssign": "error", + "noGlobalAssign": "error", + "noImportAssign": "error", + "noMisleadingCharacterClass": "error", + "noMisleadingInstantiator": "error", + "noPrototypeBuiltins": "error", + "noRedeclare": "error", + "noShadowRestrictedNames": "error", + "noUnsafeDeclarationMerging": "error", + "noUnsafeNegation": "error", + "useGetterReturn": "error", + "useValidTypeof": "error" + } + }, + "ignore": [ + "**/node_modules", + "built", + "coverage", + "jest.config.ts", + "test", + "test-d" + ] + }, + "javascript": { "globals": [] }, + "overrides": [ + { + "include": ["**/*.ts", "**/*.tsx"], + "linter": { + "rules": { + "correctness": { + "noConstAssign": "off", + "noGlobalObjectCalls": "off", + "noInvalidConstructorSuper": "off", + "noInvalidNewBuiltin": "off", + "noNewSymbol": "off", + "noSetterReturn": "off", + "noUndeclaredVariables": "off", + "noUnreachable": "off", + "noUnreachableSuper": "off" + }, + "style": { + "noArguments": "error", + "noVar": "error", + "useConst": "error" + }, + "suspicious": { + "noDuplicateClassMembers": "off", + "noDuplicateObjectKeys": "off", + "noDuplicateParameters": "off", + "noFunctionAssign": "off", + "noImportAssign": "off", + "noRedeclare": "off", + "noUnsafeNegation": "off", + "useGetterReturn": "off" + } + } + } + } + ] +} diff --git a/packages/misskey-bubble-game/package.json b/packages/misskey-bubble-game/package.json index 528eb00b74..e579c171b0 100644 --- a/packages/misskey-bubble-game/package.json +++ b/packages/misskey-bubble-game/package.json @@ -19,9 +19,13 @@ "watch": "nodemon -w package.json -e json --exec \"node ./build.js --watch\"", "eslint": "eslint './**/*.{js,jsx,ts,tsx}'", "typecheck": "tsc --noEmit", - "lint": "pnpm typecheck && pnpm eslint" + "lint": "pnpm typecheck && pnpm eslint", + "biome-lint": "pnpm typecheck && pnpm biome lint", + "format": "pnpm biome format", + "format:write": "pnpm biome format --write" }, "devDependencies": { + "@biomejs/biome": "1.9.3", "@types/matter-js": "0.19.6", "@types/seedrandom": "3.0.8", "@types/node": "20.11.5", diff --git a/packages/misskey-reversi/biome.json b/packages/misskey-reversi/biome.json new file mode 100644 index 0000000000..9b1d735681 --- /dev/null +++ b/packages/misskey-reversi/biome.json @@ -0,0 +1,122 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.8.3/schema.json", + "organizeImports": { "enabled": true }, + "linter": { + "enabled": true, + "rules": { + "recommended": false, + "complexity": { + "noBannedTypes": "error", + "noExtraBooleanCast": "error", + "noMultipleSpacesInRegularExpressionLiterals": "error", + "noUselessCatch": "error", + "noUselessTypeConstraint": "error", + "noWith": "error" + }, + "correctness": { + "noConstAssign": "error", + "noConstantCondition": "warn", + "noEmptyCharacterClassInRegex": "error", + "noEmptyPattern": "warn", + "noGlobalObjectCalls": "error", + "noInnerDeclarations": "off", + "noInvalidConstructorSuper": "error", + "noNewSymbol": "error", + "noNonoctalDecimalEscape": "error", + "noPrecisionLoss": "error", + "noSelfAssign": "error", + "noSetterReturn": "error", + "noSwitchDeclarations": "error", + "noUndeclaredVariables": "error", + "noUnreachable": "error", + "noUnreachableSuper": "error", + "noUnsafeFinally": "error", + "noUnsafeOptionalChaining": "error", + "noUnusedLabels": "error", + "noUnusedVariables": "error", + "useArrayLiterals": "off", + "useIsNan": "error", + "useValidForDirection": "error", + "useYield": "error" + }, + "style": { + "noDefaultExport": "warn", + "noInferrableTypes": "warn", + "noNamespace": "error", + "noNonNullAssertion": "warn", + "noParameterAssign": "warn", + "noVar": "error", + "useAsConstAssertion": "error" + }, + "suspicious": { + "noAsyncPromiseExecutor": "off", + "noCatchAssign": "error", + "noClassAssign": "error", + "noCompareNegZero": "error", + "noControlCharactersInRegex": "warn", + "noDebugger": "error", + "noDoubleEquals": "error", + "noDuplicateCase": "error", + "noDuplicateClassMembers": "error", + "noDuplicateObjectKeys": "error", + "noDuplicateParameters": "error", + "noEmptyBlockStatements": "off", + "noExplicitAny": "warn", + "noExtraNonNullAssertion": "error", + "noFallthroughSwitchClause": "error", + "noFunctionAssign": "error", + "noGlobalAssign": "error", + "noImportAssign": "error", + "noMisleadingCharacterClass": "error", + "noMisleadingInstantiator": "error", + "noPrototypeBuiltins": "error", + "noRedeclare": "error", + "noShadowRestrictedNames": "error", + "noUnsafeDeclarationMerging": "error", + "noUnsafeNegation": "error", + "useGetterReturn": "error", + "useValidTypeof": "error" + } + }, + "ignore": [ + "**/node_modules", + "built" + ] + }, + "javascript": { "globals": [] }, + "overrides": [ + { + "include": ["**/*.ts", "**/*.tsx"], + "linter": { + "rules": { + "correctness": { + "noConstAssign": "off", + "noGlobalObjectCalls": "off", + "noInvalidConstructorSuper": "off", + "noInvalidNewBuiltin": "off", + "noNewSymbol": "off", + "noSetterReturn": "off", + "noUndeclaredVariables": "off", + "noUnreachable": "off", + "noUnreachableSuper": "off" + }, + "style": { + "noArguments": "error", + "noVar": "error", + "useConst": "error" + }, + "suspicious": { + "noDuplicateClassMembers": "off", + "noDuplicateObjectKeys": "off", + "noDuplicateParameters": "off", + "noFunctionAssign": "off", + "noImportAssign": "off", + "noRedeclare": "off", + "noUnsafeNegation": "off", + "useGetterReturn": "off" + } + } + } + } + ] +} diff --git a/packages/misskey-reversi/package.json b/packages/misskey-reversi/package.json index c6db6e6221..bd7bd4a3e4 100644 --- a/packages/misskey-reversi/package.json +++ b/packages/misskey-reversi/package.json @@ -19,9 +19,13 @@ "watch": "nodemon -w package.json -e json --exec \"node ./build.js --watch\"", "eslint": "eslint './**/*.{js,jsx,ts,tsx}'", "typecheck": "tsc --noEmit", - "lint": "pnpm typecheck && pnpm eslint" + "lint": "pnpm typecheck && pnpm eslint", + "biome-lint": "pnpm typecheck && pnpm biome lint", + "format": "pnpm biome format", + "format:write": "pnpm biome format --write" }, "devDependencies": { + "@biomejs/biome": "1.9.3", "@types/node": "20.11.5", "@typescript-eslint/eslint-plugin": "7.1.0", "@typescript-eslint/parser": "7.1.0", diff --git a/packages/sw/biome.json b/packages/sw/biome.json new file mode 100644 index 0000000000..3157cd31ad --- /dev/null +++ b/packages/sw/biome.json @@ -0,0 +1,119 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.8.3/schema.json", + "organizeImports": { "enabled": true }, + "linter": { + "enabled": true, + "rules": { + "recommended": false, + "complexity": { + "noBannedTypes": "error", + "noExtraBooleanCast": "error", + "noMultipleSpacesInRegularExpressionLiterals": "error", + "noUselessCatch": "error", + "noUselessTypeConstraint": "error", + "noWith": "error" + }, + "correctness": { + "noConstAssign": "error", + "noConstantCondition": "warn", + "noEmptyCharacterClassInRegex": "error", + "noEmptyPattern": "warn", + "noGlobalObjectCalls": "error", + "noInnerDeclarations": "off", + "noInvalidConstructorSuper": "error", + "noNewSymbol": "error", + "noNonoctalDecimalEscape": "error", + "noPrecisionLoss": "error", + "noSelfAssign": "error", + "noSetterReturn": "error", + "noSwitchDeclarations": "error", + "noUndeclaredVariables": "error", + "noUnreachable": "error", + "noUnreachableSuper": "error", + "noUnsafeFinally": "error", + "noUnsafeOptionalChaining": "error", + "noUnusedLabels": "error", + "noUnusedVariables": "error", + "useArrayLiterals": "off", + "useIsNan": "error", + "useValidForDirection": "error", + "useYield": "error" + }, + "style": { + "noDefaultExport": "warn", + "noInferrableTypes": "warn", + "noNamespace": "error", + "noNonNullAssertion": "warn", + "noParameterAssign": "warn", + "noVar": "error", + "useAsConstAssertion": "error" + }, + "suspicious": { + "noAsyncPromiseExecutor": "off", + "noCatchAssign": "error", + "noClassAssign": "error", + "noCompareNegZero": "error", + "noControlCharactersInRegex": "warn", + "noDebugger": "error", + "noDoubleEquals": "error", + "noDuplicateCase": "error", + "noDuplicateClassMembers": "error", + "noDuplicateObjectKeys": "error", + "noDuplicateParameters": "error", + "noEmptyBlockStatements": "off", + "noExplicitAny": "warn", + "noExtraNonNullAssertion": "error", + "noFallthroughSwitchClause": "error", + "noFunctionAssign": "error", + "noGlobalAssign": "error", + "noImportAssign": "error", + "noMisleadingCharacterClass": "error", + "noMisleadingInstantiator": "error", + "noPrototypeBuiltins": "error", + "noRedeclare": "error", + "noShadowRestrictedNames": "error", + "noUnsafeDeclarationMerging": "error", + "noUnsafeNegation": "error", + "useGetterReturn": "error", + "useValidTypeof": "error" + } + }, + "ignore": ["build.js"] + }, + "javascript": { "globals": [] }, + "overrides": [ + { + "include": ["**/*.ts", "**/*.tsx"], + "linter": { + "rules": { + "correctness": { + "noConstAssign": "off", + "noGlobalObjectCalls": "off", + "noInvalidConstructorSuper": "off", + "noInvalidNewBuiltin": "off", + "noNewSymbol": "off", + "noSetterReturn": "off", + "noUndeclaredVariables": "off", + "noUnreachable": "off", + "noUnreachableSuper": "off" + }, + "style": { + "noArguments": "error", + "noVar": "error", + "useConst": "error" + }, + "suspicious": { + "noDuplicateClassMembers": "off", + "noDuplicateObjectKeys": "off", + "noDuplicateParameters": "off", + "noFunctionAssign": "off", + "noImportAssign": "off", + "noRedeclare": "off", + "noUnsafeNegation": "off", + "useGetterReturn": "off" + } + } + } + } + ] +} diff --git a/packages/sw/package.json b/packages/sw/package.json index 35f55cda99..1b85d81e0a 100644 --- a/packages/sw/package.json +++ b/packages/sw/package.json @@ -6,7 +6,10 @@ "build": "node build.js", "typecheck": "tsc --noEmit", "eslint": "eslint --quiet src/**/*.ts", - "lint": "pnpm typecheck && pnpm eslint" + "lint": "pnpm typecheck && pnpm eslint", + "biome-lint": "pnpm typecheck && pnpm biome lint", + "format": "pnpm biome format", + "format:write": "pnpm biome format --write" }, "dependencies": { "esbuild": "0.23.1", @@ -14,6 +17,7 @@ "cherrypick-js": "workspace:*" }, "devDependencies": { + "@biomejs/biome": "1.9.3", "@typescript-eslint/parser": "7.17.0", "@typescript/lib-webworker": "npm:@types/serviceworker@0.0.67", "eslint-plugin-import": "2.30.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c28b31b6c3..b5b4da8530 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -50,6 +50,9 @@ importers: specifier: 4.4.0 version: 4.4.0(encoding@0.1.13) devDependencies: + '@biomejs/biome': + specifier: 1.9.3 + version: 1.9.3 '@misskey-dev/eslint-plugin': specifier: 2.0.3 version: 2.0.3(@eslint/compat@1.1.1)(@typescript-eslint/eslint-plugin@7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint@9.8.0)(typescript@5.6.2))(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint@9.8.0))(eslint@9.8.0)(globals@15.9.0) @@ -544,6 +547,9 @@ importers: specifier: 6.0.3 version: 6.0.3 devDependencies: + '@biomejs/biome': + specifier: 1.9.3 + version: 1.9.3 '@jest/globals': specifier: 29.7.0 version: 29.7.0 @@ -713,6 +719,9 @@ importers: specifier: 4.4.0 version: 4.4.0 devDependencies: + '@biomejs/biome': + specifier: 1.9.3 + version: 1.9.3 '@microsoft/api-extractor': specifier: 7.47.9 version: 7.47.9(@types/node@20.14.12) @@ -993,6 +1002,9 @@ importers: specifier: next version: 4.1.0(vue@3.5.10(typescript@5.6.2)) devDependencies: + '@biomejs/biome': + specifier: 1.9.3 + version: 1.9.3 '@misskey-dev/summaly': specifier: 5.1.0 version: 5.1.0 @@ -1103,7 +1115,7 @@ importers: version: 7.17.0(eslint@9.8.0)(typescript@5.6.2) '@vitest/coverage-v8': specifier: 1.6.0 - version: 1.6.0(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1)(sass@1.79.3)(terser@5.33.0)) + version: 1.6.0(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.7)(utf-8-validate@6.0.3))(sass@1.79.3)(terser@5.33.0)) '@vue/runtime-core': specifier: 3.5.10 version: 3.5.10 @@ -1172,7 +1184,7 @@ importers: version: 1.0.3 vitest: specifier: 1.6.0 - version: 1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1)(sass@1.79.3)(terser@5.33.0) + version: 1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.7)(utf-8-validate@6.0.3))(sass@1.79.3)(terser@5.33.0) vitest-fetch-mock: specifier: 0.2.2 version: 0.2.2(encoding@0.1.13)(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.7)(utf-8-validate@6.0.3))(sass@1.79.3)(terser@5.33.0)) @@ -1267,6 +1279,9 @@ importers: specifier: 3.5.10 version: 3.5.10(typescript@5.6.2) devDependencies: + '@biomejs/biome': + specifier: 1.9.3 + version: 1.9.3 '@misskey-dev/summaly': specifier: 5.1.0 version: 5.1.0 @@ -1302,7 +1317,7 @@ importers: version: 7.17.0(eslint@9.8.0)(typescript@5.6.2) '@vitest/coverage-v8': specifier: 1.6.0 - version: 1.6.0(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1)(sass@1.79.3)(terser@5.33.0)) + version: 1.6.0(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.7)(utf-8-validate@6.0.3))(sass@1.79.3)(terser@5.33.0)) '@vue/runtime-core': specifier: 3.5.10 version: 3.5.10 @@ -1364,6 +1379,9 @@ importers: specifier: 3.4.37 version: 3.4.37(typescript@5.5.4) devDependencies: + '@biomejs/biome': + specifier: 1.9.3 + version: 1.9.3 '@types/node': specifier: 20.14.12 version: 20.14.12 @@ -1398,6 +1416,9 @@ importers: specifier: 3.0.5 version: 3.0.5 devDependencies: + '@biomejs/biome': + specifier: 1.9.3 + version: 1.9.3 '@types/matter-js': specifier: 0.19.6 version: 0.19.6 @@ -1435,6 +1456,9 @@ importers: specifier: 1.2.2 version: 1.2.2 devDependencies: + '@biomejs/biome': + specifier: 1.9.3 + version: 1.9.3 '@types/node': specifier: 20.11.5 version: 20.11.5 @@ -1472,6 +1496,9 @@ importers: specifier: 6.2.1 version: 6.2.1 devDependencies: + '@biomejs/biome': + specifier: 1.9.3 + version: 1.9.3 '@typescript-eslint/parser': specifier: 7.17.0 version: 7.17.0(eslint@9.8.0)(typescript@5.6.2) @@ -1916,6 +1943,59 @@ packages: '@bcoe/v8-coverage@0.2.3': resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + '@biomejs/biome@1.9.3': + resolution: {integrity: sha512-POjAPz0APAmX33WOQFGQrwLvlu7WLV4CFJMlB12b6ZSg+2q6fYu9kZwLCOA+x83zXfcPd1RpuWOKJW0GbBwLIQ==} + engines: {node: '>=14.21.3'} + hasBin: true + + '@biomejs/cli-darwin-arm64@1.9.3': + resolution: {integrity: sha512-QZzD2XrjJDUyIZK+aR2i5DDxCJfdwiYbUKu9GzkCUJpL78uSelAHAPy7m0GuPMVtF/Uo+OKv97W3P9nuWZangQ==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [darwin] + + '@biomejs/cli-darwin-x64@1.9.3': + resolution: {integrity: sha512-vSCoIBJE0BN3SWDFuAY/tRavpUtNoqiceJ5PrU3xDfsLcm/U6N93JSM0M9OAiC/X7mPPfejtr6Yc9vSgWlEgVw==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [darwin] + + '@biomejs/cli-linux-arm64-musl@1.9.3': + resolution: {integrity: sha512-VBzyhaqqqwP3bAkkBrhVq50i3Uj9+RWuj+pYmXrMDgjS5+SKYGE56BwNw4l8hR3SmYbLSbEo15GcV043CDSk+Q==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + + '@biomejs/cli-linux-arm64@1.9.3': + resolution: {integrity: sha512-vJkAimD2+sVviNTbaWOGqEBy31cW0ZB52KtpVIbkuma7PlfII3tsLhFa+cwbRAcRBkobBBhqZ06hXoZAN8NODQ==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + + '@biomejs/cli-linux-x64-musl@1.9.3': + resolution: {integrity: sha512-TJmnOG2+NOGM72mlczEsNki9UT+XAsMFAOo8J0me/N47EJ/vkLXxf481evfHLlxMejTY6IN8SdRSiPVLv6AHlA==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-linux-x64@1.9.3': + resolution: {integrity: sha512-x220V4c+romd26Mu1ptU+EudMXVS4xmzKxPVb9mgnfYlN4Yx9vD5NZraSx/onJnd3Gh/y8iPUdU5CDZJKg9COA==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-win32-arm64@1.9.3': + resolution: {integrity: sha512-lg/yZis2HdQGsycUvHWSzo9kOvnGgvtrYRgoCEwPBwwAL8/6crOp3+f47tPwI/LI1dZrhSji7PNsGKGHbwyAhw==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [win32] + + '@biomejs/cli-win32-x64@1.9.3': + resolution: {integrity: sha512-cQMy2zanBkVLpmmxXdK6YePzmZx0s5Z7KEnwmrW54rcXK3myCNbQa09SwGZ8i/8sLw0H9F3X7K4rxVNGU8/D4Q==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [win32] + '@bull-board/api@6.0.0': resolution: {integrity: sha512-O0IsIwAOU47bPTJnqRO7RtKFQToMvwRebbuPi6M+SG1gXyiqixLg9pycnfXgSeroaT9E7QQ2PsCPW1HO8VORvw==} peerDependencies: @@ -12876,6 +12956,41 @@ snapshots: '@bcoe/v8-coverage@0.2.3': {} + '@biomejs/biome@1.9.3': + optionalDependencies: + '@biomejs/cli-darwin-arm64': 1.9.3 + '@biomejs/cli-darwin-x64': 1.9.3 + '@biomejs/cli-linux-arm64': 1.9.3 + '@biomejs/cli-linux-arm64-musl': 1.9.3 + '@biomejs/cli-linux-x64': 1.9.3 + '@biomejs/cli-linux-x64-musl': 1.9.3 + '@biomejs/cli-win32-arm64': 1.9.3 + '@biomejs/cli-win32-x64': 1.9.3 + + '@biomejs/cli-darwin-arm64@1.9.3': + optional: true + + '@biomejs/cli-darwin-x64@1.9.3': + optional: true + + '@biomejs/cli-linux-arm64-musl@1.9.3': + optional: true + + '@biomejs/cli-linux-arm64@1.9.3': + optional: true + + '@biomejs/cli-linux-x64-musl@1.9.3': + optional: true + + '@biomejs/cli-linux-x64@1.9.3': + optional: true + + '@biomejs/cli-win32-arm64@1.9.3': + optional: true + + '@biomejs/cli-win32-x64@1.9.3': + optional: true + '@bull-board/api@6.0.0(@bull-board/ui@6.0.0)': dependencies: '@bull-board/ui': 6.0.0 @@ -16489,7 +16604,7 @@ snapshots: vite: 5.4.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0) vue: 3.5.10(typescript@5.6.2) - '@vitest/coverage-v8@1.6.0(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1)(sass@1.79.3)(terser@5.33.0))': + '@vitest/coverage-v8@1.6.0(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.7)(utf-8-validate@6.0.3))(sass@1.79.3)(terser@5.33.0))': dependencies: '@ampproject/remapping': 2.2.1 '@bcoe/v8-coverage': 0.2.3 @@ -16504,7 +16619,7 @@ snapshots: std-env: 3.7.0 strip-literal: 2.1.0 test-exclude: 6.0.0 - vitest: 1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1)(sass@1.79.3)(terser@5.33.0) + vitest: 1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.7)(utf-8-validate@6.0.3))(sass@1.79.3)(terser@5.33.0) transitivePeerDependencies: - supports-color @@ -24457,11 +24572,11 @@ snapshots: vitest-fetch-mock@0.2.2(encoding@0.1.13)(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.7)(utf-8-validate@6.0.3))(sass@1.79.3)(terser@5.33.0)): dependencies: cross-fetch: 3.1.5(encoding@0.1.13) - vitest: 1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1)(sass@1.79.3)(terser@5.33.0) + vitest: 1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.7)(utf-8-validate@6.0.3))(sass@1.79.3)(terser@5.33.0) transitivePeerDependencies: - encoding - vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1)(sass@1.79.3)(terser@5.33.0): + vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.7)(utf-8-validate@6.0.3))(sass@1.79.3)(terser@5.33.0): dependencies: '@vitest/expect': 1.6.0 '@vitest/runner': 1.6.0 From ab6a5d0c3dbe7146de19d72d08658b1c011fe30a Mon Sep 17 00:00:00 2001 From: NoriDev Date: Sun, 6 Oct 2024 21:03:15 +0900 Subject: [PATCH 291/315] tweak 0dc322b6 (1673beta/cherrypick#88) lint --- CHANGELOG_CHERRYPICK.md | 4 ++-- .../src/core/CreateSystemUserService.ts | 5 ++--- packages/backend/src/core/SignupService.ts | 6 ++--- packages/backend/src/misc/password.ts | 20 +++++++++++++++++ .../src/server/api/SigninApiService.ts | 22 ++++++------------- .../src/server/api/SignupApiService.ts | 6 ++--- .../api/endpoints/admin/reset-password.ts | 7 +++--- .../server/api/endpoints/i/2fa/key-done.ts | 5 ++--- .../api/endpoints/i/2fa/register-key.ts | 5 ++--- .../server/api/endpoints/i/2fa/register.ts | 5 ++--- .../server/api/endpoints/i/2fa/remove-key.ts | 5 ++--- .../server/api/endpoints/i/2fa/unregister.ts | 5 ++--- .../server/api/endpoints/i/change-password.ts | 8 +++---- .../server/api/endpoints/i/delete-account.ts | 5 ++--- .../api/endpoints/i/regenerate-token.ts | 5 ++--- .../server/api/endpoints/i/update-email.ts | 5 ++--- .../server/api/endpoints/reset-password.ts | 8 +++---- 17 files changed, 61 insertions(+), 65 deletions(-) create mode 100644 packages/backend/src/misc/password.ts diff --git a/CHANGELOG_CHERRYPICK.md b/CHANGELOG_CHERRYPICK.md index ffaef2c9ac..d6a3a29abf 100644 --- a/CHANGELOG_CHERRYPICK.md +++ b/CHANGELOG_CHERRYPICK.md @@ -33,7 +33,7 @@ Misskey의 전체 변경 사항을 확인하려면, [CHANGELOG.md#2024xx](CHANGE - 활성화하면 글자를 표시하기 위한 여유 공간이 좁을 때 디자인이 상대적으로 어색하게 보일 수 있으며, 실험실 기능이므로 이 기능이 변경하는 부분을 확실히 알고 있는 사용자만 활성화할 것을 권장합니다. - 비밀번호 해싱 알고리즘이 `bcrypt`에서 `argon2`로 변경됨 - 이 변경으로 이후에 비밀번호를 변경하거나 신규로 가입한 사용자는 `argon2`를 사용하여 비밀번호 해시가 생성됩니다. - - 이전에 가입한 사용자는 비밀번호를 변경하지 않아도 `bcypt`를 사용할 수 있으며 여전히 기존과 동일하게 호환됩니다. + - 이전에 가입한 사용자는 로그인 시 자동으로 `bcypt`에서 `argon2`로 해시가 변경됩니다. ### General - Feat: 위젯 영역을 숨길 수 있음 @@ -89,7 +89,7 @@ Misskey의 전체 변경 사항을 확인하려면, [CHANGELOG.md#2024xx](CHANGE - Fix: 노트 본문의 사용자 멘션 영역을 클릭하면 노트 상세 페이지가 표시됨 ### Server -- Enhance: 보안 향상을 위해 비밀번호 해싱 알고리즘이 `bcrypt`에서 `argon2`로 변경됨 (kokonect-link/cherrypick#511) +- Enhance: 보안 향상을 위해 비밀번호 해싱 알고리즘이 `bcrypt`에서 `argon2`로 변경됨 (kokonect-link/cherrypick#511), (1673beta/cherrypick#88) - 이제 72 바이트를 초과하는 비밀번호를 사용할 수 있습니다. - 이로써 `Sharkey`, `FireFish`, `IceShrimp` 등의 클라이언트에서 `CherryPick`으로 이전할 때 암호 호환성이 보장됩니다. - Fix: 이모지를 등록하거나 가져오려고 할 때 오류가 발생할 수 있음 (kokonect-link/cherrypick#487), (kokonect-link/cherrypick#508) diff --git a/packages/backend/src/core/CreateSystemUserService.ts b/packages/backend/src/core/CreateSystemUserService.ts index 7de67dade1..fb1f1e8f2b 100644 --- a/packages/backend/src/core/CreateSystemUserService.ts +++ b/packages/backend/src/core/CreateSystemUserService.ts @@ -5,8 +5,6 @@ import { randomUUID } from 'node:crypto'; import { Inject, Injectable } from '@nestjs/common'; -import * as argon2 from 'argon2'; -//import bcrypt from 'bcryptjs'; import { IsNull, DataSource } from 'typeorm'; import { genRsaKeyPair } from '@/misc/gen-key-pair.js'; import { MiUser } from '@/models/User.js'; @@ -17,6 +15,7 @@ import { MiUsedUsername } from '@/models/UsedUsername.js'; import { DI } from '@/di-symbols.js'; import generateNativeUserToken from '@/misc/generate-native-user-token.js'; import { bindThis } from '@/decorators.js'; +import { hashPassword } from '@/misc/password.js'; @Injectable() export class CreateSystemUserService { @@ -34,7 +33,7 @@ export class CreateSystemUserService { // Generate hash of password //const salt = await bcrypt.genSalt(8); - const hash = await argon2.hash(password); + const hash = await hashPassword(password); // Generate secret const secret = generateNativeUserToken(); diff --git a/packages/backend/src/core/SignupService.ts b/packages/backend/src/core/SignupService.ts index 5374ec09f6..f42254e338 100644 --- a/packages/backend/src/core/SignupService.ts +++ b/packages/backend/src/core/SignupService.ts @@ -5,8 +5,6 @@ import { generateKeyPair } from 'node:crypto'; import { Inject, Injectable } from '@nestjs/common'; -//import bcrypt from 'bcryptjs'; -import * as argon2 from 'argon2'; import { DataSource, IsNull } from 'typeorm'; import { DI } from '@/di-symbols.js'; import type { MiMeta, UsedUsernamesRepository, UsersRepository } from '@/models/_.js'; @@ -22,6 +20,7 @@ import { bindThis } from '@/decorators.js'; import UsersChart from '@/core/chart/charts/users.js'; import { UtilityService } from '@/core/UtilityService.js'; import { UserService } from '@/core/UserService.js'; +import { hashPassword } from '@/misc/password.js'; @Injectable() export class SignupService { @@ -70,8 +69,7 @@ export class SignupService { } // Generate hash of password - //const salt = await bcrypt.genSalt(8); - hash = await argon2.hash(password); + hash = await hashPassword(password); } // Generate secret diff --git a/packages/backend/src/misc/password.ts b/packages/backend/src/misc/password.ts new file mode 100644 index 0000000000..be40638c3f --- /dev/null +++ b/packages/backend/src/misc/password.ts @@ -0,0 +1,20 @@ +import { randomBytes } from 'crypto'; +import bcrypt from 'bcryptjs'; +import * as argon2 from 'argon2'; + +export async function hashPassword(password: string): Promise { + const salt = randomBytes(32); + return argon2.hash(password, { salt: salt, type: argon2.argon2id }); +} + +export async function comparePassword(password: string, hash: string): Promise { + if (isOldAlgorithm(hash)) { + return bcrypt.compare(password, hash); + } + + return argon2.verify(hash, password); +} + +export function isOldAlgorithm(hash: string): boolean { + return hash.startsWith('$2'); +} diff --git a/packages/backend/src/server/api/SigninApiService.ts b/packages/backend/src/server/api/SigninApiService.ts index 1554510697..e2cd5d7c14 100644 --- a/packages/backend/src/server/api/SigninApiService.ts +++ b/packages/backend/src/server/api/SigninApiService.ts @@ -4,10 +4,9 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import bcrypt from 'bcryptjs'; -import * as argon2 from 'argon2'; import * as OTPAuth from 'otpauth'; import { IsNull } from 'typeorm'; +import { comparePassword, hashPassword, isOldAlgorithm } from '@/misc/password.js'; import { DI } from '@/di-symbols.js'; import type { SigninsRepository, @@ -124,7 +123,12 @@ export class SigninApiService { const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id }); // Compare password - const same = await argon2.verify(profile.password!, password) || bcrypt.compareSync(password, profile.password!); + const same = await comparePassword(password, profile.password!); + + if (same && isOldAlgorithm(profile.password!)) { + profile.password = await hashPassword(password); + await this.userProfilesRepository.save(profile); + } const fail = async (status?: number, failure?: { id: string }) => { // Append signin history @@ -141,12 +145,6 @@ export class SigninApiService { if (!profile.twoFactorEnabled) { if (same) { - if (profile.password!.startsWith('$2')) { - const newHash = await argon2.hash(password); - this.userProfilesRepository.update(user.id, { - password: newHash, - }); - } return this.signinService.signin(request, reply, user); } else { return await fail(403, { @@ -163,12 +161,6 @@ export class SigninApiService { } try { - if (profile.password!.startsWith('$2')) { - const newHash = await argon2.hash(password); - this.userProfilesRepository.update(user.id, { - password: newHash, - }); - } await this.userAuthService.twoFactorAuthenticate(profile, token); } catch (e) { return await fail(403, { diff --git a/packages/backend/src/server/api/SignupApiService.ts b/packages/backend/src/server/api/SignupApiService.ts index f99e2761a3..16bf16a75f 100644 --- a/packages/backend/src/server/api/SignupApiService.ts +++ b/packages/backend/src/server/api/SignupApiService.ts @@ -4,9 +4,8 @@ */ import { Inject, Injectable } from '@nestjs/common'; -//import bcrypt from 'bcryptjs'; -import * as argon2 from 'argon2'; import { IsNull } from 'typeorm'; +import { hashPassword } from '@/misc/password.js'; import { DI } from '@/di-symbols.js'; import type { RegistrationTicketsRepository, UsedUsernamesRepository, UserPendingsRepository, UserProfilesRepository, UsersRepository, MiRegistrationTicket, MiMeta } from '@/models/_.js'; import type { Config } from '@/config.js'; @@ -180,8 +179,7 @@ export class SignupApiService { const code = secureRndstr(16, { chars: L_CHARS }); // Generate hash of password - //const salt = await bcrypt.genSalt(8); - const hash = await argon2.hash(password); + const hash = await hashPassword(password); const pendingUser = await this.userPendingsRepository.insertOne({ id: this.idService.gen(), diff --git a/packages/backend/src/server/api/endpoints/admin/reset-password.ts b/packages/backend/src/server/api/endpoints/admin/reset-password.ts index 828dbae712..70d3e372a3 100644 --- a/packages/backend/src/server/api/endpoints/admin/reset-password.ts +++ b/packages/backend/src/server/api/endpoints/admin/reset-password.ts @@ -4,8 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -//import bcrypt from 'bcryptjs'; -import * as argon2 from 'argon2'; +import { hashPassword } from '@/misc/password.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { UsersRepository, UserProfilesRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; @@ -63,10 +62,10 @@ export default class extends Endpoint { // eslint- throw new Error('cannot reset password of root'); } - const passwd = secureRndstr(8); + const passwd = secureRndstr(16); // Generate hash of password - const hash = await argon2.hash(passwd); + const hash = await hashPassword(passwd); await this.userProfilesRepository.update({ userId: user.id, diff --git a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts index d87df588bf..77c08bf855 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts @@ -3,8 +3,6 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -//import bcrypt from 'bcryptjs'; -import * as argon2 from 'argon2'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; @@ -14,6 +12,7 @@ import type { UserProfilesRepository, UserSecurityKeysRepository } from '@/model import { WebAuthnService } from '@/core/WebAuthnService.js'; import { ApiError } from '@/server/api/error.js'; import { UserAuthService } from '@/core/UserAuthService.js'; +import { comparePassword } from '@/misc/password.js'; export const meta = { requireCredential: true, @@ -86,7 +85,7 @@ export default class extends Endpoint { } } - const passwordMatched = await argon2.verify(profile.password ?? '', ps.password); + const passwordMatched = await comparePassword(ps.password, profile.password ?? ''); if (!passwordMatched) { throw new ApiError(meta.errors.incorrectPassword); } diff --git a/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts index 5621ff3dc1..a91e1fcab4 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts @@ -3,8 +3,6 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -//import bcrypt from 'bcryptjs'; -import * as argon2 from 'argon2'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { UserProfilesRepository } from '@/models/_.js'; @@ -12,6 +10,7 @@ import { DI } from '@/di-symbols.js'; import { WebAuthnService } from '@/core/WebAuthnService.js'; import { ApiError } from '@/server/api/error.js'; import { UserAuthService } from '@/core/UserAuthService.js'; +import { comparePassword } from '@/misc/password.js'; export const meta = { requireCredential: true, @@ -217,7 +216,7 @@ export default class extends Endpoint { } } - const passwordMatched = await argon2.verify(profile.password ?? '', ps.password); + const passwordMatched = await comparePassword(ps.password, profile.password ?? ''); if (!passwordMatched) { throw new ApiError(meta.errors.incorrectPassword); } diff --git a/packages/backend/src/server/api/endpoints/i/2fa/register.ts b/packages/backend/src/server/api/endpoints/i/2fa/register.ts index 7283159f87..a169d8b2eb 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/register.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/register.ts @@ -3,8 +3,6 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -//import bcrypt from 'bcryptjs'; -import * as argon2 from 'argon2'; import * as OTPAuth from 'otpauth'; import * as QRCode from 'qrcode'; import { Inject, Injectable } from '@nestjs/common'; @@ -14,6 +12,7 @@ import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import { ApiError } from '@/server/api/error.js'; import { UserAuthService } from '@/core/UserAuthService.js'; +import { comparePassword } from '@/misc/password.js'; export const meta = { requireCredential: true, @@ -78,7 +77,7 @@ export default class extends Endpoint { // eslint- } } - const passwordMatched = await argon2.verify(profile.password ?? '', ps.password); + const passwordMatched = await comparePassword(ps.password, profile.password ?? ''); if (!passwordMatched) { throw new ApiError(meta.errors.incorrectPassword); } diff --git a/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts index 982cb7aee5..b134801f1f 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts @@ -3,8 +3,6 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -//import bcrypt from 'bcryptjs'; -import * as argon2 from 'argon2'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { UserProfilesRepository, UserSecurityKeysRepository } from '@/models/_.js'; @@ -13,6 +11,7 @@ import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '@/server/api/error.js'; import { UserAuthService } from '@/core/UserAuthService.js'; +import { comparePassword } from '@/misc/password.js'; export const meta = { requireCredential: true, @@ -67,7 +66,7 @@ export default class extends Endpoint { // eslint- } } - const passwordMatched = await argon2.verify(profile.password ?? '', ps.password); + const passwordMatched = await comparePassword(ps.password, profile.password ?? ''); if (!passwordMatched) { throw new ApiError(meta.errors.incorrectPassword); } diff --git a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts index 8da331505b..0399698127 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts @@ -3,8 +3,6 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -//import bcrypt from 'bcryptjs'; -import * as argon2 from 'argon2'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; @@ -13,6 +11,7 @@ import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '@/server/api/error.js'; import { UserAuthService } from '@/core/UserAuthService.js'; +import { comparePassword } from '@/misc/password.js'; export const meta = { requireCredential: true, @@ -63,7 +62,7 @@ export default class extends Endpoint { // eslint- } } - const passwordMatched = await argon2.verify(profile.password ?? '', ps.password); + const passwordMatched = await comparePassword(ps.password, profile.password ?? ''); if (!passwordMatched) { throw new ApiError(meta.errors.incorrectPassword); } diff --git a/packages/backend/src/server/api/endpoints/i/change-password.ts b/packages/backend/src/server/api/endpoints/i/change-password.ts index 6aedde717c..9230e97077 100644 --- a/packages/backend/src/server/api/endpoints/i/change-password.ts +++ b/packages/backend/src/server/api/endpoints/i/change-password.ts @@ -3,13 +3,12 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -//import bcrypt from 'bcryptjs'; -import * as argon2 from 'argon2'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { UserProfilesRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { UserAuthService } from '@/core/UserAuthService.js'; +import { hashPassword, comparePassword } from '@/misc/password.js'; export const meta = { requireCredential: true, @@ -51,15 +50,14 @@ export default class extends Endpoint { // eslint- } } - const passwordMatched = await argon2.verify(profile.password!, ps.currentPassword); + const passwordMatched = await comparePassword(ps.currentPassword, profile.password!); if (!passwordMatched) { throw new Error('incorrect password'); } // Generate hash of password - //const salt = await bcrypt.genSalt(8); - const hash = await argon2.hash(ps.newPassword); + const hash = await hashPassword(ps.newPassword); await this.userProfilesRepository.update(me.id, { password: hash, diff --git a/packages/backend/src/server/api/endpoints/i/delete-account.ts b/packages/backend/src/server/api/endpoints/i/delete-account.ts index af4d601ad6..4d0204aa92 100644 --- a/packages/backend/src/server/api/endpoints/i/delete-account.ts +++ b/packages/backend/src/server/api/endpoints/i/delete-account.ts @@ -3,14 +3,13 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -//import bcrypt from 'bcryptjs'; -import * as argon2 from 'argon2'; import { Inject, Injectable } from '@nestjs/common'; import type { UsersRepository, UserProfilesRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DeleteAccountService } from '@/core/DeleteAccountService.js'; import { DI } from '@/di-symbols.js'; import { UserAuthService } from '@/core/UserAuthService.js'; +import { comparePassword } from '@/misc/password.js'; export const meta = { requireCredential: true, @@ -60,7 +59,7 @@ export default class extends Endpoint { // eslint- return; } - const passwordMatched = await argon2.verify(profile.password!, ps.password); + const passwordMatched = await comparePassword(ps.password, profile.password!); if (!passwordMatched) { throw new Error('incorrect password'); } diff --git a/packages/backend/src/server/api/endpoints/i/regenerate-token.ts b/packages/backend/src/server/api/endpoints/i/regenerate-token.ts index e1cdfdc185..ddd04cf1d3 100644 --- a/packages/backend/src/server/api/endpoints/i/regenerate-token.ts +++ b/packages/backend/src/server/api/endpoints/i/regenerate-token.ts @@ -3,14 +3,13 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -//import bcrypt from 'bcryptjs'; -import * as argon2 from 'argon2'; import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { UsersRepository, UserProfilesRepository } from '@/models/_.js'; import generateUserToken from '@/misc/generate-native-user-token.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; +import { comparePassword } from '@/misc/password.js'; export const meta = { requireCredential: true, @@ -44,7 +43,7 @@ export default class extends Endpoint { // eslint- const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id }); // Compare password - const same = await argon2.verify(profile.password!, ps.password); + const same = await comparePassword(ps.password, profile.password!); if (!same) { throw new Error('incorrect password'); diff --git a/packages/backend/src/server/api/endpoints/i/update-email.ts b/packages/backend/src/server/api/endpoints/i/update-email.ts index 0be8bfb695..718b513eab 100644 --- a/packages/backend/src/server/api/endpoints/i/update-email.ts +++ b/packages/backend/src/server/api/endpoints/i/update-email.ts @@ -5,8 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; -//import bcrypt from 'bcryptjs'; -import * as argon2 from 'argon2'; +import { comparePassword } from '@/misc/password.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { MiMeta, UserProfilesRepository } from '@/models/_.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; @@ -97,7 +96,7 @@ export default class extends Endpoint { // eslint- } } - const passwordMatched = await argon2.verify(profile.password!, ps.password); + const passwordMatched = await comparePassword(ps.password, profile.password!); if (!passwordMatched) { throw new ApiError(meta.errors.incorrectPassword); } diff --git a/packages/backend/src/server/api/endpoints/reset-password.ts b/packages/backend/src/server/api/endpoints/reset-password.ts index 1639b57bc5..56503913ee 100644 --- a/packages/backend/src/server/api/endpoints/reset-password.ts +++ b/packages/backend/src/server/api/endpoints/reset-password.ts @@ -3,13 +3,13 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -//import bcrypt from 'bcryptjs'; -import * as argon2 from 'argon2'; import { Inject, Injectable } from '@nestjs/common'; import type { UserProfilesRepository, PasswordResetRequestsRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { IdService } from '@/core/IdService.js'; +import { hashPassword } from '@/misc/password.js'; +import { secureRndstr } from '@/misc/secure-rndstr.js'; export const meta = { tags: ['reset password'], @@ -54,8 +54,8 @@ export default class extends Endpoint { // eslint- } // Generate hash of password - //const salt = await bcrypt.genSalt(8); - const hash = await argon2.hash(ps.password); + const passwd = secureRndstr(16); + const hash = await hashPassword(passwd); await this.userProfilesRepository.update(req.userId, { password: hash, From b124877741c82325cfd2a09a15296b434f198880 Mon Sep 17 00:00:00 2001 From: NoriDev Date: Sun, 6 Oct 2024 21:41:40 +0900 Subject: [PATCH 292/315] =?UTF-8?q?enhance(frontend):=20=EB=85=B8=ED=8A=B8?= =?UTF-8?q?=20=EC=9E=91=EC=84=B1=20=ED=8F=BC=EC=9D=98=20=EC=9D=BC=EB=B6=80?= =?UTF-8?q?=20=EA=B8=B0=EB=8A=A5=20=EB=B2=84=ED=8A=BC=EC=9D=84=20=EB=93=9C?= =?UTF-8?q?=EB=A1=AD=20=EB=8B=A4=EC=9A=B4=20=EB=A9=94=EB=89=B4=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG_CHERRYPICK.md | 1 + .../frontend/src/components/MkPostForm.vue | 23 +++++++++++-- .../src/components/MkPostFormSimple.vue | 33 +++++++++++++++---- 3 files changed, 48 insertions(+), 9 deletions(-) diff --git a/CHANGELOG_CHERRYPICK.md b/CHANGELOG_CHERRYPICK.md index d6a3a29abf..efeeb88960 100644 --- a/CHANGELOG_CHERRYPICK.md +++ b/CHANGELOG_CHERRYPICK.md @@ -83,6 +83,7 @@ Misskey의 전체 변경 사항을 확인하려면, [CHANGELOG.md#2024xx](CHANGE - 이제 노트에서 검색 블록을 활성화 하려면 `[검색]`으로만 사용할 수 있습니다. - Enhance: 설정 페이지의 하단 여백 디자인을 조정함 - Enhance: 노트 작성 시 `본문 미리보기`를 활성화한 경우, 본문에 내용이 있을 때만 표시되도록 변경함 +- Enhance: 노트 작성 폼의 일부 기능 버튼을 드롭 다운 메뉴로 이동함 - Fix: 환경설정 백업 시 일부 설정이 누락되어 백업될 수 있음 - Fix: 이미지 자르기를 할 때 이미지 크기 전체를 표시하지 못할 수 있음 - Fix: 페이지에서 페이지 생성 버튼이 본문에 중복으로 표시됨 diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue index d1b08558bc..540af025b7 100644 --- a/packages/frontend/src/components/MkPostForm.vue +++ b/packages/frontend/src/components/MkPostForm.vue @@ -84,15 +84,13 @@ SPDX-License-Identifier: AGPL-3.0-only
- - - +
@@ -1110,6 +1108,25 @@ function toggleScheduledNoteDelete() { } } +function showOtherMenu(ev: MouseEvent) { + os.popupMenu([{ + type: 'button', + text: i18n.ts.event, + icon: 'ti ti-calendar', + action: toggleEvent, + }, { + type: 'button', + text: i18n.ts.scheduledNoteDelete, + icon: 'ti ti-clock-hour-9', + action: toggleScheduledNoteDelete, + }, { type: 'divider' }, { + type: 'switch', + text: i18n.ts.disableRightClick, + icon: 'ti ti-mouse-off', + ref: disableRightClick, + }], ev.currentTarget ?? ev.target); +} + onMounted(() => { if (props.autofocus) { focus(); diff --git a/packages/frontend/src/components/MkPostFormSimple.vue b/packages/frontend/src/components/MkPostFormSimple.vue index 33b6420ae9..eb799e60d5 100644 --- a/packages/frontend/src/components/MkPostFormSimple.vue +++ b/packages/frontend/src/components/MkPostFormSimple.vue @@ -99,15 +99,13 @@ SPDX-License-Identifier: AGPL-3.0-only
- - - +
@@ -184,6 +182,7 @@ const props = withDefaults(defineProps<{ initialVisibleUsers: () => [], autofocus: false, mock: false, + initialLocalOnly: undefined, }); provide('mock', props.mock); @@ -200,7 +199,7 @@ const emit = defineEmits<{ const textareaEl = shallowRef(null); const cwInputEl = shallowRef(null); const hashtagsInputEl = shallowRef(null); -const visibilityButton = shallowRef(null); +const visibilityButton = shallowRef(); const showForm = ref(false); @@ -223,8 +222,8 @@ watch(showProfilePreview, () => defaultStore.set('showProfilePreview', showProfi const showAddMfmFunction = ref(defaultStore.state.enableQuickAddMfmFunction); watch(showAddMfmFunction, () => defaultStore.set('enableQuickAddMfmFunction', showAddMfmFunction.value)); const cw = ref(props.initialCw ?? null); -const localOnly = ref(props.initialLocalOnly ?? defaultStore.state.rememberNoteVisibility ? defaultStore.state.localOnly : defaultStore.state.defaultNoteLocalOnly); -const visibility = ref(props.initialVisibility ?? (defaultStore.state.rememberNoteVisibility ? defaultStore.state.visibility : defaultStore.state.defaultNoteVisibility) as typeof Misskey.noteVisibilities[number]); +const localOnly = ref(props.initialLocalOnly ?? (defaultStore.state.rememberNoteVisibility ? defaultStore.state.localOnly : defaultStore.state.defaultNoteLocalOnly)); +const visibility = ref(props.initialVisibility ?? (defaultStore.state.rememberNoteVisibility ? defaultStore.state.visibility : defaultStore.state.defaultNoteVisibility)); const visibleUsers = ref([]); if (props.initialVisibleUsers) { props.initialVisibleUsers.forEach(u => pushVisibleUser(u)); @@ -560,6 +559,9 @@ async function toggleLocalOnly() { } localOnly.value = !localOnly.value; + if (defaultStore.state.rememberNoteVisibility) { + defaultStore.set('localOnly', localOnly.value); + } } async function toggleReactionAcceptance() { @@ -1135,6 +1137,25 @@ function toggleScheduledNoteDelete() { } } +function showOtherMenu(ev: MouseEvent) { + os.popupMenu([{ + type: 'button', + text: i18n.ts.event, + icon: 'ti ti-calendar', + action: toggleEvent, + }, { + type: 'button', + text: i18n.ts.scheduledNoteDelete, + icon: 'ti ti-clock-hour-9', + action: toggleScheduledNoteDelete, + }, { type: 'divider' }, { + type: 'switch', + text: i18n.ts.disableRightClick, + icon: 'ti ti-mouse-off', + ref: disableRightClick, + }], ev.currentTarget ?? ev.target); +} + onMounted(() => { if (props.autofocus) { focus(); From 57a893b66f8d4fcde9042ede5714baf19c912ae2 Mon Sep 17 00:00:00 2001 From: NoriDev Date: Sun, 6 Oct 2024 22:39:17 +0900 Subject: [PATCH 293/315] 4.12.0-rc.1 --- package.json | 2 +- packages/cherrypick-js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 5946d3289f..ef022b2f1f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cherrypick", - "version": "4.12.0-beta.10", + "version": "4.12.0-rc.1", "basedMisskeyVersion": "2024.9.0", "codename": "nasubi", "repository": { diff --git a/packages/cherrypick-js/package.json b/packages/cherrypick-js/package.json index 3f1a04eff8..f28111137e 100644 --- a/packages/cherrypick-js/package.json +++ b/packages/cherrypick-js/package.json @@ -1,7 +1,7 @@ { "type": "module", "name": "cherrypick-js", - "version": "4.12.0-beta.10", + "version": "4.12.0-rc.1", "basedMisskeyVersion": "2024.9.0", "description": "CherryPick SDK for JavaScript", "license": "MIT", From e81a078a766eb97baadb2715a49dc3b5cd0527ca Mon Sep 17 00:00:00 2001 From: NoriDev Date: Sun, 6 Oct 2024 22:49:10 +0900 Subject: [PATCH 294/315] OpenAPI Version --- packages/backend/src/server/api/openapi/gen-spec.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/backend/src/server/api/openapi/gen-spec.ts b/packages/backend/src/server/api/openapi/gen-spec.ts index c7e15936a5..40131ca4db 100644 --- a/packages/backend/src/server/api/openapi/gen-spec.ts +++ b/packages/backend/src/server/api/openapi/gen-spec.ts @@ -13,9 +13,7 @@ export function genOpenapiSpec(config: Config, includeSelfRef = false) { openapi: '3.1.0', info: { - version: config.version, - basedMisskeyVersion: config.basedMisskeyVersion, - description: config.basedMisskeyVersion, + version: `${config.version} (${config.basedMisskeyVersion})`, title: 'CherryPick API', }, From 40a6d0b7f37b3aee6668ad942ba3b9c06f637501 Mon Sep 17 00:00:00 2001 From: NoriDev Date: Mon, 7 Oct 2024 01:52:21 +0900 Subject: [PATCH 295/315] =?UTF-8?q?feat:=20=EB=85=B8=ED=8A=B8=20=EC=9E=90?= =?UTF-8?q?=EB=8F=99=20=EB=B2=88=EC=97=AD=20=EA=B8=B0=EB=8A=A5=20=20=20-?= =?UTF-8?q?=20=EB=85=B8=ED=8A=B8=20=EC=9E=90=EB=8F=99=20=EB=B2=88=EC=97=AD?= =?UTF-8?q?=EC=9D=80=20=EB=B2=88=EC=97=AD=20=EC=84=9C=EB=B9=84=EC=8A=A4?= =?UTF-8?q?=EC=9D=98=20API=20=EC=A0=9C=ED=95=9C=EC=9D=84=20=EB=B0=A9?= =?UTF-8?q?=EC=A7=80=ED=95=98=EA=B8=B0=20=EC=9C=84=ED=95=B4=20=EC=9E=90?= =?UTF-8?q?=EB=8F=99=EC=9C=BC=EB=A1=9C=20=ED=99=9C=EC=84=B1=ED=99=94?= =?UTF-8?q?=EB=90=98=EC=A7=80=20=EC=95=8A=EC=9C=BC=EB=A9=B0,=20=EA=B8=B0?= =?UTF-8?q?=EB=B3=B8=EC=A0=81=EC=9C=BC=EB=A1=9C=20=EB=B9=84=ED=99=9C?= =?UTF-8?q?=EC=84=B1=ED=99=94=EB=90=98=EC=96=B4=20=EC=9E=88=EC=8A=B5?= =?UTF-8?q?=EB=8B=88=EB=8B=A4.=20=20=20-=20`=EC=97=AD=ED=95=A0`=EC=97=90?= =?UTF-8?q?=EC=84=9C=20`=EC=9E=90=EB=8F=99=20=EB=B2=88=EC=97=AD=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=9D=B4=EC=9A=A9=20=EA=B0=80=EB=8A=A5=20?= =?UTF-8?q?=EC=97=AC=EB=B6=80`=EB=A5=BC=20=ED=99=9C=EC=84=B1=ED=99=94=20?= =?UTF-8?q?=ED=95=98=EB=A9=B4=20=EC=9E=90=EB=8F=99=20=EB=B2=88=EC=97=AD?= =?UTF-8?q?=EC=9D=84=20=EC=82=AC=EC=9A=A9=ED=95=A0=20=EC=88=98=20=EC=9E=88?= =?UTF-8?q?=EB=8A=94=20=EC=83=81=ED=83=9C=EA=B0=80=20=EB=90=A9=EB=8B=88?= =?UTF-8?q?=EB=8B=A4.=20=20=20-=20=EC=9D=B4=ED=9B=84,=20=EA=B0=81=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=EC=9E=90=EB=B3=84=EB=A1=9C=20`=EC=84=A4?= =?UTF-8?q?=EC=A0=95`=20-=20`=EC=9D=BC=EB=B0=98`=EC=97=90=EC=84=9C=20`?= =?UTF-8?q?=EB=85=B8=ED=8A=B8=20=EC=9E=90=EB=8F=99=20=EB=B2=88=EC=97=AD`?= =?UTF-8?q?=EC=9D=84=20=ED=99=9C=EC=84=B1=ED=99=94=ED=95=9C=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=EC=9E=90=EB=8A=94=20=EC=9E=90=EB=8F=99=20=EB=B2=88?= =?UTF-8?q?=EC=97=AD=EC=9D=84=20=EC=82=AC=EC=9A=A9=ED=95=A0=20=EC=88=98=20?= =?UTF-8?q?=EC=9E=88=EC=8A=B5=EB=8B=88=EB=8B=A4.=20=20=20-=20=EB=85=B8?= =?UTF-8?q?=ED=8A=B8=EA=B0=80=20=EC=95=84=EB=9E=98=EC=99=80=20=EA=B0=99?= =?UTF-8?q?=EC=9D=B4=20=EC=84=A4=EC=A0=95=EB=90=9C=20=EA=B2=BD=EC=9A=B0?= =?UTF-8?q?=EC=97=90=EB=8A=94=20=EB=85=B8=ED=8A=B8=20=EC=9E=90=EB=8F=99=20?= =?UTF-8?q?=EB=B2=88=EC=97=AD=EC=9D=84=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EC=8A=B5=EB=8B=88=EB=8B=A4.=20=20=20=20?= =?UTF-8?q?=20-=20=EB=85=B8=ED=8A=B8=EA=B0=80=20`=EB=82=B4=EC=9A=A9=20?= =?UTF-8?q?=EA=B0=80=EB=A6=AC=EA=B8=B0`=EB=A1=9C=20=EC=84=A4=EC=A0=95?= =?UTF-8?q?=EB=90=98=EC=96=B4=20=EC=9E=88=EC=9D=8C=20=20=20=20=20-=20?= =?UTF-8?q?=EB=85=B8=ED=8A=B8=EC=9D=98=20=EB=82=B4=EC=9A=A9=EC=9D=B4=20?= =?UTF-8?q?=EA=B8=BA=20=20=20=20=20-=20=EB=85=B8=ED=8A=B8=EC=97=90=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=EC=9D=B4=20=ED=8F=AC=ED=95=A8=EB=90=98?= =?UTF-8?q?=EC=96=B4=20=EC=9E=88=EC=9D=8C=20=20=20-=20`=EC=9E=90=EB=8F=99?= =?UTF-8?q?=20=EB=B2=88=EC=97=AD=20=EA=B8=B0=EB=8A=A5=20=EC=9D=B4=EC=9A=A9?= =?UTF-8?q?=20=EA=B0=80=EB=8A=A5=20=EC=97=AC=EB=B6=80`=20=EC=97=AD?= =?UTF-8?q?=ED=95=A0=EC=9D=98=20=EA=B6=8C=ED=95=9C=EC=9D=84=20=EC=83=81?= =?UTF-8?q?=EC=8B=A4=ED=95=98=EA=B2=8C=20=EB=90=98=EB=A9=B4=20=EB=AA=A8?= =?UTF-8?q?=EB=93=A0=20=EC=82=AC=EC=9A=A9=EC=9E=90=EC=9D=98=20`=EB=85=B8?= =?UTF-8?q?=ED=8A=B8=20=EC=9E=90=EB=8F=99=20=EB=B2=88=EC=97=AD`=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=EB=8F=84=20=EC=9E=90=EB=8F=99=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=B9=84=ED=99=9C=EC=84=B1=ED=99=94=20=EB=90=A9?= =?UTF-8?q?=EB=8B=88=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG_CHERRYPICK.md | 10 ++++++++++ locales/en-US.yml | 2 ++ locales/index.d.ts | 9 +++++++++ locales/ja-JP.yml | 2 ++ locales/ko-KR.yml | 3 +++ packages/backend/src/core/RoleService.ts | 3 +++ .../backend/src/models/json-schema/role.ts | 4 ++++ packages/cherrypick-js/src/autogen/types.ts | 1 + packages/frontend-shared/js/const.ts | 1 + packages/frontend/src/components/MkNote.vue | 5 ++++- .../src/components/MkNoteDetailed.vue | 5 ++++- .../src/components/MkSubNoteContent.vue | 5 ++++- .../frontend/src/pages/admin/roles.editor.vue | 20 +++++++++++++++++++ packages/frontend/src/pages/admin/roles.vue | 10 +++++++++- .../frontend/src/pages/settings/general.vue | 14 +++++++++++++ .../frontend/src/scripts/get-note-menu.ts | 4 +++- packages/frontend/src/store.ts | 4 ++++ 17 files changed, 97 insertions(+), 5 deletions(-) diff --git a/CHANGELOG_CHERRYPICK.md b/CHANGELOG_CHERRYPICK.md index efeeb88960..1e1100ac6c 100644 --- a/CHANGELOG_CHERRYPICK.md +++ b/CHANGELOG_CHERRYPICK.md @@ -63,6 +63,15 @@ Misskey의 전체 변경 사항을 확인하려면, [CHANGELOG.md#2024xx](CHANGE - Feat: 모바일 환경에서 하단 내비게이션 바를 개인화할 수 있음 - `설정` - `내비게이션 바`의 `하단 내비게이션 바`에서 설정할 수 있습니다. - Feat: 리버시 대전 중에 상대방에게 리액션을 보낼 수 있음 (misskey-dev/misskey#13119) +- Feat: 노트 자동 번역 기능 + - 노트 자동 번역은 번역 서비스의 API 제한을 방지하기 위해 자동으로 활성화되지 않으며, 기본적으로 비활성화되어 있습니다. + - `역할`에서 `자동 번역 기능 이용 가능 여부`를 활성화 하면 자동 번역을 사용할 수 있는 상태가 됩니다. + - 이후, 각 사용자별로 `설정` - `일반`에서 `노트 자동 번역`을 활성화한 사용자는 자동 번역을 사용할 수 있습니다. + - 노트가 아래와 같이 설정된 경우에는 노트 자동 번역을 사용하지 않습니다. + - 노트가 `내용 가리기`로 설정되어 있음 + - 노트의 내용이 긺 + - 노트에 파일이 포함되어 있음 + - `자동 번역 기능 이용 가능 여부` 역할의 권한을 상실하게 되면 모든 사용자의 `노트 자동 번역` 설정도 자동으로 비활성화 됩니다. ### Client - Enhance: CherryPick 업데이트 페이지를 제어판 목록에 추가함 @@ -88,6 +97,7 @@ Misskey의 전체 변경 사항을 확인하려면, [CHANGELOG.md#2024xx](CHANGE - Fix: 이미지 자르기를 할 때 이미지 크기 전체를 표시하지 못할 수 있음 - Fix: 페이지에서 페이지 생성 버튼이 본문에 중복으로 표시됨 - Fix: 노트 본문의 사용자 멘션 영역을 클릭하면 노트 상세 페이지가 표시됨 +- Fix: 역할 권한에 의해 번역 기능을 사용할 수 없을 때도 번역 버튼이 표시됨 ### Server - Enhance: 보안 향상을 위해 비밀번호 해싱 알고리즘이 `bcrypt`에서 `argon2`로 변경됨 (kokonect-link/cherrypick#511), (1673beta/cherrypick#88) diff --git a/locales/en-US.yml b/locales/en-US.yml index 902b35f077..d8c740bbf6 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -1,5 +1,7 @@ --- _lang_: "English" +useAutoTranslate: "Automatic translation of notes" +useAutoTranslateDescription: "The server administrator has disabled this feature.\nContact your server administrator to use the feature." widgets: "Widgets" postNote: "Post note" bottomNavbar: "Bottom navigation bar" diff --git a/locales/index.d.ts b/locales/index.d.ts index 84a08564ab..d022882abd 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -13,6 +13,15 @@ export interface Locale extends ILocale { * 日本語 */ "_lang_": string; + /** + * ノートを自動翻訳 + */ + "useAutoTranslate": string; + /** + * サーバー管理者がこの機能を無効にしました。 + * 機能を使用するには、サーバー管理者にお問い合わせください。 + */ + "useAutoTranslateDescription": string; /** * ウィジェット */ diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 7dfe486332..7b4784299a 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1,5 +1,7 @@ _lang_: "日本語" +useAutoTranslate: "ノートを自動翻訳" +useAutoTranslateDescription: "サーバー管理者がこの機能を無効にしました。\n機能を使用するには、サーバー管理者にお問い合わせください。" widgets: "ウィジェット" postNote: "ノートを作成" bottomNavbar: "下のナビゲーションバー" diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index c085f31509..027c4a43df 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -1,5 +1,7 @@ --- _lang_: "한국어" +useAutoTranslate: "노트 자동 번역" +useAutoTranslateDescription: "서버 관리자가 이 기능을 사용할 수 없도록 설정했어요.\n기능을 사용하려면 서버 관리자에게 문의해 주세요." widgets: "위젯" postNote: "노트 작성" bottomNavbar: "하단 내비게이션 바" @@ -1991,6 +1993,7 @@ _role: canHideAds: "광고 숨기기" canSearchNotes: "노트 검색 이용 가능 여부" canUseTranslator: "번역 기능 이용 가능 여부" + canUseAutoTranslate: "자동 번역 기능 이용 가능 여부" avatarDecorationLimit: "최대로 붙일 수 있는 아바타 장식 개수" canImportAntennas: "안테나 가져오기 허용" canImportBlocking: "차단 목록 가져오기 허용" diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts index f7d0b4025d..c8c8a64c4b 100644 --- a/packages/backend/src/core/RoleService.ts +++ b/packages/backend/src/core/RoleService.ts @@ -45,6 +45,7 @@ export type RolePolicies = { canManageAvatarDecorations: boolean; canSearchNotes: boolean; canUseTranslator: boolean; + canUseAutoTranslate: boolean; canHideAds: boolean; driveCapacityMb: number; alwaysMarkNsfw: boolean; @@ -80,6 +81,7 @@ export const DEFAULT_POLICIES: RolePolicies = { canManageAvatarDecorations: false, canSearchNotes: false, canUseTranslator: true, + canUseAutoTranslate: false, canHideAds: false, driveCapacityMb: 100, alwaysMarkNsfw: false, @@ -386,6 +388,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit { canManageAvatarDecorations: calc('canManageAvatarDecorations', vs => vs.some(v => v === true)), canSearchNotes: calc('canSearchNotes', vs => vs.some(v => v === true)), canUseTranslator: calc('canUseTranslator', vs => vs.some(v => v === true)), + canUseAutoTranslate: calc('canUseAutoTranslate', vs => vs.some(v => v === true)), canHideAds: calc('canHideAds', vs => vs.some(v => v === true)), driveCapacityMb: calc('driveCapacityMb', vs => Math.max(...vs)), alwaysMarkNsfw: calc('alwaysMarkNsfw', vs => vs.some(v => v === true)), diff --git a/packages/backend/src/models/json-schema/role.ts b/packages/backend/src/models/json-schema/role.ts index d52896d0ba..84d8e2a79c 100644 --- a/packages/backend/src/models/json-schema/role.ts +++ b/packages/backend/src/models/json-schema/role.ts @@ -216,6 +216,10 @@ export const packedRolePoliciesSchema = { type: 'boolean', optional: false, nullable: false, }, + canUseAutoTranslate: { + type: 'boolean', + optional: false, nullable: false, + }, canHideAds: { type: 'boolean', optional: false, nullable: false, diff --git a/packages/cherrypick-js/src/autogen/types.ts b/packages/cherrypick-js/src/autogen/types.ts index 4044484940..3dd0e714c7 100644 --- a/packages/cherrypick-js/src/autogen/types.ts +++ b/packages/cherrypick-js/src/autogen/types.ts @@ -5174,6 +5174,7 @@ export type components = { canManageAvatarDecorations: boolean; canSearchNotes: boolean; canUseTranslator: boolean; + canUseAutoTranslate: boolean; canHideAds: boolean; driveCapacityMb: number; alwaysMarkNsfw: boolean; diff --git a/packages/frontend-shared/js/const.ts b/packages/frontend-shared/js/const.ts index d7f90bfa80..5b56d97870 100644 --- a/packages/frontend-shared/js/const.ts +++ b/packages/frontend-shared/js/const.ts @@ -88,6 +88,7 @@ export const ROLE_POLICIES = [ 'canManageAvatarDecorations', 'canSearchNotes', 'canUseTranslator', + 'canUseAutoTranslate', 'canHideAds', 'driveCapacityMb', 'alwaysMarkNsfw', diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue index 864df87476..ecda25d338 100644 --- a/packages/frontend/src/components/MkNote.vue +++ b/packages/frontend/src/components/MkNote.vue @@ -92,7 +92,7 @@ SPDX-License-Identifier: AGPL-3.0-only :enableEmojiMenu="true" :enableEmojiMenuReaction="true" /> -
+
@@ -741,6 +741,9 @@ const isForeignLanguage: boolean = appearNote.value.text != null && (() => { return postLang !== '' && postLang !== targetLang; })(); +if (defaultStore.state.useAutoTranslate && !$i.policies.canUseAutoTranslate) defaultStore.set('useAutoTranslate', false); +if ($i.policies.canUseTranslator && defaultStore.state.useAutoTranslate && !isLong && appearNote.value.text && isForeignLanguage) translate(); + async function translate(): Promise { if (translation.value != null) return; translating.value = true; diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue index a88ec1e9ab..3bb070a5e3 100644 --- a/packages/frontend/src/components/MkNoteDetailed.vue +++ b/packages/frontend/src/components/MkNoteDetailed.vue @@ -115,7 +115,7 @@ SPDX-License-Identifier: AGPL-3.0-only :enableEmojiMenuReaction="true" /> RN: -
+
@@ -681,6 +681,9 @@ const isForeignLanguage: boolean = appearNote.value.text != null && (() => { return postLang !== '' && postLang !== targetLang; })(); +if (defaultStore.state.useAutoTranslate && !$i.policies.canUseAutoTranslate) defaultStore.set('useAutoTranslate', false); +if ($i.policies.canUseTranslator && defaultStore.state.useAutoTranslate && appearNote.value.text && isForeignLanguage) translate(); + async function translate(): Promise { if (translation.value != null) return; translating.value = true; diff --git a/packages/frontend/src/components/MkSubNoteContent.vue b/packages/frontend/src/components/MkSubNoteContent.vue index f6f7821795..096c576175 100644 --- a/packages/frontend/src/components/MkSubNoteContent.vue +++ b/packages/frontend/src/components/MkSubNoteContent.vue @@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only :enableEmojiMenuReaction="true" /> RN: ... -
+
@@ -441,6 +441,9 @@ const isForeignLanguage: boolean = note.value.text != null && (() => { return postLang !== '' && postLang !== targetLang; })(); +if (defaultStore.state.useAutoTranslate && !$i.policies.canUseAutoTranslate) defaultStore.set('useAutoTranslate', false); +if ($i.policies.canUseTranslator && defaultStore.state.useAutoTranslate && !isLong && note.value.text && isForeignLanguage) translate(); + async function translate(): Promise { if (translation.value != null) return; translating.value = true; diff --git a/packages/frontend/src/pages/admin/roles.editor.vue b/packages/frontend/src/pages/admin/roles.editor.vue index 88979322de..7e77bc1f3f 100644 --- a/packages/frontend/src/pages/admin/roles.editor.vue +++ b/packages/frontend/src/pages/admin/roles.editor.vue @@ -358,6 +358,26 @@ SPDX-License-Identifier: AGPL-3.0-only
+ + + +
+ + + + + + + + + +
+
+