From f9a72e1ea6cd126a288368cf98f8c90829d47b97 Mon Sep 17 00:00:00 2001 From: Kaity A <kaity@theallans.com.au> Date: Sun, 30 Apr 2023 11:09:51 +1000 Subject: [PATCH 1/8] Add Libre Translate support --- .../migration/1682777547198-LibreTranslate.js | 23 ++++++++++ packages/backend/src/config/types.ts | 5 +++ packages/backend/src/models/entities/meta.ts | 12 ++++++ .../api/endpoints/admin/accounts/hosted.ts | 11 +++++ .../src/server/api/endpoints/admin/meta.ts | 5 ++- .../server/api/endpoints/admin/update-meta.ts | 18 ++++++++ .../backend/src/server/api/endpoints/meta.ts | 3 +- .../server/api/endpoints/notes/translate.ts | 43 ++++++++++++++++++- packages/client/src/pages/admin/settings.vue | 34 +++++++++++++++ 9 files changed, 150 insertions(+), 4 deletions(-) create mode 100644 packages/backend/migration/1682777547198-LibreTranslate.js diff --git a/packages/backend/migration/1682777547198-LibreTranslate.js b/packages/backend/migration/1682777547198-LibreTranslate.js new file mode 100644 index 000000000..dbaf483e6 --- /dev/null +++ b/packages/backend/migration/1682777547198-LibreTranslate.js @@ -0,0 +1,23 @@ +export class LibreTranslate1682777547198 { + name = "LibreTranslate1682777547198"; + + async up(queryRunner) { + await queryRunner.query(` + ALTER TABLE "meta" + ADD "libreTranslateApiUrl" character varying(512) + `); + await queryRunner.query(` + ALTER TABLE "meta" + ADD "libreTranslateApiKey" character varying(128) + `); + } + + async down(queryRunner) { + await queryRunner.query(` + ALTER TABLE "meta" DROP COLUMN "libreTranslateApiKey" + `); + await queryRunner.query(` + ALTER TABLE "meta" DROP COLUMN "libreTranslateApiUrl" + `); + } +} diff --git a/packages/backend/src/config/types.ts b/packages/backend/src/config/types.ts index 4f367debe..0cd8c02ad 100644 --- a/packages/backend/src/config/types.ts +++ b/packages/backend/src/config/types.ts @@ -89,6 +89,11 @@ export type Source = { authKey?: string; isPro?: boolean; }; + libreTranslate: { + managed?: boolean; + apiUrl?: string; + apiKey?: string; + }; email: { managed?: boolean; address?: string; diff --git a/packages/backend/src/models/entities/meta.ts b/packages/backend/src/models/entities/meta.ts index 26a7c9c19..2f77796c4 100644 --- a/packages/backend/src/models/entities/meta.ts +++ b/packages/backend/src/models/entities/meta.ts @@ -386,6 +386,18 @@ export class Meta { }) public deeplIsPro: boolean; + @Column('varchar', { + length: 512, + nullable: true, + }) + public libreTranslateApiUrl: string | null; + + @Column('varchar', { + length: 128, + nullable: true, + }) + public libreTranslateApiKey: string | null; + @Column('varchar', { length: 512, nullable: true, diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/hosted.ts b/packages/backend/src/server/api/endpoints/admin/accounts/hosted.ts index 15ad1f9a1..a7b6e95c2 100644 --- a/packages/backend/src/server/api/endpoints/admin/accounts/hosted.ts +++ b/packages/backend/src/server/api/endpoints/admin/accounts/hosted.ts @@ -30,6 +30,17 @@ export default define(meta, paramDef, async (ps, me) => { set.deeplIsPro = config.deepl.isPro; } } + if ( + config.libreTranslate.managed != null && + config.libreTranslate.managed === true + ) { + if (typeof config.libreTranslate.apiUrl === "string") { + set.libreTranslateApiUrl = config.libreTranslate.apiUrl; + } + if (typeof config.libreTranslate.apiKey === "string") { + set.libreTranslateApiKey = config.libreTranslate.apiKey; + } + } if (config.email.managed != null && config.email.managed === true) { set.enableEmail = true; if (typeof config.email.address === "string") { diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index c8c639f50..f0ac57892 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -512,7 +512,8 @@ export default define(meta, paramDef, async (ps, me) => { enableGithubIntegration: instance.enableGithubIntegration, enableDiscordIntegration: instance.enableDiscordIntegration, enableServiceWorker: instance.enableServiceWorker, - translatorAvailable: instance.deeplAuthKey != null, + translatorAvailable: + instance.deeplAuthKey != null || instance.libreTranslateApiUrl != null, pinnedPages: instance.pinnedPages, pinnedClipId: instance.pinnedClipId, cacheRemoteFiles: instance.cacheRemoteFiles, @@ -564,6 +565,8 @@ export default define(meta, paramDef, async (ps, me) => { objectStorageS3ForcePathStyle: instance.objectStorageS3ForcePathStyle, deeplAuthKey: instance.deeplAuthKey, deeplIsPro: instance.deeplIsPro, + libreTranslateApiUrl: instance.libreTranslateApiUrl, + libreTranslateApiKey: instance.libreTranslateApiKey, enableIpLogging: instance.enableIpLogging, enableActiveEmailValidation: instance.enableActiveEmailValidation, }; diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts index f7e79b64b..a23000732 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -124,6 +124,8 @@ export const paramDef = { summalyProxy: { type: "string", nullable: true }, deeplAuthKey: { type: "string", nullable: true }, deeplIsPro: { type: "boolean" }, + libreTranslateApiUrl: { type: "string", nullable: true }, + libreTranslateApiKey: { type: "string", nullable: true }, enableTwitterIntegration: { type: "boolean" }, twitterConsumerKey: { type: "string", nullable: true }, twitterConsumerSecret: { type: "string", nullable: true }, @@ -515,6 +517,22 @@ export default define(meta, paramDef, async (ps, me) => { set.deeplIsPro = ps.deeplIsPro; } + if (ps.libreTranslateApiUrl !== undefined) { + if (ps.libreTranslateApiUrl === "") { + set.libreTranslateApiUrl = null; + } else { + set.libreTranslateApiUrl = ps.libreTranslateApiUrl; + } + } + + if (ps.libreTranslateApiKey !== undefined) { + if (ps.libreTranslateApiKey === "") { + set.libreTranslateApiKey = null; + } else { + set.libreTranslateApiKey = ps.libreTranslateApiKey; + } + } + if (ps.enableIpLogging !== undefined) { set.enableIpLogging = ps.enableIpLogging; } diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts index 4dc1c941e..23989750f 100644 --- a/packages/backend/src/server/api/endpoints/meta.ts +++ b/packages/backend/src/server/api/endpoints/meta.ts @@ -482,7 +482,8 @@ export default define(meta, paramDef, async (ps, me) => { enableServiceWorker: instance.enableServiceWorker, - translatorAvailable: instance.deeplAuthKey != null, + translatorAvailable: + instance.deeplAuthKey != null || instance.libreTranslateApiUrl != null, defaultReaction: instance.defaultReaction, ...(ps.detail diff --git a/packages/backend/src/server/api/endpoints/notes/translate.ts b/packages/backend/src/server/api/endpoints/notes/translate.ts index c6415ceef..d86fc12a2 100644 --- a/packages/backend/src/server/api/endpoints/notes/translate.ts +++ b/packages/backend/src/server/api/endpoints/notes/translate.ts @@ -51,15 +51,54 @@ export default define(meta, paramDef, async (ps, user) => { const instance = await fetchMeta(); - if (instance.deeplAuthKey == null) { + if (instance.deeplAuthKey == null && instance.libreTranslateApiUrl == null) { return 204; // TODO: 良い感じのエラー返す } let targetLang = ps.targetLang; if (targetLang.includes("-")) targetLang = targetLang.split("-")[0]; + if (instance.libreTranslateApiUrl != null) { + const jsonBody = { + q: note.text, + source: "auto", + target: targetLang, + format: "text", + api_key: instance.libreTranslateApiKey ?? "", + }; + + const url = new URL(instance.libreTranslateApiUrl); + if (url.pathname.endsWith("/")) { + url.pathname = url.pathname.slice(0, -1); + } + if (!url.pathname.endsWith("/translate")) { + url.pathname += "/translate"; + } + const res = await fetch(url.toString(), { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(jsonBody), + agent: getAgentByUrl, + }); + + const json = (await res.json()) as { + detectedLanguage?: { + confidence: number; + language: string; + }; + translatedText: string; + }; + + return { + sourceLang: json.detectedLanguage?.language, + text: json.translatedText, + }; + } + const params = new URLSearchParams(); - params.append("auth_key", instance.deeplAuthKey); + params.append("auth_key", instance.deeplAuthKey ?? ""); params.append("text", note.text); params.append("target_lang", targetLang); diff --git a/packages/client/src/pages/admin/settings.vue b/packages/client/src/pages/admin/settings.vue index 5349df805..feedaff6d 100644 --- a/packages/client/src/pages/admin/settings.vue +++ b/packages/client/src/pages/admin/settings.vue @@ -371,6 +371,34 @@ <template #label>Pro account</template> </FormSwitch> </FormSection> + + <FormSection> + <template #label>Libre Translate</template> + + <FormInput + v-model="libreTranslateApiUrl" + class="_formBlock" + > + <template #prefix + ><i class="ph-link ph-bold ph-lg"></i + ></template> + <template #label + >Libre Translate API URL</template + > + </FormInput> + + <FormInput + v-model="libreTranslateApiKey" + class="_formBlock" + > + <template #prefix + ><i class="ph-key ph-bold ph-lg"></i + ></template> + <template #label + >Libre Translate API Key</template + > + </FormInput> + </FormSection> </div> </FormSuspense> </MkSpacer> @@ -422,6 +450,8 @@ let swPublicKey: any = $ref(null); let swPrivateKey: any = $ref(null); let deeplAuthKey: string = $ref(""); let deeplIsPro: boolean = $ref(false); +let libreTranslateApiUrl: string = $ref(""); +let libreTranslateApiKey: string = $ref(""); let defaultReaction: string = $ref(""); let defaultReactionCustom: string = $ref(""); @@ -456,6 +486,8 @@ async function init() { swPrivateKey = meta.swPrivateKey; deeplAuthKey = meta.deeplAuthKey; deeplIsPro = meta.deeplIsPro; + libreTranslateApiUrl = meta.libreTranslateApiUrl; + libreTranslateApiKey = meta.libreTranslateApiKey; defaultReaction = ["⭐", "👍", "❤️"].includes(meta.defaultReaction) ? meta.defaultReaction : "custom"; @@ -498,6 +530,8 @@ function save() { swPrivateKey, deeplAuthKey, deeplIsPro, + libreTranslateApiUrl, + libreTranslateApiKey, defaultReaction, }).then(() => { fetchInstance(); From 0f4e88cf53ed8dd07da0526b772096ae4919f7d4 Mon Sep 17 00:00:00 2001 From: Kaitlyn Allan <kaitlyn.allan@enlabs.cloud> Date: Sat, 29 Apr 2023 21:05:33 +1000 Subject: [PATCH 2/8] Remove eslint from calckey-js (use rome) --- packages/calckey-js/.eslintignore | 7 ---- packages/calckey-js/.eslintrc.js | 65 ------------------------------- packages/calckey-js/package.json | 3 +- 3 files changed, 1 insertion(+), 74 deletions(-) delete mode 100644 packages/calckey-js/.eslintignore delete mode 100644 packages/calckey-js/.eslintrc.js diff --git a/packages/calckey-js/.eslintignore b/packages/calckey-js/.eslintignore deleted file mode 100644 index f22128f04..000000000 --- a/packages/calckey-js/.eslintignore +++ /dev/null @@ -1,7 +0,0 @@ -node_modules -/built -/coverage -/.eslintrc.js -/jest.config.ts -/test -/test-d diff --git a/packages/calckey-js/.eslintrc.js b/packages/calckey-js/.eslintrc.js deleted file mode 100644 index 164cf1fbe..000000000 --- a/packages/calckey-js/.eslintrc.js +++ /dev/null @@ -1,65 +0,0 @@ -module.exports = { - root: true, - parser: "@typescript-eslint/parser", - parserOptions: { - tsconfigRootDir: __dirname, - project: ["./tsconfig.json"], - }, - plugins: ["@typescript-eslint"], - extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"], - rules: { - indent: [ - "error", - "tab", - { - SwitchCase: 1, - MemberExpression: "off", - flatTernaryExpressions: true, - ArrayExpression: "first", - ObjectExpression: "first", - }, - ], - "eol-last": ["error", "always"], - semi: ["error", "always"], - quotes: ["error", "single"], - "comma-dangle": ["error", "always-multiline"], - "keyword-spacing": [ - "error", - { - before: true, - after: true, - }, - ], - "key-spacing": [ - "error", - { - beforeColon: false, - afterColon: true, - }, - ], - "space-infix-ops": ["error"], - "space-before-blocks": ["error", "always"], - "object-curly-spacing": ["error", "always"], - "nonblock-statement-body-position": ["error", "beside"], - eqeqeq: ["error", "always", { null: "ignore" }], - "no-multiple-empty-lines": ["error", { max: 1 }], - "no-multi-spaces": ["error"], - "no-var": ["error"], - "prefer-arrow-callback": ["error"], - "no-throw-literal": ["error"], - "no-param-reassign": ["warn"], - "no-constant-condition": ["warn"], - "no-empty-pattern": ["warn"], - "@typescript-eslint/no-unnecessary-condition": ["error"], - "@typescript-eslint/no-inferrable-types": ["warn"], - "@typescript-eslint/no-non-null-assertion": ["warn"], - "@typescript-eslint/explicit-function-return-type": ["warn"], - "@typescript-eslint/no-misused-promises": [ - "error", - { - checksVoidReturn: false, - }, - ], - "@typescript-eslint/consistent-type-imports": "error", - }, -}; diff --git a/packages/calckey-js/package.json b/packages/calckey-js/package.json index d68f24175..598dd1cdb 100644 --- a/packages/calckey-js/package.json +++ b/packages/calckey-js/package.json @@ -9,9 +9,8 @@ "tsd": "tsd", "api": "pnpm api-extractor run --local --verbose", "api-prod": "pnpm api-extractor run --verbose", - "eslint": "eslint . --ext .js,.jsx,.ts,.tsx", "typecheck": "tsc --noEmit", - "lint": "pnpm typecheck && pnpm eslint", + "lint": "pnpm typecheck && pnpm rome check \"src/*.ts\"", "jest": "jest --coverage --detectOpenHandles", "test": "pnpm jest && pnpm tsd" }, From 1051da8fbf84be7abdc9b468d0e762278e1c4d88 Mon Sep 17 00:00:00 2001 From: ThatOneCalculator <kainoa@t1c.dev> Date: Sat, 29 Apr 2023 19:14:36 -0700 Subject: [PATCH 3/8] Revert "keyboard accessibility (#9725)" This reverts commit c1d5922acbe7060c0ea779ccf314e9f0e6b91bb3. --- package.json | 2 - packages/client/src/components/MkButton.vue | 3 +- packages/client/src/components/MkCwButton.vue | 18 +- .../src/components/MkDriveFileThumbnail.vue | 7 +- .../client/src/components/MkEmojiPicker.vue | 278 +++++++------ .../client/src/components/MkLaunchPad.vue | 2 +- .../client/src/components/MkMediaImage.vue | 4 - .../client/src/components/MkMenu.child.vue | 21 +- packages/client/src/components/MkMenu.vue | 382 +++++++++--------- packages/client/src/components/MkModal.vue | 86 ++-- .../src/components/MkModalPageWindow.vue | 1 - .../client/src/components/MkModalWindow.vue | 95 +++-- packages/client/src/components/MkNote.vue | 8 +- .../client/src/components/MkNotePreview.vue | 2 +- packages/client/src/components/MkNoteSub.vue | 4 +- .../client/src/components/MkPopupMenu.vue | 2 - .../src/components/MkPostFormAttaches.vue | 1 + .../src/components/MkSubNoteContent.vue | 27 +- .../client/src/components/MkSuperMenu.vue | 5 +- .../src/components/MkUserSelectDialog.vue | 2 - .../client/src/components/MkUsersTooltip.vue | 2 +- packages/client/src/components/MkWidgets.vue | 2 +- .../client/src/components/form/folder.vue | 4 +- packages/client/src/components/form/radio.vue | 3 - .../client/src/components/form/switch.vue | 3 - .../src/components/global/MkPageHeader.vue | 2 - .../src/components/global/RouterView.vue | 3 - packages/client/src/directives/focus.ts | 3 - packages/client/src/directives/index.ts | 2 - packages/client/src/directives/tooltip.ts | 33 +- packages/client/src/pages/admin/_header_.vue | 6 +- .../src/pages/admin/overview.moderators.vue | 2 +- packages/client/src/pages/follow-requests.vue | 1 - .../client/src/pages/settings/accounts.vue | 8 +- packages/client/src/style.scss | 4 + .../src/ui/_common_/navbar-for-mobile.vue | 1 - packages/client/src/ui/_common_/navbar.vue | 21 +- packages/client/src/ui/classic.header.vue | 1 - packages/client/src/ui/classic.sidebar.vue | 4 +- packages/client/src/ui/classic.vue | 2 - pnpm-lock.yaml | 41 +- 41 files changed, 495 insertions(+), 603 deletions(-) delete mode 100644 packages/client/src/directives/focus.ts diff --git a/package.json b/package.json index 51556dea5..80942ff54 100644 --- a/package.json +++ b/package.json @@ -40,8 +40,6 @@ "@bull-board/ui": "^4.10.2", "@napi-rs/cli": "^2.15.0", "@tensorflow/tfjs": "^3.21.0", - "focus-trap": "^7.2.0", - "focus-trap-vue": "^4.0.1", "js-yaml": "4.1.0", "seedrandom": "^3.0.5" }, diff --git a/packages/client/src/components/MkButton.vue b/packages/client/src/components/MkButton.vue index feac281d9..5f1a5bdb7 100644 --- a/packages/client/src/components/MkButton.vue +++ b/packages/client/src/components/MkButton.vue @@ -195,7 +195,8 @@ function onMousedown(evt: MouseEvent): void { } &:focus-visible { - outline: auto; + outline: solid 2px var(--focus); + outline-offset: 2px; } &.inline { diff --git a/packages/client/src/components/MkCwButton.vue b/packages/client/src/components/MkCwButton.vue index 1f6340510..659cb1fbb 100644 --- a/packages/client/src/components/MkCwButton.vue +++ b/packages/client/src/components/MkCwButton.vue @@ -1,6 +1,5 @@ <template> <button - ref="el" class="_button" :class="{ showLess: modelValue, fade: !modelValue }" @click.stop="toggle" @@ -13,7 +12,7 @@ </template> <script lang="ts" setup> -import { computed, ref } from "vue"; +import { computed } from "vue"; import { length } from "stringz"; import * as misskey from "calckey-js"; import { concat } from "@/scripts/array"; @@ -28,8 +27,6 @@ const emit = defineEmits<{ (ev: "update:modelValue", v: boolean): void; }>(); -const el = ref<HTMLElement>(); - const label = computed(() => { return concat([ props.note.text @@ -46,14 +43,6 @@ const label = computed(() => { const toggle = () => { emit("update:modelValue", !props.modelValue); }; - -function focus() { - el.value.focus(); -} - -defineExpose({ - focus -}); </script> <style lang="scss" scoped> @@ -73,7 +62,7 @@ defineExpose({ } } } - &:hover > span, &:focus > span { + &:hover > span { background: var(--cwFg) !important; color: var(--cwBg) !important; } @@ -84,7 +73,6 @@ defineExpose({ bottom: 0; left: 0; width: 100%; - z-index: 2; > span { display: inline-block; background: var(--panel); @@ -93,7 +81,7 @@ defineExpose({ border-radius: 999px; box-shadow: 0 2px 6px rgb(0 0 0 / 20%); } - &:hover, &:focus { + &:hover { > span { background: var(--panelHighlight); } diff --git a/packages/client/src/components/MkDriveFileThumbnail.vue b/packages/client/src/components/MkDriveFileThumbnail.vue index 48b542817..39150c10c 100644 --- a/packages/client/src/components/MkDriveFileThumbnail.vue +++ b/packages/client/src/components/MkDriveFileThumbnail.vue @@ -1,5 +1,5 @@ <template> - <button ref="thumbnail" class="zdjebgpv"> + <div ref="thumbnail" class="zdjebgpv"> <ImgWithBlurhash v-if="isThumbnailAvailable" :hash="file.blurhash" @@ -36,7 +36,7 @@ v-if="isThumbnailAvailable && is === 'video'" class="ph-file-video ph-bold ph-lg icon-sub" ></i> - </button> + </div> </template> <script lang="ts" setup> @@ -88,9 +88,6 @@ const isThumbnailAvailable = computed(() => { background: var(--panel); border-radius: 8px; overflow: clip; - border: 0; - padding: 0; - cursor: pointer; > .icon-sub { position: absolute; diff --git a/packages/client/src/components/MkEmojiPicker.vue b/packages/client/src/components/MkEmojiPicker.vue index 88d207bab..a22006951 100644 --- a/packages/client/src/components/MkEmojiPicker.vue +++ b/packages/client/src/components/MkEmojiPicker.vue @@ -1,160 +1,157 @@ <template> - <FocusTrap v-bind:active="isActive"> - <div - class="omfetrab" - :class="['s' + size, 'w' + width, 'h' + height, { asDrawer }]" - :style="{ maxHeight: maxHeight ? maxHeight + 'px' : undefined }" - tabindex="-1" - > - <input - ref="search" - v-model.trim="q" - class="search" - data-prevent-emoji-insert - :class="{ filled: q != null && q != '' }" - :placeholder="i18n.ts.search" - type="search" - @paste.stop="paste" - @keyup.enter="done()" - /> - <div ref="emojis" class="emojis"> - <section class="result"> - <div v-if="searchResultCustom.length > 0" class="body"> + <div + class="omfetrab" + :class="['s' + size, 'w' + width, 'h' + height, { asDrawer }]" + :style="{ maxHeight: maxHeight ? maxHeight + 'px' : undefined }" + > + <input + ref="search" + v-model.trim="q" + class="search" + data-prevent-emoji-insert + :class="{ filled: q != null && q != '' }" + :placeholder="i18n.ts.search" + type="search" + @paste.stop="paste" + @keyup.enter="done()" + /> + <div ref="emojis" class="emojis"> + <section class="result"> + <div v-if="searchResultCustom.length > 0" class="body"> + <button + v-for="emoji in searchResultCustom" + :key="emoji.id" + class="_button item" + :title="emoji.name" + tabindex="0" + @click="chosen(emoji, $event)" + > + <!--<MkEmoji v-if="emoji.char != null" :emoji="emoji.char"/>--> + <img + class="emoji" + :src=" + disableShowingAnimatedImages + ? getStaticImageUrl(emoji.url) + : emoji.url + " + /> + </button> + </div> + <div v-if="searchResultUnicode.length > 0" class="body"> + <button + v-for="emoji in searchResultUnicode" + :key="emoji.name" + class="_button item" + :title="emoji.name" + tabindex="0" + @click="chosen(emoji, $event)" + > + <MkEmoji class="emoji" :emoji="emoji.char" /> + </button> + </div> + </section> + + <div v-if="tab === 'index'" class="group index"> + <section v-if="showPinned"> + <div class="body"> <button - v-for="emoji in searchResultCustom" - :key="emoji.id" + v-for="emoji in pinned" + :key="emoji" class="_button item" - :title="emoji.name" tabindex="0" @click="chosen(emoji, $event)" > - <!--<MkEmoji v-if="emoji.char != null" :emoji="emoji.char"/>--> - <img + <MkEmoji class="emoji" - :src=" - disableShowingAnimatedImages - ? getStaticImageUrl(emoji.url) - : emoji.url - " + :emoji="emoji" + :normal="true" /> </button> </div> - <div v-if="searchResultUnicode.length > 0" class="body"> - <button - v-for="emoji in searchResultUnicode" - :key="emoji.name" - class="_button item" - :title="emoji.name" - tabindex="0" - @click="chosen(emoji, $event)" - > - <MkEmoji class="emoji" :emoji="emoji.char" /> - </button> - </div> </section> - <div v-if="tab === 'index'" class="group index"> - <section v-if="showPinned"> - <div class="body"> - <button - v-for="emoji in pinned" - :key="emoji" - class="_button item" - tabindex="0" - @click="chosen(emoji, $event)" - > - <MkEmoji - class="emoji" - :emoji="emoji" - :normal="true" - /> - </button> - </div> - </section> - - <section> - <header class="_acrylic"> - <i class="ph-alarm ph-bold ph-fw ph-lg"></i> - {{ i18n.ts.recentUsed }} - </header> - <div class="body"> - <button - v-for="emoji in recentlyUsedEmojis" - :key="emoji" - class="_button item" - @click="chosen(emoji, $event)" - > - <MkEmoji - class="emoji" - :emoji="emoji" - :normal="true" - /> - </button> - </div> - </section> - </div> - <div v-once class="group"> - <header>{{ i18n.ts.customEmojis }}</header> - <XSection - v-for="category in customEmojiCategories" - :key="'custom:' + category" - :initial-shown="false" - :emojis=" - customEmojis - .filter((e) => e.category === category) - .map((e) => ':' + e.name + ':') - " - @chosen="chosen" - >{{ category || i18n.ts.other }}</XSection - > - </div> - <div v-once class="group"> - <header>{{ i18n.ts.emoji }}</header> - <XSection - v-for="category in categories" - :key="category" - :emojis=" - emojilist - .filter((e) => e.category === category) - .map((e) => e.char) - " - @chosen="chosen" - >{{ category }}</XSection - > - </div> + <section> + <header class="_acrylic"> + <i class="ph-alarm ph-bold ph-fw ph-lg"></i> + {{ i18n.ts.recentUsed }} + </header> + <div class="body"> + <button + v-for="emoji in recentlyUsedEmojis" + :key="emoji" + class="_button item" + @click="chosen(emoji, $event)" + > + <MkEmoji + class="emoji" + :emoji="emoji" + :normal="true" + /> + </button> + </div> + </section> </div> - <div class="tabs"> - <button - class="_button tab" - :class="{ active: tab === 'index' }" - @click="tab = 'index'" + <div v-once class="group"> + <header>{{ i18n.ts.customEmojis }}</header> + <XSection + v-for="category in customEmojiCategories" + :key="'custom:' + category" + :initial-shown="false" + :emojis=" + customEmojis + .filter((e) => e.category === category) + .map((e) => ':' + e.name + ':') + " + @chosen="chosen" + >{{ category || i18n.ts.other }}</XSection > - <i class="ph-asterisk ph-bold ph-lg ph-fw ph-lg"></i> - </button> - <button - class="_button tab" - :class="{ active: tab === 'custom' }" - @click="tab = 'custom'" + </div> + <div v-once class="group"> + <header>{{ i18n.ts.emoji }}</header> + <XSection + v-for="category in categories" + :key="category" + :emojis=" + emojilist + .filter((e) => e.category === category) + .map((e) => e.char) + " + @chosen="chosen" + >{{ category }}</XSection > - <i class="ph-smiley ph-bold ph-lg ph-fw ph-lg"></i> - </button> - <button - class="_button tab" - :class="{ active: tab === 'unicode' }" - @click="tab = 'unicode'" - > - <i class="ph-leaf ph-bold ph-lg ph-fw ph-lg"></i> - </button> - <button - class="_button tab" - :class="{ active: tab === 'tags' }" - @click="tab = 'tags'" - > - <i class="ph-hash ph-bold ph-lg ph-fw ph-lg"></i> - </button> </div> </div> - </FocusTrap> + <div class="tabs"> + <button + class="_button tab" + :class="{ active: tab === 'index' }" + @click="tab = 'index'" + > + <i class="ph-asterisk ph-bold ph-lg ph-fw ph-lg"></i> + </button> + <button + class="_button tab" + :class="{ active: tab === 'custom' }" + @click="tab = 'custom'" + > + <i class="ph-smiley ph-bold ph-lg ph-fw ph-lg"></i> + </button> + <button + class="_button tab" + :class="{ active: tab === 'unicode' }" + @click="tab = 'unicode'" + > + <i class="ph-leaf ph-bold ph-lg ph-fw ph-lg"></i> + </button> + <button + class="_button tab" + :class="{ active: tab === 'tags' }" + @click="tab = 'tags'" + > + <i class="ph-hash ph-bold ph-lg ph-fw ph-lg"></i> + </button> + </div> + </div> </template> <script lang="ts" setup> @@ -174,7 +171,6 @@ import { deviceKind } from "@/scripts/device-kind"; import { emojiCategories, instance } from "@/instance"; import { i18n } from "@/i18n"; import { defaultStore } from "@/store"; -import { FocusTrap } from 'focus-trap-vue'; const props = withDefaults( defineProps<{ diff --git a/packages/client/src/components/MkLaunchPad.vue b/packages/client/src/components/MkLaunchPad.vue index 759c215f7..f713b4c41 100644 --- a/packages/client/src/components/MkLaunchPad.vue +++ b/packages/client/src/components/MkLaunchPad.vue @@ -139,7 +139,7 @@ function close() { height: 100px; border-radius: 10px; - &:hover, &:focus-visible { + &:hover { color: var(--accent); background: var(--accentedBg); text-decoration: none; diff --git a/packages/client/src/components/MkMediaImage.vue b/packages/client/src/components/MkMediaImage.vue index 3cfb0f465..882908040 100644 --- a/packages/client/src/components/MkMediaImage.vue +++ b/packages/client/src/components/MkMediaImage.vue @@ -138,10 +138,6 @@ watch( background-position: center; background-size: contain; background-repeat: no-repeat; - box-sizing: border-box; - &:focus-visible { - border: 2px solid var(--accent); - } > .gif { background-color: var(--fg); diff --git a/packages/client/src/components/MkMenu.child.vue b/packages/client/src/components/MkMenu.child.vue index e5ca9e4ee..6b05ab447 100644 --- a/packages/client/src/components/MkMenu.child.vue +++ b/packages/client/src/components/MkMenu.child.vue @@ -1,14 +1,14 @@ <template> - <div ref="el" class="sfhdhdhr" tabindex="-1"> - <MkMenu - ref="menu" - :items="items" - :align="align" - :width="width" - :as-drawer="false" - @close="onChildClosed" - /> - </div> + <div ref="el" class="sfhdhdhr"> + <MkMenu + ref="menu" + :items="items" + :align="align" + :width="width" + :as-drawer="false" + @close="onChildClosed" + /> + </div> </template> <script lang="ts" setup> @@ -23,6 +23,7 @@ import { } from "vue"; import MkMenu from "./MkMenu.vue"; import { MenuItem } from "@/types/menu"; +import * as os from "@/os"; const props = defineProps<{ items: MenuItem[]; diff --git a/packages/client/src/components/MkMenu.vue b/packages/client/src/components/MkMenu.vue index c71e3ac58..88c8af1c5 100644 --- a/packages/client/src/components/MkMenu.vue +++ b/packages/client/src/components/MkMenu.vue @@ -1,188 +1,191 @@ <template> - <FocusTrap v-bind:active="isActive"> - <div tabindex="-1" v-focus> - <div - ref="itemsEl" - class="rrevdjwt _popup _shadow" - :class="{ center: align === 'center', asDrawer }" - :style="{ - width: width && !asDrawer ? width + 'px' : '', - maxHeight: maxHeight ? maxHeight + 'px' : '', - }" - @contextmenu.self="(e) => e.preventDefault()" - > - <template v-for="(item, i) in items2"> - <div v-if="item === null" class="divider"></div> - <span v-else-if="item.type === 'label'" class="label item"> - <span :style="item.textStyle || ''">{{ item.text }}</span> - </span> - <span - v-else-if="item.type === 'pending'" - class="pending item" - > - <span><MkEllipsis /></span> - </span> - <MkA - v-else-if="item.type === 'link'" - :to="item.to" - class="_button item" - @click.passive="close(true)" - @mouseenter.passive="onItemMouseEnter(item)" - @mouseleave.passive="onItemMouseLeave(item)" - > - <i - v-if="item.icon" - class="ph-fw ph-lg" - :class="item.icon" - ></i> - <span v-else-if="item.icons"> - <i - v-for="icon in item.icons" - class="ph-fw ph-lg" - :class="icon" - ></i> - </span> - <MkAvatar - v-if="item.avatar" - :user="item.avatar" - class="avatar" - disableLink - /> - <span :style="item.textStyle || ''">{{ item.text }}</span> - <span v-if="item.indicate" class="indicator" - ><i class="ph-circle ph-fill"></i - ></span> - </MkA> - <a - v-else-if="item.type === 'a'" - :href="item.href" - :target="item.target" - :download="item.download" - class="_button item" - @click="close(true)" - @mouseenter.passive="onItemMouseEnter(item)" - @mouseleave.passive="onItemMouseLeave(item)" - > - <i - v-if="item.icon" - class="ph-fw ph-lg" - :class="item.icon" - ></i> - <span v-else-if="item.icons"> - <i - v-for="icon in item.icons" - class="ph-fw ph-lg" - :class="icon" - ></i> - </span> - <span :style="item.textStyle || ''">{{ item.text }}</span> - <span v-if="item.indicate" class="indicator" - ><i class="ph-circle ph-fill"></i - ></span> - </a> - <button - v-else-if="item.type === 'user' && !items.hidden" - class="_button item" - :class="{ active: item.active }" - :disabled="item.active" - @click="clicked(item.action, $event)" - @mouseenter.passive="onItemMouseEnter(item)" - @mouseleave.passive="onItemMouseLeave(item)" - > - <MkAvatar :user="item.user" class="avatar" disableLink /><MkUserName - :user="item.user" - /> - <span v-if="item.indicate" class="indicator" - ><i class="ph-circle ph-fill"></i - ></span> - </button> - <span - v-else-if="item.type === 'switch'" - class="item" - @mouseenter.passive="onItemMouseEnter(item)" - @mouseleave.passive="onItemMouseLeave(item)" - > - <FormSwitch - v-model="item.ref" - :disabled="item.disabled" - class="form-switch" - :style="item.textStyle || ''" - >{{ item.text }}</FormSwitch - > - </span> - <button - v-else-if="item.type === 'parent'" - class="_button item parent" - :class="{ childShowing: childShowingItem === item }" - @mouseenter="showChildren(item, $event)" - @click="showChildren(item, $event)" - > - <i - v-if="item.icon" - class="ph-fw ph-lg" - :class="item.icon" - ></i> - <span v-else-if="item.icons"> - <i - v-for="icon in item.icons" - class="ph-fw ph-lg" - :class="icon" - ></i> - </span> - <span :style="item.textStyle || ''">{{ item.text }}</span> - <span class="caret" - ><i class="ph-caret-right ph-bold ph-lg ph-fw ph-lg"></i - ></span> - </button> - <button - v-else-if="!item.hidden" - class="_button item" - :class="{ danger: item.danger, active: item.active }" - :disabled="item.active" - @click="clicked(item.action, $event)" - @mouseenter.passive="onItemMouseEnter(item)" - @mouseleave.passive="onItemMouseLeave(item)" - > - <i - v-if="item.icon" - class="ph-fw ph-lg" - :class="item.icon" - ></i> - <span v-else-if="item.icons"> - <i - v-for="icon in item.icons" - class="ph-fw ph-lg" - :class="icon" - ></i> - </span> - <MkAvatar - v-if="item.avatar" - :user="item.avatar" - class="avatar" - disableLink - /> - <span :style="item.textStyle || ''">{{ item.text }}</span> - <span v-if="item.indicate" class="indicator" - ><i class="ph-circle ph-fill"></i - ></span> - </button> - </template> - <span v-if="items2.length === 0" class="none item"> - <span>{{ i18n.ts.none }}</span> + <div> + <div + ref="itemsEl" + v-hotkey="keymap" + class="rrevdjwt _popup _shadow" + :class="{ center: align === 'center', asDrawer }" + :style="{ + width: width && !asDrawer ? width + 'px' : '', + maxHeight: maxHeight ? maxHeight + 'px' : '', + }" + @contextmenu.self="(e) => e.preventDefault()" + > + <template v-for="(item, i) in items2"> + <div v-if="item === null" class="divider"></div> + <span v-else-if="item.type === 'label'" class="label item"> + <span :style="item.textStyle || ''">{{ item.text }}</span> </span> - </div> - <div v-if="childMenu" class="child"> - <XChild - ref="child" - :items="childMenu" - :target-element="childTarget" - :root-element="itemsEl" - showing - @actioned="childActioned" - /> - </div> + <span + v-else-if="item.type === 'pending'" + :tabindex="i" + class="pending item" + > + <span><MkEllipsis /></span> + </span> + <MkA + v-else-if="item.type === 'link'" + :to="item.to" + :tabindex="i" + class="_button item" + @click.passive="close(true)" + @mouseenter.passive="onItemMouseEnter(item)" + @mouseleave.passive="onItemMouseLeave(item)" + > + <i + v-if="item.icon" + class="ph-fw ph-lg" + :class="item.icon" + ></i> + <span v-else-if="item.icons"> + <i + v-for="icon in item.icons" + class="ph-fw ph-lg" + :class="icon" + ></i> + </span> + <MkAvatar + v-if="item.avatar" + :user="item.avatar" + class="avatar" + /> + <span :style="item.textStyle || ''">{{ item.text }}</span> + <span v-if="item.indicate" class="indicator" + ><i class="ph-circle ph-fill"></i + ></span> + </MkA> + <a + v-else-if="item.type === 'a'" + :href="item.href" + :target="item.target" + :download="item.download" + :tabindex="i" + class="_button item" + @click="close(true)" + @mouseenter.passive="onItemMouseEnter(item)" + @mouseleave.passive="onItemMouseLeave(item)" + > + <i + v-if="item.icon" + class="ph-fw ph-lg" + :class="item.icon" + ></i> + <span v-else-if="item.icons"> + <i + v-for="icon in item.icons" + class="ph-fw ph-lg" + :class="icon" + ></i> + </span> + <span :style="item.textStyle || ''">{{ item.text }}</span> + <span v-if="item.indicate" class="indicator" + ><i class="ph-circle ph-fill"></i + ></span> + </a> + <button + v-else-if="item.type === 'user' && !items.hidden" + :tabindex="i" + class="_button item" + :class="{ active: item.active }" + :disabled="item.active" + @click="clicked(item.action, $event)" + @mouseenter.passive="onItemMouseEnter(item)" + @mouseleave.passive="onItemMouseLeave(item)" + > + <MkAvatar :user="item.user" class="avatar" /><MkUserName + :user="item.user" + /> + <span v-if="item.indicate" class="indicator" + ><i class="ph-circle ph-fill"></i + ></span> + </button> + <span + v-else-if="item.type === 'switch'" + :tabindex="i" + class="item" + @mouseenter.passive="onItemMouseEnter(item)" + @mouseleave.passive="onItemMouseLeave(item)" + > + <FormSwitch + v-model="item.ref" + :disabled="item.disabled" + class="form-switch" + :style="item.textStyle || ''" + >{{ item.text }}</FormSwitch + > + </span> + <button + v-else-if="item.type === 'parent'" + :tabindex="i" + class="_button item parent" + :class="{ childShowing: childShowingItem === item }" + @mouseenter="showChildren(item, $event)" + > + <i + v-if="item.icon" + class="ph-fw ph-lg" + :class="item.icon" + ></i> + <span v-else-if="item.icons"> + <i + v-for="icon in item.icons" + class="ph-fw ph-lg" + :class="icon" + ></i> + </span> + <span :style="item.textStyle || ''">{{ item.text }}</span> + <span class="caret" + ><i class="ph-caret-right ph-bold ph-lg ph-fw ph-lg"></i + ></span> + </button> + <button + v-else-if="!item.hidden" + :tabindex="i" + class="_button item" + :class="{ danger: item.danger, active: item.active }" + :disabled="item.active" + @click="clicked(item.action, $event)" + @mouseenter.passive="onItemMouseEnter(item)" + @mouseleave.passive="onItemMouseLeave(item)" + > + <i + v-if="item.icon" + class="ph-fw ph-lg" + :class="item.icon" + ></i> + <span v-else-if="item.icons"> + <i + v-for="icon in item.icons" + class="ph-fw ph-lg" + :class="icon" + ></i> + </span> + <MkAvatar + v-if="item.avatar" + :user="item.avatar" + class="avatar" + /> + <span :style="item.textStyle || ''">{{ item.text }}</span> + <span v-if="item.indicate" class="indicator" + ><i class="ph-circle ph-fill"></i + ></span> + </button> + </template> + <span v-if="items2.length === 0" class="none item"> + <span>{{ i18n.ts.none }}</span> + </span> </div> - </FocusTrap> + <div v-if="childMenu" class="child"> + <XChild + ref="child" + :items="childMenu" + :target-element="childTarget" + :root-element="itemsEl" + showing + @actioned="childActioned" + /> + </div> + </div> </template> <script lang="ts" setup> @@ -203,7 +206,6 @@ import FormSwitch from "@/components/form/switch.vue"; import { MenuItem, InnerMenuItem, MenuPending, MenuAction } from "@/types/menu"; import * as os from "@/os"; import { i18n } from "@/i18n"; -import { FocusTrap } from 'focus-trap-vue'; const XChild = defineAsyncComponent(() => import("./MkMenu.child.vue")); @@ -226,6 +228,12 @@ let items2: InnerMenuItem[] = $ref([]); let child = $ref<InstanceType<typeof XChild>>(); +let keymap = computed(() => ({ + "up|k|shift+tab": focusUp, + "down|j|tab": focusDown, + esc: close, +})); + let childShowingItem = $ref<MenuItem | null>(); watch( @@ -356,7 +364,8 @@ onBeforeUnmount(() => { font-size: 0.9em; line-height: 20px; text-align: left; - outline: none; + overflow: hidden; + text-overflow: ellipsis; &:before { content: ""; @@ -380,7 +389,7 @@ onBeforeUnmount(() => { transform: translateY(0em); } - &:not(:disabled):hover, &:focus-visible { + &:not(:disabled):hover { color: var(--accent); text-decoration: none; @@ -388,9 +397,6 @@ onBeforeUnmount(() => { background: var(--accentedBg); } } - &:focus-visible:before { - outline: auto; - } &.danger { color: #eb6f92; diff --git a/packages/client/src/components/MkModal.vue b/packages/client/src/components/MkModal.vue index 12e79f428..d9cd56f95 100644 --- a/packages/client/src/components/MkModal.vue +++ b/packages/client/src/components/MkModal.vue @@ -14,59 +14,54 @@ :duration="transitionDuration" appear @after-leave="emit('closed')" - @keyup.esc="emit('click')" @enter="emit('opening')" @after-enter="onOpened" > - <FocusTrap v-model:active="isActive"> + <div + v-show="manualShowing != null ? manualShowing : showing" + v-hotkey.global="keymap" + :class="[ + $style.root, + { + [$style.drawer]: type === 'drawer', + [$style.dialog]: type === 'dialog' || type === 'dialog:top', + [$style.popup]: type === 'popup', + }, + ]" + :style="{ + zIndex, + pointerEvents: (manualShowing != null ? manualShowing : showing) + ? 'auto' + : 'none', + '--transformOrigin': transformOrigin, + }" + > <div - v-show="manualShowing != null ? manualShowing : showing" - v-hotkey.global="keymap" + class="_modalBg data-cy-bg" :class="[ - $style.root, + $style.bg, { - [$style.drawer]: type === 'drawer', - [$style.dialog]: type === 'dialog' || type === 'dialog:top', - [$style.popup]: type === 'popup', + [$style.bgTransparent]: isEnableBgTransparent, + 'data-cy-transparent': isEnableBgTransparent, }, ]" - :style="{ - zIndex, - pointerEvents: (manualShowing != null ? manualShowing : showing) - ? 'auto' - : 'none', - '--transformOrigin': transformOrigin, - }" - tabindex="-1" - v-focus + :style="{ zIndex }" + @click="onBgClick" + @mousedown="onBgClick" + @contextmenu.prevent.stop="() => {}" + ></div> + <div + ref="content" + :class="[ + $style.content, + { [$style.fixed]: fixed, top: type === 'dialog:top' }, + ]" + :style="{ zIndex }" + @click.self="onBgClick" > - <div - class="_modalBg data-cy-bg" - :class="[ - $style.bg, - { - [$style.bgTransparent]: isEnableBgTransparent, - 'data-cy-transparent': isEnableBgTransparent, - }, - ]" - :style="{ zIndex }" - @click="onBgClick" - @mousedown="onBgClick" - @contextmenu.prevent.stop="() => {}" - ></div> - <div - ref="content" - :class="[ - $style.content, - { [$style.fixed]: fixed, top: type === 'dialog:top' }, - ]" - :style="{ zIndex }" - @click.self="onBgClick" - > - <slot :max-height="maxHeight" :type="type"></slot> - </div> + <slot :max-height="maxHeight" :type="type"></slot> </div> - </FocusTrap> + </div> </Transition> </template> @@ -76,7 +71,6 @@ import * as os from "@/os"; import { isTouchUsing } from "@/scripts/touch"; import { defaultStore } from "@/store"; import { deviceKind } from "@/scripts/device-kind"; -import { FocusTrap } from 'focus-trap-vue'; function getFixedContainer(el: Element | null): Element | null { if (el == null || el.tagName === "BODY") return null; @@ -172,7 +166,6 @@ let transitionDuration = $computed(() => let contentClicking = false; -const focusedElement = document.activeElement; function close(opts: { useSendAnimation?: boolean } = {}) { if (opts.useSendAnimation) { useSendAnime = true; @@ -182,12 +175,10 @@ function close(opts: { useSendAnimation?: boolean } = {}) { if (props.src) props.src.style.pointerEvents = "auto"; showing = false; emit("close"); - focusedElement.focus(); } function onBgClick() { if (contentClicking) return; - focusedElement.focus(); emit("click"); } @@ -490,7 +481,6 @@ defineExpose({ } .root { - outline: none; &.dialog { > .content { position: fixed; diff --git a/packages/client/src/components/MkModalPageWindow.vue b/packages/client/src/components/MkModalPageWindow.vue index bf4d8d0bc..361128464 100644 --- a/packages/client/src/components/MkModalPageWindow.vue +++ b/packages/client/src/components/MkModalPageWindow.vue @@ -158,7 +158,6 @@ function onContextmenu(ev: MouseEvent) { flex-direction: column; contain: content; border-radius: var(--radius); - margin: auto; --root-margin: 24px; diff --git a/packages/client/src/components/MkModalWindow.vue b/packages/client/src/components/MkModalWindow.vue index 017bfae8c..3afcff6cb 100644 --- a/packages/client/src/components/MkModalWindow.vue +++ b/packages/client/src/components/MkModalWindow.vue @@ -3,64 +3,59 @@ ref="modal" :prefer-type="'dialog'" @click="onBgClick" - @keyup.esc="$emit('close')" @closed="$emit('closed')" > - <FocusTrap v-model:active="isActive"> - <div - ref="rootEl" - class="ebkgoccj" - :style="{ - width: `${width}px`, - height: scroll - ? height - ? `${height}px` - : null - : height - ? `min(${height}px, 100%)` - : '100%', - }" - @keydown="onKeydown" - tabindex="-1" - > - <div ref="headerEl" class="header"> - <button - v-if="withOkButton" - class="_button" - @click="$emit('close')" - > - <i class="ph-x ph-bold ph-lg"></i> - </button> - <span class="title"> - <slot name="header"></slot> - </span> - <button - v-if="!withOkButton" - class="_button" - @click="$emit('close')" - > - <i class="ph-x ph-bold ph-lg"></i> - </button> - <button - v-if="withOkButton" - class="_button" - :disabled="okButtonDisabled" - @click="$emit('ok')" - > - <i class="ph-check ph-bold ph-lg"></i> - </button> - </div> - <div class="body"> - <slot :width="bodyWidth" :height="bodyHeight"></slot> - </div> + <div + ref="rootEl" + class="ebkgoccj" + :style="{ + width: `${width}px`, + height: scroll + ? height + ? `${height}px` + : null + : height + ? `min(${height}px, 100%)` + : '100%', + }" + @keydown="onKeydown" + > + <div ref="headerEl" class="header"> + <button + v-if="withOkButton" + class="_button" + @click="$emit('close')" + > + <i class="ph-x ph-bold ph-lg"></i> + </button> + <span class="title"> + <slot name="header"></slot> + </span> + <button + v-if="!withOkButton" + class="_button" + @click="$emit('close')" + > + <i class="ph-x ph-bold ph-lg"></i> + </button> + <button + v-if="withOkButton" + class="_button" + :disabled="okButtonDisabled" + @click="$emit('ok')" + > + <i class="ph-check ph-bold ph-lg"></i> + </button> </div> - </FocusTrap> + <div class="body"> + <slot :width="bodyWidth" :height="bodyHeight"></slot> + </div> + </div> </MkModal> </template> <script lang="ts" setup> import { onMounted, onUnmounted } from "vue"; -import { FocusTrap } from 'focus-trap-vue'; import MkModal from "./MkModal.vue"; const props = withDefaults( diff --git a/packages/client/src/components/MkNote.vue b/packages/client/src/components/MkNote.vue index 5d9c40d38..22a7ef93f 100644 --- a/packages/client/src/components/MkNote.vue +++ b/packages/client/src/components/MkNote.vue @@ -84,7 +84,6 @@ :detailedView="detailedView" :parentId="appearNote.parentId" @push="(e) => router.push(notePage(e))" - @focusfooter="footerEl.focus()" ></MkSubNoteContent> <div v-if="translating || translation" class="translation"> <MkLoading v-if="translating" mini /> @@ -118,7 +117,7 @@ <MkTime :time="appearNote.createdAt" mode="absolute" /> </MkA> </div> - <footer ref="footerEl" class="footer" @click.stop tabindex="-1"> + <footer ref="el" class="footer" @click.stop> <XReactionsViewer v-if="enableEmojiReactions" ref="reactionsViewer" @@ -279,7 +278,6 @@ const isRenote = note.poll == null; const el = ref<HTMLElement>(); -const footerEl = ref<HTMLElement>(); const menuButton = ref<HTMLElement>(); const starButton = ref<InstanceType<typeof XStarButton>>(); const renoteButton = ref<InstanceType<typeof XRenoteButton>>(); @@ -300,8 +298,8 @@ const keymap = { r: () => reply(true), "e|a|plus": () => react(true), q: () => renoteButton.value.renote(true), - "up|k": focusBefore, - "down|j": focusAfter, + "up|k|shift+tab": focusBefore, + "down|j|tab": focusAfter, esc: blur, "m|o": () => menu(true), s: () => showContent.value !== showContent.value, diff --git a/packages/client/src/components/MkNotePreview.vue b/packages/client/src/components/MkNotePreview.vue index 6fdd79dc6..9d388e71b 100644 --- a/packages/client/src/components/MkNotePreview.vue +++ b/packages/client/src/components/MkNotePreview.vue @@ -1,6 +1,6 @@ <template> <div v-size="{ min: [350, 500] }" class="fefdfafb"> - <MkAvatar class="avatar" :user="$i" disableLink /> + <MkAvatar class="avatar" :user="$i" /> <div class="main"> <div class="header"> <MkUserName :user="$i" /> diff --git a/packages/client/src/components/MkNoteSub.vue b/packages/client/src/components/MkNoteSub.vue index a0b70ff1f..f5e70891f 100644 --- a/packages/client/src/components/MkNoteSub.vue +++ b/packages/client/src/components/MkNoteSub.vue @@ -26,7 +26,6 @@ :note="note" :parentId="appearNote.parentId" :conversation="conversation" - @focusfooter="footerEl.focus()" /> <div v-if="translating || translation" class="translation"> <MkLoading v-if="translating" mini /> @@ -47,7 +46,7 @@ </div> </div> </div> - <footer ref="footerEl" class="footer" @click.stop tabindex="-1"> + <footer class="footer" @click.stop> <XReactionsViewer v-if="enableEmojiReactions" ref="reactionsViewer" @@ -213,7 +212,6 @@ const isRenote = note.poll == null; const el = ref<HTMLElement>(); -const footerEl = ref<HTMLElement>(); const menuButton = ref<HTMLElement>(); const starButton = ref<InstanceType<typeof XStarButton>>(); const renoteButton = ref<InstanceType<typeof XRenoteButton>>(); diff --git a/packages/client/src/components/MkPopupMenu.vue b/packages/client/src/components/MkPopupMenu.vue index 5f1ed037b..4d52616e1 100644 --- a/packages/client/src/components/MkPopupMenu.vue +++ b/packages/client/src/components/MkPopupMenu.vue @@ -7,8 +7,6 @@ :transparent-bg="true" @click="modal.close()" @closed="emit('closed')" - tabindex="-1" - v-focus > <MkMenu :items="items" diff --git a/packages/client/src/components/MkPostFormAttaches.vue b/packages/client/src/components/MkPostFormAttaches.vue index 7cf397e55..7c7f240e8 100644 --- a/packages/client/src/components/MkPostFormAttaches.vue +++ b/packages/client/src/components/MkPostFormAttaches.vue @@ -198,6 +198,7 @@ export default defineComponent({ height: 64px; margin-right: 4px; border-radius: 4px; + overflow: hidden; cursor: move; &:hover > .remove { diff --git a/packages/client/src/components/MkSubNoteContent.vue b/packages/client/src/components/MkSubNoteContent.vue index 68439527a..a1f7cc1b9 100644 --- a/packages/client/src/components/MkSubNoteContent.vue +++ b/packages/client/src/components/MkSubNoteContent.vue @@ -35,11 +35,7 @@ class="content" :class="{ collapsed, isLong, showContent: note.cw && !showContent }" > - <XCwButton ref="cwButton" v-if="note.cw && !showContent" v-model="showContent" :note="note" v-on:keydown="focusFooter" /> - <div - class="body" - v-bind="{ 'aria-label': !showContent ? '' : null, 'tabindex': !showContent ? '-1' : null }" - > + <div class="body"> <span v-if="note.deletedAt" style="opacity: 0.5" >({{ i18n.ts.deleted }})</span > @@ -100,20 +96,15 @@ <XNoteSimple :note="note.renote" /> </div> </template> - <div - v-if="note.cw && !showContent" - tabindex="0" - v-on:focus="cwButton?.focus()" - ></div> </div> <XShowMoreButton v-if="isLong" v-model="collapsed"></XShowMoreButton> - <XCwButton v-if="note.cw && showContent" v-model="showContent" :note="note" /> + <XCwButton v-if="note.cw" v-model="showContent" :note="note" /> </div> </div> </template> <script lang="ts" setup> -import { ref } from "vue"; +import {} from "vue"; import * as misskey from "calckey-js"; import * as mfm from "mfm-js"; import XNoteSimple from "@/components/MkNoteSimple.vue"; @@ -135,10 +126,8 @@ const props = defineProps<{ const emit = defineEmits<{ (ev: "push", v): void; - (ev: "focusfooter"): void; }>(); -const cwButton = ref<HTMLElement>(); const isLong = !props.detailedView && props.note.cw == null && @@ -151,13 +140,6 @@ const urls = props.note.text : null; let showContent = $ref(false); - - -function focusFooter(ev) { - if (ev.key == "Tab" && !ev.getModifierState("Shift")) { - emit("focusfooter"); - } -} </script> <style lang="scss" scoped> @@ -249,9 +231,6 @@ function focusFooter(ev) { margin-top: -50px; padding-top: 50px; overflow: hidden; - user-select: none; - -webkit-user-select: none; - -moz-user-select: none; } &.collapsed > .body { box-sizing: border-box; diff --git a/packages/client/src/components/MkSuperMenu.vue b/packages/client/src/components/MkSuperMenu.vue index 83c667070..55d6fba50 100644 --- a/packages/client/src/components/MkSuperMenu.vue +++ b/packages/client/src/components/MkSuperMenu.vue @@ -9,6 +9,7 @@ v-if="item.type === 'a'" :href="item.href" :target="item.target" + :tabindex="i" class="_button item" :class="{ danger: item.danger, active: item.active }" > @@ -21,6 +22,7 @@ </a> <button v-else-if="item.type === 'button'" + :tabindex="i" class="_button item" :class="{ danger: item.danger, active: item.active }" :disabled="item.active" @@ -36,6 +38,7 @@ <MkA v-else :to="item.to" + :tabindex="i" class="_button item" :class="{ danger: item.danger, active: item.active }" > @@ -96,7 +99,7 @@ export default defineComponent({ font-size: 0.9em; margin-bottom: 0.3rem; - &:hover, &:focus-visible { + &:hover { text-decoration: none; background: var(--panelHighlight); } diff --git a/packages/client/src/components/MkUserSelectDialog.vue b/packages/client/src/components/MkUserSelectDialog.vue index 14553ca46..506f48bd4 100644 --- a/packages/client/src/components/MkUserSelectDialog.vue +++ b/packages/client/src/components/MkUserSelectDialog.vue @@ -46,7 +46,6 @@ :user="user" class="avatar" :show-indicator="true" - disableLink /> <div class="body"> <MkUserName :user="user" class="name" /> @@ -74,7 +73,6 @@ :user="user" class="avatar" :show-indicator="true" - disableLink /> <div class="body"> <MkUserName :user="user" class="name" /> diff --git a/packages/client/src/components/MkUsersTooltip.vue b/packages/client/src/components/MkUsersTooltip.vue index 78a4f90f2..972864d1f 100644 --- a/packages/client/src/components/MkUsersTooltip.vue +++ b/packages/client/src/components/MkUsersTooltip.vue @@ -7,7 +7,7 @@ > <div class="beaffaef"> <div v-for="u in users" :key="u.id" class="user"> - <MkAvatar class="avatar" :user="u" disableLink /> + <MkAvatar class="avatar" :user="u" /> <MkUserName class="name" :user="u" :nowrap="true" /> </div> <div v-if="users.length < count" class="omitted"> diff --git a/packages/client/src/components/MkWidgets.vue b/packages/client/src/components/MkWidgets.vue index d48fc5383..07e845032 100644 --- a/packages/client/src/components/MkWidgets.vue +++ b/packages/client/src/components/MkWidgets.vue @@ -1,7 +1,7 @@ <template> <div class="vjoppmmu"> <template v-if="edit"> - <header tabindex="-1" v-focus> + <header> <MkSelect v-model="widgetAdderSelected" style="margin-bottom: var(--margin)" diff --git a/packages/client/src/components/form/folder.vue b/packages/client/src/components/form/folder.vue index a2fde5341..8868f5784 100644 --- a/packages/client/src/components/form/folder.vue +++ b/packages/client/src/components/form/folder.vue @@ -1,6 +1,6 @@ <template> <div class="dwzlatin" :class="{ opened }"> - <button class="header _button" @click="toggle"> + <div class="header _button" @click="toggle"> <span class="icon"><slot name="icon"></slot></span> <span class="text"><slot name="label"></slot></span> <span class="right"> @@ -8,7 +8,7 @@ <i v-if="opened" class="ph-caret-up ph-bold ph-lg icon"></i> <i v-else class="ph-caret-down ph-bold ph-lg icon"></i> </span> - </button> + </div> <KeepAlive> <div v-if="openedAtLeastOnce" v-show="opened" class="body"> <MkSpacer :margin-min="14" :margin-max="22"> diff --git a/packages/client/src/components/form/radio.vue b/packages/client/src/components/form/radio.vue index ef644b327..493b2d010 100644 --- a/packages/client/src/components/form/radio.vue +++ b/packages/client/src/components/form/radio.vue @@ -66,9 +66,6 @@ function toggle(): void { &:hover { border-color: var(--inputBorderHover) !important; } - &:focus-within { - outline: auto; - } &.checked { background-color: var(--accentedBg) !important; diff --git a/packages/client/src/components/form/switch.vue b/packages/client/src/components/form/switch.vue index b1c6df4e9..efaf488a9 100644 --- a/packages/client/src/components/form/switch.vue +++ b/packages/client/src/components/form/switch.vue @@ -99,9 +99,6 @@ const toggle = () => { border-color: var(--inputBorderHover) !important; } } - &:focus-within > .button { - outline: auto; - } > .label { margin-left: 12px; diff --git a/packages/client/src/components/global/MkPageHeader.vue b/packages/client/src/components/global/MkPageHeader.vue index c78ef0c10..ad1d80ca6 100644 --- a/packages/client/src/components/global/MkPageHeader.vue +++ b/packages/client/src/components/global/MkPageHeader.vue @@ -19,7 +19,6 @@ class="avatar" :user="$i" :disable-preview="true" - disableLink /> </div> <template v-if="metadata"> @@ -34,7 +33,6 @@ :user="metadata.avatar" :disable-preview="true" :show-indicator="true" - disableLink /> <i v-else-if="metadata.icon && !narrow" diff --git a/packages/client/src/components/global/RouterView.vue b/packages/client/src/components/global/RouterView.vue index 437b7c53e..8423ce773 100644 --- a/packages/client/src/components/global/RouterView.vue +++ b/packages/client/src/components/global/RouterView.vue @@ -5,9 +5,6 @@ :is="currentPageComponent" :key="key" v-bind="Object.fromEntries(currentPageProps)" - tabindex="-1" - v-focus - style="outline: none;" /> <template #fallback> diff --git a/packages/client/src/directives/focus.ts b/packages/client/src/directives/focus.ts deleted file mode 100644 index 4d34fbf1f..000000000 --- a/packages/client/src/directives/focus.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default { - mounted: (el) => el.focus() -} diff --git a/packages/client/src/directives/index.ts b/packages/client/src/directives/index.ts index 77639e2f3..0a5c32326 100644 --- a/packages/client/src/directives/index.ts +++ b/packages/client/src/directives/index.ts @@ -11,7 +11,6 @@ import anim from "./anim"; import clickAnime from "./click-anime"; import panel from "./panel"; import adaptiveBorder from "./adaptive-border"; -import focus from "./focus"; export default function (app: App) { app.directive("userPreview", userPreview); @@ -26,5 +25,4 @@ export default function (app: App) { app.directive("click-anime", clickAnime); app.directive("panel", panel); app.directive("adaptive-border", adaptiveBorder); - app.directive("focus", focus); } diff --git a/packages/client/src/directives/tooltip.ts b/packages/client/src/directives/tooltip.ts index 91024a6e3..7738d14e8 100644 --- a/packages/client/src/directives/tooltip.ts +++ b/packages/client/src/directives/tooltip.ts @@ -76,32 +76,23 @@ export default { ev.preventDefault(); }); - function showTooltip() { - window.clearTimeout(self.showTimer); - window.clearTimeout(self.hideTimer); - self.showTimer = window.setTimeout(self.show, delay); - } - function hideTooltip() { - window.clearTimeout(self.showTimer); - window.clearTimeout(self.hideTimer); - self.hideTimer = window.setTimeout(self.close, delay); - } - el.addEventListener( - start, showTooltip, - { passive: true }, - ); - el.addEventListener( - "focusin", showTooltip, + start, + () => { + window.clearTimeout(self.showTimer); + window.clearTimeout(self.hideTimer); + self.showTimer = window.setTimeout(self.show, delay); + }, { passive: true }, ); el.addEventListener( - end, hideTooltip, - { passive: true }, - ); - el.addEventListener( - "focusout", hideTooltip, + end, + () => { + window.clearTimeout(self.showTimer); + window.clearTimeout(self.hideTimer); + self.hideTimer = window.setTimeout(self.close, delay); + }, { passive: true }, ); diff --git a/packages/client/src/pages/admin/_header_.vue b/packages/client/src/pages/admin/_header_.vue index bf070e269..69fd1bc58 100644 --- a/packages/client/src/pages/admin/_header_.vue +++ b/packages/client/src/pages/admin/_header_.vue @@ -313,7 +313,11 @@ onUnmounted(() => { font-weight: normal; opacity: 0.7; - &:hover, &:focus-visible, &.active { + &:hover { + opacity: 1; + } + + &.active { opacity: 1; } diff --git a/packages/client/src/pages/admin/overview.moderators.vue b/packages/client/src/pages/admin/overview.moderators.vue index db953b890..6184cfb10 100644 --- a/packages/client/src/pages/admin/overview.moderators.vue +++ b/packages/client/src/pages/admin/overview.moderators.vue @@ -12,7 +12,7 @@ class="user" :to="`/user-info/${user.id}`" > - <MkAvatar :user="user" class="avatar" indicator disableLink /> + <MkAvatar :user="user" class="avatar" indicator /> </MkA> </div> </Transition> diff --git a/packages/client/src/pages/follow-requests.vue b/packages/client/src/pages/follow-requests.vue index 35279495b..2aac52163 100644 --- a/packages/client/src/pages/follow-requests.vue +++ b/packages/client/src/pages/follow-requests.vue @@ -23,7 +23,6 @@ class="avatar" :user="req.follower" :show-indicator="true" - disableLink /> <div class="body"> <div class="name"> diff --git a/packages/client/src/pages/settings/accounts.vue b/packages/client/src/pages/settings/accounts.vue index 3010354b6..ec2cd2477 100644 --- a/packages/client/src/pages/settings/accounts.vue +++ b/packages/client/src/pages/settings/accounts.vue @@ -6,14 +6,14 @@ {{ i18n.ts.addAccount }}</FormButton > - <button + <div v-for="account in accounts" :key="account.id" class="_panel _button lcjjdxlm" @click="menu(account, $event)" > <div class="avatar"> - <MkAvatar :user="account" class="avatar" disableLink /> + <MkAvatar :user="account" class="avatar" /> </div> <div class="body"> <div class="name"> @@ -23,7 +23,7 @@ <MkAcct :user="account" /> </div> </div> - </button> + </div> </FormSuspense> </div> </template> @@ -158,8 +158,6 @@ definePageMetadata({ .lcjjdxlm { display: flex; padding: 16px; - width: 100%; - text-align: unset; > .avatar { display: block; diff --git a/packages/client/src/style.scss b/packages/client/src/style.scss index 52c7b62f4..051edf6e0 100644 --- a/packages/client/src/style.scss +++ b/packages/client/src/style.scss @@ -204,6 +204,10 @@ hr { pointer-events: none; } + &:focus-visible { + outline: none; + } + &:disabled { opacity: 0.5; cursor: default; diff --git a/packages/client/src/ui/_common_/navbar-for-mobile.vue b/packages/client/src/ui/_common_/navbar-for-mobile.vue index 39abb7c26..43c91d147 100644 --- a/packages/client/src/ui/_common_/navbar-for-mobile.vue +++ b/packages/client/src/ui/_common_/navbar-for-mobile.vue @@ -18,7 +18,6 @@ <MkAvatar :user="$i" class="icon" - disableLink /><!-- <MkAcct class="text" :user="$i"/> --> </button> </div> diff --git a/packages/client/src/ui/_common_/navbar.vue b/packages/client/src/ui/_common_/navbar.vue index 20c177f37..380f77c3c 100644 --- a/packages/client/src/ui/_common_/navbar.vue +++ b/packages/client/src/ui/_common_/navbar.vue @@ -18,7 +18,6 @@ <MkAvatar :user="$i" class="icon" - disableLink /><!-- <MkAcct class="text" :user="$i"/> --> </button> </div> @@ -335,7 +334,6 @@ function more(ev: MouseEvent) { } &:hover, - &:focus-within, &.active { &:before { background: var(--accentLighten); @@ -400,6 +398,8 @@ function more(ev: MouseEvent) { padding-left: 30px; line-height: 2.85rem; margin-bottom: 0.5rem; + text-overflow: ellipsis; + overflow: hidden; white-space: nowrap; width: 100%; text-align: left; @@ -425,12 +425,9 @@ function more(ev: MouseEvent) { > .text { position: relative; font-size: 0.9em; - overflow: hidden; - text-overflow: ellipsis; } - &:hover, - &:focus-within { + &:hover { text-decoration: none; color: var(--navHoverFg); transition: all 0.4s ease; @@ -440,8 +437,7 @@ function more(ev: MouseEvent) { color: var(--navActive); } - &:hover, - &:focus-within, + &:hover, &.active { color: var(--accent); transition: all 0.4s ease; @@ -532,7 +528,6 @@ function more(ev: MouseEvent) { } &:hover, - &:focus-within, &.active { &:before { background: var(--accentLighten); @@ -618,7 +613,6 @@ function more(ev: MouseEvent) { } &:hover, - &:focus-within, &.active { text-decoration: none; color: var(--accent); @@ -648,12 +642,5 @@ function more(ev: MouseEvent) { } } } - - .item { - outline: none; - &:focus-visible:before { - outline: auto; - } - } } </style> diff --git a/packages/client/src/ui/classic.header.vue b/packages/client/src/ui/classic.header.vue index 99a0ab098..5c3e6b702 100644 --- a/packages/client/src/ui/classic.header.vue +++ b/packages/client/src/ui/classic.header.vue @@ -83,7 +83,6 @@ <MkAvatar :user="$i" class="avatar" /><MkAcct class="acct" :user="$i" - disableLink /> </button> <div class="post" @click="post"> diff --git a/packages/client/src/ui/classic.sidebar.vue b/packages/client/src/ui/classic.sidebar.vue index fa72c5765..b70a3c984 100644 --- a/packages/client/src/ui/classic.sidebar.vue +++ b/packages/client/src/ui/classic.sidebar.vue @@ -5,7 +5,7 @@ class="item _button account" @click="openAccountMenu" > - <MkAvatar :user="$i" class="avatar" disableLink /><MkAcct + <MkAvatar :user="$i" class="avatar" /><MkAcct class="text" :user="$i" /> @@ -299,7 +299,6 @@ function openInstanceMenu(ev: MouseEvent) { width: 46px; height: 46px; padding: 0; - margin-inline: 0 !important; } } @@ -373,7 +372,6 @@ function openInstanceMenu(ev: MouseEvent) { > i { width: 32px; - justify-content: center; } > i, diff --git a/packages/client/src/ui/classic.vue b/packages/client/src/ui/classic.vue index 266effd9a..a721ffd0b 100644 --- a/packages/client/src/ui/classic.vue +++ b/packages/client/src/ui/classic.vue @@ -227,8 +227,6 @@ onMounted(() => { } .gbhvwtnk { - display: flex; - justify-content: center; $ui-font-size: 1em; $widgets-hide-threshold: 1200px; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 60e42e11a..9f0b4195d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -19,12 +19,6 @@ importers: '@tensorflow/tfjs': specifier: ^3.21.0 version: 3.21.0(seedrandom@3.0.5) - focus-trap: - specifier: ^7.2.0 - version: 7.2.0 - focus-trap-vue: - specifier: ^4.0.1 - version: 4.0.1(focus-trap@7.2.0)(vue@3.2.45) js-yaml: specifier: 4.1.0 version: 4.1.0 @@ -3809,12 +3803,14 @@ packages: '@vue/shared': 3.2.45 estree-walker: 2.0.2 source-map: 0.6.1 + dev: true /@vue/compiler-dom@3.2.45: resolution: {integrity: sha512-tyYeUEuKqqZO137WrZkpwfPCdiiIeXYCcJ8L4gWz9vqaxzIQRccTSwSWZ/Axx5YR2z+LvpUbmPNXxuBU45lyRw==} dependencies: '@vue/compiler-core': 3.2.45 '@vue/shared': 3.2.45 + dev: true /@vue/compiler-sfc@2.7.14: resolution: {integrity: sha512-aNmNHyLPsw+sVvlQFQ2/8sjNuLtK54TC6cuKnVzAY93ks4ZBrvwQSnkkIh7bsbNhum5hJBS00wSDipQ937f5DA==} @@ -3837,12 +3833,14 @@ packages: magic-string: 0.25.9 postcss: 8.4.21 source-map: 0.6.1 + dev: true /@vue/compiler-ssr@3.2.45: resolution: {integrity: sha512-6BRaggEGqhWht3lt24CrIbQSRD5O07MTmd+LjAn5fJj568+R9eUD2F7wMQJjX859seSlrYog7sUtrZSd7feqrQ==} dependencies: '@vue/compiler-dom': 3.2.45 '@vue/shared': 3.2.45 + dev: true /@vue/reactivity-transform@3.2.45: resolution: {integrity: sha512-BHVmzYAvM7vcU5WmuYqXpwaBHjsS8T63jlKGWVtHxAHIoMIlmaMyurUSEs1Zcg46M4AYT5MtB1U274/2aNzjJQ==} @@ -3852,17 +3850,20 @@ packages: '@vue/shared': 3.2.45 estree-walker: 2.0.2 magic-string: 0.25.9 + dev: true /@vue/reactivity@3.2.45: resolution: {integrity: sha512-PRvhCcQcyEVohW0P8iQ7HDcIOXRjZfAsOds3N99X/Dzewy8TVhTCT4uXpAHfoKjVTJRA0O0K+6QNkDIZAxNi3A==} dependencies: '@vue/shared': 3.2.45 + dev: true /@vue/runtime-core@3.2.45: resolution: {integrity: sha512-gzJiTA3f74cgARptqzYswmoQx0fIA+gGYBfokYVhF8YSXjWTUA2SngRzZRku2HbGbjzB6LBYSbKGIaK8IW+s0A==} dependencies: '@vue/reactivity': 3.2.45 '@vue/shared': 3.2.45 + dev: true /@vue/runtime-dom@3.2.45: resolution: {integrity: sha512-cy88YpfP5Ue2bDBbj75Cb4bIEZUMM/mAkDMfqDTpUYVgTf/kuQ2VQ8LebuZ8k6EudgH8pYhsGWHlY0lcxlvTwA==} @@ -3870,6 +3871,7 @@ packages: '@vue/runtime-core': 3.2.45 '@vue/shared': 3.2.45 csstype: 2.6.21 + dev: true /@vue/server-renderer@3.2.45(vue@3.2.45): resolution: {integrity: sha512-ebiMq7q24WBU1D6uhPK//2OTR1iRIyxjF5iVq/1a5I1SDMDyDu4Ts6fJaMnjrvD3MqnaiFkKQj+LKAgz5WIK3g==} @@ -3879,9 +3881,11 @@ packages: '@vue/compiler-ssr': 3.2.45 '@vue/shared': 3.2.45 vue: 3.2.45 + dev: true /@vue/shared@3.2.45: resolution: {integrity: sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg==} + dev: true /@webassemblyjs/ast@1.11.1: resolution: {integrity: sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==} @@ -6070,6 +6074,7 @@ packages: /csstype@2.6.21: resolution: {integrity: sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==} + dev: true /csstype@3.1.1: resolution: {integrity: sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==} @@ -6974,6 +6979,7 @@ packages: /estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + dev: true /esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} @@ -7439,22 +7445,6 @@ packages: readable-stream: 2.3.7 dev: true - /focus-trap-vue@4.0.1(focus-trap@7.2.0)(vue@3.2.45): - resolution: {integrity: sha512-2iqOeoSvgq7Um6aL+255a/wXPskj6waLq2oKCa4gOnMORPo15JX7wN6J5bl1SMhMlTlkHXGSrQ9uJPJLPZDl5w==} - peerDependencies: - focus-trap: ^7.0.0 - vue: ^3.0.0 - dependencies: - focus-trap: 7.2.0 - vue: 3.2.45 - dev: false - - /focus-trap@7.2.0: - resolution: {integrity: sha512-v4wY6HDDYvzkBy4735kW5BUEuw6Yz9ABqMYLuTNbzAFPcBOGiGHwwcNVMvUz4G0kgSYh13wa/7TG3XwTeT4O/A==} - dependencies: - tabbable: 6.1.1 - dev: false - /follow-redirects@1.15.2(debug@4.3.4): resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} engines: {node: '>=4.0'} @@ -10360,6 +10350,7 @@ packages: resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==} dependencies: sourcemap-codec: 1.4.8 + dev: true /mailcheck@1.1.1: resolution: {integrity: sha512-3WjL8+ZDouZwKlyJBMp/4LeziLFXgleOdsYu87piGcMLqhBzCsy2QFdbtAwv757TFC/rtqd738fgJw1tFQCSgA==} @@ -13276,6 +13267,7 @@ packages: /sourcemap-codec@1.4.8: resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} deprecated: Please use @jridgewell/sourcemap-codec instead + dev: true /sparkles@1.0.1: resolution: {integrity: sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==} @@ -13694,10 +13686,6 @@ packages: resolution: {integrity: sha512-g9rPT3V1Q4WjWFZ/t5BdGC1mT/FpYnsLdBl+M5e6MlRkuE1RSR+R43wcY/3mKI59B9KEr+vxdWCuWNMD3oNHKA==} dev: true - /tabbable@6.1.1: - resolution: {integrity: sha512-4kl5w+nCB44EVRdO0g/UGoOp3vlwgycUVtkk/7DPyeLZUCuNFFKCFG6/t/DgHLrUPHjrZg6s5tNm+56Q2B0xyg==} - dev: false - /tapable@2.2.1: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} @@ -14788,6 +14776,7 @@ packages: '@vue/runtime-dom': 3.2.45 '@vue/server-renderer': 3.2.45(vue@3.2.45) '@vue/shared': 3.2.45 + dev: true /vuedraggable@4.1.0(vue@3.2.45): resolution: {integrity: sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==} From 5831f0a2c5ff69d5e51dac40749e5ca57d273b97 Mon Sep 17 00:00:00 2001 From: ThatOneCalculator <kainoa@t1c.dev> Date: Sat, 29 Apr 2023 19:14:47 -0700 Subject: [PATCH 4/8] hotfix --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 80942ff54..61c247ab2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "calckey", - "version": "13.2.0-dev40", + "version": "13.2.0-dev41", "codename": "aqua", "repository": { "type": "git", From 9c40247df21b2a8c4daf25cc7b0735fca32e9062 Mon Sep 17 00:00:00 2001 From: Laker Turner <la@laker.gay> Date: Sat, 29 Apr 2023 10:28:49 +0000 Subject: [PATCH 5/8] chore: Translated using Weblate (English) Currently translated at 100.0% (1735 of 1735 strings) Translation: Calckey/locales Translate-URL: https://hosted.weblate.org/projects/calckey/locales/en/ --- locales/en-US.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en-US.yml b/locales/en-US.yml index 2feb2cd94..f9d4d23f0 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -1042,7 +1042,7 @@ moveFromLabel: "Account you're moving from:" moveFromDescription: "This will set an alias of your old account so that you can move\ \ from that account to this current one. Do this BEFORE moving from your older account.\ \ Please enter the tag of the account formatted like @person@instance.com" -migrationConfirm: "Are you absolutely sure you want to migrate your acccount to {account}?\ +migrationConfirm: "Are you absolutely sure you want to migrate your account to {account}?\ \ Once you do this, you won't be able to reverse it, and you won't be able to use\ \ your account normally again.\nAlso, please ensure that you've set this current\ \ account as the account you're moving from." From bcfadc086bd523c408da31c69db24c228b8d7d51 Mon Sep 17 00:00:00 2001 From: jolupa <jolupameister@gmail.com> Date: Sat, 29 Apr 2023 11:53:12 +0000 Subject: [PATCH 6/8] chore: Translated using Weblate (Catalan) Currently translated at 37.1% (644 of 1735 strings) Translation: Calckey/locales Translate-URL: https://hosted.weblate.org/projects/calckey/locales/ca/ --- locales/ca-ES.yml | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml index 24e4b6d7d..b226d641e 100644 --- a/locales/ca-ES.yml +++ b/locales/ca-ES.yml @@ -675,3 +675,48 @@ useGlobalSetting: Fes servir els ajustos globals useGlobalSettingDesc: Si s'activa, es faran servir els ajustos de notificacions del teu compte. Si es desactiva , es poden fer configuracions individuals. other: Altres +menu: Menú +addItem: Afegeix un element +divider: Divisor +relays: Relés +addRelay: Afegeix un Relé +inboxUrl: Adreça de la safata d'entrada +addedRelays: Relés afegits +serviceworkerInfo: Ha de estar activat per les notificacions push. +poll: Enquesta +deletedNote: Article eliminat +disablePlayer: Tancar el reproductor de vídeo +fileIdOrUrl: ID o adreça URL del fitxer +behavior: Comportament +regenerateLoginTokenDescription: Regenera el token que es fa servir de manera interna + durant l'inici de sessió. Normalment això no és necessari. Si es torna a genera + el token, es tancarà la sessió a tots els dispositius. +setMultipleBySeparatingWithSpace: Separa diferents entrades amb espais. +reportAbuseOf: Informa sobre {name} +sample: Exemple +abuseReports: Informes +reportAbuse: Informe +reporter: Informador +reporterOrigin: Origen d'el informador +forwardReport: Envia l'informe a una instancia remota +abuseReported: El teu informe ha sigut enviat. Moltes gràcies. +reporteeOrigin: Origen de l'informe +send: Enviar +abuseMarkAsResolved: Marcar l'informe com a resolt +visibility: Visibilitat +useCw: Amaga el contingut +enablePlayer: Obre el reproductor de vídeo +yourAccountSuspendedDescription: Aquest compte ha sigut suspesa per no seguir els + termes de servei del servidor o quelcom similar. Contacte amb l'administrador si + vols conèixer la raó amb més detall. Si us plau no facis un compte nou. +invisibleNote: Article ocult +enableInfiniteScroll: Carregar més de forma automàtica +fillAbuseReportDescription: Si us plau omple els detalls sobre aquest informe. Si + es sobre un article en concret, si us plau inclou l'adreça URL. +forwardReportIsAnonymous: Com a informador a l'instància remota no es mostrarà el + teu compte, si no un compte anònim. +openInNewTab: Obrir en una pestanya nova +openInSideView: Obrir a la vista lateral +defaultNavigationBehaviour: Navegació per defecte +editTheseSettingsMayBreakAccount: Si edites aquestes configuracions pots fer mal bé + el teu compte. From b2dc43aa01d4b809d0a869b264b24557a719461f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Lepist=C3=B6?= <ville.lepisto@protonmail.com> Date: Sat, 29 Apr 2023 08:52:45 +0000 Subject: [PATCH 7/8] chore: Translated using Weblate (Finnish) Currently translated at 11.7% (204 of 1735 strings) Translation: Calckey/locales Translate-URL: https://hosted.weblate.org/projects/calckey/locales/fi/ --- locales/fi.yml | 179 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 179 insertions(+) diff --git a/locales/fi.yml b/locales/fi.yml index c9e5e06e0..558d1afc2 100644 --- a/locales/fi.yml +++ b/locales/fi.yml @@ -41,3 +41,182 @@ favorite: Lisää kirjanmerkkeihin copyContent: Kopioi sisältö deleteAndEdit: Poista ja muokkaa copyLink: Kopioi linkki +makeFollowManuallyApprove: Seuraajapyyntö vaatii hyväksymistä +follow: Seuraa +pinned: Kiinnitä profiiliin +followRequestPending: Seuraajapyyntö odottaa +you: Sinä +unrenote: Peruuta buustaus +reaction: Reaktiot +reactionSettingDescription2: Vedä uudelleenjärjestelläksesi, napsauta poistaaksesi, + paina "+" lisätäksesi. +attachCancel: Poista liite +enterFileName: Anna tiedostonimi +mute: Hiljennä +unmute: Poista hiljennys +headlineMisskey: Avoimen lähdekoodin, hajautettu sosiaalisen median alusta, joka on + ikuisesti ilmainen! 🚀 +monthAndDay: '{day}/{month}' +deleteAndEditConfirm: Oletko varma, että haluat poistaa tämän lähetyksen ja muokata + sitä? Menetät kaikki reaktiot, buustaukset ja vastaukset lähetyksestäsi. +addToList: Lisää listaan +sendMessage: Lähetä viesti +reply: Vastaa +loadMore: Lataa enemmän +showMore: Näytä enemmän +receiveFollowRequest: Seuraajapyyntö vastaanotettu +followRequestAccepted: Seuraajapyyntö hyväksytty +mentions: Maininnat +importAndExport: Tuo/Vie Tietosisältö +import: Tuo +export: Vie +files: Tiedostot +download: Lataa +unfollowConfirm: Oletko varma, ettet halua seurata enää käyttäjää {name}? +noLists: Sinulla ei ole listoja +note: Lähetys +notes: Lähetykset +following: Seuraa +createList: Luo lista +manageLists: Hallitse listoja +error: Virhe +somethingHappened: On tapahtunut virhe +retry: Yritä uudelleen +pageLoadError: Virhe ladattaessa sivua. +serverIsDead: Tämä palvelin ei vastaa. Yritä hetken kuluttua uudelleen. +youShouldUpgradeClient: Nähdäksesi tämän sivun, virkistä päivittääksesi asiakasohjelmasi. +privacy: Tietosuoja +defaultNoteVisibility: Oletusnäkyvyys +followRequest: Seuraajapyyntö +followRequests: Seuraajapyynnöt +unfollow: Poista seuraaminen +enterEmoji: Syötä emoji +renote: Buustaa +renoted: Buustattu. +cantRenote: Tätä lähetystä ei voi buustata. +cantReRenote: Buustausta ei voi buustata. +quote: Lainaus +pinnedNote: Lukittu lähetys +clickToShow: Napsauta nähdäksesi +sensitive: Herkkää sisältöä (NSFW) +add: Lisää +enableEmojiReactions: Ota käyttöön emoji-reaktiot +showEmojisInReactionNotifications: Näytä emojit reaktioilmoituksissa +reactionSetting: Reaktiot näytettäväksi reaktiovalitsimessa +rememberNoteVisibility: Muista lähetyksen näkyvyysasetukset +markAsSensitive: Merkitse herkäksi sisällöksi (NSFW) +unmarkAsSensitive: Poista merkintä herkkää sisältöä (NSFW) +renoteMute: Hiljennä buustit +renoteUnmute: Poista buustien hiljennys +block: Estä +unblock: Poista esto +unsuspend: Poista keskeytys +suspend: Keskeytys +blockConfirm: Oletko varma, että haluat estää tämän tilin? +unblockConfirm: Oletko varma, että haluat poistaa tämän tilin eston? +selectAntenna: Valitse antenni +selectWidget: Valitse vimpain +editWidgets: Muokkaa vimpaimia +editWidgetsExit: Valmis +emoji: Emoji +emojis: Emojit +emojiName: Emojin nimi +emojiUrl: Emojin URL-linkki +cacheRemoteFiles: Taltioi etätiedostot välimuistiin +flagAsBot: Merkitse tili botiksi +flagAsBotDescription: Ota tämä vaihtoehto käyttöön, jos tätä tiliä ohjaa ohjelma. + Jos se on käytössä, se toimii lippuna muille kehittäjille, jotta estetään loputtomat + vuorovaikutusketjut muiden bottien kanssa ja säädetään Calckeyn sisäiset järjestelmät + käsittelemään tätä tiliä botina. +flagAsCat: Oletko kissa? 🐱 +flagAsCatDescription: Saat kissan korvat ja puhut kuin kissa! +flagSpeakAsCat: Puhu kuin kissa +flagShowTimelineReplies: Näytä vastaukset aikajanalla +addAccount: Lisää tili +loginFailed: Kirjautuminen epäonnistui +showOnRemote: Katsele etäinstanssilla +general: Yleistä +accountMoved: 'Käyttäjä on muuttanut uuteen tiliin:' +wallpaper: Taustakuva +setWallpaper: Aseta taustakuva +searchWith: 'Etsi: {q}' +youHaveNoLists: Sinulla ei ole listoja +followConfirm: Oletko varma, että haluat seurata käyttäjää {name}? +host: Isäntä +selectUser: Valitse käyttäjä +annotation: Kommentit +registeredAt: Rekisteröity +latestRequestReceivedAt: Viimeisin pyyntö vastaanotettu +latestRequestSentAt: Viimeisin pyyntö lähetetty +storageUsage: Tallennustilan käyttö +charts: Kaaviot +stopActivityDelivery: Lopeta toimintojen lähettäminen +blockThisInstance: Estä tämä instanssi +operations: Toiminnot +metadata: Metatieto +monitor: Seuranta +jobQueue: Työjono +cpuAndMemory: Prosessori ja muisti +network: Verkko +disk: Levy +clearCachedFiles: Tyhjennä välimuisti +clearCachedFilesConfirm: Oletko varma, että haluat tyhjentää kaikki välimuistiin tallennetut + etätiedostot? +blockedInstances: Estetyt instanssit +hiddenTags: Piilotetut asiatunnisteet +mention: Maininta +copyUsername: Kopioi käyttäjänimi +searchUser: Etsi käyttäjää +showLess: Sulje +youGotNewFollower: seurasi sinua +directNotes: Yksityisviestit +driveFileDeleteConfirm: Oletko varma, että haluat poistaa tiedoston " {name}"? Lähetykset, + jotka sisältyvät tiedostoon, poistuvat myös. +importRequested: Olet pyytänyt viemistä. Tämä voi viedä hetken. +exportRequested: Olet pyytänyt tuomista. Tämä voi viedä hetken. Se lisätään asemaan + kun tuonti valmistuu. +lists: Listat +followers: Seuraajat +followsYou: Seuraa sinua +pageLoadErrorDescription: Tämä yleensä johtuu verkkovirheistä tai selaimen välimuistista. + Kokeile tyhjentämällä välimuisti ja yritä sitten hetken kuluttua uudelleen. +enterListName: Anna listalle nimi +withNFiles: '{n} tiedosto(t)' +instanceInfo: Instanssin tiedot +clearQueue: Tyhjennä jono +suspendConfirm: Oletko varma, että haluat keskeyttää tämän tilin? +unsuspendConfirm: Oletko varma, että haluat poistaa tämän tilin keskeytyksen? +selectList: Valitse lista +customEmojis: Kustomoitu Emoji +addEmoji: Lisää +settingGuide: Suositellut asetukset +cacheRemoteFilesDescription: Kun tämä asetus ei ole käytössä, etätiedostot on ladattu + suoraan etäinstanssilta. Asetuksen poistaminen käytöstä vähentää tallennustilan + käyttöä, mutta lisää verkkoliikennettä kun pienoiskuvat eivät muodostu. +flagSpeakAsCatDescription: Lähetyksesi nyanifioidaan, kun olet kissatilassa +flagShowTimelineRepliesDescription: Näyttää käyttäjien vastaukset muiden käyttäjien + lähetyksiin aikajanalla, jos se on päällä. +autoAcceptFollowed: Automaattisesti hyväksy seuraamispyynnöt käyttäjiltä, joita seuraat +perHour: Tunnissa +removeWallpaper: Poista taustakuva +recipient: Vastaanottaja(t) +federation: Federaatio +software: Ohjelmisto +proxyAccount: Proxy-tili +proxyAccountDescription: Välitystili (Proxy-tili) on tili, joka toimii käyttäjien + etäseuraajana tietyin edellytyksin. Kun käyttäjä esimerkiksi lisää etäkäyttäjän + luetteloon, etäkäyttäjän toimintaa ei toimiteta instanssiin, jos yksikään paikallinen + käyttäjä ei seuraa kyseistä käyttäjää, joten välitystili seuraa sen sijaan. +latestStatus: Viimeisin tila +selectInstance: Valitse instanssi +instances: Instanssit +perDay: Päivässä +version: Versio +statistics: Tilastot +clearQueueConfirmTitle: Oletko varma, että haluat tyhjentää jonon? +introMisskey: Tervetuloa! Calckey on avoimen lähdekoodin, hajautettu sosiaalisen median + alusta, joka on ikuisesti ilmainen! 🚀 +clearQueueConfirmText: Mitkään välittämättömät lähetykset, jotka ovat jonossa, eivät + federoidu. Yleensä tätä toimintoa ei tarvita. +blockedInstancesDescription: Lista instanssien isäntänimistä, jotka haluat estää. + Listatut instanssit eivät kykene kommunikoimaan enää tämän instanssin kanssa. From 8db240756e996edcacb811102b6c8dd25c508179 Mon Sep 17 00:00:00 2001 From: Kainoa Kanter <kainoa@t1c.dev> Date: Sat, 29 Apr 2023 08:11:18 +0000 Subject: [PATCH 8/8] chore: Translated using Weblate (Finnish) Currently translated at 11.7% (204 of 1735 strings) Translation: Calckey/locales Translate-URL: https://hosted.weblate.org/projects/calckey/locales/fi/ --- locales/fi.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/fi.yml b/locales/fi.yml index 558d1afc2..fd6c160e6 100644 --- a/locales/fi.yml +++ b/locales/fi.yml @@ -3,7 +3,7 @@ fetchingAsApObject: Hae Fedeversestä gotIt: Selvä! cancel: Peruuta enterUsername: Anna käyttäjänimi -renotedBy: Buustannut {käyttäjä} +renotedBy: Buustannut {user} noNotes: Ei lähetyksiä noNotifications: Ei ilmoituksia instance: Instanssi @@ -220,3 +220,4 @@ clearQueueConfirmText: Mitkään välittämättömät lähetykset, jotka ovat jo federoidu. Yleensä tätä toimintoa ei tarvita. blockedInstancesDescription: Lista instanssien isäntänimistä, jotka haluat estää. Listatut instanssit eivät kykene kommunikoimaan enää tämän instanssin kanssa. +_lang_: Suomi