diff options
| author | Hazelnoot <acomputerdog@gmail.com> | 2025-03-31 14:53:02 -0400 |
|---|---|---|
| committer | Hazelnoot <acomputerdog@gmail.com> | 2025-03-31 14:53:02 -0400 |
| commit | c371af34e877845e01cd92683aca0ff3334e314a (patch) | |
| tree | e34c80fc5c134a87f6ce01cbd93d6163c08c0a7d /packages | |
| parent | regenerate misskey-js (diff) | |
| download | sharkey-c371af34e877845e01cd92683aca0ff3334e314a.tar.gz sharkey-c371af34e877845e01cd92683aca0ff3334e314a.tar.bz2 sharkey-c371af34e877845e01cd92683aca0ff3334e314a.zip | |
copy sharkey settings into new frontend preferences model
Diffstat (limited to 'packages')
50 files changed, 467 insertions, 424 deletions
diff --git a/packages/frontend-embed/src/pages/note.vue b/packages/frontend-embed/src/pages/note.vue index e879430286..4e0ea9c145 100644 --- a/packages/frontend-embed/src/pages/note.vue +++ b/packages/frontend-embed/src/pages/note.vue @@ -17,7 +17,7 @@ import EmNoteDetailed from '@/components/EmNoteDetailed.vue'; import XNotFound from '@/pages/not-found.vue'; import { DI } from '@/di.js'; import { misskeyApi } from '@/misskey-api.js'; -import { assertServerContext } from '@/server-context'; +import { assertServerContext } from '@/server-context.js'; const props = defineProps<{ noteId: string; diff --git a/packages/frontend/src/boot/main-boot.ts b/packages/frontend/src/boot/main-boot.ts index 17ccb979d5..c5bd393fa5 100644 --- a/packages/frontend/src/boot/main-boot.ts +++ b/packages/frontend/src/boot/main-boot.ts @@ -25,7 +25,7 @@ import { deckStore } from '@/ui/deck/deck-store.js'; import { emojiPicker } from '@/utility/emoji-picker.js'; import { mainRouter } from '@/router.js'; import { setFavIconDot } from '@/utility/favicon-dot.js'; -import { type Keymap, makeHotkey } from '@/utility/hotkey.js'; +import { makeHotkey } from '@/utility/hotkey.js'; import { addCustomEmoji, removeCustomEmojis, updateCustomEmojis } from '@/custom-emojis.js'; import { prefer } from '@/preferences.js'; import { launchPlugins } from '@/plugin.js'; @@ -85,9 +85,7 @@ export async function mainBoot() { let reloadDialogShowing = false; stream.on('_disconnected_', async () => { - if (prefer.s.serverDisconnectedBehavior === 'reload') { - window.location.reload(); - } else if (prefer.s.serverDisconnectedBehavior === 'dialog') { + if (prefer.s.serverDisconnectedBehavior === 'dialog') { if (reloadDialogShowing) return; reloadDialogShowing = true; const { canceled } = await confirm({ @@ -351,7 +349,7 @@ export async function mainBoot() { } function attemptShowNotificationDot() { - if (defaultStore.state.enableFaviconNotificationDot) { + if (store.s.enableFaviconNotificationDot) { setFavIconDot(true); } } diff --git a/packages/frontend/src/components/DynamicNote.vue b/packages/frontend/src/components/DynamicNote.vue index 6703099591..b2133f8836 100644 --- a/packages/frontend/src/components/DynamicNote.vue +++ b/packages/frontend/src/components/DynamicNote.vue @@ -21,7 +21,7 @@ import { computed, defineAsyncComponent, shallowRef } from 'vue'; import type { ComponentExposed } from 'vue-component-type-helpers'; import type MkNote from '@/components/MkNote.vue'; import type SkNote from '@/components/SkNote.vue'; -import { defaultStore } from '@/store'; +import { defaultStore } from '@/store.js'; const XNote = computed(() => defineAsyncComponent(() => diff --git a/packages/frontend/src/components/MkDateSeparatedList.vue b/packages/frontend/src/components/MkDateSeparatedList.vue index f94f28de41..857b7e3f92 100644 --- a/packages/frontend/src/components/MkDateSeparatedList.vue +++ b/packages/frontend/src/components/MkDateSeparatedList.vue @@ -13,7 +13,7 @@ import { i18n } from '@/i18n.js'; import * as os from '@/os.js'; import { instance } from '@/instance.js'; import { prefer } from '@/preferences.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; export default defineComponent({ props: { @@ -116,7 +116,7 @@ export default defineComponent({ }); const renderChildren = () => { - const shouldHideAds = !defaultStore.state.forceShowAds && $i && $i.policies.canHideAds; + const shouldHideAds = (!prefer.s.forceShowAds && $i && $i.policies.canHideAds) ?? false; const children = renderChildrenImpl(shouldHideAds); if (isDebuggerEnabled(6864)) { diff --git a/packages/frontend/src/components/MkGoogle.vue b/packages/frontend/src/components/MkGoogle.vue index 6cdab2479e..e7ee7ffab5 100644 --- a/packages/frontend/src/components/MkGoogle.vue +++ b/packages/frontend/src/components/MkGoogle.vue @@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { ref } from 'vue'; import { i18n } from '@/i18n.js'; -import { defaultStore } from '@/store'; +import { prefer } from '@/preferences.js'; const props = defineProps<{ q: string; @@ -23,7 +23,7 @@ const query = ref(props.q); const search = () => { const searchQuery = encodeURIComponent(query.value); - const searchUrl = defaultStore.state.searchEngine.replace(/{query}|%s\b/g, searchQuery); + const searchUrl = prefer.s.searchEngine.replace(/{query}|%s\b/g, searchQuery); window.open(searchUrl, '_blank', 'noopener'); }; diff --git a/packages/frontend/src/components/MkMediaAudio.vue b/packages/frontend/src/components/MkMediaAudio.vue index 8ede22db0d..4c78854fad 100644 --- a/packages/frontend/src/components/MkMediaAudio.vue +++ b/packages/frontend/src/components/MkMediaAudio.vue @@ -95,7 +95,7 @@ import { useTemplateRef, watch, computed, ref, onDeactivated, onActivated, onMou import * as Misskey from 'misskey-js'; import type { MenuItem } from '@/types/menu.js'; import type { Keymap } from '@/utility/hotkey.js'; -import { copyToClipboard } from '@/utility/copy-to-clipboard'; +import { copyToClipboard } from '@/utility/copy-to-clipboard.js'; import { i18n } from '@/i18n.js'; import * as os from '@/os.js'; import bytes from '@/filters/bytes.js'; diff --git a/packages/frontend/src/components/MkMediaImage.vue b/packages/frontend/src/components/MkMediaImage.vue index 0f72da9ce9..2b59ed9f85 100644 --- a/packages/frontend/src/components/MkMediaImage.vue +++ b/packages/frontend/src/components/MkMediaImage.vue @@ -55,7 +55,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { watch, ref, computed } from 'vue'; import * as Misskey from 'misskey-js'; import type { MenuItem } from '@/types/menu.js'; -import { copyToClipboard } from '@/utility/copy-to-clipboard'; +import { copyToClipboard } from '@/utility/copy-to-clipboard.js'; import { getStaticImageUrl } from '@/utility/media-proxy.js'; import bytes from '@/filters/bytes.js'; import ImgWithBlurhash from '@/components/MkImgWithBlurhash.vue'; diff --git a/packages/frontend/src/components/MkMediaVideo.vue b/packages/frontend/src/components/MkMediaVideo.vue index 8db3924fbd..041257b06f 100644 --- a/packages/frontend/src/components/MkMediaVideo.vue +++ b/packages/frontend/src/components/MkMediaVideo.vue @@ -116,7 +116,7 @@ import { ref, useTemplateRef, computed, watch, onDeactivated, onActivated, onMou import * as Misskey from 'misskey-js'; import type { MenuItem } from '@/types/menu.js'; import type { Keymap } from '@/utility/hotkey.js'; -import { copyToClipboard } from '@/utility/copy-to-clipboard'; +import { copyToClipboard } from '@/utility/copy-to-clipboard.js'; import bytes from '@/filters/bytes.js'; import { hms } from '@/filters/hms.js'; import { i18n } from '@/i18n.js'; diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue index 5fcb91b338..86792931a9 100644 --- a/packages/frontend/src/components/MkNote.vue +++ b/packages/frontend/src/components/MkNote.vue @@ -52,7 +52,7 @@ SPDX-License-Identifier: AGPL-3.0-only <article v-else :class="$style.article" @contextmenu.stop="onContextmenu"> <div v-if="appearNote.channel" :class="$style.colorBar" :style="{ background: appearNote.channel.color }"></div> <MkAvatar :class="[$style.avatar, prefer.s.useStickyIcons ? $style.useSticky : null]" :user="appearNote.user" :link="!mock" :preview="!mock"/> - <div :class="[$style.main, { [$style.clickToOpen]: defaultStore.state.clickToOpen }]" @click.stop="defaultStore.state.clickToOpen ? noteclick(appearNote.id) : undefined"> + <div :class="[$style.main, { [$style.clickToOpen]: prefer.s.clickToOpen }]" @click.stop="prefer.s.clickToOpen ? noteclick(appearNote.id) : undefined"> <MkNoteHeader :note="appearNote" :mini="true" @click.stop/> <MkInstanceTicker v-if="showTicker" :host="appearNote.user.host" :instance="appearNote.user.instance"/> <div style="container-type: inline-size;"> @@ -94,7 +94,7 @@ SPDX-License-Identifier: AGPL-3.0-only </div> </div> <MkButton v-if="!allowAnim && animated" :class="$style.playMFMButton" :small="true" @click="animatedMFM()" @click.stop><i class="ph-play ph-bold ph-lg "></i> {{ i18n.ts._animatedMFM.play }}</MkButton> - <MkButton v-else-if="!defaultStore.state.animatedMfm && allowAnim && animated" :class="$style.playMFMButton" :small="true" @click="animatedMFM()" @click.stop><i class="ph-stop ph-bold ph-lg "></i> {{ i18n.ts._animatedMFM.stop }}</MkButton> + <MkButton v-else-if="!prefer.s.animatedMfm && allowAnim && animated" :class="$style.playMFMButton" :small="true" @click="animatedMFM()" @click.stop><i class="ph-stop ph-bold ph-lg "></i> {{ i18n.ts._animatedMFM.stop }}</MkButton> </div> <div v-if="appearNote.files && appearNote.files.length > 0"> <MkMediaList ref="galleryEl" :mediaList="appearNote.files" @click.stop/> @@ -211,11 +211,12 @@ import * as Misskey from 'misskey-js'; import { isLink } from '@@/js/is-link.js'; import { shouldCollapsed } from '@@/js/collapsed.js'; import { host } from '@@/js/config.js'; -import type { Ref } from 'vue'; import { computeMergedCw } from '@@/js/compute-merged-cw.js'; +import type { Ref } from 'vue'; import type { MenuItem } from '@/types/menu.js'; import type { OpenOnRemoteOptions } from '@/utility/please-login.js'; import type { Keymap } from '@/utility/hotkey.js'; +import type { Visibility } from '@/utility/boost-quote.js'; import MkNoteSub from '@/components/MkNoteSub.vue'; import MkNoteHeader from '@/components/MkNoteHeader.vue'; import MkNoteSimple from '@/components/MkNoteSimple.vue'; @@ -250,14 +251,14 @@ import { claimAchievement } from '@/utility/achievements.js'; import { getNoteSummary } from '@/utility/get-note-summary.js'; import MkRippleEffect from '@/components/MkRippleEffect.vue'; import { showMovedDialog } from '@/utility/show-moved-dialog.js'; -import { useRouter } from '@/router/supplier.js'; -import { boostMenuItems, type Visibility, computeRenoteTooltip } from '@/utility/boost-quote.js'; +import { boostMenuItems, computeRenoteTooltip } from '@/utility/boost-quote.js'; import { isEnabledUrlPreview } from '@/instance.js'; import { focusPrev, focusNext } from '@/utility/focus.js'; import { getAppearNote } from '@/utility/get-appear-note.js'; import { prefer } from '@/preferences.js'; import { getPluginHandlers } from '@/plugin.js'; import { DI } from '@/di.js'; +import { useRouter } from '@/router.js'; const props = withDefaults(defineProps<{ note: Misskey.entities.Note; @@ -285,7 +286,7 @@ const currentClip = inject<Ref<Misskey.entities.Clip> | null>('currentClip', nul const note = ref(deepClone(props.note)); function noteclick(id: string) { - const selection = document.getSelection(); + const selection = window.document.getSelection(); if (selection?.toString().length === 0) { router.push(`/notes/${id}`); } @@ -325,11 +326,11 @@ const likeButton = useTemplateRef('likeButton'); const appearNote = computed(() => getAppearNote(note.value)); const galleryEl = useTemplateRef('galleryEl'); const isMyRenote = $i && ($i.id === note.value.userId); -const showContent = ref(defaultStore.state.uncollapseCW); +const showContent = ref(prefer.s.uncollapseCW); const parsed = computed(() => appearNote.value.text ? mfm.parse(appearNote.value.text) : null); const urls = computed(() => parsed.value ? extractUrlFromMfm(parsed.value).filter((url) => appearNote.value.renote?.url !== url && appearNote.value.renote?.uri !== url) : null); const isLong = shouldCollapsed(appearNote.value, urls.value ?? []); -const collapsed = ref(defaultStore.state.expandLongNote && appearNote.value.cw == null && isLong ? false : appearNote.value.cw == null && isLong); +const collapsed = ref(prefer.s.expandLongNote && appearNote.value.cw == null && isLong ? false : appearNote.value.cw == null && isLong); const isDeleted = ref(false); const renoted = ref(false); const muted = ref(checkMute(appearNote.value, $i?.mutedWords)); @@ -345,10 +346,10 @@ const renoteCollapsed = ref( (appearNote.value.myReaction != null) ), ); -const inReplyToCollapsed = ref(defaultStore.state.collapseNotesRepliedTo); -const defaultLike = computed(() => defaultStore.state.like ? defaultStore.state.like : null); +const inReplyToCollapsed = ref(prefer.s.collapseNotesRepliedTo); +const defaultLike = computed(() => prefer.s.like ? prefer.s.like : null); const animated = computed(() => parsed.value ? checkAnimationFromMfm(parsed.value) : null); -const allowAnim = ref(defaultStore.state.advancedMfm && defaultStore.state.animatedMfm ? true : false); +const allowAnim = ref(prefer.s.advancedMfm && prefer.s.animatedMfm); const pleaseLoginContext = computed<OpenOnRemoteOptions>(() => ({ type: 'lookup', @@ -397,7 +398,7 @@ const keymap = { }, 'q': () => { if (renoteCollapsed.value) return; - if (canRenote.value && !renoted.value && !renoting) renote(defaultStore.state.visibilityOnBoost); + if (canRenote.value && !renoted.value && !renoting) renote(prefer.s.visibilityOnBoost); }, 'm': () => { if (renoteCollapsed.value) return; @@ -458,6 +459,8 @@ if (props.mock) { if (!props.mock) { useTooltip(renoteButton, async (showing) => { + if (!renoteButton.value) return; + const renotes = await misskeyApi('notes/renotes', { noteId: appearNote.value.id, limit: 11, @@ -478,6 +481,8 @@ if (!props.mock) { }); useTooltip(quoteButton, async (showing) => { + if (!quoteButton.value) return; + const renotes = await misskeyApi('notes/renotes', { noteId: appearNote.value.id, limit: 11, @@ -536,8 +541,8 @@ if (!props.mock) { function boostVisibility(forceMenu: boolean = false) { if (renoting) return; - if (!defaultStore.state.showVisibilitySelectorOnBoost && !forceMenu) { - renote(defaultStore.state.visibilityOnBoost); + if (!prefer.s.showVisibilitySelectorOnBoost && !forceMenu) { + renote(prefer.s.visibilityOnBoost); } else { os.popupMenu(boostMenuItems(appearNote, renote), renoteButton.value); } @@ -799,8 +804,8 @@ function onContextmenu(ev: MouseEvent): void { ev.preventDefault(); react(); } else { - const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted, currentClip: currentClip?.value }); - os.contextMenu(menu, ev).then(focus).finally(cleanup); + const { popupMenu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted, currentClip: currentClip?.value }); + os.contextMenu(popupMenu, ev).then(focus).finally(cleanup); } } @@ -809,15 +814,13 @@ function showMenu(): void { return; } - const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted, currentClip: currentClip?.value }); - os.popupMenu(menu, menuButton.value).then(focus).finally(cleanup); + const { popupMenu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted, currentClip: currentClip?.value }); + os.popupMenu(popupMenu, menuButton.value).then(focus).finally(cleanup); } -async function menuVersions(viaKeyboard = false): Promise<void> { - const { menu, cleanup } = await getNoteVersionsMenu({ note: note.value, menuVersionsButton }); - os.popupMenu(menu, menuVersionsButton.value, { - viaKeyboard, - }).then(focus).finally(cleanup); +async function menuVersions(): Promise<void> { + const { menu, cleanup } = await getNoteVersionsMenu({ note: note.value, menuButton: menuVersionsButton }); + os.popupMenu(menu, menuVersionsButton.value).then(focus).finally(cleanup); } async function clip(): Promise<void> { diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue index fe911a1293..42defb69a8 100644 --- a/packages/frontend/src/components/MkNoteDetailed.vue +++ b/packages/frontend/src/components/MkNoteDetailed.vue @@ -112,7 +112,7 @@ SPDX-License-Identifier: AGPL-3.0-only </div> </div> <MkButton v-if="!allowAnim && animated" :class="$style.playMFMButton" :small="true" @click="animatedMFM()" @click.stop><i class="ph-play ph-bold ph-lg "></i> {{ i18n.ts._animatedMFM.play }}</MkButton> - <MkButton v-else-if="!defaultStore.state.animatedMfm && allowAnim && animated" :class="$style.playMFMButton" :small="true" @click="animatedMFM()" @click.stop><i class="ph-stop ph-bold ph-lg "></i> {{ i18n.ts._animatedMFM.stop }}</MkButton> + <MkButton v-else-if="!prefer.s.animatedMfm && allowAnim && animated" :class="$style.playMFMButton" :small="true" @click="animatedMFM()" @click.stop><i class="ph-stop ph-bold ph-lg "></i> {{ i18n.ts._animatedMFM.stop }}</MkButton> <div v-if="appearNote.files && appearNote.files.length > 0"> <MkMediaList ref="galleryEl" :mediaList="appearNote.files"/> </div> @@ -241,7 +241,7 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <script lang="ts" setup> -import { computed, inject, onMounted, provide, ref, useTemplateRef } from 'vue'; +import { computed, inject, onMounted, provide, ref, useTemplateRef, watch } from 'vue'; import * as mfm from '@transfem-org/sfm-js'; import * as Misskey from 'misskey-js'; import { isLink } from '@@/js/is-link.js'; @@ -285,7 +285,8 @@ import MkUserCardMini from '@/components/MkUserCardMini.vue'; import MkPagination from '@/components/MkPagination.vue'; import MkReactionIcon from '@/components/MkReactionIcon.vue'; import MkButton from '@/components/MkButton.vue'; -import { boostMenuItems, type Visibility, computeRenoteTooltip } from '@/utility/boost-quote.js'; +import { boostMenuItems, computeRenoteTooltip } from '@/utility/boost-quote.js'; +import type { Visibility } from '@/utility/boost-quote.js'; import { isEnabledUrlPreview } from '@/instance.js'; import { getAppearNote } from '@/utility/get-appear-note.js'; import { prefer } from '@/preferences.js'; @@ -338,7 +339,7 @@ const likeButton = useTemplateRef('likeButton'); const appearNote = computed(() => getAppearNote(note.value)); const galleryEl = useTemplateRef('galleryEl'); const isMyRenote = $i && ($i.id === note.value.userId); -const showContent = ref(defaultStore.state.uncollapseCW); +const showContent = ref(prefer.s.uncollapseCW); const isDeleted = ref(false); const renoted = ref(false); const muted = ref($i ? checkWordMute(appearNote.value, $i, $i.mutedWords) : false); @@ -353,7 +354,7 @@ const conversation = ref<Misskey.entities.Note[]>([]); const replies = ref<Misskey.entities.Note[]>([]); const quotes = ref<Misskey.entities.Note[]>([]); const canRenote = computed(() => ['public', 'home'].includes(appearNote.value.visibility) || (appearNote.value.visibility === 'followers' && appearNote.value.userId === $i?.id)); -const defaultLike = computed(() => defaultStore.state.like ? defaultStore.state.like : null); +const defaultLike = computed(() => prefer.s.like ? prefer.s.like : null); const mergedCW = computed(() => computeMergedCw(appearNote.value)); @@ -383,7 +384,7 @@ const pleaseLoginContext = computed<OpenOnRemoteOptions>(() => ({ const keymap = { 'r': () => reply(), 'e|a|plus': () => react(), - 'q': () => { if (canRenote.value && !renoted.value && !renoting) renote(defaultStore.state.visibilityOnBoost); }, + 'q': () => { if (canRenote.value && !renoted.value && !renoting) renote(prefer.s.visibilityOnBoost); }, 'm': () => showMenu(), 'c': () => { if (!prefer.s.showClipButtonInNoteFooter) return; @@ -494,8 +495,8 @@ useTooltip(quoteButton, async (showing) => { function boostVisibility(forceMenu: boolean = false) { if (renoting) return; - if (!defaultStore.state.showVisibilitySelectorOnBoost && !forceMenu) { - renote(defaultStore.state.visibilityOnBoost); + if (!prefer.s.showVisibilitySelectorOnBoost && !forceMenu) { + renote(prefer.s.visibilityOnBoost); } else { os.popupMenu(boostMenuItems(appearNote, renote), renoteButton.value); } @@ -531,7 +532,7 @@ function renote(visibility: Visibility, localOnly: boolean = false) { renoting = true; - if (appearNote.value.channel) { + if (appearNote.value.channel && !appearNote.value.channel.allowRenoteToExternal) { const el = renoteButton.value as HTMLElement | null | undefined; if (el) { const rect = el.getBoundingClientRect(); @@ -549,7 +550,7 @@ function renote(visibility: Visibility, localOnly: boolean = false) { os.toast(i18n.ts.renoted); renoted.value = true; }).finally(() => { renoting = false; }); - } else if (!appearNote.value.channel || appearNote.value.channel.allowRenoteToExternal) { + } else { const el = renoteButton.value as HTMLElement | null | undefined; if (el) { const rect = el.getBoundingClientRect(); @@ -748,18 +749,18 @@ function onContextmenu(ev: MouseEvent): void { ev.preventDefault(); react(); } else { - const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted }); - os.contextMenu(menu, ev).then(focus).finally(cleanup); + const { popupMenu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted }); + os.contextMenu(popupMenu, ev).then(focus).finally(cleanup); } } function showMenu(): void { - const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted }); - os.popupMenu(menu, menuButton.value).then(focus).finally(cleanup); + const { popupMenu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted }); + os.popupMenu(popupMenu, menuButton.value).then(focus).finally(cleanup); } async function menuVersions(): Promise<void> { - const { menu, cleanup } = await getNoteVersionsMenu({ note: note.value, menuVersionsButton }); + const { menu, cleanup } = await getNoteVersionsMenu({ note: note.value, menuButton: menuVersionsButton }); os.popupMenu(menu, menuVersionsButton.value).then(focus).finally(cleanup); } @@ -833,7 +834,7 @@ function loadConversation() { }); } -if (appearNote.value.reply && appearNote.value.reply.replyId && defaultStore.state.autoloadConversation) loadConversation(); +if (appearNote.value.reply && appearNote.value.reply.replyId && prefer.s.autoloadConversation) loadConversation(); function animatedMFM() { if (allowAnim.value) { diff --git a/packages/frontend/src/components/MkNoteSimple.vue b/packages/frontend/src/components/MkNoteSimple.vue index 3720aa7493..4df6b1d75c 100644 --- a/packages/frontend/src/components/MkNoteSimple.vue +++ b/packages/frontend/src/components/MkNoteSimple.vue @@ -34,8 +34,8 @@ import MkNoteHeader from '@/components/MkNoteHeader.vue'; import MkSubNoteContent from '@/components/MkSubNoteContent.vue'; import MkCwButton from '@/components/MkCwButton.vue'; import MkButton from '@/components/MkButton.vue'; -import { defaultStore } from '@/store.js'; import { i18n } from '@/i18n.js'; +import { prefer } from '@/preferences.js'; const props = defineProps<{ note: Misskey.entities.Note & { @@ -46,7 +46,7 @@ const props = defineProps<{ hideFiles?: boolean; }>(); -let showContent = ref(defaultStore.state.uncollapseCW); +let showContent = ref(prefer.s.uncollapseCW); const isDeleted = ref(false); const mergedCW = computed(() => computeMergedCw(props.note)); diff --git a/packages/frontend/src/components/MkNoteSub.vue b/packages/frontend/src/components/MkNoteSub.vue index 3d1884b6e4..b796494a51 100644 --- a/packages/frontend/src/components/MkNoteSub.vue +++ b/packages/frontend/src/components/MkNoteSub.vue @@ -65,7 +65,7 @@ SPDX-License-Identifier: AGPL-3.0-only </footer> </div> </div> - <template v-if="depth < numberOfReplies"> + <template v-if="depth < store.s.numberOfReplies"> <MkNoteSub v-for="reply in replies" :key="reply.id" :note="reply" :class="$style.reply" :detail="true" :depth="depth + 1" :expandAllCws="props.expandAllCws" :onDeleteCallback="removeReply"/> </template> <div v-else :class="$style.more"> @@ -87,6 +87,9 @@ SPDX-License-Identifier: AGPL-3.0-only import { computed, ref, shallowRef, watch } from 'vue'; import * as Misskey from 'misskey-js'; import { computeMergedCw } from '@@/js/compute-merged-cw.js'; +import { host } from '@@/js/config.js'; +import type { Visibility } from '@/utility/boost-quote.js'; +import type { OpenOnRemoteOptions } from '@/utility/please-login.js'; import MkNoteHeader from '@/components/MkNoteHeader.vue'; import MkReactionsViewer from '@/components/MkReactionsViewer.vue'; import MkSubNoteContent from '@/components/MkSubNoteContent.vue'; @@ -99,16 +102,16 @@ import { i18n } from '@/i18n.js'; import { $i } from '@/i.js'; import { userPage } from '@/filters/user.js'; import { checkWordMute } from '@/utility/check-word-mute.js'; -import { defaultStore } from '@/store.js'; -import { host } from '@@/js/config.js'; -import { pleaseLogin, type OpenOnRemoteOptions } from '@/utility/please-login.js'; +import { pleaseLogin } from '@/utility/please-login.js'; import { showMovedDialog } from '@/utility/show-moved-dialog.js'; import MkRippleEffect from '@/components/MkRippleEffect.vue'; import { reactionPicker } from '@/utility/reaction-picker.js'; import { claimAchievement } from '@/utility/achievements.js'; import { getNoteMenu } from '@/utility/get-note-menu.js'; -import { useNoteCapture } from '@/utility/use-note-capture.js'; -import { boostMenuItems, type Visibility, computeRenoteTooltip } from '@/utility/boost-quote.js'; +import { boostMenuItems, computeRenoteTooltip } from '@/utility/boost-quote.js'; +import { prefer } from '@/preferences.js'; +import { useNoteCapture } from '@/use/use-note-capture.js'; +import { store } from '@/store.js'; const props = withDefaults(defineProps<{ note: Misskey.entities.Note; @@ -120,6 +123,7 @@ const props = withDefaults(defineProps<{ depth?: number; }>(), { depth: 1, + onDeleteCallback: undefined, }); const canRenote = computed(() => ['public', 'home'].includes(props.note.visibility) || props.note.userId === $i.id); @@ -130,17 +134,16 @@ const translation = ref<any>(null); const translating = ref(false); const isDeleted = ref(false); const renoted = ref(false); -const numberOfReplies = ref(defaultStore.state.numberOfReplies); const reactButton = shallowRef<HTMLElement>(); const renoteButton = shallowRef<HTMLElement>(); const quoteButton = shallowRef<HTMLElement>(); const menuButton = shallowRef<HTMLElement>(); const likeButton = shallowRef<HTMLElement>(); -const renoteTooltip = computeRenoteTooltip(computed); +const renoteTooltip = computeRenoteTooltip(renoted); let appearNote = computed(() => isRenote ? props.note.renote as Misskey.entities.Note : props.note); -const defaultLike = computed(() => defaultStore.state.like ? defaultStore.state.like : null); +const defaultLike = computed(() => prefer.s.like ? prefer.s.like : null); const replies = ref<Misskey.entities.Note[]>([]); const mergedCW = computed(() => computeMergedCw(appearNote.value)); @@ -175,8 +178,8 @@ useNoteCapture({ note: appearNote, isDeletedRef: isDeleted, // only update replies if we are, in fact, showing replies - onReplyCallback: props.detail && props.depth < numberOfReplies.value ? addReplyTo : undefined, - onDeleteCallback: props.detail && props.depth < numberOfReplies.value ? props.onDeleteCallback : undefined, + onReplyCallback: props.detail && props.depth < store.s.numberOfReplies ? addReplyTo : undefined, + onDeleteCallback: props.detail && props.depth < store.s.numberOfReplies ? props.onDeleteCallback : undefined, }); if ($i) { @@ -190,22 +193,21 @@ if ($i) { } function focus() { - el.value.focus(); + el.value?.focus(); } -function reply(viaKeyboard = false): void { +async function reply(viaKeyboard = false): Promise<void> { pleaseLogin({ openOnRemote: pleaseLoginContext.value }); showMovedDialog(); - os.post({ + await os.post({ reply: props.note, - channel: props.note.channel, + channel: props.note.channel ?? undefined, animation: !viaKeyboard, - }, () => { - focus(); }); + focus(); } -function react(viaKeyboard = false): void { +function react(): void { pleaseLogin({ openOnRemote: pleaseLoginContext.value }); showMovedDialog(); sound.playMisskeySfx('reaction'); @@ -285,15 +287,15 @@ function undoRenote() : void { } } -let showContent = ref(defaultStore.state.uncollapseCW); +let showContent = ref(prefer.s.uncollapseCW); watch(() => props.expandAllCws, (expandAllCws) => { if (expandAllCws !== showContent.value) showContent.value = expandAllCws; }); function boostVisibility(forceMenu: boolean = false) { - if (!defaultStore.state.showVisibilitySelectorOnBoost && !forceMenu) { - renote(defaultStore.state.visibilityOnBoost); + if (!prefer.s.showVisibilitySelectorOnBoost && !forceMenu) { + renote(prefer.s.visibilityOnBoost); } else { os.popupMenu(boostMenuItems(appearNote, renote), renoteButton.value); } @@ -347,65 +349,36 @@ function quote() { pleaseLogin({ openOnRemote: pleaseLoginContext.value }); showMovedDialog(); - if (appearNote.value.channel) { - os.post({ - renote: appearNote.value, - channel: appearNote.value.channel, - }).then((cancelled) => { - if (cancelled) return; - misskeyApi('notes/renotes', { - noteId: props.note.id, - userId: $i.id, - limit: 1, - quote: true, - }).then((res) => { - if (!(res.length > 0)) return; - const el = quoteButton.value as HTMLElement | null | undefined; - if (el && res.length > 0) { - const rect = el.getBoundingClientRect(); - const x = rect.left + (el.offsetWidth / 2); - const y = rect.top + (el.offsetHeight / 2); - const { dispose } = os.popup(MkRippleEffect, { x, y }, { - end: () => dispose(), - }); - } - - os.toast(i18n.ts.quoted); - }); - }); - } else { - os.post({ - renote: appearNote.value, - }).then((cancelled) => { - if (cancelled) return; - misskeyApi('notes/renotes', { - noteId: props.note.id, - userId: $i.id, - limit: 1, - quote: true, - }).then((res) => { - if (!(res.length > 0)) return; - const el = quoteButton.value as HTMLElement | null | undefined; - if (el && res.length > 0) { - const rect = el.getBoundingClientRect(); - const x = rect.left + (el.offsetWidth / 2); - const y = rect.top + (el.offsetHeight / 2); - const { dispose } = os.popup(MkRippleEffect, { x, y }, { - end: () => dispose(), - }); - } + os.post({ + renote: appearNote.value, + channel: appearNote.value.channel ?? undefined, + }).then((cancelled) => { + if (cancelled) return; + misskeyApi('notes/renotes', { + noteId: props.note.id, + userId: $i?.id, + limit: 1, + quote: true, + }).then((res) => { + if (!(res.length > 0)) return; + const popupEl = quoteButton.value as HTMLElement | null | undefined; + if (popupEl && res.length > 0) { + const rect = popupEl.getBoundingClientRect(); + const x = rect.left + (popupEl.offsetWidth / 2); + const y = rect.top + (popupEl.offsetHeight / 2); + const { dispose } = os.popup(MkRippleEffect, { x, y }, { + end: () => dispose(), + }); + } - os.toast(i18n.ts.quoted); - }); + os.toast(i18n.ts.quoted); }); - } + }); } -function menu(viaKeyboard = false): void { - const { menu, cleanup } = getNoteMenu({ note: props.note, translating, translation, menuButton, isDeleted }); - os.popupMenu(menu, menuButton.value, { - viaKeyboard, - }).then(focus).finally(cleanup); +function menu(): void { + const { popupMenu, cleanup } = getNoteMenu({ note: props.note, translating, translation, isDeleted }); + os.popupMenu(popupMenu, menuButton.value).then(focus).finally(cleanup); } if (props.detail) { diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue index c70d0d5581..8e08f135ae 100644 --- a/packages/frontend/src/components/MkPostForm.vue +++ b/packages/frontend/src/components/MkPostForm.vue @@ -884,7 +884,7 @@ async function post(ev?: MouseEvent) { } } - if (defaultStore.state.warnMissingAltText) { + if (prefer.s.warnMissingAltText) { const filesData = toRaw(files.value); const isMissingAltText = filesData.filter( @@ -952,7 +952,7 @@ async function post(ev?: MouseEvent) { } } - let token: string | undefined = undefined; + let token: string | null | undefined = undefined; if (postAccount.value) { const storedAccounts = await getAccounts(); diff --git a/packages/frontend/src/components/MkPostFormAttaches.vue b/packages/frontend/src/components/MkPostFormAttaches.vue index 43348475e9..d7747413f6 100644 --- a/packages/frontend/src/components/MkPostFormAttaches.vue +++ b/packages/frontend/src/components/MkPostFormAttaches.vue @@ -36,7 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { defineAsyncComponent, inject } from 'vue'; import * as Misskey from 'misskey-js'; import type { MenuItem } from '@/types/menu'; -import { copyToClipboard } from '@/utility/copy-to-clipboard'; +import { copyToClipboard } from '@/utility/copy-to-clipboard.js'; import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue'; import * as os from '@/os.js'; import { misskeyApi } from '@/utility/misskey-api.js'; diff --git a/packages/frontend/src/components/MkSubNoteContent.vue b/packages/frontend/src/components/MkSubNoteContent.vue index 2ac34431c4..256c84f9b6 100644 --- a/packages/frontend/src/components/MkSubNoteContent.vue +++ b/packages/frontend/src/components/MkSubNoteContent.vue @@ -5,13 +5,13 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <div :class="[$style.root, { [$style.collapsed]: collapsed }]"> - <div :class="{ [$style.clickToOpen]: defaultStore.state.clickToOpen }" @click.stop="defaultStore.state.clickToOpen ? noteclick(note.id) : undefined"> + <div :class="{ [$style.clickToOpen]: prefer.s.clickToOpen }" @click.stop="prefer.s.clickToOpen ? noteclick(note.id) : undefined"> <span v-if="note.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span> <span v-if="note.deletedAt" style="opacity: 0.5">({{ i18n.ts.deletedNote }})</span> <MkA v-if="note.replyId" :class="$style.reply" :to="`/notes/${note.replyId}`" @click.stop><i class="ph-arrow-bend-left-up ph-bold ph-lg"></i></MkA> <Mfm v-if="note.text" :text="note.text" :isBlock="true" :author="note.user" :nyaize="'respect'" :isAnim="allowAnim" :emojiUrls="note.emojis"/> <MkButton v-if="!allowAnim && animated && !hideFiles" :class="$style.playMFMButton" :small="true" @click="animatedMFM()" @click.stop><i class="ph-play ph-bold ph-lg "></i> {{ i18n.ts._animatedMFM.play }}</MkButton> - <MkButton v-else-if="!defaultStore.state.animatedMfm && allowAnim && animated && !hideFiles" :class="$style.playMFMButton" :small="true" @click="animatedMFM()" @click.stop><i class="ph-stop ph-bold ph-lg "></i> {{ i18n.ts._animatedMFM.stop }}</MkButton> + <MkButton v-else-if="!prefer.s.animatedMfm && allowAnim && animated && !hideFiles" :class="$style.playMFMButton" :small="true" @click="animatedMFM()" @click.stop><i class="ph-stop ph-bold ph-lg "></i> {{ i18n.ts._animatedMFM.stop }}</MkButton> <div v-if="note.text && translating || note.text && translation" :class="$style.translation"> <MkLoading v-if="translating" mini/> <div v-else> @@ -21,7 +21,7 @@ SPDX-License-Identifier: AGPL-3.0-only </div> <MkA v-if="note.renoteId" :class="$style.rp" :to="`/notes/${note.renoteId}`" @click.stop>RN: ...</MkA> </div> - <details v-if="note.files && note.files.length > 0" :open="!defaultStore.state.collapseFiles && !hideFiles"> + <details v-if="note.files && note.files.length > 0" :open="!prefer.s.collapseFiles && !hideFiles"> <summary>({{ i18n.tsx.withNFiles({ n: note.files.length }) }})</summary> <MkMediaList :mediaList="note.files"/> </details> @@ -47,10 +47,10 @@ import MkMediaList from '@/components/MkMediaList.vue'; import MkPoll from '@/components/MkPoll.vue'; import MkButton from '@/components/MkButton.vue'; import { i18n } from '@/i18n.js'; -import { defaultStore } from '@/store.js'; -import { useRouter } from '@/router/supplier.js'; import * as os from '@/os.js'; import { checkAnimationFromMfm } from '@/utility/check-animated-mfm.js'; +import { useRouter } from '@/router'; +import { prefer } from '@/preferences.js'; const props = defineProps<{ note: Misskey.entities.Note; @@ -63,7 +63,7 @@ const props = defineProps<{ const router = useRouter(); function noteclick(id: string) { - const selection = document.getSelection(); + const selection = window.document.getSelection(); if (selection?.toString().length === 0) { router.push(`/notes/${id}`); } @@ -71,9 +71,9 @@ function noteclick(id: string) { const parsed = computed(() => props.note.text ? mfm.parse(props.note.text) : null); const animated = computed(() => parsed.value ? checkAnimationFromMfm(parsed.value) : null); -let allowAnim = ref(defaultStore.state.advancedMfm && defaultStore.state.animatedMfm ? true : false); +let allowAnim = ref(prefer.s.advancedMfm && prefer.s.animatedMfm); -const isLong = defaultStore.state.expandLongNote && !props.hideFiles ? false : shouldCollapsed(props.note, []); +const isLong = prefer.s.expandLongNote && !props.hideFiles ? false : shouldCollapsed(props.note, []); function animatedMFM() { if (allowAnim.value) { diff --git a/packages/frontend/src/components/MkUrlPreview.vue b/packages/frontend/src/components/MkUrlPreview.vue index 11071f2f60..1e7c5cdf40 100644 --- a/packages/frontend/src/components/MkUrlPreview.vue +++ b/packages/frontend/src/components/MkUrlPreview.vue @@ -106,8 +106,9 @@ import { prefer } from '@/preferences.js'; import { misskeyApi } from '@/utility/misskey-api.js'; import { warningExternalWebsite } from '@/utility/warning-external-website.js'; +// TODO DynamicNoteSimple const XNoteSimple = defineAsyncComponent<typeof MkNoteSimple | typeof SkNoteSimple>(() => - defaultStore.state.noteDesign === 'misskey' + prefer.s.noteDesign === 'misskey' ? import('@/components/MkNoteSimple.vue') : import('@/components/SkNoteSimple.vue'), ); diff --git a/packages/frontend/src/components/MkUrlWarningDialog.vue b/packages/frontend/src/components/MkUrlWarningDialog.vue index 3bec6eecdd..f9a979f33e 100644 --- a/packages/frontend/src/components/MkUrlWarningDialog.vue +++ b/packages/frontend/src/components/MkUrlWarningDialog.vue @@ -34,7 +34,7 @@ import MkModal from '@/components/MkModal.vue'; import MkButton from '@/components/MkButton.vue'; import MkSwitch from '@/components/MkSwitch.vue'; import { i18n } from '@/i18n.js'; -import { defaultStore } from '@/store.js'; +import { prefer } from '@/preferences.js'; type Result = string | number | true | null; @@ -62,8 +62,8 @@ function done(canceled: boolean, result?: Result): void { // eslint-disable-line async function ok() { const result = true; - if (!defaultStore.state.trustedDomains.includes(domain.value) && trustThisDomain.value) { - await defaultStore.set('trustedDomains', defaultStore.state.trustedDomains.concat(domain.value)); + if (!prefer.s.trustedDomains.includes(domain.value) && trustThisDomain.value) { + prefer.r.trustedDomains.value = prefer.s.trustedDomains.concat(domain.value); } done(false, result); } @@ -77,11 +77,11 @@ function onKeydown(evt: KeyboardEvent) { } onMounted(() => { - document.addEventListener('keydown', onKeydown); + window.document.addEventListener('keydown', onKeydown); }); onBeforeUnmount(() => { - document.removeEventListener('keydown', onKeydown); + window.document.removeEventListener('keydown', onKeydown); }); </script> diff --git a/packages/frontend/src/components/MkVisibilityPicker.vue b/packages/frontend/src/components/MkVisibilityPicker.vue index 659a82d8f2..c52bbbd44f 100644 --- a/packages/frontend/src/components/MkVisibilityPicker.vue +++ b/packages/frontend/src/components/MkVisibilityPicker.vue @@ -53,9 +53,10 @@ const props = withDefaults(defineProps<{ currentVisibility: typeof Misskey.noteVisibilities[number]; isSilenced: boolean; localOnly: boolean; - src?: HTMLElement; + src?: HTMLElement | null; isReplyVisibilitySpecified?: boolean; }>(), { + src: null, }); const emit = defineEmits<{ diff --git a/packages/frontend/src/components/SkFetchNote.vue b/packages/frontend/src/components/SkFetchNote.vue index d325ef1687..a40b99ae8d 100644 --- a/packages/frontend/src/components/SkFetchNote.vue +++ b/packages/frontend/src/components/SkFetchNote.vue @@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { ref, watch } from 'vue'; import * as Misskey from 'misskey-js'; import { i18n } from '@/i18n.js'; -import { misskeyApi } from '@/utility/misskey-api'; +import { misskeyApi } from '@/utility/misskey-api.js'; import DynamicNote from '@/components/DynamicNote.vue'; const props = withDefaults(defineProps<{ diff --git a/packages/frontend/src/components/SkFlashPlayer.vue b/packages/frontend/src/components/SkFlashPlayer.vue index 2b61974ef7..0cdc7bf104 100644 --- a/packages/frontend/src/components/SkFlashPlayer.vue +++ b/packages/frontend/src/components/SkFlashPlayer.vue @@ -6,14 +6,14 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <div :class="$style.flash_player_container"> <canvas :class="$style.ratio" height="300" width="300"></canvas> - + <div v-if="hide" :class="$style.flash_player_disabled" @click="toggleVisible()"> <div> <b><i class="ph-eye ph-bold ph-lg"></i> {{ i18n.ts.sensitive }}</b> <span>{{ i18n.ts.clickToShow }}</span> </div> </div> - + <div v-else :class="$style.flash_player_enabled"> <div :class="$style.flash_display"> <div v-if="playerHide" :class="$style.player_hide" @click="dismissWarning()"> @@ -59,12 +59,12 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { ref, onDeactivated } from 'vue'; import * as Misskey from 'misskey-js'; +import type { PublicAPI, PublicAPILike } from '@/types/ruffle/setup'; +import type { PlayerElement } from '@/types/ruffle/player.js'; import MkEllipsis from '@/components/global/MkEllipsis.vue'; import MkLoading from '@/components/global/MkLoading.vue'; import { i18n } from '@/i18n.js'; -import { defaultStore } from '@/store.js'; -import { PublicAPI, PublicAPILike } from '@/types/ruffle/setup'; // This gives us the types for window.RufflePlayer, etc via side effects -import { PlayerElement } from '@/types/ruffle/player'; +import { prefer } from '@/preferences.js'; const props = defineProps<{ flashFile: Misskey.entities.DriveFile @@ -73,7 +73,7 @@ const props = defineProps<{ const isSensitive = props.flashFile.isSensitive; const url = props.flashFile.url; const comment = props.flashFile.comment ?? ''; -let hide = ref((defaultStore.state.nsfw === 'force') || isSensitive && (defaultStore.state.nsfw !== 'ignore')); +let hide = ref((prefer.s.nsfw === 'force') || isSensitive && (prefer.s.nsfw !== 'ignore')); let playerHide = ref(true); let ruffleContainer = ref<HTMLDivElement>(); let playPauseButtonKey = ref<number>(0); @@ -126,7 +126,7 @@ async function loadRuffle() { 'maxExecutionDuration': 15, 'logLevel': 'error', 'base': null, - 'menu': true, + 'popupMenu': true, 'salign': '', 'forceAlign': false, 'scale': 'showAll', diff --git a/packages/frontend/src/components/SkModPlayer.vue b/packages/frontend/src/components/SkModPlayer.vue index 82f5705d00..572e01c748 100644 --- a/packages/frontend/src/components/SkModPlayer.vue +++ b/packages/frontend/src/components/SkModPlayer.vue @@ -44,9 +44,9 @@ SPDX-License-Identifier: AGPL-3.0-only import { ref, nextTick, watch, onDeactivated, onMounted } from 'vue'; import * as Misskey from 'misskey-js'; import { i18n } from '@/i18n.js'; -import { defaultStore } from '@/store.js'; import { ChiptuneJsPlayer, ChiptuneJsConfig } from '@/utility/chiptune2.js'; import { isTouchUsing } from '@/utility/touch.js'; +import { prefer } from '@/preferences.js'; const colours = { background: '#000000', @@ -72,7 +72,7 @@ const props = defineProps<{ const isSensitive = props.module.isSensitive; const url = props.module.url; -let hide = ref((defaultStore.state.nsfw === 'force') ? true : isSensitive && (defaultStore.state.nsfw !== 'ignore')); +let hide = ref((prefer.s.nsfw === 'force') ? true : isSensitive && (prefer.s.nsfw !== 'ignore')); let patternHide = ref(false); let playing = ref(false); let displayCanvas = ref<HTMLCanvasElement>(); diff --git a/packages/frontend/src/components/SkNote.vue b/packages/frontend/src/components/SkNote.vue index 67c82f95aa..10552ea861 100644 --- a/packages/frontend/src/components/SkNote.vue +++ b/packages/frontend/src/components/SkNote.vue @@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only v-show="!isDeleted" ref="rootEl" v-hotkey="keymap" - :class="[$style.root, { [$style.showActionsOnlyHover]: defaultStore.state.showNoteActionsOnlyHover, [$style.skipRender]: defaultStore.state.skipNoteRender }]" + :class="[$style.root, { [$style.showActionsOnlyHover]: prefer.s.showNoteActionsOnlyHover, [$style.skipRender]: prefer.s.skipNoteRender }]" :tabindex="isDeleted ? '-1' : '0'" > <SkNoteSub v-if="appearNote.reply" v-show="!renoteCollapsed && !inReplyToCollapsed" :note="appearNote.reply" :class="$style.replyTo"/> @@ -60,7 +60,7 @@ SPDX-License-Identifier: AGPL-3.0-only <SkNoteHeader :note="appearNote" :mini="true"/> </div> </div> - <div :class="[{ [$style.clickToOpen]: defaultStore.state.clickToOpen }]" @click.stop="defaultStore.state.clickToOpen ? noteclick(appearNote.id) : undefined"> + <div :class="[{ [$style.clickToOpen]: prefer.s.clickToOpen }]" @click.stop="prefer.s.clickToOpen ? noteclick(appearNote.id) : undefined"> <div style="container-type: inline-size;"> <p v-if="mergedCW != null" :class="$style.cw"> <Mfm @@ -97,7 +97,7 @@ SPDX-License-Identifier: AGPL-3.0-only </div> </div> <MkButton v-if="!allowAnim && animated" :class="$style.playMFMButton" :small="true" @click="animatedMFM()" @click.stop><i class="ph-play ph-bold ph-lg "></i> {{ i18n.ts._animatedMFM.play }}</MkButton> - <MkButton v-else-if="!defaultStore.state.animatedMfm && allowAnim && animated" :class="$style.playMFMButton" :small="true" @click="animatedMFM()" @click.stop><i class="ph-stop ph-bold ph-lg "></i> {{ i18n.ts._animatedMFM.stop }}</MkButton> + <MkButton v-else-if="!prefer.s.animatedMfm && allowAnim && animated" :class="$style.playMFMButton" :small="true" @click="animatedMFM()" @click.stop><i class="ph-stop ph-bold ph-lg "></i> {{ i18n.ts._animatedMFM.stop }}</MkButton> </div> <div v-if="appearNote.files && appearNote.files.length > 0"> <MkMediaList ref="galleryEl" :mediaList="appearNote.files" @click.stop/> @@ -160,9 +160,9 @@ SPDX-License-Identifier: AGPL-3.0-only <i v-else-if="appearNote.myReaction != null" class="ti ti-minus" style="color: var(--MI_THEME-accent);"></i> <i v-else-if="appearNote.reactionAcceptance === 'likeOnly'" class="ti ti-heart"></i> <i v-else class="ph-smiley ph-bold ph-lg"></i> - <p v-if="(appearNote.reactionAcceptance === 'likeOnly' || defaultStore.state.showReactionsCount) && appearNote.reactionCount > 0" :class="$style.footerButtonCount">{{ number(appearNote.reactionCount) }}</p> + <p v-if="(appearNote.reactionAcceptance === 'likeOnly' || prefer.s.showReactionsCount) && appearNote.reactionCount > 0" :class="$style.footerButtonCount">{{ number(appearNote.reactionCount) }}</p> </button> - <button v-if="defaultStore.state.showClipButtonInNoteFooter" ref="clipButton" :class="$style.footerButton" class="_button" @mousedown.prevent="clip()"> + <button v-if="prefer.s.showClipButtonInNoteFooter" ref="clipButton" :class="$style.footerButton" class="_button" @mousedown.prevent="clip()"> <i class="ti ti-paperclip"></i> </button> <button ref="menuButton" :class="$style.footerButton" class="_button" @mousedown.prevent="showMenu()"> @@ -215,6 +215,8 @@ import { shouldCollapsed } from '@@/js/collapsed.js'; import { host } from '@@/js/config.js'; import { computeMergedCw } from '@@/js/compute-merged-cw.js'; import type { MenuItem } from '@/types/menu.js'; +import type { Keymap } from '@/utility/hotkey.js'; +import type { Visibility } from '@/utility/boost-quote.js'; import SkNoteSub from '@/components/SkNoteSub.vue'; import SkNoteHeader from '@/components/SkNoteHeader.vue'; import SkNoteSimple from '@/components/SkNoteSimple.vue'; @@ -234,27 +236,27 @@ import number from '@/filters/number.js'; import * as os from '@/os.js'; import { misskeyApi, misskeyApiGet } from '@/utility/misskey-api.js'; import * as sound from '@/utility/sound.js'; -import { defaultStore, noteViewInterruptors } from '@/store.js'; import { reactionPicker } from '@/utility/reaction-picker.js'; import { extractUrlFromMfm } from '@/utility/extract-url-from-mfm.js'; import { checkAnimationFromMfm } from '@/utility/check-animated-mfm.js'; -import { $i } from '@/account.js'; import { i18n } from '@/i18n.js'; import { getAbuseNoteMenu, getCopyNoteLinkMenu, getNoteClipMenu, getNoteMenu } from '@/utility/get-note-menu.js'; import { getNoteVersionsMenu } from '@/utility/get-note-versions-menu.js'; -import { useNoteCapture } from '@/utility/use-note-capture.js'; +import { useNoteCapture } from '@/use/use-note-capture.js'; import { deepClone } from '@/utility/clone.js'; -import { useTooltip } from '@/utility/use-tooltip.js'; +import { useTooltip } from '@/use/use-tooltip.js'; import { claimAchievement } from '@/utility/achievements.js'; import { getNoteSummary } from '@/utility/get-note-summary.js'; import MkRippleEffect from '@/components/MkRippleEffect.vue'; import { showMovedDialog } from '@/utility/show-moved-dialog.js'; -import { useRouter } from '@/router/supplier.js'; -import { boostMenuItems, type Visibility, computeRenoteTooltip } from '@/utility/boost-quote.js'; +import { boostMenuItems, computeRenoteTooltip } from '@/utility/boost-quote.js'; import { isEnabledUrlPreview } from '@/instance.js'; -import { type Keymap } from '@/utility/hotkey.js'; import { focusPrev, focusNext } from '@/utility/focus.js'; import { getAppearNote } from '@/utility/get-appear-note.js'; +import { $i } from '@/i'; +import { prefer } from '@/preferences'; +import { useRouter } from '@/router'; +import { getPluginHandlers } from '@/plugin.js'; const props = withDefaults(defineProps<{ note: Misskey.entities.Note; @@ -282,13 +284,14 @@ const currentClip = inject<Ref<Misskey.entities.Clip> | null>('currentClip', nul const note = ref(deepClone(props.note)); function noteclick(id: string) { - const selection = document.getSelection(); + const selection = window.document.getSelection(); if (selection?.toString().length === 0) { router.push(`/notes/${id}`); } } // plugin +const noteViewInterruptors = getPluginHandlers('note_view_interruptor'); if (noteViewInterruptors.length > 0) { onMounted(async () => { let result: Misskey.entities.Note | null = deepClone(note.value); @@ -321,30 +324,30 @@ const likeButton = shallowRef<HTMLElement>(); const appearNote = computed(() => getAppearNote(note.value)); const galleryEl = shallowRef<InstanceType<typeof MkMediaList>>(); const isMyRenote = $i && ($i.id === note.value.userId); -const showContent = ref(defaultStore.state.uncollapseCW); +const showContent = ref(prefer.s.uncollapseCW); const parsed = computed(() => appearNote.value.text ? mfm.parse(appearNote.value.text) : null); const urls = computed(() => parsed.value ? extractUrlFromMfm(parsed.value).filter((url) => appearNote.value.renote?.url !== url && appearNote.value.renote?.uri !== url) : null); const isLong = shouldCollapsed(appearNote.value, urls.value ?? []); -const collapsed = ref(defaultStore.state.expandLongNote && appearNote.value.cw == null && isLong ? false : appearNote.value.cw == null && isLong); +const collapsed = ref(prefer.s.expandLongNote && appearNote.value.cw == null && isLong ? false : appearNote.value.cw == null && isLong); const isDeleted = ref(false); const renoted = ref(false); const muted = ref(checkMute(appearNote.value, $i?.mutedWords)); const hardMuted = ref(props.withHardMute && checkMute(appearNote.value, $i?.hardMutedWords, true)); -const showSoftWordMutedWord = computed(() => defaultStore.state.showSoftWordMutedWord); +const showSoftWordMutedWord = computed(() => prefer.s.showSoftWordMutedWord); const translation = ref<Misskey.entities.NotesTranslateResponse | null>(null); const translating = ref(false); -const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.value.user.instance); +const showTicker = (prefer.s.instanceTicker === 'always') || (prefer.s.instanceTicker === 'remote' && appearNote.value.user.instance); const canRenote = computed(() => ['public', 'home'].includes(appearNote.value.visibility) || (appearNote.value.visibility === 'followers' && appearNote.value.userId === $i?.id)); const renoteCollapsed = ref( - defaultStore.state.collapseRenotes && isRenote && ( + prefer.s.collapseRenotes && isRenote && ( ($i && ($i.id === note.value.userId || $i.id === appearNote.value.userId)) || // `||` must be `||`! See https://github.com/misskey-dev/misskey/issues/13131 (appearNote.value.myReaction != null) ) ); -const inReplyToCollapsed = ref(defaultStore.state.collapseNotesRepliedTo); -const defaultLike = computed(() => defaultStore.state.like ? defaultStore.state.like : null); +const inReplyToCollapsed = ref(prefer.s.collapseNotesRepliedTo); +const defaultLike = computed(() => prefer.s.like ? prefer.s.like : null); const animated = computed(() => parsed.value ? checkAnimationFromMfm(parsed.value) : null); -const allowAnim = ref(defaultStore.state.advancedMfm && defaultStore.state.animatedMfm ? true : false); +const allowAnim = ref(prefer.s.advancedMfm && prefer.s.animatedMfm ? true : false); const mergedCW = computed(() => computeMergedCw(appearNote.value)); @@ -393,7 +396,7 @@ const keymap = { }, 'q': () => { if (renoteCollapsed.value) return; - if (canRenote.value && !renoted.value && !renoting) renote(defaultStore.state.visibilityOnBoost); + if (canRenote.value && !renoted.value && !renoting) renote(prefer.s.visibilityOnBoost); }, 'm': () => { if (renoteCollapsed.value) return; @@ -401,7 +404,7 @@ const keymap = { }, 'c': () => { if (renoteCollapsed.value) return; - if (!defaultStore.state.showClipButtonInNoteFooter) return; + if (!prefer.s.showClipButtonInNoteFooter) return; clip(); }, 'o': () => { @@ -531,8 +534,8 @@ if (!props.mock) { function boostVisibility(forceMenu: boolean = false) { if (renoting) return; - if (!defaultStore.state.showVisibilitySelectorOnBoost && !forceMenu) { - renote(defaultStore.state.visibilityOnBoost); + if (!prefer.s.showVisibilitySelectorOnBoost && !forceMenu) { + renote(prefer.s.visibilityOnBoost); } else { os.popupMenu(boostMenuItems(appearNote, renote), renoteButton.value); } @@ -781,12 +784,12 @@ function onContextmenu(ev: MouseEvent): void { if (ev.target && isLink(ev.target as HTMLElement)) return; if (window.getSelection()?.toString() !== '') return; - if (defaultStore.state.useReactionPickerForContextMenu) { + if (prefer.s.useReactionPickerForContextMenu) { ev.preventDefault(); react(); } else { - const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted, currentClip: currentClip?.value }); - os.contextMenu(menu, ev).then(focus).finally(cleanup); + const { popupMenu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted, currentClip: currentClip?.value }); + os.contextMenu(popupMenu, ev).then(focus).finally(cleanup); } } @@ -795,8 +798,8 @@ function showMenu(): void { return; } - const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted, currentClip: currentClip?.value }); - os.popupMenu(menu, menuButton.value).then(focus).finally(cleanup); + const { popupMenu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted, currentClip: currentClip?.value }); + os.popupMenu(popupMenu, menuButton.value).then(focus).finally(cleanup); } async function menuVersions(viaKeyboard = false): Promise<void> { diff --git a/packages/frontend/src/components/SkNoteDetailed.vue b/packages/frontend/src/components/SkNoteDetailed.vue index 8870d12164..a088fd5684 100644 --- a/packages/frontend/src/components/SkNoteDetailed.vue +++ b/packages/frontend/src/components/SkNoteDetailed.vue @@ -116,7 +116,7 @@ SPDX-License-Identifier: AGPL-3.0-only </div> </div> <MkButton v-if="!allowAnim && animated" :class="$style.playMFMButton" :small="true" @click="animatedMFM()" @click.stop><i class="ph-play ph-bold ph-lg "></i> {{ i18n.ts._animatedMFM.play }}</MkButton> - <MkButton v-else-if="!defaultStore.state.animatedMfm && allowAnim && animated" :class="$style.playMFMButton" :small="true" @click="animatedMFM()" @click.stop><i class="ph-stop ph-bold ph-lg "></i> {{ i18n.ts._animatedMFM.stop }}</MkButton> + <MkButton v-else-if="!prefer.s.animatedMfm && allowAnim && animated" :class="$style.playMFMButton" :small="true" @click="animatedMFM()" @click.stop><i class="ph-stop ph-bold ph-lg "></i> {{ i18n.ts._animatedMFM.stop }}</MkButton> <div v-if="appearNote.files && appearNote.files.length > 0"> <MkMediaList ref="galleryEl" :mediaList="appearNote.files"/> </div> @@ -174,9 +174,9 @@ SPDX-License-Identifier: AGPL-3.0-only <i v-else-if="appearNote.myReaction != null" class="ti ti-minus" style="color: var(--MI_THEME-accent);"></i> <i v-else-if="appearNote.reactionAcceptance === 'likeOnly'" class="ti ti-heart"></i> <i v-else class="ph-smiley ph-bold ph-lg"></i> - <p v-if="(appearNote.reactionAcceptance === 'likeOnly' || defaultStore.state.showReactionsCount) && appearNote.reactionCount > 0" :class="$style.noteFooterButtonCount">{{ number(appearNote.reactionCount) }}</p> + <p v-if="(appearNote.reactionAcceptance === 'likeOnly' || prefer.s.showReactionsCount) && appearNote.reactionCount > 0" :class="$style.noteFooterButtonCount">{{ number(appearNote.reactionCount) }}</p> </button> - <button v-if="defaultStore.state.showClipButtonInNoteFooter" ref="clipButton" class="_button" :class="$style.noteFooterButton" @mousedown.prevent="clip()"> + <button v-if="prefer.s.showClipButtonInNoteFooter" ref="clipButton" class="_button" :class="$style.noteFooterButton" @mousedown.prevent="clip()"> <i class="ti ti-paperclip"></i> </button> <button ref="menuButton" class="_button" :class="$style.noteFooterButton" @mousedown.prevent="showMenu()"> @@ -338,7 +338,7 @@ const likeButton = shallowRef<HTMLElement>(); const appearNote = computed(() => getAppearNote(note.value)); const galleryEl = shallowRef<InstanceType<typeof MkMediaList>>(); const isMyRenote = $i && ($i.id === note.value.userId); -const showContent = ref(defaultStore.state.uncollapseCW); +const showContent = ref(prefer.s.uncollapseCW); const isDeleted = ref(false); const renoted = ref(false); const muted = ref($i ? checkWordMute(appearNote.value, $i, $i.mutedWords) : false); @@ -347,13 +347,13 @@ const translating = ref(false); const parsed = appearNote.value.text ? mfm.parse(appearNote.value.text) : null; const urls = parsed ? extractUrlFromMfm(parsed).filter((url) => appearNote.value.renote?.url !== url && appearNote.value.renote?.uri !== url) : null; const animated = computed(() => parsed ? checkAnimationFromMfm(parsed) : null); -const allowAnim = ref(defaultStore.state.advancedMfm && defaultStore.state.animatedMfm ? true : false); -const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.value.user.instance); +const allowAnim = ref(prefer.s.advancedMfm && prefer.s.animatedMfm ? true : false); +const showTicker = (prefer.s.instanceTicker === 'always') || (prefer.s.instanceTicker === 'remote' && appearNote.value.user.instance); const conversation = ref<Misskey.entities.Note[]>([]); const replies = ref<Misskey.entities.Note[]>([]); const quotes = ref<Misskey.entities.Note[]>([]); const canRenote = computed(() => ['public', 'home'].includes(appearNote.value.visibility) || (appearNote.value.visibility === 'followers' && appearNote.value.userId === $i?.id)); -const defaultLike = computed(() => defaultStore.state.like ? defaultStore.state.like : null); +const defaultLike = computed(() => prefer.s.like ? prefer.s.like : null); const mergedCW = computed(() => computeMergedCw(appearNote.value)); @@ -383,10 +383,10 @@ const pleaseLoginContext = computed<OpenOnRemoteOptions>(() => ({ const keymap = { 'r': () => reply(), 'e|a|plus': () => react(), - 'q': () => { if (canRenote.value && !renoted.value && !renoting) renote(defaultStore.state.visibilityOnBoost); }, + 'q': () => { if (canRenote.value && !renoted.value && !renoting) renote(prefer.s.visibilityOnBoost); }, 'm': () => showMenu(), 'c': () => { - if (!defaultStore.state.showClipButtonInNoteFooter) return; + if (!prefer.s.showClipButtonInNoteFooter) return; clip(); }, 'o': () => galleryEl.value?.openGallery(), @@ -493,8 +493,8 @@ useTooltip(quoteButton, async (showing) => { function boostVisibility(forceMenu: boolean = false) { if (renoting) return; - if (!defaultStore.state.showVisibilitySelectorOnBoost && !forceMenu) { - renote(defaultStore.state.visibilityOnBoost); + if (!prefer.s.showVisibilitySelectorOnBoost && !forceMenu) { + renote(prefer.s.visibilityOnBoost); } else { os.popupMenu(boostMenuItems(appearNote, renote), renoteButton.value); } @@ -734,18 +734,18 @@ function onContextmenu(ev: MouseEvent): void { if (ev.target && isLink(ev.target as HTMLElement)) return; if (window.getSelection()?.toString() !== '') return; - if (defaultStore.state.useReactionPickerForContextMenu) { + if (prefer.s.useReactionPickerForContextMenu) { ev.preventDefault(); react(); } else { - const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted }); - os.contextMenu(menu, ev).then(focus).finally(cleanup); + const { popupMenu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted }); + os.contextMenu(popupMenu, ev).then(focus).finally(cleanup); } } function showMenu(): void { - const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted }); - os.popupMenu(menu, menuButton.value).then(focus).finally(cleanup); + const { popupMenu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted }); + os.popupMenu(popupMenu, menuButton.value).then(focus).finally(cleanup); } async function menuVersions(): Promise<void> { @@ -824,7 +824,7 @@ function loadConversation() { }); } -if (appearNote.value.reply && appearNote.value.reply.replyId && defaultStore.state.autoloadConversation) loadConversation(); +if (appearNote.value.reply && appearNote.value.reply.replyId && prefer.s.autoloadConversation) loadConversation(); function animatedMFM() { if (allowAnim.value) { diff --git a/packages/frontend/src/components/SkNoteHeader.vue b/packages/frontend/src/components/SkNoteHeader.vue index 12ba9f187a..9c6e6d9b67 100644 --- a/packages/frontend/src/components/SkNoteHeader.vue +++ b/packages/frontend/src/components/SkNoteHeader.vue @@ -54,7 +54,7 @@ SPDX-License-Identifier: AGPL-3.0-only <div v-if="note.user.badgeRoles" :class="$style.badgeRoles"> <img v-for="role in note.user.badgeRoles" :key="role.id" v-tooltip="role.name" :class="$style.badgeRole" :src="role.iconUrl"/> </div> - <SkInstanceTicker v-if="showTicker && !isMobile && defaultStore.state.showTickerOnReplies" style="cursor: pointer; max-height: 5px; top: 3px; position: relative; margin-top: 0px !important;" :instance="note.user.instance" :host="note.user.host" @click.stop="showOnRemote()"/> + <SkInstanceTicker v-if="showTicker && !isMobile && prefer.s.showTickerOnReplies" style="cursor: pointer; max-height: 5px; top: 3px; position: relative; margin-top: 0px !important;" :instance="note.user.instance" :host="note.user.host" @click.stop="showOnRemote()"/> <div :class="$style.classicInfo"> <div v-if="mock"> <MkTime :time="note.createdAt" colored/> @@ -95,7 +95,7 @@ const props = defineProps<{ const menuVersionsButton = shallowRef<HTMLElement>(); const router = useRouter(); -const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && props.note.user.instance); +const showTicker = (prefer.s.instanceTicker === 'always') || (prefer.s.instanceTicker === 'remote' && props.note.user.instance); const MOBILE_THRESHOLD = 500; const isMobile = ref(deviceKind === 'smartphone' || window.innerWidth <= MOBILE_THRESHOLD); diff --git a/packages/frontend/src/components/SkNoteSimple.vue b/packages/frontend/src/components/SkNoteSimple.vue index 71a5bd4df8..d15f52d29b 100644 --- a/packages/frontend/src/components/SkNoteSimple.vue +++ b/packages/frontend/src/components/SkNoteSimple.vue @@ -36,7 +36,7 @@ const props = defineProps<{ hideFiles?: boolean; }>(); -let showContent = ref(defaultStore.state.uncollapseCW); +let showContent = ref(prefer.s.uncollapseCW); const mergedCW = computed(() => computeMergedCw(props.note)); diff --git a/packages/frontend/src/components/SkNoteSub.vue b/packages/frontend/src/components/SkNoteSub.vue index aa05e1b0cf..a890489459 100644 --- a/packages/frontend/src/components/SkNoteSub.vue +++ b/packages/frontend/src/components/SkNoteSub.vue @@ -73,7 +73,7 @@ SPDX-License-Identifier: AGPL-3.0-only </footer> </div> </div> - <template v-if="depth < numberOfReplies"> + <template v-if="depth < store.s.numberOfReplies"> <SkNoteSub v-for="reply in replies" :key="reply.id" :note="reply" :class="[$style.reply, { [$style.single]: replies.length === 1 }]" :detail="true" :depth="depth + 1" :expandAllCws="props.expandAllCws" :onDeleteCallback="removeReply" :isReply="props.isReply"/> </template> <div v-else :class="$style.more"> @@ -95,6 +95,9 @@ SPDX-License-Identifier: AGPL-3.0-only import { computed, ref, shallowRef, watch } from 'vue'; import * as Misskey from 'misskey-js'; import { computeMergedCw } from '@@/js/compute-merged-cw.js'; +import { host } from '@@/js/config.js'; +import type { OpenOnRemoteOptions } from '@/utility/please-login.js'; +import type { Visibility } from '@/utility/boost-quote.js'; import SkNoteHeader from '@/components/SkNoteHeader.vue'; import MkReactionsViewer from '@/components/MkReactionsViewer.vue'; import MkSubNoteContent from '@/components/MkSubNoteContent.vue'; @@ -104,22 +107,22 @@ import * as os from '@/os.js'; import { misskeyApi } from '@/utility/misskey-api.js'; import * as sound from '@/utility/sound.js'; import { i18n } from '@/i18n.js'; -import { $i } from '@/account.js'; import { userPage } from '@/filters/user.js'; import { checkWordMute } from '@/utility/check-word-mute.js'; -import { defaultStore } from '@/store.js'; -import { host } from '@@/js/config.js'; -import { pleaseLogin, type OpenOnRemoteOptions } from '@/utility/please-login.js'; +import { pleaseLogin } from '@/utility/please-login.js'; import { showMovedDialog } from '@/utility/show-moved-dialog.js'; import MkRippleEffect from '@/components/MkRippleEffect.vue'; import { reactionPicker } from '@/utility/reaction-picker.js'; import { claimAchievement } from '@/utility/achievements.js'; import { getNoteMenu } from '@/utility/get-note-menu.js'; -import { useNoteCapture } from '@/utility/use-note-capture.js'; -import { boostMenuItems, type Visibility, computeRenoteTooltip } from '@/utility/boost-quote.js'; +import { useNoteCapture } from '@/use/use-note-capture.js'; +import { boostMenuItems, computeRenoteTooltip } from '@/utility/boost-quote.js'; +import { prefer } from '@/preferences'; +import { store } from '@/store'; +import { $i } from '@/i.js'; const canRenote = computed(() => ['public', 'home'].includes(props.note.visibility) || props.note.userId === $i.id); -const hideLine = computed(() => { return props.detail ? true : false; }); +const hideLine = computed(() => props.detail); const props = withDefaults(defineProps<{ note: Misskey.entities.Note; @@ -144,7 +147,6 @@ const translation = ref<any>(null); const translating = ref(false); const isDeleted = ref(false); const renoted = ref(false); -const numberOfReplies = ref(defaultStore.state.numberOfReplies); const reactButton = shallowRef<HTMLElement>(); const renoteButton = shallowRef<HTMLElement>(); const quoteButton = shallowRef<HTMLElement>(); @@ -154,7 +156,7 @@ const likeButton = shallowRef<HTMLElement>(); const renoteTooltip = computeRenoteTooltip(renoted); let appearNote = computed(() => isRenote ? props.note.renote as Misskey.entities.Note : props.note); -const defaultLike = computed(() => defaultStore.state.like ? defaultStore.state.like : null); +const defaultLike = computed(() => prefer.s.like ? prefer.s.like : null); const replies = ref<Misskey.entities.Note[]>([]); const mergedCW = computed(() => computeMergedCw(appearNote.value)); @@ -189,8 +191,8 @@ useNoteCapture({ note: appearNote, isDeletedRef: isDeleted, // only update replies if we are, in fact, showing replies - onReplyCallback: props.detail && props.depth < numberOfReplies.value ? addReplyTo : undefined, - onDeleteCallback: props.detail && props.depth < numberOfReplies.value ? props.onDeleteCallback : undefined, + onReplyCallback: props.detail && props.depth < store.s.numberOfReplies ? addReplyTo : undefined, + onDeleteCallback: props.detail && props.depth < store.s.numberOfReplies ? props.onDeleteCallback : undefined, }); if ($i) { @@ -299,15 +301,15 @@ function undoRenote() : void { } } -let showContent = ref(defaultStore.state.uncollapseCW); +let showContent = ref(prefer.s.uncollapseCW); watch(() => props.expandAllCws, (expandAllCws) => { if (expandAllCws !== showContent.value) showContent.value = expandAllCws; }); function boostVisibility(forceMenu: boolean = false) { - if (!defaultStore.state.showVisibilitySelectorOnBoost && !forceMenu) { - renote(defaultStore.state.visibilityOnBoost); + if (!prefer.s.showVisibilitySelectorOnBoost && !forceMenu) { + renote(prefer.s.visibilityOnBoost); } else { os.popupMenu(boostMenuItems(appearNote, renote), renoteButton.value); } @@ -415,17 +417,15 @@ function quote() { } } -function menu(viaKeyboard = false): void { - const { menu, cleanup } = getNoteMenu({ note: props.note, translating, translation, menuButton, isDeleted }); - os.popupMenu(menu, menuButton.value, { - viaKeyboard, - }).then(focus).finally(cleanup); +function menu(): void { + const { popupMenu, cleanup } = getNoteMenu({ note: props.note, translating, translation, isDeleted }); + os.popupMenu(popupMenu, menuButton.value).then(focus).finally(cleanup); } if (props.detail) { misskeyApi('notes/children', { noteId: props.note.id, - limit: numberOfReplies.value, + limit: store.s.numberOfReplies, showQuotes: false, }).then(res => { replies.value = res; diff --git a/packages/frontend/src/components/SkOldNoteWindow.vue b/packages/frontend/src/components/SkOldNoteWindow.vue index 52b135649d..463a71ccd0 100644 --- a/packages/frontend/src/components/SkOldNoteWindow.vue +++ b/packages/frontend/src/components/SkOldNoteWindow.vue @@ -92,11 +92,12 @@ import MkPoll from '@/components/MkPoll.vue'; import MkUrlPreview from '@/components/MkUrlPreview.vue'; import MkInstanceTicker from '@/components/MkInstanceTicker.vue'; import { userPage } from '@/filters/user.js'; -import { defaultStore, noteViewInterruptors } from '@/store.js'; import { extractUrlFromMfm } from '@/utility/extract-url-from-mfm.js'; import { i18n } from '@/i18n.js'; import { deepClone } from '@/utility/clone.js'; import { dateTimeFormat } from '@/utility/intl-const.js'; +import { prefer } from '@/preferences'; +import { getPluginHandlers } from '@/plugin.js'; const props = defineProps<{ note: Misskey.entities.Note; @@ -113,6 +114,7 @@ const inChannel = inject('inChannel', null); let note = ref(deepClone(props.note)); // plugin +const noteViewInterruptors = getPluginHandlers('note_view_interruptor'); if (noteViewInterruptors.length > 0) { onMounted(async () => { let result = deepClone(note.value); @@ -132,7 +134,7 @@ replaceContent(); const isRenote = ( note.value.renote != null && note.value.text == null && - note.value.fileIds.length === 0 && + !note.value.fileIds?.length && note.value.poll == null ); @@ -145,7 +147,7 @@ const showContent = ref(false); const translation = ref(null); const translating = ref(false); const urls = appearNote.value.text ? extractUrlFromMfm(mfm.parse(appearNote.value.text)).filter(u => u !== renoteUrl && u !== renoteUri) : null; -const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.value.user.instance); +const showTicker = (prefer.s.instanceTicker === 'always') || (prefer.s.instanceTicker === 'remote' && appearNote.value.user.instance); </script> diff --git a/packages/frontend/src/components/global/MkMfm.ts b/packages/frontend/src/components/global/MkMfm.ts index 424150e9a3..7ec2b34751 100644 --- a/packages/frontend/src/components/global/MkMfm.ts +++ b/packages/frontend/src/components/global/MkMfm.ts @@ -66,7 +66,7 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven //provide('linkNavigationBehavior', props.linkNavigationBehavior); const isNote = props.isNote ?? true; - const shouldNyaize = props.nyaize === 'respect' && props.author?.isCat && props.author?.speakAsCat && !defaultStore.state.disableCatSpeak; + const shouldNyaize = props.nyaize === 'respect' && props.author?.isCat && props.author?.speakAsCat && !prefer.s.disableCatSpeak; // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (props.text == null || props.text === '') return; diff --git a/packages/frontend/src/pages/about-sharkey.vue b/packages/frontend/src/pages/about-sharkey.vue index 23e75ed7f1..833aed2c89 100644 --- a/packages/frontend/src/pages/about-sharkey.vue +++ b/packages/frontend/src/pages/about-sharkey.vue @@ -30,7 +30,7 @@ SPDX-License-Identifier: AGPL-3.0-only <FormSection v-if="instance.repositoryUrl !== 'https://activitypub.software/TransFem-org/Sharkey/'"> <div class="_gaps_s"> <MkInfo> - {{ i18n.tsx._aboutMisskey.thisIsModifiedVersion({ name: instance.name }) }} + {{ i18n.tsx._aboutMisskey.thisIsModifiedVersion({ name: instance.name ?? '' }) }} </MkInfo> <FormLink v-if="instance.repositoryUrl" :to="instance.repositoryUrl" external> <template #icon><i class="ti ti-code"></i></template> @@ -91,7 +91,7 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <script lang="ts" setup> -import { nextTick, onBeforeUnmount, ref, shallowRef, computed } from 'vue'; +import { nextTick, onBeforeUnmount, ref, computed, useTemplateRef } from 'vue'; import { version } from '@@/js/config.js'; import FormLink from '@/components/form/link.vue'; import FormSection from '@/components/form/section.vue'; @@ -101,12 +101,12 @@ import MkInfo from '@/components/MkInfo.vue'; import { physics } from '@/utility/physics.js'; import { i18n } from '@/i18n.js'; import { instance } from '@/instance.js'; -import { defaultStore } from '@/store.js'; import * as os from '@/os.js'; import { misskeyApi } from '@/utility/misskey-api.js'; -import { definePageMetadata } from '@/utility/page-metadata.js'; import { claimAchievement, claimedAchievements } from '@/utility/achievements.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i'; +import { definePage } from '@/page'; +import { store } from '@/store.js'; type Section = { heading: string, @@ -124,8 +124,13 @@ type Section = { const thereIsTreasure = ref($i && !claimedAchievements.includes('foundTreasure')); let easterEggReady = false; -const easterEggEmojis = ref([]); -const easterEggEngine = ref(null); +const easterEggEmojis = ref<{ + id: string, + top: number, + left: number, + emoji: string +}[]>([]); +const easterEggEngine = ref<{ stop: () => void } | null>(null); const everyone = ref<Section[]>([ { heading: i18n.ts._aboutMisskey.projectMembers, @@ -262,7 +267,7 @@ const everyone = ref<Section[]>([ ], }, ]); -const containerEl = shallowRef<HTMLElement>(); +const containerEl = useTemplateRef('containerEl'); await misskeyApi('sponsors', { forceUpdate: false }).then((res) => { const section: Section = { @@ -293,7 +298,7 @@ function fisher_yates<T>(array: T[]): T[] { } function iconLoaded() { - const emojis = defaultStore.state.reactions; + const emojis = store.s.reactions; const containerWidth = containerEl.value.offsetWidth; for (let i = 0; i < 32; i++) { easterEggEmojis.value.push({ @@ -337,7 +342,7 @@ const headerActions = computed(() => []); const headerTabs = computed(() => []); -definePageMetadata(() => ({ +definePage(() => ({ title: i18n.ts.aboutMisskey, icon: null, })); diff --git a/packages/frontend/src/pages/admin/users.vue b/packages/frontend/src/pages/admin/users.vue index e33ec8a1e6..3b95b78c72 100644 --- a/packages/frontend/src/pages/admin/users.vue +++ b/packages/frontend/src/pages/admin/users.vue @@ -63,7 +63,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { computed, useTemplateRef, ref, watchEffect } from 'vue'; import XHeader from './_header_.vue'; -import { defaultMemoryStorage } from '@/memory-storage'; +import { defaultMemoryStorage } from '@/memory-storage.js'; import MkButton from '@/components/MkButton.vue'; import MkInput from '@/components/MkInput.vue'; import MkSelect from '@/components/MkSelect.vue'; diff --git a/packages/frontend/src/pages/channel.vue b/packages/frontend/src/pages/channel.vue index 303558a621..f08c9c8916 100644 --- a/packages/frontend/src/pages/channel.vue +++ b/packages/frontend/src/pages/channel.vue @@ -100,6 +100,7 @@ import { notesSearchAvailable } from '@/utility/check-permissions.js'; import { miLocalStorage } from '@/local-storage.js'; import { useRouter } from '@/router.js'; import { deepMerge } from '@/utility/merge.js'; +import { store } from '@/store.js'; const router = useRouter(); @@ -122,12 +123,12 @@ const featuredPagination = computed(() => ({ }, })); const withRenotes = computed<boolean>({ - get: () => defaultStore.reactiveState.tl.value.filter.withRenotes, + get: () => store.r.tl.value.filter.withRenotes, set: (x) => saveTlFilter('withRenotes', x), }); const onlyFiles = computed<boolean>({ - get: () => defaultStore.reactiveState.tl.value.filter.onlyFiles, + get: () => store.r.tl.value.filter.onlyFiles, set: (x) => saveTlFilter('onlyFiles', x), }); @@ -150,10 +151,9 @@ watch(() => props.channelId, async () => { } }, { immediate: true }); -function saveTlFilter(key: keyof typeof defaultStore.state.tl.filter, newValue: boolean) { +function saveTlFilter(key: keyof typeof store.s.tl.filter, newValue: boolean) { if (key !== 'withReplies' || $i) { - const out = deepMerge({ filter: { [key]: newValue } }, defaultStore.state.tl); - defaultStore.set('tl', out); + store.r.tl.value = deepMerge({ filter: { [key]: newValue } }, store.s.tl); } } diff --git a/packages/frontend/src/pages/favorites.vue b/packages/frontend/src/pages/favorites.vue index 456f7800f7..b9f1cbf38e 100644 --- a/packages/frontend/src/pages/favorites.vue +++ b/packages/frontend/src/pages/favorites.vue @@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template #default="{ items }"> <MkDateSeparatedList v-slot="{ item }" :items="items" :direction="'down'" :noGap="false" :ad="false"> - <MkNote :key="item.id" :note="item.note" :class="$style.note"/> + <DynamicNote :key="item.id" :note="item.note" :class="$style.note"/> </MkDateSeparatedList> </template> </MkPagination> @@ -27,17 +27,10 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import MkPagination from '@/components/MkPagination.vue'; import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue'; -import { defineAsyncComponent } from 'vue'; import { i18n } from '@/i18n.js'; import { definePage } from '@/page.js'; import { infoImageUrl } from '@/instance.js'; -import { defaultStore } from '@/store.js'; - -const MkNote = defineAsyncComponent(() => - (defaultStore.state.noteDesign === 'misskey') ? import('@/components/MkNote.vue') : - (defaultStore.state.noteDesign === 'sharkey') ? import('@/components/SkNote.vue') : - null -); +import DynamicNote from '@/components/DynamicNote.vue'; const pagination = { endpoint: 'i/favorites' as const, diff --git a/packages/frontend/src/pages/note.vue b/packages/frontend/src/pages/note.vue index dece69bfc8..cd3da94375 100644 --- a/packages/frontend/src/pages/note.vue +++ b/packages/frontend/src/pages/note.vue @@ -70,8 +70,9 @@ import { $i } from '@/i.js'; // contextは非ログイン状態の情報しかないためログイン時は利用できない const CTX_NOTE = !$i && assertServerContext(serverContext, 'note') ? serverContext.note : null; +// TODO DynamicNoteDetailed const MkNoteDetailed = defineAsyncComponent(() => - (defaultStore.state.noteDesign === 'misskey') + (prefer.s.noteDesign === 'misskey') ? import('@/components/MkNoteDetailed.vue') : import('@/components/SkNoteDetailed.vue'), ); diff --git a/packages/frontend/src/pages/settings/preferences.vue b/packages/frontend/src/pages/settings/preferences.vue index 9de03ceec5..1b364bdba3 100644 --- a/packages/frontend/src/pages/settings/preferences.vue +++ b/packages/frontend/src/pages/settings/preferences.vue @@ -936,15 +936,15 @@ const noteDesign = prefer.model('noteDesign'); const uncollapseCW = prefer.model('uncollapseCW'); const expandLongNote = prefer.model('expandLongNote'); const disableCatSpeak = prefer.model('disableCatSpeak'); -const enableFaviconNotificationDot = prefer.model('enableFaviconNotificationDot'); +const enableFaviconNotificationDot = computed(store.makeGetterSetter('enableFaviconNotificationDot')); const warnMissingAltText = prefer.model('warnMissingAltText'); -const notificationClickable = prefer.model('notificationClickable'); +const notificationClickable = computed(store.makeGetterSetter('notificationClickable')); const warnExternalUrl = prefer.model('warnExternalUrl'); const showVisibilitySelectorOnBoost = prefer.model('showVisibilitySelectorOnBoost'); const visibilityOnBoost = prefer.model('visibilityOnBoost'); -const cornerRadius = prefer.model('cornerRadius'); -const oneko = prefer.model('oneko'); -const numberOfReplies = prefer.model('numberOfReplies'); +const cornerRadius = computed(store.makeGetterSetter('cornerRadius')); +const oneko = computed(store.makeGetterSetter('oneko')); +const numberOfReplies = computed(store.makeGetterSetter('numberOfReplies')); const useCustomSearchEngine = computed(() => !Object.keys(searchEngineMap).includes(searchEngine.value)); diff --git a/packages/frontend/src/pages/settings/profile.vue b/packages/frontend/src/pages/settings/profile.vue index fc2743df0f..cbb0889acf 100644 --- a/packages/frontend/src/pages/settings/profile.vue +++ b/packages/frontend/src/pages/settings/profile.vue @@ -7,15 +7,15 @@ SPDX-License-Identifier: AGPL-3.0-only <SearchMarker path="/settings/profile" :label="i18n.ts.profile" :keywords="['profile']" icon="ti ti-user"> <div class="_gaps_m"> <div class="_panel"> - <div :class="$style.banner" :style="{ backgroundImage: $i.bannerUrl ? `url(${ $i.bannerUrl })` : null }"> + <div :class="$style.banner" :style="{ backgroundImage: $i.bannerUrl ? `url(${ $i.bannerUrl })` : '' }"> <div :class="$style.bannerEdit"> <SearchMarker :keywords="['banner', 'change', 'remove']"> - <MkButton primary rounded @click="changeOrRemoveBanner">{{ <SearchLabel>{{ i18n.ts._profile.changeBanner }}</SearchLabel> }}</MkButton> + <MkButton primary rounded @click="changeOrRemoveBanner"><SearchLabel>{{ i18n.ts._profile.changeBanner }}</SearchLabel></MkButton> </SearchMarker> </div> <div :class="$style.backgroundEdit"> <SearchMarker :keywords="['background', 'change', 'remove']"> - <MkButton primary rounded @click="changeOrRemoveBackground">{{ <SearchLabel>{{ i18n.ts._profile.changeBackground }}</SearchLabel> }}</MkButton> + <MkButton primary rounded @click="changeOrRemoveBackground"><SearchLabel>{{ i18n.ts._profile.changeBackground }}</SearchLabel></MkButton> </SearchMarker> </div> </div> @@ -187,6 +187,7 @@ import { claimAchievement } from '@/utility/achievements.js'; import { store } from '@/store.js'; import MkInfo from '@/components/MkInfo.vue'; import MkTextarea from '@/components/MkTextarea.vue'; +import { globalEvents } from '@/events.js'; const $i = ensureSignin(); @@ -314,7 +315,6 @@ function changeAvatar(ev) { $i.avatarId = i.avatarId; $i.avatarUrl = i.avatarUrl; claimAchievement('profileFilled'); - globalEvents.emit('requestClearPageCache'); }); } @@ -365,7 +365,6 @@ function changeBackground(ev) { }); $i.backgroundId = i.backgroundId; $i.backgroundUrl = i.backgroundUrl; - globalEvents.emit('requestClearPageCache'); }); } @@ -382,7 +381,6 @@ function changeOrRemoveAvatar(ev) { }); $i.avatarId = i.avatarId; $i.avatarUrl = i.avatarUrl; - globalEvents.emit('requestClearPageCache'); }, }], ev.currentTarget ?? ev.target); } else { @@ -404,7 +402,6 @@ function changeOrRemoveBanner(ev) { }); $i.bannerId = i.bannerId; $i.bannerUrl = i.bannerUrl; - globalEvents.emit('requestClearPageCache'); }, }], ev.currentTarget ?? ev.target); } else { @@ -426,7 +423,6 @@ function changeOrRemoveBackground(ev) { }); $i.backgroundId = i.backgroundId; $i.backgroundUrl = i.backgroundUrl; - globalEvents.emit('requestClearPageCache'); }, }], ev.currentTarget ?? ev.target); } else { diff --git a/packages/frontend/src/pages/user-list-timeline.vue b/packages/frontend/src/pages/user-list-timeline.vue index 45fe53328a..754ae2467e 100644 --- a/packages/frontend/src/pages/user-list-timeline.vue +++ b/packages/frontend/src/pages/user-list-timeline.vue @@ -33,10 +33,10 @@ import { misskeyApi } from '@/utility/misskey-api.js'; import { definePage } from '@/page.js'; import { i18n } from '@/i18n.js'; import { useRouter } from '@/router.js'; -import { defaultStore } from '@/store.js'; +import { store } from '@/store.js'; import { deepMerge } from '@/utility/merge.js'; import * as os from '@/os.js'; -import { $i } from '@/account.js'; +import { $i } from '@/i.js'; const router = useRouter(); @@ -50,19 +50,18 @@ const tlEl = useTemplateRef('tlEl'); const rootEl = useTemplateRef('rootEl'); const withRenotes = computed<boolean>({ - get: () => defaultStore.reactiveState.tl.value.filter.withRenotes, + get: () => store.r.tl.value.filter.withRenotes, set: (x) => saveTlFilter('withRenotes', x), }); const onlyFiles = computed<boolean>({ - get: () => defaultStore.reactiveState.tl.value.filter.onlyFiles, + get: () => store.r.tl.value.filter.onlyFiles, set: (x) => saveTlFilter('onlyFiles', x), }); -function saveTlFilter(key: keyof typeof defaultStore.state.tl.filter, newValue: boolean) { +function saveTlFilter(key: keyof typeof store.s.tl.filter, newValue: boolean) { if (key !== 'withReplies' || $i) { - const out = deepMerge({ filter: { [key]: newValue } }, defaultStore.state.tl); - defaultStore.set('tl', out); + store.r.tl.value = deepMerge({ filter: { [key]: newValue } }, store.s.tl); } } diff --git a/packages/frontend/src/pages/user/home.vue b/packages/frontend/src/pages/user/home.vue index 802de9778c..9f2b3f3585 100644 --- a/packages/frontend/src/pages/user/home.vue +++ b/packages/frontend/src/pages/user/home.vue @@ -175,7 +175,7 @@ SPDX-License-Identifier: AGPL-3.0-only <div>{{ i18n.ts.noNotes }}</div> </div> <div v-else class="_panel"> - <MkNote v-for="note of user.pinnedNotes" :key="note.id" class="note" :class="$style.pinnedNote" :note="note" :pinned="true"/> + <DynamicNote v-for="note of user.pinnedNotes" :key="note.id" class="note" :class="$style.pinnedNote" :note="note" :pinned="true"/> </div> </div> <MkNotes v-else :class="$style.tl" :noGap="true" :pagination="AllPagination"/> @@ -221,12 +221,7 @@ import { getStaticImageUrl } from '@/utility/media-proxy.js'; import { infoImageUrl } from '@/instance.js'; import MkSparkle from '@/components/MkSparkle.vue'; import { prefer } from '@/preferences.js'; - -const MkNote = defineAsyncComponent(() => - defaultStore.state.noteDesign === 'sharkey' - ? import('@/components/SkNote.vue') - : import('@/components/MkNote.vue'), -); +import DynamicNote from '@/components/DynamicNote.vue'; function calcAge(birthdate: string): number { const date = new Date(birthdate); @@ -277,7 +272,7 @@ const listenbrainzdata = ref(false); if (props.user.listenbrainz) { (async function() { try { - const response = await fetch(`https://api.listenbrainz.org/1/user/${props.user.listenbrainz}/playing-now`, { + const response = await window.fetch(`https://api.listenbrainz.org/1/user/${props.user.listenbrainz}/playing-now`, { method: 'GET', headers: { 'Content-Type': 'application/json', @@ -295,7 +290,7 @@ if (props.user.listenbrainz) { const background = computed(() => { if (props.user.backgroundUrl == null) return {}; - if (defaultStore.state.disableShowingAnimatedImages) { + if (prefer.s.disableShowingAnimatedImages) { return { '--backgroundImageStatic': `url('${getStaticImageUrl(props.user.backgroundUrl)}')`, }; diff --git a/packages/frontend/src/pages/user/index.timeline.vue b/packages/frontend/src/pages/user/index.timeline.vue index 3aa9b10214..eebdebc8e4 100644 --- a/packages/frontend/src/pages/user/index.timeline.vue +++ b/packages/frontend/src/pages/user/index.timeline.vue @@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only <div>{{ i18n.ts.noNotes }}</div> </div> <div v-else class="_panel"> - <MkNote v-for="note of user.pinnedNotes" :key="note.id" class="note" :class="$style.pinnedNote" :note="note" :pinned="true"/> + <DynamicNote v-for="note of user.pinnedNotes" :key="note.id" class="note" :class="$style.pinnedNote" :note="note" :pinned="true"/> </div> </div> <MkNotes v-else :noGap="true" :pagination="pagination" :class="$style.tl"/> @@ -28,19 +28,13 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <script lang="ts" setup> -import { ref, computed, defineAsyncComponent } from 'vue'; +import { ref, computed } from 'vue'; import * as Misskey from 'misskey-js'; import MkNotes from '@/components/MkNotes.vue'; import MkTab from '@/components/MkTab.vue'; import { i18n } from '@/i18n.js'; import { infoImageUrl } from '@/instance.js'; -import { defaultStore } from '@/store.js'; - -const MkNote = defineAsyncComponent(() => - defaultStore.state.noteDesign === 'sharkey' - ? import('@/components/SkNote.vue') - : import('@/components/MkNote.vue'), -); +import DynamicNote from '@/components/DynamicNote.vue'; const props = defineProps<{ user: Misskey.entities.UserDetailed; diff --git a/packages/frontend/src/pref-migrate.ts b/packages/frontend/src/pref-migrate.ts index 5d48508def..c216d9bda1 100644 --- a/packages/frontend/src/pref-migrate.ts +++ b/packages/frontend/src/pref-migrate.ts @@ -5,6 +5,7 @@ import { v4 as uuid } from 'uuid'; import type { DeckProfile } from '@/deck.js'; +import type { Theme } from '@/theme.js'; import { ColdDeviceStorage, store } from '@/store.js'; import { prefer } from '@/preferences.js'; import { misskeyApi } from '@/utility/misskey-api.js'; @@ -14,7 +15,7 @@ import { unisonReload } from '@/utility/unison-reload.js'; // TODO: そのうち消す export function migrateOldSettings() { store.loaded.then(async () => { - const themes = await misskeyApi('i/registry/get', { scope: ['client'], key: 'themes' }).catch(() => []); + const themes = await misskeyApi('i/registry/get', { scope: ['client'], key: 'themes' }).catch(() => []) as Theme[]; if (themes.length > 0) { prefer.commit('themes', themes); } @@ -133,6 +134,23 @@ export function migrateOldSettings() { prefer.commit('sound.on.reaction', store.s.sound_reaction as any); prefer.commit('defaultNoteVisibility', store.s.defaultNoteVisibility); prefer.commit('defaultNoteLocalOnly', store.s.defaultNoteLocalOnly); + // Sharkey migrations + prefer.commit('collapseNotesRepliedTo', store.s.collapseNotesRepliedTo); + prefer.commit('collapseFiles', store.s.collapseFiles); + prefer.commit('uncollapseCW', store.s.uncollapseCW); + prefer.commit('expandLongNote', store.s.expandLongNote); + prefer.commit('like', store.s.like); + prefer.commit('autoloadConversation', store.s.autoloadConversation); + prefer.commit('showVisibilitySelectorOnBoost', store.s.showVisibilitySelectorOnBoost); + prefer.commit('visibilityOnBoost', store.s.visibilityOnBoost); + prefer.commit('trustedDomains', store.s.trustedDomains); + prefer.commit('warnExternalUrl', store.s.warnExternalUrl); + prefer.commit('followingFeed', store.s.followingFeed); + prefer.commit('warnMissingAltText', store.s.warnMissingAltText); + prefer.commit('disableCatSpeak', store.s.disableCatSpeak); + prefer.commit('showTickerOnReplies', store.s.showTickerOnReplies); + prefer.commit('searchEngine', store.s.searchEngine); + prefer.commit('noteDesign', store.s.noteDesign); window.setTimeout(() => { unisonReload(); diff --git a/packages/frontend/src/preferences/def.ts b/packages/frontend/src/preferences/def.ts index 37fa9471ee..2ed3b52b2a 100644 --- a/packages/frontend/src/preferences/def.ts +++ b/packages/frontend/src/preferences/def.ts @@ -11,7 +11,10 @@ import type { Plugin } from '@/plugin.js'; import type { DeviceKind } from '@/utility/device-kind.js'; import type { DeckProfile } from '@/deck.js'; import type { PreferencesDefinition } from './manager.js'; +import type { FollowingFeedState } from '@/utility/following-feed-utils.js'; import { DEFAULT_DEVICE_KIND } from '@/utility/device-kind.js'; +import { searchEngineMap } from '@/utility/search-engine-map.js'; +import { defaultFollowingFeedState } from '@/utility/following-feed-utils.js'; /** サウンド設定 */ export type SoundStore = { @@ -125,22 +128,68 @@ export const PREF_DEF = { default: false, }, collapseRenotes: { + default: false, + }, + collapseNotesRepliedTo: { + default: false, + }, + collapseFiles: { + default: false, + }, + uncollapseCW: { + default: false, + }, + expandLongNote: { + default: false, + }, + like: { + default: null as string | null, + }, + autoloadConversation: { + default: true, + }, + showVisibilitySelectorOnBoost: { default: true, }, + visibilityOnBoost: { + default: 'public' as 'public' | 'home' | 'followers', + }, + trustedDomains: { + default: [] as string[], + }, + warnExternalUrl: { + default: true, + }, + followingFeed: { + default: defaultFollowingFeedState as Partial<FollowingFeedState>, + }, + warnMissingAltText: { + default: true, + }, + disableCatSpeak: { + default: false, + }, + showTickerOnReplies: { + default: false, + }, + searchEngine: { + default: Object.keys(searchEngineMap)[0], + }, + noteDesign: { + default: 'sharkey' as 'sharkey' | 'misskey', + }, menu: { default: [ 'notifications', - 'clips', - 'drive', + 'explore', 'followRequests', - 'chat', '-', - 'explore', 'announcements', - 'channels', 'search', '-', - 'ui', + 'favorites', + 'drive', + 'achievements', ], }, statusbars: { @@ -154,7 +203,7 @@ export const PREF_DEF = { }[], }, serverDisconnectedBehavior: { - default: 'quiet' as 'quiet' | 'reload' | 'dialog', + default: 'quiet' as 'quiet' | 'disabled' | 'dialog', }, nsfw: { default: 'respect' as 'respect' | 'force' | 'ignore', @@ -232,7 +281,7 @@ export const PREF_DEF = { default: 'auto' as 'auto' | 'popup' | 'drawer', }, squareAvatars: { - default: false, + default: true, }, showAvatarDecorations: { default: true, diff --git a/packages/frontend/src/signout.ts b/packages/frontend/src/signout.ts index e7d7cdfd22..161125e0ec 100644 --- a/packages/frontend/src/signout.ts +++ b/packages/frontend/src/signout.ts @@ -4,7 +4,7 @@ */ import { apiUrl } from '@@/js/config.js'; -import { defaultMemoryStorage } from '@/memory-storage'; +import { defaultMemoryStorage } from '@/memory-storage.js'; import { waiting } from '@/os.js'; import { unisonReload, reloadChannel } from '@/utility/unison-reload.js'; import { $i } from '@/i.js'; diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts index 0ba1a17969..7628be9334 100644 --- a/packages/frontend/src/store.ts +++ b/packages/frontend/src/store.ts @@ -5,7 +5,6 @@ import { markRaw, ref } from 'vue'; import * as Misskey from 'misskey-js'; -import { searchEngineMap } from '@/utility/search-engine-map.js'; import lightTheme from '@@/themes/l-light.json5'; import darkTheme from '@@/themes/d-green-lime.json5'; import { hemisphere } from '@@/js/intl-const.js'; @@ -14,7 +13,8 @@ import type { Plugin } from '@/plugin.js'; import { miLocalStorage } from '@/local-storage.js'; import { Pizzax } from '@/lib/pizzax.js'; import { DEFAULT_DEVICE_KIND } from '@/utility/device-kind.js'; -import { defaultFollowingFeedState } from '@/utility/following-feed-utils.js'; +import { defaultFollowingFeedState, type FollowingFeedState } from '@/utility/following-feed-utils'; +import { searchEngineMap } from '@/utility/search-engine-map.js'; /** * 「状態」を管理するストア(not「設定」) @@ -134,22 +134,6 @@ export const store = markRaw(new Pizzax('base', { where: 'account', default: [], }, - collapseNotesRepliedTo: { - where: 'account', - default: false, - }, - collapseFiles: { - where: 'account', - default: false, - }, - uncollapseCW: { - where: 'account', - default: false, - }, - expandLongNote: { - where: 'device', - default: false, - }, widgets: { where: 'account', default: [] as { @@ -195,30 +179,6 @@ export const store = markRaw(new Pizzax('base', { where: 'account', default: false, }, - like: { - where: 'account', - default: null as string | null, - }, - autoloadConversation: { - where: 'account', - default: true, - }, - showVisibilitySelectorOnBoost: { - where: 'account', - default: true, - }, - visibilityOnBoost: { - where: 'account', - default: 'public' as 'public' | 'home' | 'followers', - }, - trustedDomains: { - where: 'account', - default: [] as string[], - }, - warnExternalUrl: { - where: 'account', - default: true, - }, menu: { where: 'deviceAccount', default: [ @@ -249,10 +209,6 @@ export const store = markRaw(new Pizzax('base', { where: 'deviceAccount', default: [] as Misskey.entities.UserList[], }, - followingFeed: { - where: 'account', - default: defaultFollowingFeedState, - }, serverDisconnectedBehavior: { where: 'device', default: 'disabled' as 'quiet' | 'dialog' | 'disabled', @@ -289,14 +245,6 @@ export const store = markRaw(new Pizzax('base', { where: 'device', default: false, }, - warnMissingAltText: { - where: 'device', - default: true, - }, - enableFaviconNotificationDot: { - where: 'device', - default: true, - }, imageNewTab: { where: 'device', default: false, @@ -305,10 +253,6 @@ export const store = markRaw(new Pizzax('base', { where: 'device', default: window.matchMedia('(prefers-reduced-motion)').matches, }, - disableCatSpeak: { - where: 'account', - default: false, - }, emojiStyle: { where: 'device', default: 'twemoji', // twemoji / fluentEmoji / native @@ -333,18 +277,6 @@ export const store = markRaw(new Pizzax('base', { where: 'device', default: false, }, - showTickerOnReplies: { - where: 'device', - default: false, - }, - searchEngine: { - where: 'account', - default: Object.keys(searchEngineMap)[0], - }, - noteDesign: { - where: 'device', - default: 'sharkey' as 'sharkey' | 'misskey', - }, enableInfiniteScroll: { where: 'device', default: true, @@ -393,10 +325,6 @@ export const store = markRaw(new Pizzax('base', { where: 'device', default: 3, }, - numberOfReplies: { - where: 'device', - default: 5, - }, showNoteActionsOnlyHover: { where: 'device', default: false, @@ -417,14 +345,6 @@ export const store = markRaw(new Pizzax('base', { where: 'device', default: false, }, - oneko: { - where: 'device', - default: false, - }, - clickToOpen: { - where: 'device', - default: true, - }, aiChanMode: { where: 'device', default: false, @@ -445,10 +365,6 @@ export const store = markRaw(new Pizzax('base', { where: 'device', default: 'horizontal' as 'vertical' | 'horizontal', }, - notificationClickable: { - where: 'device', - default: false, - }, enableCondensedLine: { where: 'device', default: true, @@ -553,7 +469,95 @@ export const store = markRaw(new Pizzax('base', { sfxVolume: 1, }, }, + collapseNotesRepliedTo: { + where: 'device', + default: false, + }, + collapseFiles: { + where: 'device', + default: false, + }, + uncollapseCW: { + where: 'device', + default: false, + }, + expandLongNote: { + where: 'device', + default: false, + }, + like: { + where: 'device', + default: null as string | null, + }, + autoloadConversation: { + where: 'device', + default: true, + }, + showVisibilitySelectorOnBoost: { + where: 'device', + default: true, + }, + visibilityOnBoost: { + where: 'device', + default: 'public' as 'public' | 'home' | 'followers', + }, + trustedDomains: { + where: 'device', + default: [] as string[], + }, + warnExternalUrl: { + where: 'device', + default: true, + }, + followingFeed: { + where: 'device', + default: defaultFollowingFeedState as Partial<FollowingFeedState>, + }, + warnMissingAltText: { + where: 'device', + default: true, + }, + disableCatSpeak: { + where: 'device', + default: false, + }, + showTickerOnReplies: { + where: 'device', + default: false, + }, + searchEngine: { + where: 'device', + default: Object.keys(searchEngineMap)[0], + }, + noteDesign: { + where: 'device', + default: 'sharkey' as 'sharkey' | 'misskey', + }, //#endregion + oneko: { + where: 'device', + default: false, + }, + enableFaviconNotificationDot: { + where: 'device', + default: true, + }, + numberOfReplies: { + where: 'device', + default: 5, + }, + notificationClickable: { + where: 'device', + default: false, + }, + cornerRadius: { + where: 'device', + default: null as 'Misskey' | null, + }, + clickToOpen: { + where: 'device', + default: true, + }, })); // TODO: 他のタブと永続化されたstateを同期 diff --git a/packages/frontend/src/ui/_common_/common.vue b/packages/frontend/src/ui/_common_/common.vue index ae22b4a015..4335b00ef0 100644 --- a/packages/frontend/src/ui/_common_/common.vue +++ b/packages/frontend/src/ui/_common_/common.vue @@ -48,7 +48,7 @@ SPDX-License-Identifier: AGPL-3.0-only <div v-if="$i && $i.isBot" id="botWarn"><span>{{ i18n.ts.loggedInAsBot }}</span></div> -<SkOneko v-if="defaultStore.state.oneko"/> +<SkOneko v-if="store.r.oneko"/> </template> <script lang="ts" setup> @@ -65,6 +65,7 @@ import { useStream } from '@/stream.js'; import { i18n } from '@/i18n.js'; import { prefer } from '@/preferences.js'; import { globalEvents } from '@/events.js'; +import { store } from '@/store.js'; const SkOneko = defineAsyncComponent(() => import('@/components/SkOneko.vue')); @@ -108,7 +109,7 @@ if ($i) { } function getPointerEvents() { - return defaultStore.state.notificationClickable ? 'all' : 'none'; + return store.s.notificationClickable ? 'all' : 'none'; } </script> diff --git a/packages/frontend/src/use/use-note-capture.ts b/packages/frontend/src/use/use-note-capture.ts index 9cc4778edf..54d0e128e1 100644 --- a/packages/frontend/src/use/use-note-capture.ts +++ b/packages/frontend/src/use/use-note-capture.ts @@ -4,11 +4,11 @@ */ import { onUnmounted } from 'vue'; -import type { Ref, ShallowRef } from 'vue'; import * as Misskey from 'misskey-js'; +import type { Ref, ShallowRef } from 'vue'; import { useStream } from '@/stream.js'; import { $i } from '@/i.js'; -import { misskeyApi } from './misskey-api.js'; +import { misskeyApi } from '@/utility/misskey-api.js'; export function useNoteCapture(props: { rootEl: ShallowRef<HTMLElement | undefined>; diff --git a/packages/frontend/src/utility/boost-quote.ts b/packages/frontend/src/utility/boost-quote.ts index feb949772b..b5d2c7e6fe 100644 --- a/packages/frontend/src/utility/boost-quote.ts +++ b/packages/frontend/src/utility/boost-quote.ts @@ -3,11 +3,13 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { ref, Ref, computed, ComputedRef } from 'vue'; +import { ref, computed } from 'vue'; import * as Misskey from 'misskey-js'; +import type { Ref, ComputedRef } from 'vue'; +import type { MenuItem } from '@/types/menu.js'; import { i18n } from '@/i18n.js'; -import { defaultStore } from '@/store.js'; -import { MenuItem } from '@/types/menu.js'; +import { prefer } from '@/preferences'; +import { store } from '@/store.js'; /* this script should eventually contain all Sharkey-specific bits of @@ -30,7 +32,7 @@ export function visibilityIsAtLeast(a: Visibility | string, b: Visibility | stri } export function boostMenuItems(appearNote: Ref<Misskey.entities.Note>, renote: (v: Visibility, l: boolean) => void): MenuItem[] { - const localOnly = ref(defaultStore.state.rememberNoteVisibility ? defaultStore.state.localOnly : defaultStore.state.defaultNoteLocalOnly); + const localOnly = ref(prefer.s.rememberNoteVisibility ? store.s.localOnly : prefer.s.defaultNoteLocalOnly); const effectiveVisibility = ( appearNote.value.channel?.isSensitive ? smallerVisibility(appearNote.value.visibility, 'home') @@ -83,7 +85,7 @@ export function boostMenuItems(appearNote: Ref<Misskey.entities.Note>, renote: ( export function computeRenoteTooltip(renoted: Ref<boolean>): ComputedRef<string> { return computed(() => { if (renoted.value) return i18n.ts.unrenote; - if (defaultStore.state.showVisibilitySelectorOnBoost) return i18n.ts.renote; + if (prefer.s.showVisibilitySelectorOnBoost) return i18n.ts.renote; return i18n.ts.renoteShift; }); } diff --git a/packages/frontend/src/utility/deep-equal.ts b/packages/frontend/src/utility/deep-equal.ts index 2859641dc7..09d2ff289e 100644 --- a/packages/frontend/src/utility/deep-equal.ts +++ b/packages/frontend/src/utility/deep-equal.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -type JsonLike = string | number | boolean | null | undefined | JsonLike[] | { [key: string]: JsonLike } | Map<string, JsonLike>; +type JsonLike = string | number | boolean | null | undefined | JsonLike[] | { [key: string]: JsonLike | undefined } | Map<string, JsonLike>; export function deepEqual(a: JsonLike, b: JsonLike): boolean { if (a === b) return true; diff --git a/packages/frontend/src/utility/following-feed-utils.ts b/packages/frontend/src/utility/following-feed-utils.ts index 4d1e81a183..47692aaacf 100644 --- a/packages/frontend/src/utility/following-feed-utils.ts +++ b/packages/frontend/src/utility/following-feed-utils.ts @@ -10,7 +10,7 @@ import type { MenuItem } from '@/types/menu.js'; import { deepMerge } from '@/utility/merge.js'; import { i18n } from '@/i18n.js'; import { popupMenu } from '@/os.js'; -import { prefer } from '@/preferences'; +import { prefer } from '@/preferences.js'; export const followingTab = 'following' as const; export const mutualsTab = 'mutuals' as const; @@ -37,7 +37,7 @@ export type FollowingFeedModel = { [Key in keyof FollowingFeedState]: WritableComputedRef<FollowingFeedState[Key]>; }; -export interface FollowingFeedState { +export type FollowingFeedState = { withNonPublic: boolean, withQuotes: boolean, withBots: boolean, @@ -45,7 +45,7 @@ export interface FollowingFeedState { onlyFiles: boolean, userList: FollowingFeedTab, remoteWarningDismissed: boolean, -} +}; export const defaultFollowingFeedState: FollowingFeedState = { withNonPublic: false, @@ -169,9 +169,9 @@ export function createModel(storage?: Ref<StorageInterface>): FollowingFeedModel }; } -function createDefaultStorage() { +function createDefaultStorage(): Ref<StorageInterface> { return computed(() => ({ - state: prefer.s.followingFeed, + state: prefer.r.followingFeed, save(updated: typeof prefer.s.followingFeed) { prefer.s.followingFeed = updated; }, diff --git a/packages/frontend/src/utility/get-note-menu.ts b/packages/frontend/src/utility/get-note-menu.ts index 6762ce7e43..3e09c74647 100644 --- a/packages/frontend/src/utility/get-note-menu.ts +++ b/packages/frontend/src/utility/get-note-menu.ts @@ -196,7 +196,7 @@ export function getNoteMenu(props: { noteId: appearNote.id, }); - if (Date.now() - new Date(appearNote.createdAt).getTime() < 1000 * 60 && appearNote.userId === $i.id) { + if (Date.now() - new Date(appearNote.createdAt).getTime() < 1000 * 60 && appearNote.userId === $i?.id) { claimAchievement('noteDeletedWithin1min'); } }); @@ -541,7 +541,7 @@ export function getNoteMenu(props: { }; return { - menu: menuItems, + popupMenu: menuItems, cleanup, }; } diff --git a/packages/frontend/src/utility/get-note-versions-menu.ts b/packages/frontend/src/utility/get-note-versions-menu.ts index 345cec9018..f5182b1193 100644 --- a/packages/frontend/src/utility/get-note-versions-menu.ts +++ b/packages/frontend/src/utility/get-note-versions-menu.ts @@ -3,13 +3,19 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { Ref, defineAsyncComponent } from 'vue'; +import { defineAsyncComponent } from 'vue'; import * as Misskey from 'misskey-js'; -import { i18n } from '@/i18n.js'; -import * as os from '@/os.js'; import { misskeyApi } from './misskey-api.js'; -import { MenuItem } from '@/types/menu.js'; import { dateTimeFormat } from './intl-const.js'; +import type { Ref } from 'vue'; +import type { MenuItem } from '@/types/menu.js'; +import * as os from '@/os.js'; + +interface NoteEdit { + oldDate: string; + updatedAt: string; + text: string | null; +} export async function getNoteVersionsMenu(props: { note: Misskey.entities.Note; @@ -18,7 +24,7 @@ export async function getNoteVersionsMenu(props: { const isRenote = ( props.note.renote != null && props.note.text == null && - props.note.fileIds.length === 0 && + !props.note.fileIds?.length && props.note.poll == null ); @@ -26,11 +32,11 @@ export async function getNoteVersionsMenu(props: { const cleanups = [] as (() => void)[]; - function openVersion(info): void { + function openVersion(info: NoteEdit): void { const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/SkOldNoteWindow.vue')), { note: appearNote, - oldText: info.text, - updatedAt: info.oldDate ? info.oldDate : info.updatedAt, + oldText: info.text ?? '', + updatedAt: info.updatedAt, }, { closed: () => dispose(), }); @@ -43,9 +49,7 @@ export async function getNoteVersionsMenu(props: { await statePromise.then((versions) => { for (const edit of versions) { - const _time = edit.oldDate == null ? NaN : - typeof edit.oldDate === 'number' ? edit.oldDate : - (edit.oldDate instanceof Date ? edit.oldDate : new Date(edit.oldDate)).getTime(); + const _time = new Date(edit.oldDate).getTime(); menu.push({ icon: 'ph-pencil-simple ph-bold ph-lg', diff --git a/packages/frontend/src/utility/merge.ts b/packages/frontend/src/utility/merge.ts index 6fd28bb314..6ad1577353 100644 --- a/packages/frontend/src/utility/merge.ts +++ b/packages/frontend/src/utility/merge.ts @@ -6,7 +6,7 @@ import { deepClone } from './clone.js'; import type { Cloneable } from './clone.js'; -export type DeepPartial<T> = { +export type DeepPartial<T> = T | { [P in keyof T]?: T[P] extends Record<PropertyKey, unknown> ? DeepPartial<T[P]> : T[P]; }; @@ -18,7 +18,7 @@ function isPureObject(value: unknown): value is Record<PropertyKey, unknown> { * valueにないキーをdefからもらう(再帰的)\ * nullはそのまま、undefinedはdefの値 **/ -export function deepMerge<X extends object | Record<PropertyKey, unknown>>(value: DeepPartial<X>, def: X): X { +export function deepMerge<X extends Record<PropertyKey, unknown>>(value: DeepPartial<X>, def: X): X { if (isPureObject(value) && isPureObject(def)) { const result = deepClone(value as Cloneable) as X; for (const [k, v] of Object.entries(def) as [keyof X, X[keyof X]][]) { |