0
0

refactor(frontend): popupMenuの項目作成時に三項演算子をなるべく使わないように (#14554)

* refactor(frontend): popupMenuの項目作成時に三項演算子をなるべく使わないように

* type import

* fix

* lint
This commit is contained in:
かっこかり 2024-09-23 21:50:30 +09:00 committed by GitHub
parent e673c143a9
commit 0c6d1ec524
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
36 changed files with 851 additions and 614 deletions

View File

@ -8,7 +8,7 @@ import * as Misskey from 'misskey-js';
import { showSuspendedDialog } from '@/scripts/show-suspended-dialog.js'; import { showSuspendedDialog } from '@/scripts/show-suspended-dialog.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { miLocalStorage } from '@/local-storage.js'; import { miLocalStorage } from '@/local-storage.js';
import { MenuButton } from '@/types/menu.js'; import type { MenuItem, MenuButton } from '@/types/menu.js';
import { del, get, set } from '@/scripts/idb-proxy.js'; import { del, get, set } from '@/scripts/idb-proxy.js';
import { apiUrl } from '@@/js/config.js'; import { apiUrl } from '@@/js/config.js';
import { waiting, popup, popupMenu, success, alert } from '@/os.js'; import { waiting, popup, popupMenu, success, alert } from '@/os.js';
@ -288,14 +288,26 @@ export async function openAccountMenu(opts: {
}); });
})); }));
const menuItems: MenuItem[] = [];
if (opts.withExtraOperation) { if (opts.withExtraOperation) {
popupMenu([...[{ menuItems.push({
type: 'link' as const, type: 'link',
text: i18n.ts.profile, text: i18n.ts.profile,
to: `/@${ $i.username }`, to: `/@${$i.username}`,
avatar: $i, avatar: $i,
}, { type: 'divider' as const }, ...(opts.includeCurrentAccount ? [createItem($i)] : []), ...accountItemPromises, { }, {
type: 'parent' as const, type: 'divider',
});
if (opts.includeCurrentAccount) {
menuItems.push(createItem($i));
}
menuItems.push(...accountItemPromises);
menuItems.push({
type: 'parent',
icon: 'ti ti-plus', icon: 'ti ti-plus',
text: i18n.ts.addAccount, text: i18n.ts.addAccount,
children: [{ children: [{
@ -306,18 +318,22 @@ export async function openAccountMenu(opts: {
action: () => { createAccount(); }, action: () => { createAccount(); },
}], }],
}, { }, {
type: 'link' as const, type: 'link',
icon: 'ti ti-users', icon: 'ti ti-users',
text: i18n.ts.manageAccounts, text: i18n.ts.manageAccounts,
to: '/settings/accounts', to: '/settings/accounts',
}]], ev.currentTarget ?? ev.target, {
align: 'left',
}); });
} else { } else {
popupMenu([...(opts.includeCurrentAccount ? [createItem($i)] : []), ...accountItemPromises], ev.currentTarget ?? ev.target, { if (opts.includeCurrentAccount) {
align: 'left', menuItems.push(createItem($i));
}); }
menuItems.push(...accountItemPromises);
} }
popupMenu(menuItems, ev.currentTarget ?? ev.target, {
align: 'left',
});
} }
if (_DEV_) { if (_DEV_) {

View File

@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, onBeforeUnmount, shallowRef, ref } from 'vue'; import { onMounted, onBeforeUnmount, shallowRef, ref } from 'vue';
import MkMenu from './MkMenu.vue'; import MkMenu from './MkMenu.vue';
import { MenuItem } from '@/types/menu.js'; import type { MenuItem } from '@/types/menu.js';
import contains from '@/scripts/contains.js'; import contains from '@/scripts/contains.js';
import { defaultStore } from '@/store.js'; import { defaultStore } from '@/store.js';
import * as os from '@/os.js'; import * as os from '@/os.js';

View File

@ -42,7 +42,7 @@ import { i18n } from '@/i18n.js';
import { defaultStore } from '@/store.js'; import { defaultStore } from '@/store.js';
import { claimAchievement } from '@/scripts/achievements.js'; import { claimAchievement } from '@/scripts/achievements.js';
import { copyToClipboard } from '@/scripts/copy-to-clipboard.js'; import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
import { MenuItem } from '@/types/menu.js'; import type { MenuItem } from '@/types/menu.js';
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
folder: Misskey.entities.DriveFolder; folder: Misskey.entities.DriveFolder;

View File

@ -620,7 +620,9 @@ function fetchMoreFiles() {
} }
function getMenu() { function getMenu() {
const menu: MenuItem[] = [{ const menu: MenuItem[] = [];
menu.push({
type: 'switch', type: 'switch',
text: i18n.ts.keepOriginalUploading, text: i18n.ts.keepOriginalUploading,
ref: keepOriginal, ref: keepOriginal,
@ -638,19 +640,25 @@ function getMenu() {
}, { type: 'divider' }, { }, { type: 'divider' }, {
text: folder.value ? folder.value.name : i18n.ts.drive, text: folder.value ? folder.value.name : i18n.ts.drive,
type: 'label', type: 'label',
}, folder.value ? { });
text: i18n.ts.renameFolder,
icon: 'ti ti-forms', if (folder.value) {
action: () => { if (folder.value) renameFolder(folder.value); }, menu.push({
} : undefined, folder.value ? { text: i18n.ts.renameFolder,
text: i18n.ts.deleteFolder, icon: 'ti ti-forms',
icon: 'ti ti-trash', action: () => { if (folder.value) renameFolder(folder.value); },
action: () => { deleteFolder(folder.value as Misskey.entities.DriveFolder); }, }, {
} : undefined, { text: i18n.ts.deleteFolder,
icon: 'ti ti-trash',
action: () => { deleteFolder(folder.value as Misskey.entities.DriveFolder); },
});
}
menu.push({
text: i18n.ts.createFolder, text: i18n.ts.createFolder,
icon: 'ti ti-folder-plus', icon: 'ti ti-folder-plus',
action: () => { createFolder(); }, action: () => { createFolder(); },
}]; });
return menu; return menu;
} }

View File

@ -172,9 +172,7 @@ async function show() {
const menuShowing = ref(false); const menuShowing = ref(false);
function showMenu(ev: MouseEvent) { function showMenu(ev: MouseEvent) {
let menu: MenuItem[] = []; const menu: MenuItem[] = [
menu = [
// TODO: // TODO:
{ {
type: 'switch', type: 'switch',
@ -222,7 +220,7 @@ function showMenu(ev: MouseEvent) {
menu.push({ menu.push({
type: 'divider', type: 'divider',
}, { }, {
type: 'link' as const, type: 'link',
text: i18n.ts._fileViewer.title, text: i18n.ts._fileViewer.title,
icon: 'ti ti-info-circle', icon: 'ti ti-info-circle',
to: `/my/drive/file/${props.audio.id}`, to: `/my/drive/file/${props.audio.id}`,

View File

@ -60,6 +60,7 @@ import { defaultStore } from '@/store.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import * as os from '@/os.js'; import * as os from '@/os.js';
import { $i, iAmModerator } from '@/account.js'; import { $i, iAmModerator } from '@/account.js';
import type { MenuItem } from '@/types/menu.js';
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
image: Misskey.entities.DriveFile; image: Misskey.entities.DriveFile;
@ -111,27 +112,39 @@ watch(() => props.image, () => {
}); });
function showMenu(ev: MouseEvent) { function showMenu(ev: MouseEvent) {
os.popupMenu([{ const menuItems: MenuItem[] = [];
menuItems.push({
text: i18n.ts.hide, text: i18n.ts.hide,
icon: 'ti ti-eye-off', icon: 'ti ti-eye-off',
action: () => { action: () => {
hide.value = true; hide.value = true;
}, },
}, ...(iAmModerator ? [{ });
text: i18n.ts.markAsSensitive,
icon: 'ti ti-eye-exclamation', if (iAmModerator) {
danger: true, menuItems.push({
action: () => { text: i18n.ts.markAsSensitive,
os.apiWithDialog('drive/files/update', { fileId: props.image.id, isSensitive: true }); icon: 'ti ti-eye-exclamation',
}, danger: true,
}] : []), ...($i?.id === props.image.userId ? [{ action: () => {
type: 'divider' as const, os.apiWithDialog('drive/files/update', { fileId: props.image.id, isSensitive: true });
}, { },
type: 'link' as const, });
text: i18n.ts._fileViewer.title, }
icon: 'ti ti-info-circle',
to: `/my/drive/file/${props.image.id}`, if ($i?.id === props.image.userId) {
}] : [])], ev.currentTarget ?? ev.target); menuItems.push({
type: 'divider',
}, {
type: 'link',
text: i18n.ts._fileViewer.title,
icon: 'ti ti-info-circle',
to: `/my/drive/file/${props.image.id}`,
});
}
os.popupMenu(menuItems, ev.currentTarget ?? ev.target);
} }
</script> </script>

View File

@ -192,9 +192,7 @@ async function show() {
const menuShowing = ref(false); const menuShowing = ref(false);
function showMenu(ev: MouseEvent) { function showMenu(ev: MouseEvent) {
let menu: MenuItem[] = []; const menu: MenuItem[] = [
menu = [
// TODO: // TODO:
{ {
type: 'switch', type: 'switch',
@ -247,7 +245,7 @@ function showMenu(ev: MouseEvent) {
menu.push({ menu.push({
type: 'divider', type: 'divider',
}, { }, {
type: 'link' as const, type: 'link',
text: i18n.ts._fileViewer.title, text: i18n.ts._fileViewer.title,
icon: 'ti ti-info-circle', icon: 'ti ti-info-circle',
to: `/my/drive/file/${props.video.id}`, to: `/my/drive/file/${props.video.id}`,

View File

@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import { nextTick, onMounted, onUnmounted, provide, shallowRef, watch } from 'vue'; import { nextTick, onMounted, onUnmounted, provide, shallowRef, watch } from 'vue';
import MkMenu from './MkMenu.vue'; import MkMenu from './MkMenu.vue';
import { MenuItem } from '@/types/menu.js'; import type { MenuItem } from '@/types/menu.js';
const props = defineProps<{ const props = defineProps<{
items: MenuItem[]; items: MenuItem[];

View File

@ -193,7 +193,7 @@ import { deepClone } from '@/scripts/clone.js';
import { useTooltip } from '@/scripts/use-tooltip.js'; import { useTooltip } from '@/scripts/use-tooltip.js';
import { claimAchievement } from '@/scripts/achievements.js'; import { claimAchievement } from '@/scripts/achievements.js';
import { getNoteSummary } from '@/scripts/get-note-summary.js'; import { getNoteSummary } from '@/scripts/get-note-summary.js';
import { MenuItem } from '@/types/menu.js'; import type { MenuItem } from '@/types/menu.js';
import MkRippleEffect from '@/components/MkRippleEffect.vue'; import MkRippleEffect from '@/components/MkRippleEffect.vue';
import { showMovedDialog } from '@/scripts/show-moved-dialog.js'; import { showMovedDialog } from '@/scripts/show-moved-dialog.js';
import { shouldCollapsed } from '@@/js/collapsed.js'; import { shouldCollapsed } from '@@/js/collapsed.js';

View File

@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
import { ref, shallowRef } from 'vue'; import { ref, shallowRef } from 'vue';
import MkModal from './MkModal.vue'; import MkModal from './MkModal.vue';
import MkMenu from './MkMenu.vue'; import MkMenu from './MkMenu.vue';
import { MenuItem } from '@/types/menu.js'; import type { MenuItem } from '@/types/menu.js';
defineProps<{ defineProps<{
items: MenuItem[]; items: MenuItem[];

View File

@ -26,6 +26,7 @@ import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue';
import * as os from '@/os.js'; import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js'; import { misskeyApi } from '@/scripts/misskey-api.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import type { MenuItem } from '@/types/menu.js';
const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default)); const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
@ -136,7 +137,10 @@ function showFileMenu(file: Misskey.entities.DriveFile, ev: MouseEvent): void {
if (menuShowing) return; if (menuShowing) return;
const isImage = file.type.startsWith('image/'); const isImage = file.type.startsWith('image/');
os.popupMenu([{
const menuItems: MenuItem[] = [];
menuItems.push({
text: i18n.ts.renameFile, text: i18n.ts.renameFile,
icon: 'ti ti-forms', icon: 'ti ti-forms',
action: () => { rename(file); }, action: () => { rename(file); },
@ -148,11 +152,17 @@ function showFileMenu(file: Misskey.entities.DriveFile, ev: MouseEvent): void {
text: i18n.ts.describeFile, text: i18n.ts.describeFile,
icon: 'ti ti-text-caption', icon: 'ti ti-text-caption',
action: () => { describe(file); }, action: () => { describe(file); },
}, ...isImage ? [{ });
text: i18n.ts.cropImage,
icon: 'ti ti-crop', if (isImage) {
action: () : void => { crop(file); }, menuItems.push({
}] : [], { text: i18n.ts.cropImage,
icon: 'ti ti-crop',
action: () : void => { crop(file); },
});
}
menuItems.push({
type: 'divider', type: 'divider',
}, { }, {
text: i18n.ts.attachCancel, text: i18n.ts.attachCancel,
@ -163,7 +173,9 @@ function showFileMenu(file: Misskey.entities.DriveFile, ev: MouseEvent): void {
icon: 'ti ti-trash', icon: 'ti ti-trash',
danger: true, danger: true,
action: () => { detachAndDeleteMedia(file); }, action: () => { detachAndDeleteMedia(file); },
}], ev.currentTarget ?? ev.target).then(() => menuShowing = false); });
os.popupMenu(menuItems, ev.currentTarget ?? ev.target).then(() => menuShowing = false);
menuShowing = true; menuShowing = true;
} }
</script> </script>

View File

@ -46,7 +46,7 @@ import MkButton from '@/components/MkButton.vue';
import * as os from '@/os.js'; import * as os from '@/os.js';
import { useInterval } from '@@/js/use-interval.js'; import { useInterval } from '@@/js/use-interval.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { MenuItem } from '@/types/menu.js'; import type { MenuItem } from '@/types/menu.js';
const props = defineProps<{ const props = defineProps<{
modelValue: string | null; modelValue: string | null;

View File

@ -56,7 +56,7 @@ SPDX-License-Identifier: AGPL-3.0-only
import { onBeforeUnmount, onMounted, provide, shallowRef, ref } from 'vue'; import { onBeforeUnmount, onMounted, provide, shallowRef, ref } from 'vue';
import contains from '@/scripts/contains.js'; import contains from '@/scripts/contains.js';
import * as os from '@/os.js'; import * as os from '@/os.js';
import { MenuItem } from '@/types/menu.js'; import type { MenuItem } from '@/types/menu.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { defaultStore } from '@/store.js'; import { defaultStore } from '@/store.js';

View File

@ -35,6 +35,7 @@ import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
import * as sound from '@/scripts/sound.js'; import * as sound from '@/scripts/sound.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import MkCustomEmojiDetailedDialog from '@/components/MkCustomEmojiDetailedDialog.vue'; import MkCustomEmojiDetailedDialog from '@/components/MkCustomEmojiDetailedDialog.vue';
import type { MenuItem } from '@/types/menu.js';
const props = defineProps<{ const props = defineProps<{
name: string; name: string;
@ -85,7 +86,9 @@ const errored = ref(url.value == null);
function onClick(ev: MouseEvent) { function onClick(ev: MouseEvent) {
if (props.menu) { if (props.menu) {
os.popupMenu([{ const menuItems: MenuItem[] = [];
menuItems.push({
type: 'label', type: 'label',
text: `:${props.name}:`, text: `:${props.name}:`,
}, { }, {
@ -95,14 +98,20 @@ function onClick(ev: MouseEvent) {
copyToClipboard(`:${props.name}:`); copyToClipboard(`:${props.name}:`);
os.success(); os.success();
}, },
}, ...(props.menuReaction && react ? [{ });
text: i18n.ts.doReaction,
icon: 'ti ti-plus', if (props.menuReaction && react) {
action: () => { menuItems.push({
react(`:${props.name}:`); text: i18n.ts.doReaction,
sound.playMisskeySfx('reaction'); icon: 'ti ti-plus',
}, action: () => {
}] : []), { react(`:${props.name}:`);
sound.playMisskeySfx('reaction');
},
});
}
menuItems.push({
text: i18n.ts.info, text: i18n.ts.info,
icon: 'ti ti-info-circle', icon: 'ti ti-info-circle',
action: async () => { action: async () => {
@ -114,7 +123,9 @@ function onClick(ev: MouseEvent) {
closed: () => dispose(), closed: () => dispose(),
}); });
}, },
}], ev.currentTarget ?? ev.target); });
os.popupMenu(menuItems, ev.currentTarget ?? ev.target);
} }
} }
</script> </script>

View File

@ -17,6 +17,7 @@ import * as os from '@/os.js';
import { copyToClipboard } from '@/scripts/copy-to-clipboard.js'; import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
import * as sound from '@/scripts/sound.js'; import * as sound from '@/scripts/sound.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import type { MenuItem } from '@/types/menu.js';
const props = defineProps<{ const props = defineProps<{
emoji: string; emoji: string;
@ -39,7 +40,9 @@ function computeTitle(event: PointerEvent): void {
function onClick(ev: MouseEvent) { function onClick(ev: MouseEvent) {
if (props.menu) { if (props.menu) {
os.popupMenu([{ const menuItems: MenuItem[] = [];
menuItems.push({
type: 'label', type: 'label',
text: props.emoji, text: props.emoji,
}, { }, {
@ -49,14 +52,20 @@ function onClick(ev: MouseEvent) {
copyToClipboard(props.emoji); copyToClipboard(props.emoji);
os.success(); os.success();
}, },
}, ...(props.menuReaction && react ? [{ });
text: i18n.ts.doReaction,
icon: 'ti ti-plus', if (props.menuReaction && react) {
action: () => { menuItems.push({
react(props.emoji); text: i18n.ts.doReaction,
sound.playMisskeySfx('reaction'); icon: 'ti ti-plus',
}, action: () => {
}] : [])], ev.currentTarget ?? ev.target); react(props.emoji);
sound.playMisskeySfx('reaction');
},
});
}
os.popupMenu(menuItems, ev.currentTarget ?? ev.target);
} }
} }
</script> </script>

View File

@ -125,7 +125,7 @@ export const navbarItemDef = reactive({
ui: { ui: {
title: i18n.ts.switchUi, title: i18n.ts.switchUi,
icon: 'ti ti-devices', icon: 'ti ti-devices',
action: (ev) => { action: (ev: MouseEvent) => {
os.popupMenu([{ os.popupMenu([{
text: i18n.ts.default, text: i18n.ts.default,
active: ui === 'default' || ui === null, active: ui === 'default' || ui === null,

View File

@ -22,7 +22,7 @@ import MkPasswordDialog from '@/components/MkPasswordDialog.vue';
import MkEmojiPickerDialog from '@/components/MkEmojiPickerDialog.vue'; import MkEmojiPickerDialog from '@/components/MkEmojiPickerDialog.vue';
import MkPopupMenu from '@/components/MkPopupMenu.vue'; import MkPopupMenu from '@/components/MkPopupMenu.vue';
import MkContextMenu from '@/components/MkContextMenu.vue'; import MkContextMenu from '@/components/MkContextMenu.vue';
import { MenuItem } from '@/types/menu.js'; import type { MenuItem } from '@/types/menu.js';
import { copyToClipboard } from '@/scripts/copy-to-clipboard.js'; import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
import { pleaseLogin } from '@/scripts/please-login.js'; import { pleaseLogin } from '@/scripts/please-login.js';
import { showMovedDialog } from '@/scripts/show-moved-dialog.js'; import { showMovedDialog } from '@/scripts/show-moved-dialog.js';

View File

@ -45,6 +45,7 @@ import { clipsCache } from '@/cache.js';
import { isSupportShare } from '@/scripts/navigator.js'; import { isSupportShare } from '@/scripts/navigator.js';
import { copyToClipboard } from '@/scripts/copy-to-clipboard.js'; import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
import { genEmbedCode } from '@/scripts/get-embed-code.js'; import { genEmbedCode } from '@/scripts/get-embed-code.js';
import type { MenuItem } from '@/types/menu.js';
const props = defineProps<{ const props = defineProps<{
clipId: string, clipId: string,
@ -131,7 +132,9 @@ const headerActions = computed(() => clip.value && isOwned.value ? [{
icon: 'ti ti-share', icon: 'ti ti-share',
text: i18n.ts.share, text: i18n.ts.share,
handler: (ev: MouseEvent): void => { handler: (ev: MouseEvent): void => {
os.popupMenu([{ const menuItems: MenuItem[] = [];
menuItems.push({
icon: 'ti ti-link', icon: 'ti ti-link',
text: i18n.ts.copyUrl, text: i18n.ts.copyUrl,
action: () => { action: () => {
@ -144,17 +147,23 @@ const headerActions = computed(() => clip.value && isOwned.value ? [{
action: () => { action: () => {
genEmbedCode('clips', clip.value!.id); genEmbedCode('clips', clip.value!.id);
}, },
}, ...(isSupportShare() ? [{ });
icon: 'ti ti-share',
text: i18n.ts.share, if (isSupportShare()) {
action: async () => { menuItems.push({
navigator.share({ icon: 'ti ti-share',
title: clip.value!.name, text: i18n.ts.share,
text: clip.value!.description ?? '', action: async () => {
url: `${url}/clips/${clip.value!.id}`, navigator.share({
}); title: clip.value!.name,
}, text: clip.value!.description ?? '',
}] : [])], ev.currentTarget ?? ev.target); url: `${url}/clips/${clip.value!.id}`,
});
},
});
}
os.popupMenu(menuItems, ev.currentTarget ?? ev.target);
}, },
}] : []), { }] : []), {
icon: 'ti ti-trash', icon: 'ti ti-trash',

View File

@ -80,7 +80,7 @@ import { defaultStore } from '@/store.js';
import { $i } from '@/account.js'; import { $i } from '@/account.js';
import { isSupportShare } from '@/scripts/navigator.js'; import { isSupportShare } from '@/scripts/navigator.js';
import { copyToClipboard } from '@/scripts/copy-to-clipboard.js'; import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
import { MenuItem } from '@/types/menu'; import type { MenuItem } from '@/types/menu.js';
import { pleaseLogin } from '@/scripts/please-login.js'; import { pleaseLogin } from '@/scripts/please-login.js';
const props = defineProps<{ const props = defineProps<{
@ -104,18 +104,23 @@ function fetchFlash() {
function share(ev: MouseEvent) { function share(ev: MouseEvent) {
if (!flash.value) return; if (!flash.value) return;
os.popupMenu([ const menuItems: MenuItem[] = [];
{
text: i18n.ts.shareWithNote, menuItems.push({
icon: 'ti ti-pencil', text: i18n.ts.shareWithNote,
action: shareWithNote, icon: 'ti ti-pencil',
}, action: shareWithNote,
...(isSupportShare() ? [{ });
if (isSupportShare()) {
menuItems.push({
text: i18n.ts.share, text: i18n.ts.share,
icon: 'ti ti-share', icon: 'ti ti-share',
action: shareWithNavigator, action: shareWithNavigator,
}] : []), });
], ev.currentTarget ?? ev.target); }
os.popupMenu(menuItems, ev.currentTarget ?? ev.target);
} }
function copyLink() { function copyLink() {

View File

@ -80,7 +80,7 @@ import { $i } from '@/account.js';
import { isSupportShare } from '@/scripts/navigator.js'; import { isSupportShare } from '@/scripts/navigator.js';
import { copyToClipboard } from '@/scripts/copy-to-clipboard.js'; import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
import { useRouter } from '@/router/supplier.js'; import { useRouter } from '@/router/supplier.js';
import { MenuItem } from '@/types/menu'; import type { MenuItem } from '@/types/menu.js';
const router = useRouter(); const router = useRouter();
@ -171,35 +171,35 @@ function reportAbuse() {
function showMenu(ev: MouseEvent) { function showMenu(ev: MouseEvent) {
if (!post.value) return; if (!post.value) return;
const menu: MenuItem[] = [ const menuItems: MenuItem[] = [];
...($i && $i.id !== post.value.userId ? [
{
icon: 'ti ti-exclamation-circle',
text: i18n.ts.reportAbuse,
action: reportAbuse,
},
...($i.isModerator || $i.isAdmin ? [
{
type: 'divider' as const,
},
{
icon: 'ti ti-trash',
text: i18n.ts.delete,
danger: true,
action: () => os.confirm({
type: 'warning',
text: i18n.ts.deleteConfirm,
}).then(({ canceled }) => {
if (canceled || !post.value) return;
os.apiWithDialog('gallery/posts/delete', { postId: post.value.id }); if ($i && $i.id !== post.value.userId) {
}), menuItems.push({
}, icon: 'ti ti-exclamation-circle',
] : []), text: i18n.ts.reportAbuse,
] : []), action: reportAbuse,
]; });
os.popupMenu(menu, ev.currentTarget ?? ev.target); if ($i.isModerator || $i.isAdmin) {
menuItems.push({
type: 'divider',
}, {
icon: 'ti ti-trash',
text: i18n.ts.delete,
danger: true,
action: () => os.confirm({
type: 'warning',
text: i18n.ts.deleteConfirm,
}).then(({ canceled }) => {
if (canceled || !post.value) return;
os.apiWithDialog('gallery/posts/delete', { postId: post.value.id });
}),
});
}
}
os.popupMenu(menuItems, ev.currentTarget ?? ev.target);
} }
watch(() => props.postId, fetchPost, { immediate: true }); watch(() => props.postId, fetchPost, { immediate: true });

View File

@ -134,12 +134,14 @@ async function removeUser(item, ev) {
async function showMembershipMenu(item, ev) { async function showMembershipMenu(item, ev) {
const withRepliesRef = ref(item.withReplies); const withRepliesRef = ref(item.withReplies);
os.popupMenu([{ os.popupMenu([{
type: 'switch', type: 'switch',
text: i18n.ts.showRepliesToOthersInTimeline, text: i18n.ts.showRepliesToOthersInTimeline,
icon: 'ti ti-messages', icon: 'ti ti-messages',
ref: withRepliesRef, ref: withRepliesRef,
}], ev.currentTarget ?? ev.target); }], ev.currentTarget ?? ev.target);
watch(withRepliesRef, withReplies => { watch(withRepliesRef, withReplies => {
misskeyApi('users/lists/update-membership', { misskeyApi('users/lists/update-membership', {
listId: list.value!.id, listId: list.value!.id,

View File

@ -121,7 +121,7 @@ import { instance } from '@/instance.js';
import { getStaticImageUrl } from '@/scripts/media-proxy.js'; import { getStaticImageUrl } from '@/scripts/media-proxy.js';
import { copyToClipboard } from '@/scripts/copy-to-clipboard.js'; import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
import { useRouter } from '@/router/supplier.js'; import { useRouter } from '@/router/supplier.js';
import { MenuItem } from '@/types/menu'; import type { MenuItem } from '@/types/menu.js';
const router = useRouter(); const router = useRouter();
@ -165,18 +165,23 @@ function fetchPage() {
function share(ev: MouseEvent) { function share(ev: MouseEvent) {
if (!page.value) return; if (!page.value) return;
os.popupMenu([ const menuItems: MenuItem[] = [];
{
text: i18n.ts.shareWithNote, menuItems.push({
icon: 'ti ti-pencil', text: i18n.ts.shareWithNote,
action: shareWithNote, icon: 'ti ti-pencil',
}, action: shareWithNote,
...(isSupportShare() ? [{ });
if (isSupportShare()) {
menuItems.push({
text: i18n.ts.share, text: i18n.ts.share,
icon: 'ti ti-share', icon: 'ti ti-share',
action: shareWithNavigator, action: shareWithNavigator,
}] : []), });
], ev.currentTarget ?? ev.target); }
os.popupMenu(menuItems, ev.currentTarget ?? ev.target);
} }
function copyLink() { function copyLink() {
@ -256,51 +261,59 @@ function reportAbuse() {
function showMenu(ev: MouseEvent) { function showMenu(ev: MouseEvent) {
if (!page.value) return; if (!page.value) return;
const menu: MenuItem[] = [ const menuItems: MenuItem[] = [];
...($i && $i.id === page.value.userId ? [
{ if ($i && $i.id === page.value.userId) {
icon: 'ti ti-code', menuItems.push({
text: i18n.ts._pages.viewSource, icon: 'ti ti-pencil',
action: () => router.push(`/@${props.username}/pages/${props.pageName}/view-source`), text: i18n.ts.editThisPage,
}, action: () => router.push(`/pages/edit/${page.value.id}`),
...($i.pinnedPageId === page.value.id ? [{ });
if ($i.pinnedPageId === page.value.id) {
menuItems.push({
icon: 'ti ti-pinned-off', icon: 'ti ti-pinned-off',
text: i18n.ts.unpin, text: i18n.ts.unpin,
action: () => pin(false), action: () => pin(false),
}] : [{ });
} else {
menuItems.push({
icon: 'ti ti-pin', icon: 'ti ti-pin',
text: i18n.ts.pin, text: i18n.ts.pin,
action: () => pin(true), action: () => pin(true),
}]), });
] : []), }
...($i && $i.id !== page.value.userId ? [ } else if ($i && $i.id !== page.value.userId) {
{ menuItems.push({
icon: 'ti ti-exclamation-circle', icon: 'ti ti-code',
text: i18n.ts.reportAbuse, text: i18n.ts._pages.viewSource,
action: reportAbuse, action: () => router.push(`/@${props.username}/pages/${props.pageName}/view-source`),
}, }, {
...($i.isModerator || $i.isAdmin ? [ icon: 'ti ti-exclamation-circle',
{ text: i18n.ts.reportAbuse,
type: 'divider' as const, action: reportAbuse,
}, });
{
icon: 'ti ti-trash',
text: i18n.ts.delete,
danger: true,
action: () => os.confirm({
type: 'warning',
text: i18n.ts.deleteConfirm,
}).then(({ canceled }) => {
if (canceled || !page.value) return;
os.apiWithDialog('pages/delete', { pageId: page.value.id }); if ($i.isModerator || $i.isAdmin) {
}), menuItems.push({
}, type: 'divider',
] : []), }, {
] : []), icon: 'ti ti-trash',
]; text: i18n.ts.delete,
danger: true,
action: () => os.confirm({
type: 'warning',
text: i18n.ts.deleteConfirm,
}).then(({ canceled }) => {
if (canceled || !page.value) return;
os.popupMenu(menu, ev.currentTarget ?? ev.target); os.apiWithDialog('pages/delete', { pageId: page.value.id });
}),
});
}
}
os.popupMenu(menuItems, ev.currentTarget ?? ev.target);
} }
watch(() => path.value, fetchPage, { immediate: true }); watch(() => path.value, fetchPage, { immediate: true });

View File

@ -121,7 +121,7 @@ import MkRadios from '@/components/MkRadios.vue';
import MkSwitch from '@/components/MkSwitch.vue'; import MkSwitch from '@/components/MkSwitch.vue';
import MkFolder from '@/components/MkFolder.vue'; import MkFolder from '@/components/MkFolder.vue';
import * as os from '@/os.js'; import * as os from '@/os.js';
import { MenuItem } from '@/types/menu.js'; import type { MenuItem } from '@/types/menu.js';
import { useRouter } from '@/router/supplier.js'; import { useRouter } from '@/router/supplier.js';
const $i = signinRequired(); const $i = signinRequired();

View File

@ -50,7 +50,7 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
import { antennasCache, userListsCache, favoritedChannelsCache } from '@/cache.js'; import { antennasCache, userListsCache, favoritedChannelsCache } from '@/cache.js';
import { deviceKind } from '@/scripts/device-kind.js'; import { deviceKind } from '@/scripts/device-kind.js';
import { deepMerge } from '@/scripts/merge.js'; import { deepMerge } from '@/scripts/merge.js';
import { MenuItem } from '@/types/menu.js'; import type { MenuItem } from '@/types/menu.js';
import { miLocalStorage } from '@/local-storage.js'; import { miLocalStorage } from '@/local-storage.js';
import { availableBasicTimelines, hasWithReplies, isAvailableBasicTimeline, isBasicTimeline, basicTimelineIconClass } from '@/timelines.js'; import { availableBasicTimelines, hasWithReplies, isAvailableBasicTimeline, isBasicTimeline, basicTimelineIconClass } from '@/timelines.js';
import type { BasicTimelineType } from '@/timelines.js'; import type { BasicTimelineType } from '@/timelines.js';
@ -189,7 +189,7 @@ async function chooseChannel(ev: MouseEvent): Promise<void> {
}), }),
(channels.length === 0 ? undefined : { type: 'divider' }), (channels.length === 0 ? undefined : { type: 'divider' }),
{ {
type: 'link' as const, type: 'link',
icon: 'ti ti-plus', icon: 'ti ti-plus',
text: i18n.ts.createNew, text: i18n.ts.createNew,
to: '/channels', to: '/channels',
@ -258,16 +258,24 @@ const headerActions = computed(() => {
icon: 'ti ti-dots', icon: 'ti ti-dots',
text: i18n.ts.options, text: i18n.ts.options,
handler: (ev) => { handler: (ev) => {
os.popupMenu([{ const menuItems: MenuItem[] = [];
menuItems.push({
type: 'switch', type: 'switch',
text: i18n.ts.showRenotes, text: i18n.ts.showRenotes,
ref: withRenotes, ref: withRenotes,
}, isBasicTimeline(src.value) && hasWithReplies(src.value) ? { });
type: 'switch',
text: i18n.ts.showRepliesToOthersInTimeline, if (isBasicTimeline(src.value) && hasWithReplies(src.value)) {
ref: withReplies, menuItems.push({
disabled: onlyFiles, type: 'switch',
} : undefined, { text: i18n.ts.showRepliesToOthersInTimeline,
ref: withReplies,
disabled: onlyFiles,
});
}
menuItems.push({
type: 'switch', type: 'switch',
text: i18n.ts.withSensitive, text: i18n.ts.withSensitive,
ref: withSensitive, ref: withSensitive,
@ -276,7 +284,9 @@ const headerActions = computed(() => {
text: i18n.ts.fileAttachedOnly, text: i18n.ts.fileAttachedOnly,
ref: onlyFiles, ref: onlyFiles,
disabled: isBasicTimeline(src.value) && hasWithReplies(src.value) ? withReplies : false, disabled: isBasicTimeline(src.value) && hasWithReplies(src.value) ? withReplies : false,
}], ev.currentTarget ?? ev.target); });
os.popupMenu(menuItems, ev.currentTarget ?? ev.target);
}, },
}, },
]; ];

View File

@ -9,7 +9,7 @@ import { i18n } from '@/i18n.js';
import { copyToClipboard } from '@/scripts/copy-to-clipboard.js'; import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
import * as os from '@/os.js'; import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js'; import { misskeyApi } from '@/scripts/misskey-api.js';
import { MenuItem } from '@/types/menu.js'; import type { MenuItem } from '@/types/menu.js';
import { defaultStore } from '@/store.js'; import { defaultStore } from '@/store.js';
function rename(file: Misskey.entities.DriveFile) { function rename(file: Misskey.entities.DriveFile) {
@ -87,8 +87,10 @@ async function deleteFile(file: Misskey.entities.DriveFile) {
export function getDriveFileMenu(file: Misskey.entities.DriveFile, folder?: Misskey.entities.DriveFolder | null): MenuItem[] { export function getDriveFileMenu(file: Misskey.entities.DriveFile, folder?: Misskey.entities.DriveFolder | null): MenuItem[] {
const isImage = file.type.startsWith('image/'); const isImage = file.type.startsWith('image/');
let menu;
menu = [{ const menuItems: MenuItem[] = [];
menuItems.push({
type: 'link', type: 'link',
to: `/my/drive/file/${file.id}`, to: `/my/drive/file/${file.id}`,
text: i18n.ts._fileViewer.title, text: i18n.ts._fileViewer.title,
@ -109,14 +111,20 @@ export function getDriveFileMenu(file: Misskey.entities.DriveFile, folder?: Miss
text: i18n.ts.describeFile, text: i18n.ts.describeFile,
icon: 'ti ti-text-caption', icon: 'ti ti-text-caption',
action: () => describe(file), action: () => describe(file),
}, ...isImage ? [{ });
text: i18n.ts.cropImage,
icon: 'ti ti-crop', if (isImage) {
action: () => os.cropImage(file, { menuItems.push({
aspectRatio: NaN, text: i18n.ts.cropImage,
uploadFolder: folder ? folder.id : folder, icon: 'ti ti-crop',
}), action: () => os.cropImage(file, {
}] : [], { type: 'divider' }, { aspectRatio: NaN,
uploadFolder: folder ? folder.id : folder,
}),
});
}
menuItems.push({ type: 'divider' }, {
text: i18n.ts.createNoteFromTheFile, text: i18n.ts.createNoteFromTheFile,
icon: 'ti ti-pencil', icon: 'ti ti-pencil',
action: () => os.post({ action: () => os.post({
@ -138,17 +146,17 @@ export function getDriveFileMenu(file: Misskey.entities.DriveFile, folder?: Miss
icon: 'ti ti-trash', icon: 'ti ti-trash',
danger: true, danger: true,
action: () => deleteFile(file), action: () => deleteFile(file),
}]; });
if (defaultStore.state.devMode) { if (defaultStore.state.devMode) {
menu = menu.concat([{ type: 'divider' }, { menuItems.push({ type: 'divider' }, {
icon: 'ti ti-id', icon: 'ti ti-id',
text: i18n.ts.copyFileId, text: i18n.ts.copyFileId,
action: () => { action: () => {
copyToClipboard(file.id); copyToClipboard(file.id);
}, },
}]); });
} }
return menu; return menuItems;
} }

View File

@ -17,7 +17,7 @@ import { defaultStore, noteActions } from '@/store.js';
import { miLocalStorage } from '@/local-storage.js'; import { miLocalStorage } from '@/local-storage.js';
import { getUserMenu } from '@/scripts/get-user-menu.js'; import { getUserMenu } from '@/scripts/get-user-menu.js';
import { clipsCache, favoritedChannelsCache } from '@/cache.js'; import { clipsCache, favoritedChannelsCache } from '@/cache.js';
import { MenuItem } from '@/types/menu.js'; import type { MenuItem } from '@/types/menu.js';
import MkRippleEffect from '@/components/MkRippleEffect.vue'; import MkRippleEffect from '@/components/MkRippleEffect.vue';
import { isSupportShare } from '@/scripts/navigator.js'; import { isSupportShare } from '@/scripts/navigator.js';
import { getAppearNote } from '@/scripts/get-appear-note.js'; import { getAppearNote } from '@/scripts/get-appear-note.js';
@ -99,11 +99,13 @@ export async function getNoteClipMenu(props: {
const { canceled, result } = await os.form(i18n.ts.createNewClip, { const { canceled, result } = await os.form(i18n.ts.createNewClip, {
name: { name: {
type: 'string', type: 'string',
default: null,
label: i18n.ts.name, label: i18n.ts.name,
}, },
description: { description: {
type: 'string', type: 'string',
required: false, required: false,
default: null,
multiline: true, multiline: true,
label: i18n.ts.description, label: i18n.ts.description,
}, },
@ -264,7 +266,7 @@ export function getNoteMenu(props: {
title: i18n.ts.numberOfDays, title: i18n.ts.numberOfDays,
}); });
if (canceled) return; if (canceled || days == null) return;
os.apiWithDialog('admin/promo/create', { os.apiWithDialog('admin/promo/create', {
noteId: appearNote.id, noteId: appearNote.id,
@ -295,161 +297,23 @@ export function getNoteMenu(props: {
props.translation.value = res; props.translation.value = res;
} }
let menu: MenuItem[]; const menuItems: MenuItem[] = [];
if ($i) { if ($i) {
const statePromise = misskeyApi('notes/state', { const statePromise = misskeyApi('notes/state', {
noteId: appearNote.id, noteId: appearNote.id,
}); });
menu = [ if (props.currentClip?.userId === $i.id) {
...( menuItems.push({
props.currentClip?.userId === $i.id ? [{ icon: 'ti ti-backspace',
icon: 'ti ti-backspace', text: i18n.ts.unclip,
text: i18n.ts.unclip, danger: true,
danger: true, action: unclip,
action: unclip, }, { type: 'divider' });
}, { type: 'divider' }] : [] }
), {
icon: 'ti ti-info-circle',
text: i18n.ts.details,
action: openDetail,
}, {
icon: 'ti ti-copy',
text: i18n.ts.copyContent,
action: copyContent,
}, getCopyNoteLinkMenu(appearNote, i18n.ts.copyLink)
, (appearNote.url || appearNote.uri) ? {
icon: 'ti ti-external-link',
text: i18n.ts.showOnRemote,
action: () => {
window.open(appearNote.url ?? appearNote.uri, '_blank', 'noopener');
},
} : getNoteEmbedCodeMenu(appearNote, i18n.ts.genEmbedCode),
...(isSupportShare() ? [{
icon: 'ti ti-share',
text: i18n.ts.share,
action: share,
}] : []),
$i && $i.policies.canUseTranslator && instance.translatorAvailable ? {
icon: 'ti ti-language-hiragana',
text: i18n.ts.translate,
action: translate,
} : undefined,
{ type: 'divider' },
statePromise.then(state => state.isFavorited ? {
icon: 'ti ti-star-off',
text: i18n.ts.unfavorite,
action: () => toggleFavorite(false),
} : {
icon: 'ti ti-star',
text: i18n.ts.favorite,
action: () => toggleFavorite(true),
}),
{
type: 'parent' as const,
icon: 'ti ti-paperclip',
text: i18n.ts.clip,
children: () => getNoteClipMenu(props),
},
statePromise.then(state => state.isMutedThread ? {
icon: 'ti ti-message-off',
text: i18n.ts.unmuteThread,
action: () => toggleThreadMute(false),
} : {
icon: 'ti ti-message-off',
text: i18n.ts.muteThread,
action: () => toggleThreadMute(true),
}),
appearNote.userId === $i.id ? ($i.pinnedNoteIds ?? []).includes(appearNote.id) ? {
icon: 'ti ti-pinned-off',
text: i18n.ts.unpin,
action: () => togglePin(false),
} : {
icon: 'ti ti-pin',
text: i18n.ts.pin,
action: () => togglePin(true),
} : undefined,
{
type: 'parent' as const,
icon: 'ti ti-user',
text: i18n.ts.user,
children: async () => {
const user = appearNote.userId === $i?.id ? $i : await misskeyApi('users/show', { userId: appearNote.userId });
const { menu, cleanup } = getUserMenu(user);
cleanups.push(cleanup);
return menu;
},
},
/*
...($i.isModerator || $i.isAdmin ? [
{ type: 'divider' },
{
icon: 'ti ti-speakerphone',
text: i18n.ts.promote,
action: promote
}]
: []
),*/
...(appearNote.userId !== $i.id ? [
{ type: 'divider' },
appearNote.userId !== $i.id ? getAbuseNoteMenu(appearNote, i18n.ts.reportAbuse) : undefined,
]
: []
),
...(appearNote.channel && (appearNote.channel.userId === $i.id || $i.isModerator || $i.isAdmin) ? [
{ type: 'divider' },
{
type: 'parent' as const,
icon: 'ti ti-device-tv',
text: i18n.ts.channel,
children: async () => {
const channelChildMenu = [] as MenuItem[];
const channel = await misskeyApi('channels/show', { channelId: appearNote.channel!.id }); menuItems.push({
if (channel.pinnedNoteIds.includes(appearNote.id)) {
channelChildMenu.push({
icon: 'ti ti-pinned-off',
text: i18n.ts.unpin,
action: () => os.apiWithDialog('channels/update', {
channelId: appearNote.channel!.id,
pinnedNoteIds: channel.pinnedNoteIds.filter(id => id !== appearNote.id),
}),
});
} else {
channelChildMenu.push({
icon: 'ti ti-pin',
text: i18n.ts.pin,
action: () => os.apiWithDialog('channels/update', {
channelId: appearNote.channel!.id,
pinnedNoteIds: [...channel.pinnedNoteIds, appearNote.id],
}),
});
}
return channelChildMenu;
},
},
]
: []
),
...(appearNote.userId === $i.id || $i.isModerator || $i.isAdmin ? [
{ type: 'divider' },
appearNote.userId === $i.id ? {
icon: 'ti ti-edit',
text: i18n.ts.deleteAndEdit,
action: delEdit,
} : undefined,
{
icon: 'ti ti-trash',
text: i18n.ts.delete,
danger: true,
action: del,
}]
: []
)]
.filter(x => x !== undefined);
} else {
menu = [{
icon: 'ti ti-info-circle', icon: 'ti ti-info-circle',
text: i18n.ts.details, text: i18n.ts.details,
action: openDetail, action: openDetail,
@ -457,35 +321,194 @@ export function getNoteMenu(props: {
icon: 'ti ti-copy', icon: 'ti ti-copy',
text: i18n.ts.copyContent, text: i18n.ts.copyContent,
action: copyContent, action: copyContent,
}, getCopyNoteLinkMenu(appearNote, i18n.ts.copyLink), }, getCopyNoteLinkMenu(appearNote, i18n.ts.copyLink));
(appearNote.url || appearNote.uri) ? {
icon: 'ti ti-external-link', if (appearNote.url || appearNote.uri) {
text: i18n.ts.showOnRemote, menuItems.push({
action: () => { icon: 'ti ti-external-link',
window.open(appearNote.url ?? appearNote.uri, '_blank', 'noopener'); text: i18n.ts.showOnRemote,
action: () => {
window.open(appearNote.url ?? appearNote.uri, '_blank', 'noopener');
},
});
} else {
menuItems.push(getNoteEmbedCodeMenu(appearNote, i18n.ts.genEmbedCode));
}
if (isSupportShare()) {
menuItems.push({
icon: 'ti ti-share',
text: i18n.ts.share,
action: share,
});
}
if ($i.policies.canUseTranslator && instance.translatorAvailable) {
menuItems.push({
icon: 'ti ti-language-hiragana',
text: i18n.ts.translate,
action: translate,
});
}
menuItems.push({ type: 'divider' });
menuItems.push(statePromise.then(state => state.isFavorited ? {
icon: 'ti ti-star-off',
text: i18n.ts.unfavorite,
action: () => toggleFavorite(false),
} : {
icon: 'ti ti-star',
text: i18n.ts.favorite,
action: () => toggleFavorite(true),
}));
menuItems.push({
type: 'parent',
icon: 'ti ti-paperclip',
text: i18n.ts.clip,
children: () => getNoteClipMenu(props),
});
menuItems.push(statePromise.then(state => state.isMutedThread ? {
icon: 'ti ti-message-off',
text: i18n.ts.unmuteThread,
action: () => toggleThreadMute(false),
} : {
icon: 'ti ti-message-off',
text: i18n.ts.muteThread,
action: () => toggleThreadMute(true),
}));
if (appearNote.userId === $i.id) {
if (($i.pinnedNoteIds ?? []).includes(appearNote.id)) {
menuItems.push({
icon: 'ti ti-pinned-off',
text: i18n.ts.unpin,
action: () => togglePin(false),
});
} else {
menuItems.push({
icon: 'ti ti-pin',
text: i18n.ts.pin,
action: () => togglePin(true),
});
}
}
menuItems.push({
type: 'parent',
icon: 'ti ti-user',
text: i18n.ts.user,
children: async () => {
const user = appearNote.userId === $i?.id ? $i : await misskeyApi('users/show', { userId: appearNote.userId });
const { menu, cleanup } = getUserMenu(user);
cleanups.push(cleanup);
return menu;
}, },
} : getNoteEmbedCodeMenu(appearNote, i18n.ts.genEmbedCode)] });
.filter(x => x !== undefined);
if (appearNote.userId !== $i.id) {
menuItems.push({ type: 'divider' });
menuItems.push(getAbuseNoteMenu(appearNote, i18n.ts.reportAbuse));
}
if (appearNote.channel && (appearNote.channel.userId === $i.id || $i.isModerator || $i.isAdmin)) {
menuItems.push({ type: 'divider' });
menuItems.push({
type: 'parent',
icon: 'ti ti-device-tv',
text: i18n.ts.channel,
children: async () => {
const channelChildMenu = [] as MenuItem[];
const channel = await misskeyApi('channels/show', { channelId: appearNote.channel!.id });
if (channel.pinnedNoteIds.includes(appearNote.id)) {
channelChildMenu.push({
icon: 'ti ti-pinned-off',
text: i18n.ts.unpin,
action: () => os.apiWithDialog('channels/update', {
channelId: appearNote.channel!.id,
pinnedNoteIds: channel.pinnedNoteIds.filter(id => id !== appearNote.id),
}),
});
} else {
channelChildMenu.push({
icon: 'ti ti-pin',
text: i18n.ts.pin,
action: () => os.apiWithDialog('channels/update', {
channelId: appearNote.channel!.id,
pinnedNoteIds: [...channel.pinnedNoteIds, appearNote.id],
}),
});
}
return channelChildMenu;
},
});
}
if (appearNote.userId === $i.id || $i.isModerator || $i.isAdmin) {
menuItems.push({ type: 'divider' });
if (appearNote.userId === $i.id) {
menuItems.push({
icon: 'ti ti-edit',
text: i18n.ts.deleteAndEdit,
action: delEdit,
});
}
menuItems.push({
icon: 'ti ti-trash',
text: i18n.ts.delete,
danger: true,
action: del,
});
}
} else {
menuItems.push({
icon: 'ti ti-info-circle',
text: i18n.ts.details,
action: openDetail,
}, {
icon: 'ti ti-copy',
text: i18n.ts.copyContent,
action: copyContent,
}, getCopyNoteLinkMenu(appearNote, i18n.ts.copyLink));
if (appearNote.url || appearNote.uri) {
menuItems.push({
icon: 'ti ti-external-link',
text: i18n.ts.showOnRemote,
action: () => {
window.open(appearNote.url ?? appearNote.uri, '_blank', 'noopener');
},
});
} else {
menuItems.push(getNoteEmbedCodeMenu(appearNote, i18n.ts.genEmbedCode));
}
} }
if (noteActions.length > 0) { if (noteActions.length > 0) {
menu = menu.concat([{ type: 'divider' }, ...noteActions.map(action => ({ menuItems.push({ type: 'divider' });
menuItems.push(...noteActions.map(action => ({
icon: 'ti ti-plug', icon: 'ti ti-plug',
text: action.title, text: action.title,
action: () => { action: () => {
action.handler(appearNote); action.handler(appearNote);
}, },
}))]); })));
} }
if (defaultStore.state.devMode) { if (defaultStore.state.devMode) {
menu = menu.concat([{ type: 'divider' }, { menuItems.push({ type: 'divider' }, {
icon: 'ti ti-id', icon: 'ti ti-id',
text: i18n.ts.copyNoteId, text: i18n.ts.copyNoteId,
action: () => { action: () => {
copyToClipboard(appearNote.id); copyToClipboard(appearNote.id);
os.success();
}, },
}]); });
} }
const cleanup = () => { const cleanup = () => {
@ -496,7 +519,7 @@ export function getNoteMenu(props: {
}; };
return { return {
menu, menu: menuItems,
cleanup, cleanup,
}; };
} }

View File

@ -18,7 +18,7 @@ import { IRouter } from '@/nirax.js';
import { antennasCache, rolesCache, userListsCache } from '@/cache.js'; import { antennasCache, rolesCache, userListsCache } from '@/cache.js';
import { mainRouter } from '@/router/main.js'; import { mainRouter } from '@/router/main.js';
import { genEmbedCode } from '@/scripts/get-embed-code.js'; import { genEmbedCode } from '@/scripts/get-embed-code.js';
import { MenuItem } from '@/types/menu.js'; import type { MenuItem } from '@/types/menu.js';
export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter = mainRouter) { export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter = mainRouter) {
const meId = $i ? $i.id : null; const meId = $i ? $i.id : null;
@ -148,133 +148,154 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter
}); });
} }
let menu: MenuItem[] = [{ const menuItems: MenuItem[] = [];
menuItems.push({
icon: 'ti ti-at', icon: 'ti ti-at',
text: i18n.ts.copyUsername, text: i18n.ts.copyUsername,
action: () => { action: () => {
copyToClipboard(`@${user.username}@${user.host ?? host}`); copyToClipboard(`@${user.username}@${user.host ?? host}`);
}, },
}, ...( notesSearchAvailable && (user.host == null || canSearchNonLocalNotes) ? [{ });
icon: 'ti ti-search',
text: i18n.ts.searchThisUsersNotes, if (notesSearchAvailable && (user.host == null || canSearchNonLocalNotes)) {
action: () => { menuItems.push({
router.push(`/search?username=${encodeURIComponent(user.username)}${user.host != null ? '&host=' + encodeURIComponent(user.host) : ''}`); icon: 'ti ti-search',
}, text: i18n.ts.searchThisUsersNotes,
}] : []) action: () => {
, ...(iAmModerator ? [{ router.push(`/search?username=${encodeURIComponent(user.username)}${user.host != null ? '&host=' + encodeURIComponent(user.host) : ''}`);
icon: 'ti ti-user-exclamation', },
text: i18n.ts.moderation, });
action: () => { }
router.push(`/admin/user/${user.id}`);
}, if (iAmModerator) {
}] : []), { menuItems.push({
icon: 'ti ti-user-exclamation',
text: i18n.ts.moderation,
action: () => {
router.push(`/admin/user/${user.id}`);
},
});
}
menuItems.push({
icon: 'ti ti-rss', icon: 'ti ti-rss',
text: i18n.ts.copyRSS, text: i18n.ts.copyRSS,
action: () => { action: () => {
copyToClipboard(`${user.host ?? host}/@${user.username}.atom`); copyToClipboard(`${user.host ?? host}/@${user.username}.atom`);
}, },
}, ...(user.host != null && user.url != null ? [{ });
icon: 'ti ti-external-link',
text: i18n.ts.showOnRemote, if (user.host != null && user.url != null) {
action: () => { menuItems.push({
if (user.url == null) return; icon: 'ti ti-external-link',
window.open(user.url, '_blank', 'noopener'); text: i18n.ts.showOnRemote,
},
}] : [{
icon: 'ti ti-code',
text: i18n.ts.genEmbedCode,
type: 'parent' as const,
children: [{
text: i18n.ts.noteOfThisUser,
action: () => { action: () => {
genEmbedCode('user-timeline', user.id); if (user.url == null) return;
window.open(user.url, '_blank', 'noopener');
}, },
}], // TODO: ユーザーカードの埋め込みなど });
}]), { } else {
menuItems.push({
icon: 'ti ti-code',
text: i18n.ts.genEmbedCode,
type: 'parent',
children: [{
text: i18n.ts.noteOfThisUser,
action: () => {
genEmbedCode('user-timeline', user.id);
},
}], // TODO: ユーザーカードの埋め込みなど
});
}
menuItems.push({
icon: 'ti ti-share', icon: 'ti ti-share',
text: i18n.ts.copyProfileUrl, text: i18n.ts.copyProfileUrl,
action: () => { action: () => {
const canonical = user.host === null ? `@${user.username}` : `@${user.username}@${toUnicode(user.host)}`; const canonical = user.host === null ? `@${user.username}` : `@${user.username}@${toUnicode(user.host)}`;
copyToClipboard(`${url}/${canonical}`); copyToClipboard(`${url}/${canonical}`);
}, },
}, ...($i ? [{ });
icon: 'ti ti-mail',
text: i18n.ts.sendMessage,
action: () => {
const canonical = user.host === null ? `@${user.username}` : `@${user.username}@${user.host}`;
os.post({ specified: user, initialText: `${canonical} ` });
},
}, { type: 'divider' }, {
icon: 'ti ti-pencil',
text: i18n.ts.editMemo,
action: () => {
editMemo();
},
}, {
type: 'parent',
icon: 'ti ti-list',
text: i18n.ts.addToList,
children: async () => {
const lists = await userListsCache.fetch();
return lists.map(list => {
const isListed = ref(list.userIds.includes(user.id));
cleanups.push(watch(isListed, () => {
if (isListed.value) {
os.apiWithDialog('users/lists/push', {
listId: list.id,
userId: user.id,
}).then(() => {
list.userIds.push(user.id);
});
} else {
os.apiWithDialog('users/lists/pull', {
listId: list.id,
userId: user.id,
}).then(() => {
list.userIds.splice(list.userIds.indexOf(user.id), 1);
});
}
}));
return { if ($i) {
type: 'switch', menuItems.push({
text: list.name, icon: 'ti ti-mail',
ref: isListed, text: i18n.ts.sendMessage,
}; action: () => {
}); const canonical = user.host === null ? `@${user.username}` : `@${user.username}@${user.host}`;
}, os.post({ specified: user, initialText: `${canonical} ` });
}, { },
type: 'parent', }, { type: 'divider' }, {
icon: 'ti ti-antenna', icon: 'ti ti-pencil',
text: i18n.ts.addToAntenna, text: i18n.ts.editMemo,
children: async () => { action: editMemo,
const antennas = await antennasCache.fetch(); }, {
const canonical = user.host === null ? `@${user.username}` : `@${user.username}@${toUnicode(user.host)}`; type: 'parent',
return antennas.filter((a) => a.src === 'users').map(antenna => ({ icon: 'ti ti-list',
text: antenna.name, text: i18n.ts.addToList,
action: async () => { children: async () => {
await os.apiWithDialog('antennas/update', { const lists = await userListsCache.fetch();
antennaId: antenna.id, return lists.map(list => {
name: antenna.name, const isListed = ref(list.userIds?.includes(user.id) ?? false);
keywords: antenna.keywords, cleanups.push(watch(isListed, () => {
excludeKeywords: antenna.excludeKeywords, if (isListed.value) {
src: antenna.src, os.apiWithDialog('users/lists/push', {
userListId: antenna.userListId, listId: list.id,
users: [...antenna.users, canonical], userId: user.id,
caseSensitive: antenna.caseSensitive, }).then(() => {
withReplies: antenna.withReplies, list.userIds?.push(user.id);
withFile: antenna.withFile, });
notify: antenna.notify, } else {
}); os.apiWithDialog('users/lists/pull', {
antennasCache.delete(); listId: list.id,
}, userId: user.id,
})); }).then(() => {
}, list.userIds?.splice(list.userIds?.indexOf(user.id), 1);
}] : [])] as any; });
}
}));
return {
type: 'switch',
text: list.name,
ref: isListed,
};
});
},
}, {
type: 'parent',
icon: 'ti ti-antenna',
text: i18n.ts.addToAntenna,
children: async () => {
const antennas = await antennasCache.fetch();
const canonical = user.host === null ? `@${user.username}` : `@${user.username}@${toUnicode(user.host)}`;
return antennas.filter((a) => a.src === 'users').map(antenna => ({
text: antenna.name,
action: async () => {
await os.apiWithDialog('antennas/update', {
antennaId: antenna.id,
name: antenna.name,
keywords: antenna.keywords,
excludeKeywords: antenna.excludeKeywords,
src: antenna.src,
userListId: antenna.userListId,
users: [...antenna.users, canonical],
caseSensitive: antenna.caseSensitive,
withReplies: antenna.withReplies,
withFile: antenna.withFile,
notify: antenna.notify,
});
antennasCache.delete();
},
}));
},
});
}
if ($i && meId !== user.id) { if ($i && meId !== user.id) {
if (iAmModerator) { if (iAmModerator) {
menu = menu.concat([{ menuItems.push({
type: 'parent', type: 'parent',
icon: 'ti ti-badges', icon: 'ti ti-badges',
text: i18n.ts.roles, text: i18n.ts.roles,
@ -312,13 +333,14 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter
}, },
})); }));
}, },
}]); });
} }
// フォローしたとしても user.isFollowing はリアルタイム更新されないので不便なため // フォローしたとしても user.isFollowing はリアルタイム更新されないので不便なため
//if (user.isFollowing) { //if (user.isFollowing) {
const withRepliesRef = ref(user.withReplies); const withRepliesRef = ref(user.withReplies ?? false);
menu = menu.concat([{
menuItems.push({
type: 'switch', type: 'switch',
icon: 'ti ti-messages', icon: 'ti ti-messages',
text: i18n.ts.showRepliesToOthersInTimeline, text: i18n.ts.showRepliesToOthersInTimeline,
@ -327,7 +349,8 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter
icon: user.notify === 'none' ? 'ti ti-bell' : 'ti ti-bell-off', icon: user.notify === 'none' ? 'ti ti-bell' : 'ti ti-bell-off',
text: user.notify === 'none' ? i18n.ts.notifyNotes : i18n.ts.unnotifyNotes, text: user.notify === 'none' ? i18n.ts.notifyNotes : i18n.ts.unnotifyNotes,
action: toggleNotify, action: toggleNotify,
}]); });
watch(withRepliesRef, (withReplies) => { watch(withRepliesRef, (withReplies) => {
misskeyApi('following/update', { misskeyApi('following/update', {
userId: user.id, userId: user.id,
@ -338,7 +361,7 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter
}); });
//} //}
menu = menu.concat([{ type: 'divider' }, { menuItems.push({ type: 'divider' }, {
icon: user.isMuted ? 'ti ti-eye' : 'ti ti-eye-off', icon: user.isMuted ? 'ti ti-eye' : 'ti ti-eye-off',
text: user.isMuted ? i18n.ts.unmute : i18n.ts.mute, text: user.isMuted ? i18n.ts.unmute : i18n.ts.mute,
action: toggleMute, action: toggleMute,
@ -350,70 +373,68 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter
icon: 'ti ti-ban', icon: 'ti ti-ban',
text: user.isBlocking ? i18n.ts.unblock : i18n.ts.block, text: user.isBlocking ? i18n.ts.unblock : i18n.ts.block,
action: toggleBlock, action: toggleBlock,
}]); });
if (user.isFollowed) { if (user.isFollowed) {
menu = menu.concat([{ menuItems.push({
icon: 'ti ti-link-off', icon: 'ti ti-link-off',
text: i18n.ts.breakFollow, text: i18n.ts.breakFollow,
action: invalidateFollow, action: invalidateFollow,
}]); });
} }
menu = menu.concat([{ type: 'divider' }, { menuItems.push({ type: 'divider' }, {
icon: 'ti ti-exclamation-circle', icon: 'ti ti-exclamation-circle',
text: i18n.ts.reportAbuse, text: i18n.ts.reportAbuse,
action: reportAbuse, action: reportAbuse,
}]); });
} }
if (user.host !== null) { if (user.host !== null) {
menu = menu.concat([{ type: 'divider' }, { menuItems.push({ type: 'divider' }, {
icon: 'ti ti-refresh', icon: 'ti ti-refresh',
text: i18n.ts.updateRemoteUser, text: i18n.ts.updateRemoteUser,
action: userInfoUpdate, action: userInfoUpdate,
}]); });
} }
if (defaultStore.state.devMode) { if (defaultStore.state.devMode) {
menu = menu.concat([{ type: 'divider' }, { menuItems.push({ type: 'divider' }, {
icon: 'ti ti-id', icon: 'ti ti-id',
text: i18n.ts.copyUserId, text: i18n.ts.copyUserId,
action: () => { action: () => {
copyToClipboard(user.id); copyToClipboard(user.id);
}, },
}]); });
} }
if ($i && meId === user.id) { if ($i && meId === user.id) {
menu = menu.concat([{ type: 'divider' }, { menuItems.push({ type: 'divider' }, {
icon: 'ti ti-pencil', icon: 'ti ti-pencil',
text: i18n.ts.editProfile, text: i18n.ts.editProfile,
action: () => { action: () => {
router.push('/settings/profile'); router.push('/settings/profile');
}, },
}]); });
} }
if (userActions.length > 0) { if (userActions.length > 0) {
menu = menu.concat([{ type: 'divider' }, ...userActions.map(action => ({ menuItems.push({ type: 'divider' }, ...userActions.map(action => ({
icon: 'ti ti-plug', icon: 'ti ti-plug',
text: action.title, text: action.title,
action: () => { action: () => {
action.handler(user); action.handler(user);
}, },
}))]); })));
} }
const cleanup = () => {
if (_DEV_) console.log('user menu cleanup', cleanups);
for (const cl of cleanups) {
cl();
}
};
return { return {
menu, menu: menuItems,
cleanup, cleanup: () => {
if (_DEV_) console.log('user menu cleanup', cleanups);
for (const cl of cleanups) {
cl();
}
},
}; };
} }

View File

@ -41,7 +41,9 @@ function toolsMenuItems(): MenuItem[] {
} }
export function openInstanceMenu(ev: MouseEvent) { export function openInstanceMenu(ev: MouseEvent) {
os.popupMenu([{ const menuItems: MenuItem[] = [];
menuItems.push({
text: instance.name ?? host, text: instance.name ?? host,
type: 'label', type: 'label',
}, { }, {
@ -69,12 +71,18 @@ export function openInstanceMenu(ev: MouseEvent) {
text: i18n.ts.ads, text: i18n.ts.ads,
icon: 'ti ti-ad', icon: 'ti ti-ad',
to: '/ads', to: '/ads',
}, ($i && ($i.isAdmin || $i.policies.canInvite) && instance.disableRegistration) ? { });
type: 'link',
to: '/invite', if ($i && ($i.isAdmin || $i.policies.canInvite) && instance.disableRegistration) {
text: i18n.ts.invite, menuItems.push({
icon: 'ti ti-user-plus', type: 'link',
} : undefined, { to: '/invite',
text: i18n.ts.invite,
icon: 'ti ti-user-plus',
});
}
menuItems.push({
type: 'parent', type: 'parent',
text: i18n.ts.tools, text: i18n.ts.tools,
icon: 'ti ti-tool', icon: 'ti ti-tool',
@ -84,43 +92,69 @@ export function openInstanceMenu(ev: MouseEvent) {
text: i18n.ts.inquiry, text: i18n.ts.inquiry,
icon: 'ti ti-help-circle', icon: 'ti ti-help-circle',
to: '/contact', to: '/contact',
}, (instance.impressumUrl) ? { });
type: 'a',
text: i18n.ts.impressum, if (instance.impressumUrl) {
icon: 'ti ti-file-invoice', menuItems.push({
href: instance.impressumUrl, type: 'a',
target: '_blank', text: i18n.ts.impressum,
} : undefined, (instance.tosUrl) ? { icon: 'ti ti-file-invoice',
type: 'a', href: instance.impressumUrl,
text: i18n.ts.termsOfService, target: '_blank',
icon: 'ti ti-notebook', });
href: instance.tosUrl, }
target: '_blank',
} : undefined, (instance.privacyPolicyUrl) ? { if (instance.tosUrl) {
type: 'a', menuItems.push({
text: i18n.ts.privacyPolicy, type: 'a',
icon: 'ti ti-shield-lock', text: i18n.ts.termsOfService,
href: instance.privacyPolicyUrl, icon: 'ti ti-notebook',
target: '_blank', href: instance.tosUrl,
} : undefined, (!instance.impressumUrl && !instance.tosUrl && !instance.privacyPolicyUrl) ? undefined : { type: 'divider' }, { target: '_blank',
});
}
if (instance.privacyPolicyUrl) {
menuItems.push({
type: 'a',
text: i18n.ts.privacyPolicy,
icon: 'ti ti-shield-lock',
href: instance.privacyPolicyUrl,
target: '_blank',
});
}
if (!instance.impressumUrl && !instance.tosUrl && !instance.privacyPolicyUrl) {
menuItems.push({ type: 'divider' });
}
menuItems.push({
type: 'a', type: 'a',
text: i18n.ts.document, text: i18n.ts.document,
icon: 'ti ti-bulb', icon: 'ti ti-bulb',
href: 'https://misskey-hub.net/docs/for-users/', href: 'https://misskey-hub.net/docs/for-users/',
target: '_blank', target: '_blank',
}, ($i) ? { });
text: i18n.ts._initialTutorial.launchTutorial,
icon: 'ti ti-presentation', if ($i) {
action: () => { menuItems.push({
const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkTutorialDialog.vue')), {}, { text: i18n.ts._initialTutorial.launchTutorial,
closed: () => dispose(), icon: 'ti ti-presentation',
}); action: () => {
}, const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkTutorialDialog.vue')), {}, {
} : undefined, { closed: () => dispose(),
});
},
});
}
menuItems.push({
type: 'link', type: 'link',
text: i18n.ts.aboutMisskey, text: i18n.ts.aboutMisskey,
to: '/about-misskey', to: '/about-misskey',
}], ev.currentTarget ?? ev.target, { });
os.popupMenu(menuItems, ev.currentTarget ?? ev.target, {
align: 'left', align: 'left',
}); });
} }

View File

@ -118,7 +118,7 @@ import XMentionsColumn from '@/ui/deck/mentions-column.vue';
import XDirectColumn from '@/ui/deck/direct-column.vue'; import XDirectColumn from '@/ui/deck/direct-column.vue';
import XRoleTimelineColumn from '@/ui/deck/role-timeline-column.vue'; import XRoleTimelineColumn from '@/ui/deck/role-timeline-column.vue';
import { mainRouter } from '@/router/main.js'; import { mainRouter } from '@/router/main.js';
import { MenuItem } from '@/types/menu.js'; import type { MenuItem } from '@/types/menu.js';
const XStatusBars = defineAsyncComponent(() => import('@/ui/_common_/statusbars.vue')); const XStatusBars = defineAsyncComponent(() => import('@/ui/_common_/statusbars.vue'));
const XAnnouncements = defineAsyncComponent(() => import('@/ui/_common_/announcements.vue')); const XAnnouncements = defineAsyncComponent(() => import('@/ui/_common_/announcements.vue'));

View File

@ -22,7 +22,7 @@ import MkTimeline from '@/components/MkTimeline.vue';
import * as os from '@/os.js'; import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js'; import { misskeyApi } from '@/scripts/misskey-api.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { MenuItem } from '@/types/menu.js'; import type { MenuItem } from '@/types/menu.js';
import { antennasCache } from '@/cache.js'; import { antennasCache } from '@/cache.js';
import { SoundStore } from '@/store.js'; import { SoundStore } from '@/store.js';
import { soundSettingsButton } from '@/ui/deck/tl-note-notification.js'; import { soundSettingsButton } from '@/ui/deck/tl-note-notification.js';

View File

@ -29,7 +29,7 @@ import * as os from '@/os.js';
import { favoritedChannelsCache } from '@/cache.js'; import { favoritedChannelsCache } from '@/cache.js';
import { misskeyApi } from '@/scripts/misskey-api.js'; import { misskeyApi } from '@/scripts/misskey-api.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { MenuItem } from '@/types/menu.js'; import type { MenuItem } from '@/types/menu.js';
import { SoundStore } from '@/store.js'; import { SoundStore } from '@/store.js';
import { soundSettingsButton } from '@/ui/deck/tl-note-notification.js'; import { soundSettingsButton } from '@/ui/deck/tl-note-notification.js';
import * as sound from '@/scripts/sound.js'; import * as sound from '@/scripts/sound.js';

View File

@ -46,7 +46,7 @@ import { onBeforeUnmount, onMounted, provide, watch, shallowRef, ref, computed }
import { updateColumn, swapLeftColumn, swapRightColumn, swapUpColumn, swapDownColumn, stackLeftColumn, popRightColumn, removeColumn, swapColumn, Column } from './deck-store.js'; import { updateColumn, swapLeftColumn, swapRightColumn, swapUpColumn, swapDownColumn, stackLeftColumn, popRightColumn, removeColumn, swapColumn, Column } from './deck-store.js';
import * as os from '@/os.js'; import * as os from '@/os.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { MenuItem } from '@/types/menu.js'; import type { MenuItem } from '@/types/menu.js';
provide('shouldHeaderThin', true); provide('shouldHeaderThin', true);
provide('shouldOmitHeaderTitle', true); provide('shouldOmitHeaderTitle', true);
@ -104,7 +104,27 @@ function toggleActive() {
} }
function getMenu() { function getMenu() {
let items: MenuItem[] = [{ const menuItems: MenuItem[] = [];
if (props.menu) {
menuItems.push(...props.menu, {
type: 'divider',
});
}
if (props.refresher) {
menuItems.push({
icon: 'ti ti-refresh',
text: i18n.ts.reload,
action: () => {
if (props.refresher) {
props.refresher();
}
},
});
}
menuItems.push({
icon: 'ti ti-settings', icon: 'ti ti-settings',
text: i18n.ts._deck.configureColumn, text: i18n.ts._deck.configureColumn,
action: async () => { action: async () => {
@ -129,74 +149,73 @@ function getMenu() {
if (canceled) return; if (canceled) return;
updateColumn(props.column.id, result); updateColumn(props.column.id, result);
}, },
});
const moveToMenuItems: MenuItem[] = [];
moveToMenuItems.push({
icon: 'ti ti-arrow-left',
text: i18n.ts._deck.swapLeft,
action: () => {
swapLeftColumn(props.column.id);
},
}, { }, {
type: 'parent', icon: 'ti ti-arrow-right',
text: i18n.ts.move + '...', text: i18n.ts._deck.swapRight,
icon: 'ti ti-arrows-move', action: () => {
children: [{ swapRightColumn(props.column.id);
icon: 'ti ti-arrow-left', },
text: i18n.ts._deck.swapLeft, });
action: () => {
swapLeftColumn(props.column.id); if (props.isStacked) {
}, moveToMenuItems.push({
}, {
icon: 'ti ti-arrow-right',
text: i18n.ts._deck.swapRight,
action: () => {
swapRightColumn(props.column.id);
},
}, props.isStacked ? {
icon: 'ti ti-arrow-up', icon: 'ti ti-arrow-up',
text: i18n.ts._deck.swapUp, text: i18n.ts._deck.swapUp,
action: () => { action: () => {
swapUpColumn(props.column.id); swapUpColumn(props.column.id);
}, },
} : undefined, props.isStacked ? { }, {
icon: 'ti ti-arrow-down', icon: 'ti ti-arrow-down',
text: i18n.ts._deck.swapDown, text: i18n.ts._deck.swapDown,
action: () => { action: () => {
swapDownColumn(props.column.id); swapDownColumn(props.column.id);
}, },
} : undefined], });
}
menuItems.push({
type: 'parent',
text: i18n.ts.move + '...',
icon: 'ti ti-arrows-move',
children: moveToMenuItems,
}, { }, {
icon: 'ti ti-stack-2', icon: 'ti ti-stack-2',
text: i18n.ts._deck.stackLeft, text: i18n.ts._deck.stackLeft,
action: () => { action: () => {
stackLeftColumn(props.column.id); stackLeftColumn(props.column.id);
}, },
}, props.isStacked ? { });
icon: 'ti ti-window-maximize',
text: i18n.ts._deck.popRight, if (props.isStacked) {
action: () => { menuItems.push({
popRightColumn(props.column.id); icon: 'ti ti-window-maximize',
}, text: i18n.ts._deck.popRight,
} : undefined, { type: 'divider' }, { action: () => {
popRightColumn(props.column.id);
},
});
}
menuItems.push({ type: 'divider' }, {
icon: 'ti ti-trash', icon: 'ti ti-trash',
text: i18n.ts.remove, text: i18n.ts.remove,
danger: true, danger: true,
action: () => { action: () => {
removeColumn(props.column.id); removeColumn(props.column.id);
}, },
}]; });
if (props.menu) { return menuItems;
items.unshift({ type: 'divider' });
items = props.menu.concat(items);
}
if (props.refresher) {
items = [{
icon: 'ti ti-refresh',
text: i18n.ts.reload,
action: () => {
if (props.refresher) {
props.refresher();
}
},
}, ...items];
}
return items;
} }
function showSettingsMenu(ev: MouseEvent) { function showSettingsMenu(ev: MouseEvent) {

View File

@ -22,7 +22,7 @@ import MkTimeline from '@/components/MkTimeline.vue';
import * as os from '@/os.js'; import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js'; import { misskeyApi } from '@/scripts/misskey-api.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { MenuItem } from '@/types/menu.js'; import type { MenuItem } from '@/types/menu.js';
import { SoundStore } from '@/store.js'; import { SoundStore } from '@/store.js';
import { userListsCache } from '@/cache.js'; import { userListsCache } from '@/cache.js';
import { soundSettingsButton } from '@/ui/deck/tl-note-notification.js'; import { soundSettingsButton } from '@/ui/deck/tl-note-notification.js';

View File

@ -21,7 +21,7 @@ import MkTimeline from '@/components/MkTimeline.vue';
import * as os from '@/os.js'; import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js'; import { misskeyApi } from '@/scripts/misskey-api.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { MenuItem } from '@/types/menu.js'; import type { MenuItem } from '@/types/menu.js';
import { SoundStore } from '@/store.js'; import { SoundStore } from '@/store.js';
import { soundSettingsButton } from '@/ui/deck/tl-note-notification.js'; import { soundSettingsButton } from '@/ui/deck/tl-note-notification.js';
import * as sound from '@/scripts/sound.js'; import * as sound from '@/scripts/sound.js';

View File

@ -113,29 +113,41 @@ function onNote() {
sound.playMisskeySfxFile(soundSetting.value); sound.playMisskeySfxFile(soundSetting.value);
} }
const menu = computed<MenuItem[]>(() => [{ const menu = computed<MenuItem[]>(() => {
icon: 'ti ti-pencil', const menuItems: MenuItem[] = [];
text: i18n.ts.timeline,
action: setType, menuItems.push({
}, { icon: 'ti ti-pencil',
icon: 'ti ti-bell', text: i18n.ts.timeline,
text: i18n.ts._deck.newNoteNotificationSettings, action: setType,
action: () => soundSettingsButton(soundSetting), }, {
}, { icon: 'ti ti-bell',
type: 'switch', text: i18n.ts._deck.newNoteNotificationSettings,
text: i18n.ts.showRenotes, action: () => soundSettingsButton(soundSetting),
ref: withRenotes, }, {
}, hasWithReplies(props.column.tl) ? { type: 'switch',
type: 'switch', text: i18n.ts.showRenotes,
text: i18n.ts.showRepliesToOthersInTimeline, ref: withRenotes,
ref: withReplies, });
disabled: onlyFiles,
} : undefined, { if (hasWithReplies(props.column.tl)) {
type: 'switch', menuItems.push({
text: i18n.ts.fileAttachedOnly, type: 'switch',
ref: onlyFiles, text: i18n.ts.showRepliesToOthersInTimeline,
disabled: hasWithReplies(props.column.tl) ? withReplies : false, ref: withReplies,
}]); disabled: onlyFiles,
});
}
menuItems.push({
type: 'switch',
text: i18n.ts.fileAttachedOnly,
ref: onlyFiles,
disabled: hasWithReplies(props.column.tl) ? withReplies : false,
});
return menuItems;
});
</script> </script>
<style lang="scss" module> <style lang="scss" module>

View File

@ -40,6 +40,7 @@ import MkContainer from '@/components/MkContainer.vue';
import MkTimeline from '@/components/MkTimeline.vue'; import MkTimeline from '@/components/MkTimeline.vue';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { availableBasicTimelines, isAvailableBasicTimeline, isBasicTimeline, basicTimelineIconClass } from '@/timelines.js'; import { availableBasicTimelines, isAvailableBasicTimeline, isBasicTimeline, basicTimelineIconClass } from '@/timelines.js';
import type { MenuItem } from '@/types/menu.js';
const name = 'timeline'; const name = 'timeline';
@ -109,11 +110,26 @@ const choose = async (ev) => {
setSrc('list'); setSrc('list');
}, },
})); }));
os.popupMenu([...availableBasicTimelines().map(tl => ({
const menuItems: MenuItem[] = [];
menuItems.push(...availableBasicTimelines().map(tl => ({
text: i18n.ts._timelines[tl], text: i18n.ts._timelines[tl],
icon: basicTimelineIconClass(tl), icon: basicTimelineIconClass(tl),
action: () => { setSrc(tl); }, action: () => { setSrc(tl); },
})), antennaItems.length > 0 ? { type: 'divider' } : undefined, ...antennaItems, listItems.length > 0 ? { type: 'divider' } : undefined, ...listItems], ev.currentTarget ?? ev.target).then(() => { })));
if (antennaItems.length > 0) {
menuItems.push({ type: 'divider' });
menuItems.push(...antennaItems);
}
if (listItems.length > 0) {
menuItems.push({ type: 'divider' });
menuItems.push(...listItems);
}
os.popupMenu(menuItems, ev.currentTarget ?? ev.target).then(() => {
menuOpened.value = false; menuOpened.value = false;
}); });
}; };