From aa72db76c4ce5c90ea502aca0ca0cc5d5b18829b Mon Sep 17 00:00:00 2001 From: dakkar Date: Fri, 16 Feb 2024 12:24:35 +0000 Subject: [PATCH] rework boost visibility #388 * only show allowed visibilities * "local only" is a switch notice that the backend will limit the visibility, too --- packages/frontend/src/components/MkNote.vue | 60 ++------------ .../src/components/MkNoteDetailed.vue | 59 ++------------ .../frontend/src/components/MkNoteSub.vue | 47 ++--------- packages/frontend/src/components/SkNote.vue | 60 ++------------ .../src/components/SkNoteDetailed.vue | 59 ++------------ .../frontend/src/components/SkNoteSub.vue | 47 ++--------- .../frontend/src/pages/settings/general.vue | 1 - packages/frontend/src/scripts/boost-quote.ts | 81 +++++++++++++++++++ packages/frontend/src/store.ts | 2 +- 9 files changed, 118 insertions(+), 298 deletions(-) create mode 100644 packages/frontend/src/scripts/boost-quote.ts diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue index ff9bf3c39..158fdc2c3 100644 --- a/packages/frontend/src/components/MkNote.vue +++ b/packages/frontend/src/components/MkNote.vue @@ -216,6 +216,7 @@ import MkRippleEffect from '@/components/MkRippleEffect.vue'; import { showMovedDialog } from '@/scripts/show-moved-dialog.js'; import { shouldCollapsed } from '@/scripts/collapsed.js'; import { useRouter } from '@/router/supplier.js'; +import { boostMenuItems, type Visibility } from '@/scripts/boost-quote.js'; const props = withDefaults(defineProps<{ note: Misskey.entities.Note; @@ -407,58 +408,15 @@ if (!props.mock) { } } -type Visibility = 'public' | 'home' | 'followers' | 'specified'; - -// defaultStore.state.visibilityがstringなためstringも受け付けている -function smallerVisibility(a: Visibility | string, b: Visibility | string): Visibility { - if (a === 'specified' || b === 'specified') return 'specified'; - if (a === 'followers' || b === 'followers') return 'followers'; - if (a === 'home' || b === 'home') return 'home'; - // if (a === 'public' || b === 'public') - return 'public'; -} - function boostVisibility() { if (!defaultStore.state.showVisibilitySelectorOnBoost) { renote(defaultStore.state.visibilityOnBoost); } else { - os.popupMenu([ - { - type: 'button', - icon: 'ph-globe-hemisphere-west ph-bold ph-lg', - text: i18n.ts._visibility['public'], - action: () => { - renote('public'); - }, - }, - { - type: 'button', - icon: 'ph-house ph-bold ph-lg', - text: i18n.ts._visibility['home'], - action: () => { - renote('home'); - }, - }, - { - type: 'button', - icon: 'ph-lock ph-bold ph-lg', - text: i18n.ts._visibility['followers'], - action: () => { - renote('followers'); - }, - }, - { - type: 'button', - icon: 'ph-planet ph-bold ph-lg', - text: i18n.ts._timelines.local, - action: () => { - renote('local'); - }, - }], renoteButton.value); + os.popupMenu(boostMenuItems(appearNote, renote), renoteButton.value); } } -function renote(visibility: Visibility | 'local') { +function renote(visibility: Visibility, localOnly: boolean = false) { pleaseLogin(); showMovedDialog(); @@ -489,18 +447,10 @@ function renote(visibility: Visibility | 'local') { os.popup(MkRippleEffect, { x, y }, {}, 'end'); } - const configuredVisibility = defaultStore.state.rememberNoteVisibility ? defaultStore.state.visibility : defaultStore.state.defaultNoteVisibility; - const localOnlySetting = defaultStore.state.rememberNoteVisibility ? defaultStore.state.localOnly : defaultStore.state.defaultNoteLocalOnly; - - let noteVisibility = visibility === 'local' || visibility === 'specified' ? smallerVisibility(appearNote.value.visibility, configuredVisibility) : smallerVisibility(visibility, configuredVisibility); - if (appearNote.value.channel?.isSensitive) { - noteVisibility = smallerVisibility(visibility === 'local' || visibility === 'specified' ? appearNote.value.visibility : visibility, 'home'); - } - if (!props.mock) { misskeyApi('notes/create', { - localOnly: visibility === 'local' ? true : localOnlySetting, - visibility: noteVisibility, + localOnly: localOnly, + visibility: visibility, renoteId: appearNote.value.id, }).then(() => { os.toast(i18n.ts.renoted); diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue index a2e3a747d..28c1f5d6f 100644 --- a/packages/frontend/src/components/MkNoteDetailed.vue +++ b/packages/frontend/src/components/MkNoteDetailed.vue @@ -257,6 +257,7 @@ import MkUserCardMini from '@/components/MkUserCardMini.vue'; import MkPagination, { type Paging } from '@/components/MkPagination.vue'; import MkReactionIcon from '@/components/MkReactionIcon.vue'; import MkButton from '@/components/MkButton.vue'; +import { boostMenuItems, type Visibility } from '@/scripts/boost-quote.js'; const props = defineProps<{ note: Misskey.entities.Note; @@ -429,57 +430,15 @@ useTooltip(quoteButton, async (showing) => { }, {}, 'closed'); }); -type Visibility = 'public' | 'home' | 'followers' | 'specified'; - -function smallerVisibility(a: Visibility | string, b: Visibility | string): Visibility { - if (a === 'specified' || b === 'specified') return 'specified'; - if (a === 'followers' || b === 'followers') return 'followers'; - if (a === 'home' || b === 'home') return 'home'; - // if (a === 'public' || b === 'public') - return 'public'; -} - function boostVisibility() { if (!defaultStore.state.showVisibilitySelectorOnBoost) { renote(defaultStore.state.visibilityOnBoost); } else { - os.popupMenu([ - { - type: 'button', - icon: 'ph-globe-hemisphere-west ph-bold ph-lg', - text: i18n.ts._visibility['public'], - action: () => { - renote('public'); - }, - }, - { - type: 'button', - icon: 'ph-house ph-bold ph-lg', - text: i18n.ts._visibility['home'], - action: () => { - renote('home'); - }, - }, - { - type: 'button', - icon: 'ph-lock ph-bold ph-lg', - text: i18n.ts._visibility['followers'], - action: () => { - renote('followers'); - }, - }, - { - type: 'button', - icon: 'ph-planet ph-bold ph-lg', - text: i18n.ts._timelines.local, - action: () => { - renote('local'); - }, - }], renoteButton.value); + os.popupMenu(boostMenuItems(appearNote, renote), renoteButton.value); } } -function renote(visibility: Visibility | 'local') { +function renote(visibility: Visibility, localOnly: boolean = false) { pleaseLogin(); showMovedDialog(); @@ -508,17 +467,9 @@ function renote(visibility: Visibility | 'local') { os.popup(MkRippleEffect, { x, y }, {}, 'end'); } - const configuredVisibility = defaultStore.state.rememberNoteVisibility ? defaultStore.state.visibility : defaultStore.state.defaultNoteVisibility; - const localOnlySetting = defaultStore.state.rememberNoteVisibility ? defaultStore.state.localOnly : defaultStore.state.defaultNoteLocalOnly; - - let noteVisibility = visibility === 'local' || visibility === 'specified' ? smallerVisibility(appearNote.value.visibility, configuredVisibility) : smallerVisibility(visibility, configuredVisibility); - if (appearNote.value.channel?.isSensitive) { - noteVisibility = smallerVisibility(visibility === 'local' || visibility === 'specified' ? appearNote.value.visibility : visibility, 'home'); - } - misskeyApi('notes/create', { - localOnly: visibility === 'local' ? true : localOnlySetting, - visibility: noteVisibility, + localOnly: localOnly, + visibility: visibility, renoteId: appearNote.value.id, }).then(() => { os.toast(i18n.ts.renoted); diff --git a/packages/frontend/src/components/MkNoteSub.vue b/packages/frontend/src/components/MkNoteSub.vue index bb30fe53c..6bd99b845 100644 --- a/packages/frontend/src/components/MkNoteSub.vue +++ b/packages/frontend/src/components/MkNoteSub.vue @@ -105,6 +105,7 @@ import { reactionPicker } from '@/scripts/reaction-picker.js'; import { claimAchievement } from '@/scripts/achievements.js'; import { getNoteMenu } from '@/scripts/get-note-menu.js'; import { useNoteCapture } from '@/scripts/use-note-capture.js'; +import { boostMenuItems, type Visibility } from '@/scripts/boost-quote.js'; const canRenote = computed(() => ['public', 'home'].includes(props.note.visibility) || props.note.userId === $i.id); @@ -276,43 +277,11 @@ function boostVisibility() { if (!defaultStore.state.showVisibilitySelectorOnBoost) { renote(defaultStore.state.visibilityOnBoost); } else { - os.popupMenu([ - { - type: 'button', - icon: 'ph-globe-hemisphere-west ph-bold ph-lg', - text: i18n.ts._visibility['public'], - action: () => { - renote('public'); - }, - }, - { - type: 'button', - icon: 'ph-house ph-bold ph-lg', - text: i18n.ts._visibility['home'], - action: () => { - renote('home'); - }, - }, - { - type: 'button', - icon: 'ph-lock ph-bold ph-lg', - text: i18n.ts._visibility['followers'], - action: () => { - renote('followers'); - }, - }, - { - type: 'button', - icon: 'ph-planet ph-bold ph-lg', - text: i18n.ts._timelines.local, - action: () => { - renote('local'); - }, - }], renoteButton.value); + os.popupMenu(boostMenuItems(appearNote, renote), renoteButton.value); } } -function renote(visibility: 'public' | 'home' | 'followers' | 'specified' | 'local') { +function renote(visibility: Visibility, localOnly: boolean = false) { pleaseLogin(); showMovedDialog(); @@ -326,8 +295,8 @@ function renote(visibility: 'public' | 'home' | 'followers' | 'specified' | 'loc } misskeyApi('notes/create', { - renoteId: props.note.id, - channelId: props.note.channelId, + renoteId: appearNote.value.id, + channelId: appearNote.value.channelId, }).then(() => { os.toast(i18n.ts.renoted); renoted.value = true; @@ -342,9 +311,9 @@ function renote(visibility: 'public' | 'home' | 'followers' | 'specified' | 'loc } misskeyApi('notes/create', { - renoteId: props.note.id, - localOnly: visibility === 'local' ? true : false, - visibility: visibility === 'local' || visibility === 'specified' ? props.note.visibility : visibility, + renoteId: appearNote.value.id, + localOnly: localOnly, + visibility: visibility, }).then(() => { os.toast(i18n.ts.renoted); renoted.value = true; diff --git a/packages/frontend/src/components/SkNote.vue b/packages/frontend/src/components/SkNote.vue index dc74c4928..09decad1a 100644 --- a/packages/frontend/src/components/SkNote.vue +++ b/packages/frontend/src/components/SkNote.vue @@ -217,6 +217,7 @@ import MkRippleEffect from '@/components/MkRippleEffect.vue'; import { showMovedDialog } from '@/scripts/show-moved-dialog.js'; import { shouldCollapsed } from '@/scripts/collapsed.js'; import { useRouter } from '@/router/supplier.js'; +import { boostMenuItems, type Visibility } from '@/scripts/boost-quote.js'; const props = withDefaults(defineProps<{ note: Misskey.entities.Note; @@ -408,58 +409,15 @@ if (!props.mock) { } } -type Visibility = 'public' | 'home' | 'followers' | 'specified'; - -// defaultStore.state.visibilityがstringなためstringも受け付けている -function smallerVisibility(a: Visibility | string, b: Visibility | string): Visibility { - if (a === 'specified' || b === 'specified') return 'specified'; - if (a === 'followers' || b === 'followers') return 'followers'; - if (a === 'home' || b === 'home') return 'home'; - // if (a === 'public' || b === 'public') - return 'public'; -} - function boostVisibility() { if (!defaultStore.state.showVisibilitySelectorOnBoost) { renote(defaultStore.state.visibilityOnBoost); } else { - os.popupMenu([ - { - type: 'button', - icon: 'ph-globe-hemisphere-west ph-bold ph-lg', - text: i18n.ts._visibility['public'], - action: () => { - renote('public'); - }, - }, - { - type: 'button', - icon: 'ph-house ph-bold ph-lg', - text: i18n.ts._visibility['home'], - action: () => { - renote('home'); - }, - }, - { - type: 'button', - icon: 'ph-lock ph-bold ph-lg', - text: i18n.ts._visibility['followers'], - action: () => { - renote('followers'); - }, - }, - { - type: 'button', - icon: 'ph-planet ph-bold ph-lg', - text: i18n.ts._timelines.local, - action: () => { - renote('local'); - }, - }], renoteButton.value); + os.popupMenu(boostMenuItems(appearNote, renote), renoteButton.value); } } -function renote(visibility: Visibility | 'local') { +function renote(visibility: Visibility, localOnly: boolean = false) { pleaseLogin(); showMovedDialog(); @@ -490,18 +448,10 @@ function renote(visibility: Visibility | 'local') { os.popup(MkRippleEffect, { x, y }, {}, 'end'); } - const configuredVisibility = defaultStore.state.rememberNoteVisibility ? defaultStore.state.visibility : defaultStore.state.defaultNoteVisibility; - const localOnlySetting = defaultStore.state.rememberNoteVisibility ? defaultStore.state.localOnly : defaultStore.state.defaultNoteLocalOnly; - - let noteVisibility = visibility === 'local' || visibility === 'specified' ? smallerVisibility(appearNote.value.visibility, configuredVisibility) : smallerVisibility(visibility, configuredVisibility); - if (appearNote.value.channel?.isSensitive) { - noteVisibility = smallerVisibility(visibility === 'local' || visibility === 'specified' ? appearNote.value.visibility : visibility, 'home'); - } - if (!props.mock) { misskeyApi('notes/create', { - localOnly: visibility === 'local' ? true : localOnlySetting, - visibility: noteVisibility, + localOnly: localOnly, + visibility: visibility, renoteId: appearNote.value.id, }).then(() => { os.toast(i18n.ts.renoted); diff --git a/packages/frontend/src/components/SkNoteDetailed.vue b/packages/frontend/src/components/SkNoteDetailed.vue index ef17a2e8a..80d6ac369 100644 --- a/packages/frontend/src/components/SkNoteDetailed.vue +++ b/packages/frontend/src/components/SkNoteDetailed.vue @@ -265,6 +265,7 @@ import MkUserCardMini from '@/components/MkUserCardMini.vue'; import MkPagination, { type Paging } from '@/components/MkPagination.vue'; import MkReactionIcon from '@/components/MkReactionIcon.vue'; import MkButton from '@/components/MkButton.vue'; +import { boostMenuItems, type Visibility } from '@/scripts/boost-quote.js'; const props = defineProps<{ note: Misskey.entities.Note; @@ -438,57 +439,15 @@ useTooltip(quoteButton, async (showing) => { }, {}, 'closed'); }); -type Visibility = 'public' | 'home' | 'followers' | 'specified'; - -function smallerVisibility(a: Visibility | string, b: Visibility | string): Visibility { - if (a === 'specified' || b === 'specified') return 'specified'; - if (a === 'followers' || b === 'followers') return 'followers'; - if (a === 'home' || b === 'home') return 'home'; - // if (a === 'public' || b === 'public') - return 'public'; -} - function boostVisibility() { if (!defaultStore.state.showVisibilitySelectorOnBoost) { renote(defaultStore.state.visibilityOnBoost); } else { - os.popupMenu([ - { - type: 'button', - icon: 'ph-globe-hemisphere-west ph-bold ph-lg', - text: i18n.ts._visibility['public'], - action: () => { - renote('public'); - }, - }, - { - type: 'button', - icon: 'ph-house ph-bold ph-lg', - text: i18n.ts._visibility['home'], - action: () => { - renote('home'); - }, - }, - { - type: 'button', - icon: 'ph-lock ph-bold ph-lg', - text: i18n.ts._visibility['followers'], - action: () => { - renote('followers'); - }, - }, - { - type: 'button', - icon: 'ph-planet ph-bold ph-lg', - text: i18n.ts._timelines.local, - action: () => { - renote('local'); - }, - }], renoteButton.value); + os.popupMenu(boostMenuItems(appearNote, renote), renoteButton.value); } } -function renote(visibility: Visibility | 'local') { +function renote(visibility: Visibility, localOnly: boolean = false) { pleaseLogin(); showMovedDialog(); @@ -517,17 +476,9 @@ function renote(visibility: Visibility | 'local') { os.popup(MkRippleEffect, { x, y }, {}, 'end'); } - const configuredVisibility = defaultStore.state.rememberNoteVisibility ? defaultStore.state.visibility : defaultStore.state.defaultNoteVisibility; - const localOnlySetting = defaultStore.state.rememberNoteVisibility ? defaultStore.state.localOnly : defaultStore.state.defaultNoteLocalOnly; - - let noteVisibility = visibility === 'local' || visibility === 'specified' ? smallerVisibility(appearNote.value.visibility, configuredVisibility) : smallerVisibility(visibility, configuredVisibility); - if (appearNote.value.channel?.isSensitive) { - noteVisibility = smallerVisibility(visibility === 'local' || visibility === 'specified' ? appearNote.value.visibility : visibility, 'home'); - } - misskeyApi('notes/create', { - localOnly: visibility === 'local' ? true : localOnlySetting, - visibility: noteVisibility, + localOnly: localOnly, + visibility: visibility, renoteId: appearNote.value.id, }).then(() => { os.toast(i18n.ts.renoted); diff --git a/packages/frontend/src/components/SkNoteSub.vue b/packages/frontend/src/components/SkNoteSub.vue index 1641f8a5a..1486742f4 100644 --- a/packages/frontend/src/components/SkNoteSub.vue +++ b/packages/frontend/src/components/SkNoteSub.vue @@ -113,6 +113,7 @@ import { reactionPicker } from '@/scripts/reaction-picker.js'; import { claimAchievement } from '@/scripts/achievements.js'; import { getNoteMenu } from '@/scripts/get-note-menu.js'; import { useNoteCapture } from '@/scripts/use-note-capture.js'; +import { boostMenuItems, type Visibility } from '@/scripts/boost-quote.js'; const canRenote = computed(() => ['public', 'home'].includes(props.note.visibility) || props.note.userId === $i.id); const hideLine = computed(() => { return props.detail ? true : false; }); @@ -290,43 +291,11 @@ function boostVisibility() { if (!defaultStore.state.showVisibilitySelectorOnBoost) { renote(defaultStore.state.visibilityOnBoost); } else { - os.popupMenu([ - { - type: 'button', - icon: 'ph-globe-hemisphere-west ph-bold ph-lg', - text: i18n.ts._visibility['public'], - action: () => { - renote('public'); - }, - }, - { - type: 'button', - icon: 'ph-house ph-bold ph-lg', - text: i18n.ts._visibility['home'], - action: () => { - renote('home'); - }, - }, - { - type: 'button', - icon: 'ph-lock ph-bold ph-lg', - text: i18n.ts._visibility['followers'], - action: () => { - renote('followers'); - }, - }, - { - type: 'button', - icon: 'ph-planet ph-bold ph-lg', - text: i18n.ts._timelines.local, - action: () => { - renote('local'); - }, - }], renoteButton.value); + os.popupMenu(boostMenuItems(appearNote, renote), renoteButton.value); } } -function renote(visibility: 'public' | 'home' | 'followers' | 'specified' | 'local') { +function renote(visibility: Visibility, localOnly: boolean = false) { pleaseLogin(); showMovedDialog(); @@ -340,8 +309,8 @@ function renote(visibility: 'public' | 'home' | 'followers' | 'specified' | 'loc } misskeyApi('notes/create', { - renoteId: props.note.id, - channelId: props.note.channelId, + renoteId: appearNote.value.id, + channelId: appearNote.value.channelId, }).then(() => { os.toast(i18n.ts.renoted); renoted.value = true; @@ -356,9 +325,9 @@ function renote(visibility: 'public' | 'home' | 'followers' | 'specified' | 'loc } misskeyApi('notes/create', { - renoteId: props.note.id, - localOnly: visibility === 'local' ? true : false, - visibility: visibility === 'local' || visibility === 'specified' ? props.note.visibility : visibility, + renoteId: appearNote.value.id, + localOnly: localOnly, + visibility: visibility, }).then(() => { os.toast(i18n.ts.renoted); renoted.value = true; diff --git a/packages/frontend/src/pages/settings/general.vue b/packages/frontend/src/pages/settings/general.vue index 3b2946e2b..a27902c46 100644 --- a/packages/frontend/src/pages/settings/general.vue +++ b/packages/frontend/src/pages/settings/general.vue @@ -212,7 +212,6 @@ SPDX-License-Identifier: AGPL-3.0-only - diff --git a/packages/frontend/src/scripts/boost-quote.ts b/packages/frontend/src/scripts/boost-quote.ts new file mode 100644 index 000000000..4e025f5d4 --- /dev/null +++ b/packages/frontend/src/scripts/boost-quote.ts @@ -0,0 +1,81 @@ +/* + * SPDX-FileCopyrightText: dakkar and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only +*/ + +import { ref, Ref } from 'vue'; +import * as Misskey from 'misskey-js'; +import { i18n } from '@/i18n.js'; +import { defaultStore } from '@/store.js'; +import { MenuItem } from '@/types/menu.js'; + +/* + this script should eventually contain all Sharkey-specific bits of + boosting and quoting that we would otherwise have to replicate in + `{M,S}kNote{,Detailed,Sub}.vue` + */ + +export type Visibility = 'public' | 'home' | 'followers' | 'specified'; + +export function smallerVisibility(a: Visibility | string, b: Visibility | string): Visibility { + if (a === 'specified' || b === 'specified') return 'specified'; + if (a === 'followers' || b === 'followers') return 'followers'; + if (a === 'home' || b === 'home') return 'home'; + // if (a === 'public' || b === 'public') + return 'public'; +} + +export function visibilityIsAtLeast(a: Visibility | string, b: Visibility | string): boolean { + return smallerVisibility(a, b) === b; +} + +export function boostMenuItems(appearNote: Ref, renote: (v: Visibility, l: boolean) => void): MenuItem[] { + const localOnly = ref(defaultStore.state.rememberNoteVisibility ? defaultStore.state.localOnly : defaultStore.state.defaultNoteLocalOnly); + const effectiveVisibility = ( + appearNote.value.channel?.isSensitive + ? smallerVisibility(appearNote.value.visibility, 'home') + : appearNote.value.visibility + ); + + const menuItems: MenuItem[] = []; + if (visibilityIsAtLeast(effectiveVisibility, 'public')) { + menuItems.push({ + type: 'button', + icon: 'ph-globe-hemisphere-west ph-bold ph-lg', + text: i18n.ts._visibility['public'], + action: () => { + renote('public', localOnly.value); + }, + } as MenuItem); + } + if (visibilityIsAtLeast(effectiveVisibility, 'home')) { + menuItems.push({ + type: 'button', + icon: 'ph-house ph-bold ph-lg', + text: i18n.ts._visibility['home'], + action: () => { + renote('home', localOnly.value); + }, + } as MenuItem); + } + if (visibilityIsAtLeast(effectiveVisibility, 'followers')) { + menuItems.push({ + type: 'button', + icon: 'ph-lock ph-bold ph-lg', + text: i18n.ts._visibility['followers'], + action: () => { + renote('followers', localOnly.value); + }, + } as MenuItem); + } + + return [ + ...menuItems, + { + type: 'switch', + icon: 'ph-planet ph-bold ph-lg', + text: i18n.ts._timelines.local, + ref: localOnly, + } as MenuItem, + ]; +} diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts index d695caa95..53d259cf3 100644 --- a/packages/frontend/src/store.ts +++ b/packages/frontend/src/store.ts @@ -159,7 +159,7 @@ export const defaultStore = markRaw(new Storage('base', { }, visibilityOnBoost: { where: 'account', - default: 'public' as 'public' | 'home' | 'followers' | 'local', + default: 'public' as 'public' | 'home' | 'followers', }, menu: {