diff --git a/packages/frontend/src/account.ts b/packages/frontend/src/account.ts
index f388397466..84d89b1b3f 100644
--- a/packages/frontend/src/account.ts
+++ b/packages/frontend/src/account.ts
@@ -8,7 +8,7 @@ import * as Misskey from 'misskey-js';
import { showSuspendedDialog } from '@/scripts/show-suspended-dialog.js';
import { i18n } from '@/i18n.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 { apiUrl } from '@@/js/config.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) {
- popupMenu([...[{
- type: 'link' as const,
+ menuItems.push({
+ type: 'link',
text: i18n.ts.profile,
- to: `/@${ $i.username }`,
+ to: `/@${$i.username}`,
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',
text: i18n.ts.addAccount,
children: [{
@@ -306,18 +318,22 @@ export async function openAccountMenu(opts: {
action: () => { createAccount(); },
}],
}, {
- type: 'link' as const,
+ type: 'link',
icon: 'ti ti-users',
text: i18n.ts.manageAccounts,
to: '/settings/accounts',
- }]], ev.currentTarget ?? ev.target, {
- align: 'left',
});
} else {
- popupMenu([...(opts.includeCurrentAccount ? [createItem($i)] : []), ...accountItemPromises], ev.currentTarget ?? ev.target, {
- align: 'left',
- });
+ if (opts.includeCurrentAccount) {
+ menuItems.push(createItem($i));
+ }
+
+ menuItems.push(...accountItemPromises);
}
+
+ popupMenu(menuItems, ev.currentTarget ?? ev.target, {
+ align: 'left',
+ });
}
if (_DEV_) {
diff --git a/packages/frontend/src/components/MkContextMenu.vue b/packages/frontend/src/components/MkContextMenu.vue
index 8ea8fa6cf3..f51fefa0c0 100644
--- a/packages/frontend/src/components/MkContextMenu.vue
+++ b/packages/frontend/src/components/MkContextMenu.vue
@@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
diff --git a/packages/frontend/src/components/MkMediaVideo.vue b/packages/frontend/src/components/MkMediaVideo.vue
index 7c5a365148..1b1915e6c8 100644
--- a/packages/frontend/src/components/MkMediaVideo.vue
+++ b/packages/frontend/src/components/MkMediaVideo.vue
@@ -192,9 +192,7 @@ async function show() {
const menuShowing = ref(false);
function showMenu(ev: MouseEvent) {
- let menu: MenuItem[] = [];
-
- menu = [
+ const menu: MenuItem[] = [
// TODO: 再生キューに追加
{
type: 'switch',
@@ -247,7 +245,7 @@ function showMenu(ev: MouseEvent) {
menu.push({
type: 'divider',
}, {
- type: 'link' as const,
+ type: 'link',
text: i18n.ts._fileViewer.title,
icon: 'ti ti-info-circle',
to: `/my/drive/file/${props.video.id}`,
diff --git a/packages/frontend/src/components/MkMenu.child.vue b/packages/frontend/src/components/MkMenu.child.vue
index 235790556c..086573ba6d 100644
--- a/packages/frontend/src/components/MkMenu.child.vue
+++ b/packages/frontend/src/components/MkMenu.child.vue
@@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
diff --git a/packages/frontend/src/components/MkSelect.vue b/packages/frontend/src/components/MkSelect.vue
index 360d697d7c..343524fc82 100644
--- a/packages/frontend/src/components/MkSelect.vue
+++ b/packages/frontend/src/components/MkSelect.vue
@@ -46,7 +46,7 @@ import MkButton from '@/components/MkButton.vue';
import * as os from '@/os.js';
import { useInterval } from '@@/js/use-interval.js';
import { i18n } from '@/i18n.js';
-import { MenuItem } from '@/types/menu.js';
+import type { MenuItem } from '@/types/menu.js';
const props = defineProps<{
modelValue: string | null;
diff --git a/packages/frontend/src/components/MkWindow.vue b/packages/frontend/src/components/MkWindow.vue
index 26ba598498..08906a1205 100644
--- a/packages/frontend/src/components/MkWindow.vue
+++ b/packages/frontend/src/components/MkWindow.vue
@@ -56,7 +56,7 @@ SPDX-License-Identifier: AGPL-3.0-only
import { onBeforeUnmount, onMounted, provide, shallowRef, ref } from 'vue';
import contains from '@/scripts/contains.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 { defaultStore } from '@/store.js';
diff --git a/packages/frontend/src/components/global/MkCustomEmoji.vue b/packages/frontend/src/components/global/MkCustomEmoji.vue
index dff56cd7f0..66f82a7898 100644
--- a/packages/frontend/src/components/global/MkCustomEmoji.vue
+++ b/packages/frontend/src/components/global/MkCustomEmoji.vue
@@ -35,6 +35,7 @@ import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
import * as sound from '@/scripts/sound.js';
import { i18n } from '@/i18n.js';
import MkCustomEmojiDetailedDialog from '@/components/MkCustomEmojiDetailedDialog.vue';
+import type { MenuItem } from '@/types/menu.js';
const props = defineProps<{
name: string;
@@ -85,7 +86,9 @@ const errored = ref(url.value == null);
function onClick(ev: MouseEvent) {
if (props.menu) {
- os.popupMenu([{
+ const menuItems: MenuItem[] = [];
+
+ menuItems.push({
type: 'label',
text: `:${props.name}:`,
}, {
@@ -95,14 +98,20 @@ function onClick(ev: MouseEvent) {
copyToClipboard(`:${props.name}:`);
os.success();
},
- }, ...(props.menuReaction && react ? [{
- text: i18n.ts.doReaction,
- icon: 'ti ti-plus',
- action: () => {
- react(`:${props.name}:`);
- sound.playMisskeySfx('reaction');
- },
- }] : []), {
+ });
+
+ if (props.menuReaction && react) {
+ menuItems.push({
+ text: i18n.ts.doReaction,
+ icon: 'ti ti-plus',
+ action: () => {
+ react(`:${props.name}:`);
+ sound.playMisskeySfx('reaction');
+ },
+ });
+ }
+
+ menuItems.push({
text: i18n.ts.info,
icon: 'ti ti-info-circle',
action: async () => {
@@ -114,7 +123,9 @@ function onClick(ev: MouseEvent) {
closed: () => dispose(),
});
},
- }], ev.currentTarget ?? ev.target);
+ });
+
+ os.popupMenu(menuItems, ev.currentTarget ?? ev.target);
}
}
diff --git a/packages/frontend/src/components/global/MkEmoji.vue b/packages/frontend/src/components/global/MkEmoji.vue
index fc3745c009..f0acd3bc27 100644
--- a/packages/frontend/src/components/global/MkEmoji.vue
+++ b/packages/frontend/src/components/global/MkEmoji.vue
@@ -17,6 +17,7 @@ import * as os from '@/os.js';
import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
import * as sound from '@/scripts/sound.js';
import { i18n } from '@/i18n.js';
+import type { MenuItem } from '@/types/menu.js';
const props = defineProps<{
emoji: string;
@@ -39,7 +40,9 @@ function computeTitle(event: PointerEvent): void {
function onClick(ev: MouseEvent) {
if (props.menu) {
- os.popupMenu([{
+ const menuItems: MenuItem[] = [];
+
+ menuItems.push({
type: 'label',
text: props.emoji,
}, {
@@ -49,14 +52,20 @@ function onClick(ev: MouseEvent) {
copyToClipboard(props.emoji);
os.success();
},
- }, ...(props.menuReaction && react ? [{
- text: i18n.ts.doReaction,
- icon: 'ti ti-plus',
- action: () => {
- react(props.emoji);
- sound.playMisskeySfx('reaction');
- },
- }] : [])], ev.currentTarget ?? ev.target);
+ });
+
+ if (props.menuReaction && react) {
+ menuItems.push({
+ text: i18n.ts.doReaction,
+ icon: 'ti ti-plus',
+ action: () => {
+ react(props.emoji);
+ sound.playMisskeySfx('reaction');
+ },
+ });
+ }
+
+ os.popupMenu(menuItems, ev.currentTarget ?? ev.target);
}
}
diff --git a/packages/frontend/src/navbar.ts b/packages/frontend/src/navbar.ts
index a96a4f0539..ac730f8021 100644
--- a/packages/frontend/src/navbar.ts
+++ b/packages/frontend/src/navbar.ts
@@ -125,7 +125,7 @@ export const navbarItemDef = reactive({
ui: {
title: i18n.ts.switchUi,
icon: 'ti ti-devices',
- action: (ev) => {
+ action: (ev: MouseEvent) => {
os.popupMenu([{
text: i18n.ts.default,
active: ui === 'default' || ui === null,
diff --git a/packages/frontend/src/os.ts b/packages/frontend/src/os.ts
index f42e2ed3c5..60e4218a48 100644
--- a/packages/frontend/src/os.ts
+++ b/packages/frontend/src/os.ts
@@ -22,7 +22,7 @@ import MkPasswordDialog from '@/components/MkPasswordDialog.vue';
import MkEmojiPickerDialog from '@/components/MkEmojiPickerDialog.vue';
import MkPopupMenu from '@/components/MkPopupMenu.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 { pleaseLogin } from '@/scripts/please-login.js';
import { showMovedDialog } from '@/scripts/show-moved-dialog.js';
diff --git a/packages/frontend/src/pages/clip.vue b/packages/frontend/src/pages/clip.vue
index 7bfa343b1d..7e5f0423f6 100644
--- a/packages/frontend/src/pages/clip.vue
+++ b/packages/frontend/src/pages/clip.vue
@@ -45,6 +45,7 @@ import { clipsCache } from '@/cache.js';
import { isSupportShare } from '@/scripts/navigator.js';
import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
import { genEmbedCode } from '@/scripts/get-embed-code.js';
+import type { MenuItem } from '@/types/menu.js';
const props = defineProps<{
clipId: string,
@@ -131,7 +132,9 @@ const headerActions = computed(() => clip.value && isOwned.value ? [{
icon: 'ti ti-share',
text: i18n.ts.share,
handler: (ev: MouseEvent): void => {
- os.popupMenu([{
+ const menuItems: MenuItem[] = [];
+
+ menuItems.push({
icon: 'ti ti-link',
text: i18n.ts.copyUrl,
action: () => {
@@ -144,17 +147,23 @@ const headerActions = computed(() => clip.value && isOwned.value ? [{
action: () => {
genEmbedCode('clips', clip.value!.id);
},
- }, ...(isSupportShare() ? [{
- icon: 'ti ti-share',
- text: i18n.ts.share,
- action: async () => {
- navigator.share({
- title: clip.value!.name,
- text: clip.value!.description ?? '',
- url: `${url}/clips/${clip.value!.id}`,
- });
- },
- }] : [])], ev.currentTarget ?? ev.target);
+ });
+
+ if (isSupportShare()) {
+ menuItems.push({
+ icon: 'ti ti-share',
+ text: i18n.ts.share,
+ action: async () => {
+ navigator.share({
+ title: clip.value!.name,
+ text: clip.value!.description ?? '',
+ url: `${url}/clips/${clip.value!.id}`,
+ });
+ },
+ });
+ }
+
+ os.popupMenu(menuItems, ev.currentTarget ?? ev.target);
},
}] : []), {
icon: 'ti ti-trash',
diff --git a/packages/frontend/src/pages/flash/flash.vue b/packages/frontend/src/pages/flash/flash.vue
index 3b4deaf537..cf10bee0f5 100644
--- a/packages/frontend/src/pages/flash/flash.vue
+++ b/packages/frontend/src/pages/flash/flash.vue
@@ -80,7 +80,7 @@ import { defaultStore } from '@/store.js';
import { $i } from '@/account.js';
import { isSupportShare } from '@/scripts/navigator.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';
const props = defineProps<{
@@ -104,18 +104,23 @@ function fetchFlash() {
function share(ev: MouseEvent) {
if (!flash.value) return;
- os.popupMenu([
- {
- text: i18n.ts.shareWithNote,
- icon: 'ti ti-pencil',
- action: shareWithNote,
- },
- ...(isSupportShare() ? [{
+ const menuItems: MenuItem[] = [];
+
+ menuItems.push({
+ text: i18n.ts.shareWithNote,
+ icon: 'ti ti-pencil',
+ action: shareWithNote,
+ });
+
+ if (isSupportShare()) {
+ menuItems.push({
text: i18n.ts.share,
icon: 'ti ti-share',
action: shareWithNavigator,
- }] : []),
- ], ev.currentTarget ?? ev.target);
+ });
+ }
+
+ os.popupMenu(menuItems, ev.currentTarget ?? ev.target);
}
function copyLink() {
diff --git a/packages/frontend/src/pages/gallery/post.vue b/packages/frontend/src/pages/gallery/post.vue
index dfee66d906..8c4dfc3b83 100644
--- a/packages/frontend/src/pages/gallery/post.vue
+++ b/packages/frontend/src/pages/gallery/post.vue
@@ -80,7 +80,7 @@ import { $i } from '@/account.js';
import { isSupportShare } from '@/scripts/navigator.js';
import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
import { useRouter } from '@/router/supplier.js';
-import { MenuItem } from '@/types/menu';
+import type { MenuItem } from '@/types/menu.js';
const router = useRouter();
@@ -171,35 +171,35 @@ function reportAbuse() {
function showMenu(ev: MouseEvent) {
if (!post.value) return;
- const menu: 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;
+ const menuItems: MenuItem[] = [];
- 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 });
diff --git a/packages/frontend/src/pages/my-lists/list.vue b/packages/frontend/src/pages/my-lists/list.vue
index a2ceb222fe..5f195693cc 100644
--- a/packages/frontend/src/pages/my-lists/list.vue
+++ b/packages/frontend/src/pages/my-lists/list.vue
@@ -134,12 +134,14 @@ async function removeUser(item, ev) {
async function showMembershipMenu(item, ev) {
const withRepliesRef = ref(item.withReplies);
+
os.popupMenu([{
type: 'switch',
text: i18n.ts.showRepliesToOthersInTimeline,
icon: 'ti ti-messages',
ref: withRepliesRef,
}], ev.currentTarget ?? ev.target);
+
watch(withRepliesRef, withReplies => {
misskeyApi('users/lists/update-membership', {
listId: list.value!.id,
diff --git a/packages/frontend/src/pages/page.vue b/packages/frontend/src/pages/page.vue
index 381b80cd29..7926dab88b 100644
--- a/packages/frontend/src/pages/page.vue
+++ b/packages/frontend/src/pages/page.vue
@@ -121,7 +121,7 @@ import { instance } from '@/instance.js';
import { getStaticImageUrl } from '@/scripts/media-proxy.js';
import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
import { useRouter } from '@/router/supplier.js';
-import { MenuItem } from '@/types/menu';
+import type { MenuItem } from '@/types/menu.js';
const router = useRouter();
@@ -165,18 +165,23 @@ function fetchPage() {
function share(ev: MouseEvent) {
if (!page.value) return;
- os.popupMenu([
- {
- text: i18n.ts.shareWithNote,
- icon: 'ti ti-pencil',
- action: shareWithNote,
- },
- ...(isSupportShare() ? [{
+ const menuItems: MenuItem[] = [];
+
+ menuItems.push({
+ text: i18n.ts.shareWithNote,
+ icon: 'ti ti-pencil',
+ action: shareWithNote,
+ });
+
+ if (isSupportShare()) {
+ menuItems.push({
text: i18n.ts.share,
icon: 'ti ti-share',
action: shareWithNavigator,
- }] : []),
- ], ev.currentTarget ?? ev.target);
+ });
+ }
+
+ os.popupMenu(menuItems, ev.currentTarget ?? ev.target);
}
function copyLink() {
@@ -256,51 +261,59 @@ function reportAbuse() {
function showMenu(ev: MouseEvent) {
if (!page.value) return;
- const menu: MenuItem[] = [
- ...($i && $i.id === page.value.userId ? [
- {
- icon: 'ti ti-code',
- text: i18n.ts._pages.viewSource,
- action: () => router.push(`/@${props.username}/pages/${props.pageName}/view-source`),
- },
- ...($i.pinnedPageId === page.value.id ? [{
+ const menuItems: MenuItem[] = [];
+
+ if ($i && $i.id === page.value.userId) {
+ menuItems.push({
+ icon: 'ti ti-pencil',
+ text: i18n.ts.editThisPage,
+ action: () => router.push(`/pages/edit/${page.value.id}`),
+ });
+
+ if ($i.pinnedPageId === page.value.id) {
+ menuItems.push({
icon: 'ti ti-pinned-off',
text: i18n.ts.unpin,
action: () => pin(false),
- }] : [{
+ });
+ } else {
+ menuItems.push({
icon: 'ti ti-pin',
text: i18n.ts.pin,
action: () => pin(true),
- }]),
- ] : []),
- ...($i && $i.id !== page.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 || !page.value) return;
+ });
+ }
+ } else if ($i && $i.id !== page.value.userId) {
+ menuItems.push({
+ icon: 'ti ti-code',
+ text: i18n.ts._pages.viewSource,
+ action: () => router.push(`/@${props.username}/pages/${props.pageName}/view-source`),
+ }, {
+ icon: 'ti ti-exclamation-circle',
+ text: i18n.ts.reportAbuse,
+ action: reportAbuse,
+ });
- 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 });
diff --git a/packages/frontend/src/pages/reversi/game.setting.vue b/packages/frontend/src/pages/reversi/game.setting.vue
index 31c0003130..08bb3cb76c 100644
--- a/packages/frontend/src/pages/reversi/game.setting.vue
+++ b/packages/frontend/src/pages/reversi/game.setting.vue
@@ -121,7 +121,7 @@ import MkRadios from '@/components/MkRadios.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import MkFolder from '@/components/MkFolder.vue';
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';
const $i = signinRequired();
diff --git a/packages/frontend/src/pages/timeline.vue b/packages/frontend/src/pages/timeline.vue
index cc1ed3d01f..12e2db2293 100644
--- a/packages/frontend/src/pages/timeline.vue
+++ b/packages/frontend/src/pages/timeline.vue
@@ -50,7 +50,7 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
import { antennasCache, userListsCache, favoritedChannelsCache } from '@/cache.js';
import { deviceKind } from '@/scripts/device-kind.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 { availableBasicTimelines, hasWithReplies, isAvailableBasicTimeline, isBasicTimeline, basicTimelineIconClass } from '@/timelines.js';
import type { BasicTimelineType } from '@/timelines.js';
@@ -189,7 +189,7 @@ async function chooseChannel(ev: MouseEvent): Promise {
}),
(channels.length === 0 ? undefined : { type: 'divider' }),
{
- type: 'link' as const,
+ type: 'link',
icon: 'ti ti-plus',
text: i18n.ts.createNew,
to: '/channels',
@@ -258,16 +258,24 @@ const headerActions = computed(() => {
icon: 'ti ti-dots',
text: i18n.ts.options,
handler: (ev) => {
- os.popupMenu([{
+ const menuItems: MenuItem[] = [];
+
+ menuItems.push({
type: 'switch',
text: i18n.ts.showRenotes,
ref: withRenotes,
- }, isBasicTimeline(src.value) && hasWithReplies(src.value) ? {
- type: 'switch',
- text: i18n.ts.showRepliesToOthersInTimeline,
- ref: withReplies,
- disabled: onlyFiles,
- } : undefined, {
+ });
+
+ if (isBasicTimeline(src.value) && hasWithReplies(src.value)) {
+ menuItems.push({
+ type: 'switch',
+ text: i18n.ts.showRepliesToOthersInTimeline,
+ ref: withReplies,
+ disabled: onlyFiles,
+ });
+ }
+
+ menuItems.push({
type: 'switch',
text: i18n.ts.withSensitive,
ref: withSensitive,
@@ -276,7 +284,9 @@ const headerActions = computed(() => {
text: i18n.ts.fileAttachedOnly,
ref: onlyFiles,
disabled: isBasicTimeline(src.value) && hasWithReplies(src.value) ? withReplies : false,
- }], ev.currentTarget ?? ev.target);
+ });
+
+ os.popupMenu(menuItems, ev.currentTarget ?? ev.target);
},
},
];
diff --git a/packages/frontend/src/scripts/get-drive-file-menu.ts b/packages/frontend/src/scripts/get-drive-file-menu.ts
index 108648d640..c8ab9238d3 100644
--- a/packages/frontend/src/scripts/get-drive-file-menu.ts
+++ b/packages/frontend/src/scripts/get-drive-file-menu.ts
@@ -9,7 +9,7 @@ import { i18n } from '@/i18n.js';
import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
import * as os from '@/os.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';
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[] {
const isImage = file.type.startsWith('image/');
- let menu;
- menu = [{
+
+ const menuItems: MenuItem[] = [];
+
+ menuItems.push({
type: 'link',
to: `/my/drive/file/${file.id}`,
text: i18n.ts._fileViewer.title,
@@ -109,14 +111,20 @@ export function getDriveFileMenu(file: Misskey.entities.DriveFile, folder?: Miss
text: i18n.ts.describeFile,
icon: 'ti ti-text-caption',
action: () => describe(file),
- }, ...isImage ? [{
- text: i18n.ts.cropImage,
- icon: 'ti ti-crop',
- action: () => os.cropImage(file, {
- aspectRatio: NaN,
- uploadFolder: folder ? folder.id : folder,
- }),
- }] : [], { type: 'divider' }, {
+ });
+
+ if (isImage) {
+ menuItems.push({
+ text: i18n.ts.cropImage,
+ icon: 'ti ti-crop',
+ action: () => os.cropImage(file, {
+ aspectRatio: NaN,
+ uploadFolder: folder ? folder.id : folder,
+ }),
+ });
+ }
+
+ menuItems.push({ type: 'divider' }, {
text: i18n.ts.createNoteFromTheFile,
icon: 'ti ti-pencil',
action: () => os.post({
@@ -138,17 +146,17 @@ export function getDriveFileMenu(file: Misskey.entities.DriveFile, folder?: Miss
icon: 'ti ti-trash',
danger: true,
action: () => deleteFile(file),
- }];
+ });
if (defaultStore.state.devMode) {
- menu = menu.concat([{ type: 'divider' }, {
+ menuItems.push({ type: 'divider' }, {
icon: 'ti ti-id',
text: i18n.ts.copyFileId,
action: () => {
copyToClipboard(file.id);
},
- }]);
+ });
}
- return menu;
+ return menuItems;
}
diff --git a/packages/frontend/src/scripts/get-note-menu.ts b/packages/frontend/src/scripts/get-note-menu.ts
index 49f3199887..4ffa0ab94d 100644
--- a/packages/frontend/src/scripts/get-note-menu.ts
+++ b/packages/frontend/src/scripts/get-note-menu.ts
@@ -17,7 +17,7 @@ import { defaultStore, noteActions } from '@/store.js';
import { miLocalStorage } from '@/local-storage.js';
import { getUserMenu } from '@/scripts/get-user-menu.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 { isSupportShare } from '@/scripts/navigator.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, {
name: {
type: 'string',
+ default: null,
label: i18n.ts.name,
},
description: {
type: 'string',
required: false,
+ default: null,
multiline: true,
label: i18n.ts.description,
},
@@ -264,7 +266,7 @@ export function getNoteMenu(props: {
title: i18n.ts.numberOfDays,
});
- if (canceled) return;
+ if (canceled || days == null) return;
os.apiWithDialog('admin/promo/create', {
noteId: appearNote.id,
@@ -295,161 +297,23 @@ export function getNoteMenu(props: {
props.translation.value = res;
}
- let menu: MenuItem[];
+ const menuItems: MenuItem[] = [];
+
if ($i) {
const statePromise = misskeyApi('notes/state', {
noteId: appearNote.id,
});
- menu = [
- ...(
- props.currentClip?.userId === $i.id ? [{
- icon: 'ti ti-backspace',
- text: i18n.ts.unclip,
- danger: true,
- action: unclip,
- }, { 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[];
+ if (props.currentClip?.userId === $i.id) {
+ menuItems.push({
+ icon: 'ti ti-backspace',
+ text: i18n.ts.unclip,
+ danger: true,
+ action: unclip,
+ }, { type: 'divider' });
+ }
- 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;
- },
- },
- ]
- : []
- ),
- ...(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 = [{
+ menuItems.push({
icon: 'ti ti-info-circle',
text: i18n.ts.details,
action: openDetail,
@@ -457,35 +321,194 @@ export function getNoteMenu(props: {
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');
+ }, 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 (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) {
- menu = menu.concat([{ type: 'divider' }, ...noteActions.map(action => ({
+ menuItems.push({ type: 'divider' });
+
+ menuItems.push(...noteActions.map(action => ({
icon: 'ti ti-plug',
text: action.title,
action: () => {
action.handler(appearNote);
},
- }))]);
+ })));
}
if (defaultStore.state.devMode) {
- menu = menu.concat([{ type: 'divider' }, {
+ menuItems.push({ type: 'divider' }, {
icon: 'ti ti-id',
text: i18n.ts.copyNoteId,
action: () => {
copyToClipboard(appearNote.id);
+ os.success();
},
- }]);
+ });
}
const cleanup = () => {
@@ -496,7 +519,7 @@ export function getNoteMenu(props: {
};
return {
- menu,
+ menu: menuItems,
cleanup,
};
}
diff --git a/packages/frontend/src/scripts/get-user-menu.ts b/packages/frontend/src/scripts/get-user-menu.ts
index 33316b4ab6..d15279d633 100644
--- a/packages/frontend/src/scripts/get-user-menu.ts
+++ b/packages/frontend/src/scripts/get-user-menu.ts
@@ -18,7 +18,7 @@ import { IRouter } from '@/nirax.js';
import { antennasCache, rolesCache, userListsCache } from '@/cache.js';
import { mainRouter } from '@/router/main.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) {
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',
text: i18n.ts.copyUsername,
action: () => {
copyToClipboard(`@${user.username}@${user.host ?? host}`);
},
- }, ...( notesSearchAvailable && (user.host == null || canSearchNonLocalNotes) ? [{
- icon: 'ti ti-search',
- text: i18n.ts.searchThisUsersNotes,
- action: () => {
- router.push(`/search?username=${encodeURIComponent(user.username)}${user.host != null ? '&host=' + encodeURIComponent(user.host) : ''}`);
- },
- }] : [])
- , ...(iAmModerator ? [{
- icon: 'ti ti-user-exclamation',
- text: i18n.ts.moderation,
- action: () => {
- router.push(`/admin/user/${user.id}`);
- },
- }] : []), {
+ });
+
+ if (notesSearchAvailable && (user.host == null || canSearchNonLocalNotes)) {
+ menuItems.push({
+ icon: 'ti ti-search',
+ text: i18n.ts.searchThisUsersNotes,
+ action: () => {
+ router.push(`/search?username=${encodeURIComponent(user.username)}${user.host != null ? '&host=' + encodeURIComponent(user.host) : ''}`);
+ },
+ });
+ }
+
+ 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',
text: i18n.ts.copyRSS,
action: () => {
copyToClipboard(`${user.host ?? host}/@${user.username}.atom`);
},
- }, ...(user.host != null && user.url != null ? [{
- icon: 'ti ti-external-link',
- text: i18n.ts.showOnRemote,
- action: () => {
- if (user.url == null) return;
- window.open(user.url, '_blank', 'noopener');
- },
- }] : [{
- icon: 'ti ti-code',
- text: i18n.ts.genEmbedCode,
- type: 'parent' as const,
- children: [{
- text: i18n.ts.noteOfThisUser,
+ });
+
+ if (user.host != null && user.url != null) {
+ menuItems.push({
+ icon: 'ti ti-external-link',
+ text: i18n.ts.showOnRemote,
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',
text: i18n.ts.copyProfileUrl,
action: () => {
const canonical = user.host === null ? `@${user.username}` : `@${user.username}@${toUnicode(user.host)}`;
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 {
- 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();
- },
- }));
- },
- }] : [])] as any;
+ if ($i) {
+ menuItems.push({
+ 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) ?? false);
+ 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 {
+ 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 (iAmModerator) {
- menu = menu.concat([{
+ menuItems.push({
type: 'parent',
icon: 'ti ti-badges',
text: i18n.ts.roles,
@@ -312,13 +333,14 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter
},
}));
},
- }]);
+ });
}
// フォローしたとしても user.isFollowing はリアルタイム更新されないので不便なため
//if (user.isFollowing) {
- const withRepliesRef = ref(user.withReplies);
- menu = menu.concat([{
+ const withRepliesRef = ref(user.withReplies ?? false);
+
+ menuItems.push({
type: 'switch',
icon: 'ti ti-messages',
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',
text: user.notify === 'none' ? i18n.ts.notifyNotes : i18n.ts.unnotifyNotes,
action: toggleNotify,
- }]);
+ });
+
watch(withRepliesRef, (withReplies) => {
misskeyApi('following/update', {
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',
text: user.isMuted ? i18n.ts.unmute : i18n.ts.mute,
action: toggleMute,
@@ -350,70 +373,68 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter
icon: 'ti ti-ban',
text: user.isBlocking ? i18n.ts.unblock : i18n.ts.block,
action: toggleBlock,
- }]);
+ });
if (user.isFollowed) {
- menu = menu.concat([{
+ menuItems.push({
icon: 'ti ti-link-off',
text: i18n.ts.breakFollow,
action: invalidateFollow,
- }]);
+ });
}
- menu = menu.concat([{ type: 'divider' }, {
+ menuItems.push({ type: 'divider' }, {
icon: 'ti ti-exclamation-circle',
text: i18n.ts.reportAbuse,
action: reportAbuse,
- }]);
+ });
}
if (user.host !== null) {
- menu = menu.concat([{ type: 'divider' }, {
+ menuItems.push({ type: 'divider' }, {
icon: 'ti ti-refresh',
text: i18n.ts.updateRemoteUser,
action: userInfoUpdate,
- }]);
+ });
}
if (defaultStore.state.devMode) {
- menu = menu.concat([{ type: 'divider' }, {
+ menuItems.push({ type: 'divider' }, {
icon: 'ti ti-id',
text: i18n.ts.copyUserId,
action: () => {
copyToClipboard(user.id);
},
- }]);
+ });
}
if ($i && meId === user.id) {
- menu = menu.concat([{ type: 'divider' }, {
+ menuItems.push({ type: 'divider' }, {
icon: 'ti ti-pencil',
text: i18n.ts.editProfile,
action: () => {
router.push('/settings/profile');
},
- }]);
+ });
}
if (userActions.length > 0) {
- menu = menu.concat([{ type: 'divider' }, ...userActions.map(action => ({
+ menuItems.push({ type: 'divider' }, ...userActions.map(action => ({
icon: 'ti ti-plug',
text: action.title,
action: () => {
action.handler(user);
},
- }))]);
+ })));
}
- const cleanup = () => {
- if (_DEV_) console.log('user menu cleanup', cleanups);
- for (const cl of cleanups) {
- cl();
- }
- };
-
return {
- menu,
- cleanup,
+ menu: menuItems,
+ cleanup: () => {
+ if (_DEV_) console.log('user menu cleanup', cleanups);
+ for (const cl of cleanups) {
+ cl();
+ }
+ },
};
}
diff --git a/packages/frontend/src/ui/_common_/common.ts b/packages/frontend/src/ui/_common_/common.ts
index b067e721a5..f908803f01 100644
--- a/packages/frontend/src/ui/_common_/common.ts
+++ b/packages/frontend/src/ui/_common_/common.ts
@@ -41,7 +41,9 @@ function toolsMenuItems(): MenuItem[] {
}
export function openInstanceMenu(ev: MouseEvent) {
- os.popupMenu([{
+ const menuItems: MenuItem[] = [];
+
+ menuItems.push({
text: instance.name ?? host,
type: 'label',
}, {
@@ -69,12 +71,18 @@ export function openInstanceMenu(ev: MouseEvent) {
text: i18n.ts.ads,
icon: 'ti ti-ad',
to: '/ads',
- }, ($i && ($i.isAdmin || $i.policies.canInvite) && instance.disableRegistration) ? {
- type: 'link',
- to: '/invite',
- text: i18n.ts.invite,
- icon: 'ti ti-user-plus',
- } : undefined, {
+ });
+
+ if ($i && ($i.isAdmin || $i.policies.canInvite) && instance.disableRegistration) {
+ menuItems.push({
+ type: 'link',
+ to: '/invite',
+ text: i18n.ts.invite,
+ icon: 'ti ti-user-plus',
+ });
+ }
+
+ menuItems.push({
type: 'parent',
text: i18n.ts.tools,
icon: 'ti ti-tool',
@@ -84,43 +92,69 @@ export function openInstanceMenu(ev: MouseEvent) {
text: i18n.ts.inquiry,
icon: 'ti ti-help-circle',
to: '/contact',
- }, (instance.impressumUrl) ? {
- type: 'a',
- text: i18n.ts.impressum,
- icon: 'ti ti-file-invoice',
- href: instance.impressumUrl,
- target: '_blank',
- } : undefined, (instance.tosUrl) ? {
- type: 'a',
- text: i18n.ts.termsOfService,
- icon: 'ti ti-notebook',
- href: instance.tosUrl,
- target: '_blank',
- } : undefined, (instance.privacyPolicyUrl) ? {
- type: 'a',
- text: i18n.ts.privacyPolicy,
- icon: 'ti ti-shield-lock',
- href: instance.privacyPolicyUrl,
- target: '_blank',
- } : undefined, (!instance.impressumUrl && !instance.tosUrl && !instance.privacyPolicyUrl) ? undefined : { type: 'divider' }, {
+ });
+
+ if (instance.impressumUrl) {
+ menuItems.push({
+ type: 'a',
+ text: i18n.ts.impressum,
+ icon: 'ti ti-file-invoice',
+ href: instance.impressumUrl,
+ target: '_blank',
+ });
+ }
+
+ if (instance.tosUrl) {
+ menuItems.push({
+ type: 'a',
+ text: i18n.ts.termsOfService,
+ icon: 'ti ti-notebook',
+ href: instance.tosUrl,
+ 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',
text: i18n.ts.document,
icon: 'ti ti-bulb',
href: 'https://misskey-hub.net/docs/for-users/',
target: '_blank',
- }, ($i) ? {
- text: i18n.ts._initialTutorial.launchTutorial,
- icon: 'ti ti-presentation',
- action: () => {
- const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkTutorialDialog.vue')), {}, {
- closed: () => dispose(),
- });
- },
- } : undefined, {
+ });
+
+ if ($i) {
+ menuItems.push({
+ text: i18n.ts._initialTutorial.launchTutorial,
+ icon: 'ti ti-presentation',
+ action: () => {
+ const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkTutorialDialog.vue')), {}, {
+ closed: () => dispose(),
+ });
+ },
+ });
+ }
+
+ menuItems.push({
type: 'link',
text: i18n.ts.aboutMisskey,
to: '/about-misskey',
- }], ev.currentTarget ?? ev.target, {
+ });
+
+ os.popupMenu(menuItems, ev.currentTarget ?? ev.target, {
align: 'left',
});
}
diff --git a/packages/frontend/src/ui/deck.vue b/packages/frontend/src/ui/deck.vue
index 9c3addc482..750cdca90e 100644
--- a/packages/frontend/src/ui/deck.vue
+++ b/packages/frontend/src/ui/deck.vue
@@ -118,7 +118,7 @@ import XMentionsColumn from '@/ui/deck/mentions-column.vue';
import XDirectColumn from '@/ui/deck/direct-column.vue';
import XRoleTimelineColumn from '@/ui/deck/role-timeline-column.vue';
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 XAnnouncements = defineAsyncComponent(() => import('@/ui/_common_/announcements.vue'));
diff --git a/packages/frontend/src/ui/deck/antenna-column.vue b/packages/frontend/src/ui/deck/antenna-column.vue
index 987bd4db55..a41639e71c 100644
--- a/packages/frontend/src/ui/deck/antenna-column.vue
+++ b/packages/frontend/src/ui/deck/antenna-column.vue
@@ -22,7 +22,7 @@ import MkTimeline from '@/components/MkTimeline.vue';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.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 { SoundStore } from '@/store.js';
import { soundSettingsButton } from '@/ui/deck/tl-note-notification.js';
diff --git a/packages/frontend/src/ui/deck/channel-column.vue b/packages/frontend/src/ui/deck/channel-column.vue
index 42c07056e7..5479b53d90 100644
--- a/packages/frontend/src/ui/deck/channel-column.vue
+++ b/packages/frontend/src/ui/deck/channel-column.vue
@@ -29,7 +29,7 @@ import * as os from '@/os.js';
import { favoritedChannelsCache } from '@/cache.js';
import { misskeyApi } from '@/scripts/misskey-api.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 { soundSettingsButton } from '@/ui/deck/tl-note-notification.js';
import * as sound from '@/scripts/sound.js';
diff --git a/packages/frontend/src/ui/deck/column.vue b/packages/frontend/src/ui/deck/column.vue
index 893301122e..b97d86f4a3 100644
--- a/packages/frontend/src/ui/deck/column.vue
+++ b/packages/frontend/src/ui/deck/column.vue
@@ -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 * as os from '@/os.js';
import { i18n } from '@/i18n.js';
-import { MenuItem } from '@/types/menu.js';
+import type { MenuItem } from '@/types/menu.js';
provide('shouldHeaderThin', true);
provide('shouldOmitHeaderTitle', true);
@@ -104,7 +104,27 @@ function toggleActive() {
}
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',
text: i18n.ts._deck.configureColumn,
action: async () => {
@@ -129,74 +149,73 @@ function getMenu() {
if (canceled) return;
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',
- text: i18n.ts.move + '...',
- icon: 'ti ti-arrows-move',
- children: [{
- icon: 'ti ti-arrow-left',
- text: i18n.ts._deck.swapLeft,
- action: () => {
- swapLeftColumn(props.column.id);
- },
- }, {
- icon: 'ti ti-arrow-right',
- text: i18n.ts._deck.swapRight,
- action: () => {
- swapRightColumn(props.column.id);
- },
- }, props.isStacked ? {
+ icon: 'ti ti-arrow-right',
+ text: i18n.ts._deck.swapRight,
+ action: () => {
+ swapRightColumn(props.column.id);
+ },
+ });
+
+ if (props.isStacked) {
+ moveToMenuItems.push({
icon: 'ti ti-arrow-up',
text: i18n.ts._deck.swapUp,
action: () => {
swapUpColumn(props.column.id);
},
- } : undefined, props.isStacked ? {
+ }, {
icon: 'ti ti-arrow-down',
text: i18n.ts._deck.swapDown,
action: () => {
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',
text: i18n.ts._deck.stackLeft,
action: () => {
stackLeftColumn(props.column.id);
},
- }, props.isStacked ? {
- icon: 'ti ti-window-maximize',
- text: i18n.ts._deck.popRight,
- action: () => {
- popRightColumn(props.column.id);
- },
- } : undefined, { type: 'divider' }, {
+ });
+
+ if (props.isStacked) {
+ menuItems.push({
+ icon: 'ti ti-window-maximize',
+ text: i18n.ts._deck.popRight,
+ action: () => {
+ popRightColumn(props.column.id);
+ },
+ });
+ }
+
+ menuItems.push({ type: 'divider' }, {
icon: 'ti ti-trash',
text: i18n.ts.remove,
danger: true,
action: () => {
removeColumn(props.column.id);
},
- }];
+ });
- if (props.menu) {
- 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;
+ return menuItems;
}
function showSettingsMenu(ev: MouseEvent) {
diff --git a/packages/frontend/src/ui/deck/list-column.vue b/packages/frontend/src/ui/deck/list-column.vue
index 9aa8f06476..8bb8fe7225 100644
--- a/packages/frontend/src/ui/deck/list-column.vue
+++ b/packages/frontend/src/ui/deck/list-column.vue
@@ -22,7 +22,7 @@ import MkTimeline from '@/components/MkTimeline.vue';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.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 { userListsCache } from '@/cache.js';
import { soundSettingsButton } from '@/ui/deck/tl-note-notification.js';
diff --git a/packages/frontend/src/ui/deck/role-timeline-column.vue b/packages/frontend/src/ui/deck/role-timeline-column.vue
index a375e9c574..beb4237978 100644
--- a/packages/frontend/src/ui/deck/role-timeline-column.vue
+++ b/packages/frontend/src/ui/deck/role-timeline-column.vue
@@ -21,7 +21,7 @@ import MkTimeline from '@/components/MkTimeline.vue';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.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 { soundSettingsButton } from '@/ui/deck/tl-note-notification.js';
import * as sound from '@/scripts/sound.js';
diff --git a/packages/frontend/src/ui/deck/tl-column.vue b/packages/frontend/src/ui/deck/tl-column.vue
index e210ee7b7a..01da92f731 100644
--- a/packages/frontend/src/ui/deck/tl-column.vue
+++ b/packages/frontend/src/ui/deck/tl-column.vue
@@ -113,29 +113,41 @@ function onNote() {
sound.playMisskeySfxFile(soundSetting.value);
}
-const menu = computed