diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 9684489927041813bb894fa3c862f5f95249e9c9..ce25095d5acdfb05667ea2e26068921f04a2b3b4 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -203,6 +203,7 @@ done: "完了" processing: "処ç†ä¸" preview: "プレビュー" default: "デフォルト" +defaultValueIs: "デフォルト: {value}" noCustomEmojis: "絵文å—ã¯ã‚ã‚Šã¾ã›ã‚“" noJobs: "ジョブã¯ã‚ã‚Šã¾ã›ã‚“" federating: "連åˆä¸" @@ -855,6 +856,8 @@ noEmailServerWarning: "メールサーãƒãƒ¼ã®è¨å®šãŒã•ã‚Œã¦ã„ã¾ã›ã‚“。 thereIsUnresolvedAbuseReportWarning: "未対応ã®é€šå ±ãŒã‚ã‚Šã¾ã™ã€‚" recommended: "推奨" check: "ãƒã‚§ãƒƒã‚¯" +driveCapOverrideLabel: "ã“ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã®ãƒ‰ãƒ©ã‚¤ãƒ–容é‡ä¸Šé™ã‚’変更" +driveCapOverrideCaption: "0以下を指定ã™ã‚‹ã¨è§£é™¤ã•ã‚Œã¾ã™ã€‚" requireAdminForView: "閲覧ã™ã‚‹ã«ã¯ç®¡ç†è€…アカウントã§ãƒã‚°ã‚¤ãƒ³ã—ã¦ã„ã‚‹å¿…è¦ãŒã‚ã‚Šã¾ã™ã€‚" isSystemAccount: "システムã«ã‚ˆã‚Šè‡ªå‹•ã§ä½œæˆãƒ»ç®¡ç†ã•ã‚Œã¦ã„るアカウントã§ã™ã€‚" typeToConfirm: "ã“ã®æ“作を行ã†ã«ã¯ {x} ã¨å…¥åŠ›ã—ã¦ãã ã•ã„" diff --git a/packages/backend/migration/1655813815729-driveCapacityOverrideMb.js b/packages/backend/migration/1655813815729-driveCapacityOverrideMb.js new file mode 100644 index 0000000000000000000000000000000000000000..f257cd112fa340dd9f3c2ee5e44ba38e1f9e0857 --- /dev/null +++ b/packages/backend/migration/1655813815729-driveCapacityOverrideMb.js @@ -0,0 +1,13 @@ +export class driveCapacityOverrideMb1655813815729 { + name = 'driveCapacityOverrideMb1655813815729' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "user" ADD "driveCapacityOverrideMb" integer`); + await queryRunner.query(`COMMENT ON COLUMN "user"."driveCapacityOverrideMb" IS 'Overrides user drive capacity limit'`); + } + + async down(queryRunner) { + await queryRunner.query(`COMMENT ON COLUMN "user"."driveCapacityOverrideMb" IS 'Overrides user drive capacity limit'`); + await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "driveCapacityOverrideMb"`); + } +} diff --git a/packages/backend/src/models/entities/user.ts b/packages/backend/src/models/entities/user.ts index df92fb8259f90e673265d238c69ee5e56cd206bd..bc9446be4108a386082ae35fb63fb1d93c811b56 100644 --- a/packages/backend/src/models/entities/user.ts +++ b/packages/backend/src/models/entities/user.ts @@ -218,6 +218,12 @@ export class User { }) public token: string | null; + @Column('integer', { + nullable: true, + comment: 'Overrides user drive capacity limit', + }) + public driveCapacityOverrideMb: number | null; + constructor(data: Partial<User>) { if (data == null) return; diff --git a/packages/backend/src/models/repositories/user.ts b/packages/backend/src/models/repositories/user.ts index 8a4e48efdd84f079092d491b02ea7e3131c59938..645091395a8a0a38bf4e02546f0503dbca0ee009 100644 --- a/packages/backend/src/models/repositories/user.ts +++ b/packages/backend/src/models/repositories/user.ts @@ -315,6 +315,7 @@ export const UserRepository = db.getRepository(User).extend({ } : undefined) : undefined, emojis: populateEmojis(user.emojis, user.host), onlineStatus: this.getOnlineStatus(user), + driveCapacityOverrideMb: user.driveCapacityOverrideMb, ...(opts.detail ? { url: profile!.url, diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index 4a2ecebd8697283a70b702eaec7b974a7d36c5ec..4644f34d9469a873004c61562eb8592127728b55 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -314,6 +314,7 @@ import * as ep___users_search from './endpoints/users/search.js'; import * as ep___users_show from './endpoints/users/show.js'; import * as ep___users_stats from './endpoints/users/stats.js'; import * as ep___fetchRss from './endpoints/fetch-rss.js'; +import * as ep___admin_driveCapOverride from './endpoints/admin/drive-capacity-override.js'; const eps = [ ['admin/meta', ep___admin_meta], @@ -629,6 +630,7 @@ const eps = [ ['users/search', ep___users_search], ['users/show', ep___users_show], ['users/stats', ep___users_stats], + ['admin/drive-capacity-override', ep___admin_driveCapOverride], ['fetch-rss', ep___fetchRss], ]; diff --git a/packages/backend/src/server/api/endpoints/admin/drive-capacity-override.ts b/packages/backend/src/server/api/endpoints/admin/drive-capacity-override.ts new file mode 100644 index 0000000000000000000000000000000000000000..a4b29770e1fa0ade1897f2d56986c517b0e9cc3d --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/drive-capacity-override.ts @@ -0,0 +1,47 @@ +import define from '../../define.js'; +import { Users } from '@/models/index.js'; +import { User } from '@/models/entities/user.js'; +import { insertModerationLog } from '@/services/insert-moderation-log.js'; +export const meta = { + tags: ['admin'], + + requireCredential: true, + requireModerator: true, +} as const; + +export const paramDef = { + type: 'object', + properties: { + userId: { type: 'string', format: 'misskey:id' }, + overrideMb: { type: 'number', nullable: true }, + }, + required: ['userId', 'overrideMb'], +} as const; + +// eslint-disable-next-line import/no-default-export +export default define(meta, paramDef, async (ps, me) => { + const user = await Users.findOneBy({ id: ps.userId }); + + if (user == null) { + throw new Error('user not found'); + } + + if (!Users.isLocalUser(user)) { + throw new Error('user is not local user'); + } + + /*if (user.isAdmin) { + throw new Error('cannot suspend admin'); + } + if (user.isModerator) { + throw new Error('cannot suspend moderator'); + }*/ + + await Users.update(user.id, { + driveCapacityOverrideMb: ps.overrideMb, + }); + + insertModerationLog(me, 'change-drive-capacity-override', { + targetId: user.id, + }); +}); diff --git a/packages/backend/src/server/api/endpoints/drive.ts b/packages/backend/src/server/api/endpoints/drive.ts index 47e940cddd51e352569d31b4720bb32e90b21fc0..82497adefa4b807c8f1411620a37e03a8446317c 100644 --- a/packages/backend/src/server/api/endpoints/drive.ts +++ b/packages/backend/src/server/api/endpoints/drive.ts @@ -39,7 +39,7 @@ export default define(meta, paramDef, async (ps, user) => { const usage = await DriveFiles.calcDriveUsageOf(user.id); return { - capacity: 1024 * 1024 * instance.localDriveCapacityMb, + capacity: 1024 * 1024 * (user.driveCapacityOverrideMb || instance.localDriveCapacityMb), usage: usage, }; }); diff --git a/packages/backend/src/services/drive/add-file.ts b/packages/backend/src/services/drive/add-file.ts index a25413187b8c7a7bb3706fd70efe3eb5bc13bb7d..0dfad11cfb04833b8311855de936a18cd7174835 100644 --- a/packages/backend/src/services/drive/add-file.ts +++ b/packages/backend/src/services/drive/add-file.ts @@ -307,7 +307,7 @@ async function deleteOldFile(user: IRemoteUser) { type AddFileArgs = { /** User who wish to add file */ - user: { id: User['id']; host: User['host'] } | null; + user: { id: User['id']; host: User['host']; driveCapacityOverrideMb: User['driveCapacityOverrideMb'] } | null; /** File path */ path: string; /** Name */ @@ -371,9 +371,16 @@ export async function addFile({ //#region Check drive usage if (user && !isLink) { const usage = await DriveFiles.calcDriveUsageOf(user); + const u = await Users.findOneBy({ id: user.id }); const instance = await fetchMeta(); - const driveCapacity = 1024 * 1024 * (Users.isLocalUser(user) ? instance.localDriveCapacityMb : instance.remoteDriveCapacityMb); + let driveCapacity = 1024 * 1024 * (Users.isLocalUser(user) ? instance.localDriveCapacityMb : instance.remoteDriveCapacityMb); + + if (Users.isLocalUser(user) && u?.driveCapacityOverrideMb != null) { + driveCapacity = 1024 * 1024 * u.driveCapacityOverrideMb; + logger.debug('drive capacity override applied'); + logger.debug(`overrideCap: ${driveCapacity}bytes, usage: ${usage}bytes, u+s: ${usage + info.size}bytes`); + } logger.debug(`drive usage is ${usage} (max: ${driveCapacity})`); diff --git a/packages/client/src/components/code-core.vue b/packages/client/src/components/code-core.vue index 45a38afe04df64d32377f1e4afb1d0f8839528f1..65dee5cdaeaa18ae82c5ab439724e77b5380e5f1 100644 --- a/packages/client/src/components/code-core.vue +++ b/packages/client/src/components/code-core.vue @@ -5,7 +5,7 @@ <script lang="ts" setup> import { computed } from 'vue'; -import 'prismjs'; +import { Prism } from 'prismjs'; import 'prismjs/themes/prism-okaidia.css'; const props = defineProps<{ diff --git a/packages/client/src/components/form-dialog.vue b/packages/client/src/components/form-dialog.vue index 5fd9ec460b3a7d86181247d6e52f33e49fae5464..f05dde16f870d0917ac8b89d4c2ab831222b072c 100644 --- a/packages/client/src/components/form-dialog.vue +++ b/packages/client/src/components/form-dialog.vue @@ -98,7 +98,7 @@ export default defineComponent({ created() { for (const item in this.form) { - this.values[item] = this.form[item].hasOwnProperty('default') ? this.form[item].default : null; + this.values[item] = this.form[item].default ?? null; } }, diff --git a/packages/client/src/components/global/router-view.vue b/packages/client/src/components/global/router-view.vue index 7138faaa9dc23251dffe90f9d71c03d359215b9e..fca2371f0dc018cbc86d44fb43c04d128f599eb5 100644 --- a/packages/client/src/components/global/router-view.vue +++ b/packages/client/src/components/global/router-view.vue @@ -13,9 +13,6 @@ const props = defineProps<{ router?: Router; }>(); -const emit = defineEmits<{ -}>(); - const router = props.router ?? inject('router'); if (router == null) { diff --git a/packages/client/src/components/tag-cloud.vue b/packages/client/src/components/tag-cloud.vue index 5ffa7321e476cfc55a7ca580202f6a81093a592c..9f3bc1c603d3defa7772c69ff1bce65cbee02122 100644 --- a/packages/client/src/components/tag-cloud.vue +++ b/packages/client/src/components/tag-cloud.vue @@ -13,8 +13,6 @@ import { onMounted, ref, watch, PropType, onBeforeUnmount } from 'vue'; import tinycolor from 'tinycolor2'; -const props = defineProps<{}>(); - const loaded = !!window.TagCanvas; const SAFE_FOR_HTML_ID = 'abcdefghijklmnopqrstuvwxyz'; const computedStyle = getComputedStyle(document.documentElement); diff --git a/packages/client/src/pages/admin/_header_.vue b/packages/client/src/pages/admin/_header_.vue index 73747e1164c58fcba308cd2ec7cbd7c8526006d8..aea2663c3924d58a63f61cc7645c38a173fcdcd8 100644 --- a/packages/client/src/pages/admin/_header_.vue +++ b/packages/client/src/pages/admin/_header_.vue @@ -75,7 +75,6 @@ const hasTabs = computed(() => { const showTabsPopup = (ev: MouseEvent) => { if (!hasTabs.value) return; - if (!narrow.value) return; ev.preventDefault(); ev.stopPropagation(); const menu = props.tabs.map(tab => ({ diff --git a/packages/client/src/pages/admin/bot-protection.vue b/packages/client/src/pages/admin/bot-protection.vue index d2e7919b4ff6a7e756ca1df85321d0e3af36afc9..d316f973bcd2f72bcad9fe6672352b872afe7549 100644 --- a/packages/client/src/pages/admin/bot-protection.vue +++ b/packages/client/src/pages/admin/bot-protection.vue @@ -61,27 +61,22 @@ let hcaptchaSecretKey: string | null = $ref(null); let recaptchaSiteKey: string | null = $ref(null); let recaptchaSecretKey: string | null = $ref(null); -const enableHcaptcha = $computed(() => provider === 'hcaptcha'); -const enableRecaptcha = $computed(() => provider === 'recaptcha'); - async function init() { const meta = await os.api('admin/meta'); - enableHcaptcha = meta.enableHcaptcha; hcaptchaSiteKey = meta.hcaptchaSiteKey; hcaptchaSecretKey = meta.hcaptchaSecretKey; - enableRecaptcha = meta.enableRecaptcha; recaptchaSiteKey = meta.recaptchaSiteKey; recaptchaSecretKey = meta.recaptchaSecretKey; - provider = enableHcaptcha ? 'hcaptcha' : enableRecaptcha ? 'recaptcha' : null; + provider = meta.enableHcaptcha ? 'hcaptcha' : meta.enableRecaptcha ? 'recaptcha' : null; } function save() { os.apiWithDialog('admin/update-meta', { - enableHcaptcha, + enableHcaptcha: provider === 'hcaptcha', hcaptchaSiteKey, hcaptchaSecretKey, - enableRecaptcha, + enableRecaptcha: provider === 'recaptcha', recaptchaSiteKey, recaptchaSecretKey, }).then(() => { diff --git a/packages/client/src/pages/admin/overview.user.vue b/packages/client/src/pages/admin/overview.user.vue index 40592b280bd0e4fa08a17edf42498a4f405a7c1a..d70336f3c2716bbae14073cf9fa7cf1ef9565f3d 100644 --- a/packages/client/src/pages/admin/overview.user.vue +++ b/packages/client/src/pages/admin/overview.user.vue @@ -19,7 +19,7 @@ const props = defineProps<{ user: misskey.entities.User; }>(); -const chart = $ref(null); +let chart = $ref(null); os.apiGet('charts/user/notes', { userId: props.user.id, limit: 16, span: 'day' }).then(res => { chart = res; diff --git a/packages/client/src/pages/gallery/post.vue b/packages/client/src/pages/gallery/post.vue index e16ccc315023ac6f3db1d472c069e3a23a2dd4eb..e87a541e987eefb727fe02201b194a2cdf0b2756 100644 --- a/packages/client/src/pages/gallery/post.vue +++ b/packages/client/src/pages/gallery/post.vue @@ -74,8 +74,8 @@ const props = defineProps<{ postId: string; }>(); -const post = $ref(null); -const error = $ref(null); +let post = $ref(null); +let error = $ref(null); const otherPostsPagination = { endpoint: 'users/gallery/posts' as const, limit: 6, diff --git a/packages/client/src/pages/my-antennas/editor.vue b/packages/client/src/pages/my-antennas/editor.vue index 6f3c4afbfec74e264b25c213373ca6618f4a4674..9470257c6c8f9c5333c283bf099e1dd93e1dd353 100644 --- a/packages/client/src/pages/my-antennas/editor.vue +++ b/packages/client/src/pages/my-antennas/editor.vue @@ -46,6 +46,7 @@ <script lang="ts" setup> import { watch } from 'vue'; +import * as Acct from 'misskey-js/built/acct'; import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import MkTextarea from '@/components/form/textarea.vue'; diff --git a/packages/client/src/pages/my-lists/list.vue b/packages/client/src/pages/my-lists/list.vue index 5bc0bf41dd8526800328c6706034696c1eef99bc..892878ae88df13f1756b791bb08cd4136c0f32d0 100644 --- a/packages/client/src/pages/my-lists/list.vue +++ b/packages/client/src/pages/my-lists/list.vue @@ -41,6 +41,7 @@ import MkButton from '@/components/ui/button.vue'; import * as os from '@/os'; import { mainRouter } from '@/router'; import { definePageMetadata } from '@/scripts/page-metadata'; +import { i18n } from '@/i18n'; const props = defineProps<{ listId: string; diff --git a/packages/client/src/pages/theme-editor.vue b/packages/client/src/pages/theme-editor.vue index cec38335946801b42c2ec8423c7c30bc6f10afca..44b5a05f27548c6cd89f28f7b73153bb51642abf 100644 --- a/packages/client/src/pages/theme-editor.vue +++ b/packages/client/src/pages/theme-editor.vue @@ -78,6 +78,7 @@ import FormButton from '@/components/ui/button.vue'; import FormTextarea from '@/components/form/textarea.vue'; import FormFolder from '@/components/form/folder.vue'; +import { $i } from '@/account'; import { Theme, applyTheme } from '@/scripts/theme'; import lightTheme from '@/themes/_light.json5'; import darkTheme from '@/themes/_dark.json5'; @@ -118,7 +119,7 @@ const fgColors = [ { color: 'pink', forLight: '#84667d', forDark: '#e4d1e0', forPreview: '#b12390' }, ]; -const theme = $ref<Partial<Theme>>({ +let theme = $ref<Partial<Theme>>({ base: 'light', props: lightTheme.props, }); diff --git a/packages/client/src/pages/user-info.vue b/packages/client/src/pages/user-info.vue index 204ece7eb66edc9f325380fa6d28b7d43491fb2a..51d224dfddd924218a14c3bdf3e0cf1cc04c5349 100644 --- a/packages/client/src/pages/user-info.vue +++ b/packages/client/src/pages/user-info.vue @@ -85,6 +85,17 @@ </FormSection> </div> <div v-else-if="tab === 'moderation'" class="_formRoot"> + <FormSection> + <template #label>Drive Capacity Override</template> + + <FormInput v-if="user.host == null" v-model="driveCapacityOverrideMb" inline :manual-save="true" type="number" :placeholder="i18n.t('defaultValueIs', { value: instance.driveCapacityPerLocalUserMb })" @update:model-value="applyDriveCapacityOverride"> + <template #label>{{ i18n.ts.driveCapOverrideLabel }}</template> + <template #suffix>MB</template> + <template #caption> + {{ i18n.ts.driveCapOverrideCaption }} + </template> + </FormInput> + </FormSection> <FormSwitch v-if="user.host == null && $i.isAdmin && (moderator || !user.isAdmin)" v-model="moderator" class="_formBlock" @update:modelValue="toggleModerator">{{ $ts.moderator }}</FormSwitch> <FormSwitch v-model="silenced" class="_formBlock" @update:modelValue="toggleSilence">{{ $ts.silence }}</FormSwitch> <FormSwitch v-model="suspended" class="_formBlock" @update:modelValue="toggleSuspend">{{ $ts.suspend }}</FormSwitch> @@ -141,7 +152,7 @@ </template> <script lang="ts" setup> -import { computed, defineAsyncComponent, defineComponent, watch } from 'vue'; +import { computed, watch } from 'vue'; import * as misskey from 'misskey-js'; import MkChart from '@/components/chart.vue'; import MkObjectView from '@/components/object-view.vue'; @@ -150,6 +161,8 @@ import FormSwitch from '@/components/form/switch.vue'; import FormLink from '@/components/form/link.vue'; import FormSection from '@/components/form/section.vue'; import FormButton from '@/components/ui/button.vue'; +import FormInput from '@/components/form/input.vue'; +import FormSplit from '@/components/form/split.vue'; import FormFolder from '@/components/form/folder.vue'; import MkKeyValue from '@/components/key-value.vue'; import MkSelect from '@/components/form/select.vue'; @@ -164,6 +177,7 @@ import { userPage, acct } from '@/filters/user'; import { definePageMetadata } from '@/scripts/page-metadata'; import { i18n } from '@/i18n'; import { iAmAdmin, iAmModerator } from '@/account'; +import { instance } from '@/instance'; const props = defineProps<{ userId: string; @@ -172,13 +186,14 @@ const props = defineProps<{ let tab = $ref('overview'); let chartSrc = $ref('per-user-notes'); let user = $ref<null | misskey.entities.UserDetailed>(); -let init = $ref(); +let init = $ref<ReturnType<typeof createFetcher>>(); let info = $ref(); let ips = $ref(null); let ap = $ref(null); let moderator = $ref(false); let silenced = $ref(false); let suspended = $ref(false); +let driveCapacityOverrideMb: number | null = $ref(0); let moderationNote = $ref(''); const filesPagination = { endpoint: 'admin/drive/files' as const, @@ -203,6 +218,7 @@ function createFetcher() { moderator = info.isModerator; silenced = info.isSilenced; suspended = info.isSuspended; + driveCapacityOverrideMb = user.driveCapacityOverrideMb; moderationNote = info.moderationNote; watch($$(moderationNote), async () => { @@ -289,6 +305,22 @@ async function deleteAllFiles() { await refreshUser(); } +async function applyDriveCapacityOverride() { + let driveCapOrMb = driveCapacityOverrideMb; + if (driveCapacityOverrideMb && driveCapacityOverrideMb < 0) { + driveCapOrMb = null; + } + try { + await os.apiWithDialog('admin/drive-capacity-override', { userId: user.id, overrideMb: driveCapOrMb }); + await refreshUser(); + } catch (e) { + os.alert({ + type: 'error', + text: e.toString(), + }); + } +} + async function deleteAccount() { const confirm = await os.confirm({ type: 'warning', @@ -319,7 +351,7 @@ watch(() => props.userId, () => { immediate: true, }); -watch(() => user, () => { +watch($$(user), () => { os.api('ap/get', { uri: user.uri ?? `${url}/users/${user.id}`, }).then(res => { diff --git a/packages/client/src/plugin.ts b/packages/client/src/plugin.ts index ca7b4b73d30f03a6b192d536ec8e57de4714c1f8..de1c9556750a233fed5addcc3cc7130d21d99782 100644 --- a/packages/client/src/plugin.ts +++ b/packages/client/src/plugin.ts @@ -38,7 +38,7 @@ export function install(plugin) { function createPluginEnv(opts) { const config = new Map(); for (const [k, v] of Object.entries(opts.plugin.config || {})) { - config.set(k, jsToVal(opts.plugin.configData.hasOwnProperty(k) ? opts.plugin.configData[k] : v.default)); + config.set(k, jsToVal(typeof opts.plugin.configData[k] !== 'undefined' ? opts.plugin.configData[k] : v.default)); } return { diff --git a/packages/client/src/scripts/array.ts b/packages/client/src/scripts/array.ts index 29d027de1438c1a0d40d89535a3b7f9342e48ded..26c6195d66f8ae76f50b56d69aa64bbb593c69f5 100644 --- a/packages/client/src/scripts/array.ts +++ b/packages/client/src/scripts/array.ts @@ -98,7 +98,7 @@ export function groupOn<T, S>(f: (x: T) => S, xs: T[]): T[][] { export function groupByX<T>(collections: T[], keySelector: (x: T) => string) { return collections.reduce((obj: Record<string, T[]>, item: T) => { const key = keySelector(item); - if (!obj.hasOwnProperty(key)) { + if (typeof obj[key] === 'undefined') { obj[key] = []; } diff --git a/packages/client/src/scripts/autocomplete.ts b/packages/client/src/scripts/autocomplete.ts index 8d9bdee8f5dbc9802b8ff40972aaf88327814f9a..3ef6224175f2beeb03aec94f81ca3ae5aff4b5e3 100644 --- a/packages/client/src/scripts/autocomplete.ts +++ b/packages/client/src/scripts/autocomplete.ts @@ -8,7 +8,7 @@ export class Autocomplete { x: Ref<number>; y: Ref<number>; q: Ref<string | null>; - close: Function; + close: () => void; } | null; private textarea: HTMLInputElement | HTMLTextAreaElement; private currentType: string; diff --git a/packages/client/src/scripts/hotkey.ts b/packages/client/src/scripts/hotkey.ts index fd9c74f6c87935f04322425b54bca3ccc687e4b5..bd8c3b6cab4efd394ded4b389998549308ebf8b9 100644 --- a/packages/client/src/scripts/hotkey.ts +++ b/packages/client/src/scripts/hotkey.ts @@ -1,6 +1,8 @@ import keyCode from './keycode'; -type Keymap = Record<string, Function>; +type Callback = (ev: KeyboardEvent) => void; + +type Keymap = Record<string, Callback>; type Pattern = { which: string[]; @@ -11,14 +13,14 @@ type Pattern = { type Action = { patterns: Pattern[]; - callback: Function; + callback: Callback; allowRepeat: boolean; }; const parseKeymap = (keymap: Keymap) => Object.entries(keymap).map(([patterns, callback]): Action => { const result = { patterns: [], - callback: callback, + callback, allowRepeat: true } as Action; diff --git a/packages/client/src/scripts/url.ts b/packages/client/src/scripts/url.ts index 542b00e0f0e3dd985720d71d0ca1d92bd428202c..86735de9f09a274fc7db44b460986ec809209c7f 100644 --- a/packages/client/src/scripts/url.ts +++ b/packages/client/src/scripts/url.ts @@ -1,4 +1,4 @@ -export function query(obj: {}): string { +export function query(obj: Record<string, any>): string { const params = Object.entries(obj) .filter(([, v]) => Array.isArray(v) ? v.length : v !== undefined) .reduce((a, [k, v]) => (a[k] = v, a), {} as Record<string, any>); diff --git a/packages/client/src/ui/classic.vue b/packages/client/src/ui/classic.vue index 310232aec05820b8605e6a849ac09ea9af3a3fa1..a2c26f536e68b0b085de31b6649388b9857337e4 100644 --- a/packages/client/src/ui/classic.vue +++ b/packages/client/src/ui/classic.vue @@ -60,8 +60,8 @@ const DESKTOP_THRESHOLD = 1100; let isDesktop = $ref(window.innerWidth >= DESKTOP_THRESHOLD); let pageMetadata = $ref<null | ComputedRef<PageMetadata>>(); -const widgetsShowing = $ref(false); -const fullView = $ref(false); +let widgetsShowing = $ref(false); +let fullView = $ref(false); let globalHeaderHeight = $ref(0); const wallpaper = localStorage.getItem('wallpaper') != null; const showMenuOnTop = $computed(() => defaultStore.state.menuDisplay === 'top'); diff --git a/packages/client/src/ui/deck/main-column.vue b/packages/client/src/ui/deck/main-column.vue index 670b4a212bab257e4e9d7a5dff09ccbb5d582dcc..9a5fd43af79143bd75cb832ad702b665ebea62e7 100644 --- a/packages/client/src/ui/deck/main-column.vue +++ b/packages/client/src/ui/deck/main-column.vue @@ -53,7 +53,7 @@ function onContextmenu(ev: MouseEvent) { if (isLink(ev.target as HTMLElement)) return; if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes((ev.target as HTMLElement).tagName) || (ev.target as HTMLElement).attributes['contenteditable']) return; if (window.getSelection()?.toString() !== '') return; - const path = router.currentRoute.value.path; + const path = mainRouter.currentRoute.value.path; os.contextMenu([{ type: 'label', text: path, diff --git a/packages/client/src/widgets/widget.ts b/packages/client/src/widgets/widget.ts index 9626d01619b73fe9d30fa42805fc60ecdcb3efc3..9fdfe7f3e176963063c23512f50888cf87bfa7c0 100644 --- a/packages/client/src/widgets/widget.ts +++ b/packages/client/src/widgets/widget.ts @@ -36,8 +36,9 @@ export const useWidgetPropsManager = <F extends Form & Record<string, { default: const mergeProps = () => { for (const prop of Object.keys(propsDef)) { - if (widgetProps.hasOwnProperty(prop)) continue; - widgetProps[prop] = propsDef[prop].default; + if (typeof widgetProps[prop] === 'undefined') { + widgetProps[prop] = propsDef[prop].default; + } } }; watch(widgetProps, () => {