From d2b5cc36e9750403d0cc9ee48db842d993c3f7e6 Mon Sep 17 00:00:00 2001 From: CHANX Date: Mon, 7 Oct 2024 18:04:14 +0800 Subject: [PATCH] feat(ui): enhance context menu with hover effects, disable option, and quick list creation (#787) * feat: disabled when exceed invite limitation * feat: add item hover * fix: create list minimum value is 0 * feat: add actions settings loading * feat: hide menu when enabled is false * feat: change opacity when menu is disabled * feat: add quick access for add feed to list * fix: unify list item hover style Signed-off-by: Innei * refactor: create list modal Signed-off-by: Innei * update Signed-off-by: Innei * unify style Signed-off-by: Innei * native app style Signed-off-by: Innei --------- Signed-off-by: Innei Co-authored-by: Innei --- .../ui/context-menu/context-menu.tsx | 1 + .../renderer/src/hooks/biz/useFeedActions.tsx | 55 +++--- apps/renderer/src/lib/native-menu.ts | 3 +- .../src/modules/entry-column/item.tsx | 11 +- .../entry-column/layouts/EntryItemWrapper.tsx | 22 +-- .../entry-column/layouts/EntryListHeader.tsx | 2 +- .../src/modules/feed-column/category.tsx | 46 +++-- .../src/modules/feed-column/index.tsx | 4 +- .../renderer/src/modules/feed-column/item.tsx | 10 +- .../renderer/src/modules/feed-column/list.tsx | 6 +- .../src/modules/feed-column/styles.ts | 8 + .../src/modules/settings/modal/layout.tsx | 4 +- .../src/modules/settings/tabs/invitations.tsx | 3 + .../src/modules/settings/tabs/lists/index.tsx | 159 ++++++++++++++++++ .../tabs/{lists.tsx => lists/modals.tsx} | 149 +--------------- .../src/pages/(external)/redirect.tsx | 9 +- .../src/pages/settings/(settings)/actions.tsx | 13 ++ .../src/providers/context-menu-provider.tsx | 15 +- locales/app/en.json | 1 + locales/app/zh-CN.json | 1 + locales/app/zh-HK.json | 1 + locales/app/zh-TW.json | 1 + locales/settings/zh-CN.json | 2 +- 23 files changed, 304 insertions(+), 222 deletions(-) create mode 100644 apps/renderer/src/modules/feed-column/styles.ts create mode 100644 apps/renderer/src/modules/settings/tabs/lists/index.tsx rename apps/renderer/src/modules/settings/tabs/{lists.tsx => lists/modals.tsx} (62%) diff --git a/apps/renderer/src/components/ui/context-menu/context-menu.tsx b/apps/renderer/src/components/ui/context-menu/context-menu.tsx index f1ca2dab7..d96396edf 100644 --- a/apps/renderer/src/components/ui/context-menu/context-menu.tsx +++ b/apps/renderer/src/components/ui/context-menu/context-menu.tsx @@ -28,6 +28,7 @@ const ContextMenuSubTrigger = React.forwardRef< inset && "pl-8", "center gap-2", className, + props.disabled && "cursor-not-allowed opacity-30", )} {...props} > diff --git a/apps/renderer/src/hooks/biz/useFeedActions.tsx b/apps/renderer/src/hooks/biz/useFeedActions.tsx index 57efba69c..18cd34da1 100644 --- a/apps/renderer/src/hooks/biz/useFeedActions.tsx +++ b/apps/renderer/src/hooks/biz/useFeedActions.tsx @@ -11,6 +11,7 @@ import { useFeedClaimModal } from "~/modules/claim" import { FeedForm } from "~/modules/discover/feed-form" import { InboxForm } from "~/modules/discover/inbox-form" import { ListForm } from "~/modules/discover/list-form" +import { ListCreationModalContent } from "~/modules/settings/tabs/lists/modals" import { getFeedById, useAddFeedToFeedList, @@ -37,7 +38,6 @@ export const useFeedActions = ({ const { t } = useTranslation() const feed = useFeedById(feedId) const subscription = useSubscriptionByFeedId(feedId) - const { present } = useModalStack() const deleteSubscription = useDeleteSubscription({}) const claimFeed = useFeedClaimModal({ @@ -92,28 +92,43 @@ export const useFeedActions = ({ { type: "text" as const, label: t("sidebar.feed_column.context_menu.add_feeds_to_list"), - enabled: !!listByView?.length, - submenu: listByView?.map((list) => { - const isIncluded = list.feedIds.includes(feedId) - return { - label: list.title || "", + submenu: [ + ...listByView.map((list) => { + const isIncluded = list.feedIds.includes(feedId) + return { + label: list.title || "", + type: "text" as const, + checked: isIncluded, + click() { + if (!isIncluded) { + addFeedToListMutation({ + feedId, + listId: list.id, + }) + } else { + removeFeedFromListMutation({ + feedId, + listId: list.id, + }) + } + }, + } + }), + { + type: "separator", + }, + { + label: t("sidebar.feed_actions.create_list"), type: "text" as const, - checked: isIncluded, + icon: , click() { - if (!isIncluded) { - addFeedToListMutation({ - feedId, - listId: list.id, - }) - } else { - removeFeedFromListMutation({ - feedId, - listId: list.id, - }) - } + present({ + title: t("sidebar.feed_actions.create_list"), + content: () => , + }) }, - } - }), + }, + ], }, { type: "separator" as const, diff --git a/apps/renderer/src/lib/native-menu.ts b/apps/renderer/src/lib/native-menu.ts index 21fc153ac..38ad3a95e 100644 --- a/apps/renderer/src/lib/native-menu.ts +++ b/apps/renderer/src/lib/native-menu.ts @@ -8,7 +8,6 @@ export type NativeMenuItem = ( type: "text" label: string click?: () => void - enabled?: boolean /** only work in web app */ icon?: React.ReactNode shortcut?: string @@ -130,7 +129,7 @@ export const showNativeMenu = async ( return { ...item, icon: undefined, - enabled: item.enabled ?? (item.click !== undefined || !!item.submenu), + enabled: item.click !== undefined || (!!item.submenu && item.submenu.length > 0), click: undefined, submenu: item.submenu ? transformMenuItems(item.submenu) : undefined, shortcut: item.shortcut?.replace("Meta", "CmdOrCtrl"), diff --git a/apps/renderer/src/modules/entry-column/item.tsx b/apps/renderer/src/modules/entry-column/item.tsx index 039d4570d..5720ff12b 100644 --- a/apps/renderer/src/modules/entry-column/item.tsx +++ b/apps/renderer/src/modules/entry-column/item.tsx @@ -5,7 +5,6 @@ import { LoadingCircle } from "~/components/ui/loading" import { views } from "~/constants" import { useAuthQuery } from "~/hooks/common" import type { FeedViewType } from "~/lib/enum" -import { FeedViewType as FeedViewTypeEnum } from "~/lib/enum" import { cn } from "~/lib/utils" import { Queries } from "~/queries" import type { FlatEntryModel } from "~/store/entry" @@ -39,17 +38,9 @@ function EntryItemImpl({ entry, view }: { entry: FlatEntryModel; view?: number } ) const Item: EntryListItemFC = getItemComponentByView(view as FeedViewType) - const overlayItemClassName = - view === FeedViewTypeEnum.Pictures || view === FeedViewTypeEnum.Videos ? "top-0" : "" return ( - + ) diff --git a/apps/renderer/src/modules/entry-column/layouts/EntryItemWrapper.tsx b/apps/renderer/src/modules/entry-column/layouts/EntryItemWrapper.tsx index 5bc7bc638..b4274dedd 100644 --- a/apps/renderer/src/modules/entry-column/layouts/EntryItemWrapper.tsx +++ b/apps/renderer/src/modules/entry-column/layouts/EntryItemWrapper.tsx @@ -4,13 +4,14 @@ import { useTranslation } from "react-i18next" import { useDebounceCallback } from "usehooks-ts" import { useGeneralSettingKey } from "~/atoms/settings/general" -import { ListItemHoverOverlay } from "~/components/ui/list-item-hover-overlay" +import { views } from "~/constants/tabs" import { useAsRead } from "~/hooks/biz/useAsRead" import { useEntryActions } from "~/hooks/biz/useEntryActions" import { useFeedActions } from "~/hooks/biz/useFeedActions" import { useNavigateEntry } from "~/hooks/biz/useNavigateEntry" import { useRouteParamsSelector } from "~/hooks/biz/useRouteParams" import { useAnyPointDown } from "~/hooks/common" +import type { FeedViewType } from "~/lib/enum" import { showNativeMenu } from "~/lib/native-menu" import { cn } from "~/lib/utils" import type { FlatEntryModel } from "~/store/entry" @@ -21,11 +22,9 @@ export const EntryItemWrapper: FC< entry: FlatEntryModel view?: number itemClassName?: string - overlayItemClassName?: string - overlay?: boolean style?: React.CSSProperties } & PropsWithChildren -> = ({ entry, view, overlay, children, itemClassName, overlayItemClassName, style }) => { +> = ({ entry, view, children, itemClassName, style }) => { const { items } = useEntryActions({ view, entry, @@ -118,7 +117,9 @@ export const EntryItemWrapper: FC< className={cn( "relative", asRead ? "text-zinc-700 dark:text-neutral-400" : "text-zinc-900 dark:text-neutral-300", - + views[view as FeedViewType]?.wideMode ? "rounded-md" : "-mx-2 px-2", + "duration-200 hover:bg-theme-item-hover", + (isActive || isContextMenuOpen) && "!bg-theme-item-active", itemClassName, )} onClick={handleClick} @@ -127,16 +128,7 @@ export const EntryItemWrapper: FC< onDoubleClick={handleDoubleClick} onContextMenu={handleContextMenu} > - {overlay ? ( - - {children} - - ) : ( - children - )} + {children} ) diff --git a/apps/renderer/src/modules/entry-column/layouts/EntryListHeader.tsx b/apps/renderer/src/modules/entry-column/layouts/EntryListHeader.tsx index 5544eb85c..288e59d5a 100644 --- a/apps/renderer/src/modules/entry-column/layouts/EntryListHeader.tsx +++ b/apps/renderer/src/modules/entry-column/layouts/EntryListHeader.tsx @@ -257,7 +257,7 @@ const WideModeButton = () => { : t("entry_list_header.switch_to_normalmode") } > - + ) diff --git a/apps/renderer/src/modules/feed-column/category.tsx b/apps/renderer/src/modules/feed-column/category.tsx index 5d08e6782..10ba1c023 100644 --- a/apps/renderer/src/modules/feed-column/category.tsx +++ b/apps/renderer/src/modules/feed-column/category.tsx @@ -21,9 +21,11 @@ import { subscriptionActions, useSubscriptionByFeedId } from "~/store/subscripti import { useFeedUnreadStore } from "~/store/unread" import { useModalStack } from "../../components/ui/modal/stacked/hooks" +import { ListCreationModalContent } from "../settings/tabs/lists/modals" import { useFeedListSortSelector } from "./atom" import { CategoryRemoveDialogContent } from "./category-remove-dialog" import { FeedItem } from "./item" +import { feedColumnStyles } from "./styles" import { UnreadNumber } from "./unread-number" type FeedId = string @@ -135,9 +137,10 @@ function FeedCategoryImpl({ data: ids, view, categoryOpenStateData }: FeedCatego
{!!showCollapse && (
{ e.stopPropagation() @@ -162,22 +165,37 @@ function FeedCategoryImpl({ data: ids, view, categoryOpenStateData }: FeedCatego { type: "text", label: t("sidebar.feed_column.context_menu.add_feeds_to_list"), - enabled: !!listList?.length, - submenu: listList?.map((list) => ({ - label: list.title || "", - type: "text", - click() { - return addMutation.mutate({ - feedIds: ids, - listId: list.id, - }) - }, - })), + // @ts-expect-error + submenu: listList + ?.map((list) => ({ + label: list.title || "", + type: "text", + click() { + return addMutation.mutate({ + feedIds: ids, + listId: list.id, + }) + }, + })) + .concat([ + // @ts-expect-error + { type: "separator" as const }, + { + label: t("sidebar.feed_actions.create_list"), + type: "text" as const, + + click() { + present({ + title: t("sidebar.feed_actions.create_list"), + content: () => , + }) + }, + }, + ]), }, { type: "separator" }, { type: "text", - enabled: !!(folderName && typeof view === "number"), label: t("sidebar.feed_column.context_menu.change_to_other_view"), submenu: views .filter((v) => v.view !== view) diff --git a/apps/renderer/src/modules/feed-column/index.tsx b/apps/renderer/src/modules/feed-column/index.tsx index 0388ced0a..98f8ca9ec 100644 --- a/apps/renderer/src/modules/feed-column/index.tsx +++ b/apps/renderer/src/modules/feed-column/index.tsx @@ -160,8 +160,8 @@ export function FeedColumn({ children, className }: PropsWithChildren<{ classNam className={cn( active === index && item.className, "flex h-11 flex-col items-center gap-1 text-xl", - ELECTRON ? "hover:!bg-theme-vibrancyBg" : "", - active === index && useHotkeysSwitch ? "bg-zinc-500/30" : "", + ELECTRON ? "hover:!bg-theme-item-hover" : "", + active === index && useHotkeysSwitch ? "bg-theme-item-active" : "", )} onClick={(e) => { setActive(index) diff --git a/apps/renderer/src/modules/feed-column/item.tsx b/apps/renderer/src/modules/feed-column/item.tsx index 6ab284d48..77e90c1c1 100644 --- a/apps/renderer/src/modules/feed-column/item.tsx +++ b/apps/renderer/src/modules/feed-column/item.tsx @@ -24,6 +24,7 @@ import { useListById } from "~/store/list" import { subscriptionActions, useSubscriptionByFeedId } from "~/store/subscription" import { useFeedUnreadStore } from "~/store/unread" +import { feedColumnStyles } from "./styles" import { UnreadNumber } from "./unread-number" interface FeedItemProps { @@ -72,9 +73,10 @@ const FeedItemImpl = ({ view, feedId, className }: FeedItemProps) => { <>
{ @@ -167,9 +168,10 @@ function FeedListImpl({ className, view }: { className?: string; view: number })
{ e.stopPropagation() diff --git a/apps/renderer/src/modules/feed-column/styles.ts b/apps/renderer/src/modules/feed-column/styles.ts new file mode 100644 index 000000000..ddcb75670 --- /dev/null +++ b/apps/renderer/src/modules/feed-column/styles.ts @@ -0,0 +1,8 @@ +import clsx from "clsx" + +export const feedColumnStyles = { + item: clsx( + !window.electron && tw`duration-200 hover:bg-theme-item-hover`, + tw`data-[active=true]:!bg-theme-item-active`, + ), +} diff --git a/apps/renderer/src/modules/settings/modal/layout.tsx b/apps/renderer/src/modules/settings/modal/layout.tsx index be7183bf0..35cc0fc6f 100644 --- a/apps/renderer/src/modules/settings/modal/layout.tsx +++ b/apps/renderer/src/modules/settings/modal/layout.tsx @@ -119,8 +119,8 @@ export function SettingModalLayout( {settings.map((t) => (
+ +
+ + {listList.data?.length ? ( + + + + {t.settings("lists.title")} + {t.settings("lists.view")} + {t.settings("lists.fee.label")} + {t.settings("lists.subscriptions")} + {t.settings("lists.earnings")} + + {t.common("words.actions")} + + + + + {listList.data?.map((row) => ( + + + + {row.image && ( + + + + )} + {row.title} + + + + + + + {views[row.view].icon} + + + + {t(views[row.view].name)} + + + + +
+ {row.fee} + +
+
+ {row.subscriptionCount} + + {BigInt(row.purchaseAmount || 0n)} + + + + + + + + {t.common("words.manage")} + + + + + + + + {t.common("words.edit")} + + + +
+ ))} +
+
+ ) : listList.isLoading ? ( + + ) : ( +
+

{t.settings("lists.noLists")}

+
+ )} +
+
+ + ) +} diff --git a/apps/renderer/src/modules/settings/tabs/lists.tsx b/apps/renderer/src/modules/settings/tabs/lists/modals.tsx similarity index 62% rename from apps/renderer/src/modules/settings/tabs/lists.tsx rename to apps/renderer/src/modules/settings/tabs/lists/modals.tsx index 50660c169..1787d6398 100644 --- a/apps/renderer/src/modules/settings/tabs/lists.tsx +++ b/apps/renderer/src/modules/settings/tabs/lists/modals.tsx @@ -24,8 +24,7 @@ import { FormMessage, } from "~/components/ui/form" import { Input } from "~/components/ui/input" -import { LoadingCircle } from "~/components/ui/loading" -import { useModalStack } from "~/components/ui/modal" +import { useCurrentModal } from "~/components/ui/modal" import { ScrollArea } from "~/components/ui/scroll-area" import { Table, @@ -35,15 +34,12 @@ import { TableHeader, TableRow, } from "~/components/ui/table" -import { Tooltip, TooltipContent, TooltipPortal, TooltipTrigger } from "~/components/ui/tooltip" import { views } from "~/constants" -import { useAuthQuery, useI18n } from "~/hooks/common" import { apiClient } from "~/lib/api-fetch" import { createErrorToaster } from "~/lib/error-parser" -import { cn, isBizId } from "~/lib/utils" +import { isBizId } from "~/lib/utils" import type { FeedModel } from "~/models" import { ViewSelectorRadioGroup } from "~/modules/shared/ViewSelectorRadioGroup" -import { Balance } from "~/modules/wallet/balance" import { Queries } from "~/queries" import { getFeedById, @@ -54,143 +50,6 @@ import { import { useListById } from "~/store/list" import { subscriptionActions, useSubscriptionStore } from "~/store/subscription" -export const SettingLists = () => { - const t = useI18n() - const listList = useAuthQuery(Queries.lists.list()) - - const { present } = useModalStack() - - return ( -
-
-

{t.settings("lists.info")}

-
- - -
- - {listList.data?.length ? ( - - - - {t.settings("lists.title")} - {t.settings("lists.view")} - {t.settings("lists.fee.label")} - {t.settings("lists.subscriptions")} - {t.settings("lists.earnings")} - - {t.common("words.actions")} - - - - - {listList.data?.map((row) => ( - - - - {row.image && ( - - - - )} - {row.title} - - - - - - - {views[row.view].icon} - - - - {t(views[row.view].name)} - - - - -
- {row.fee} - -
-
- {row.subscriptionCount} - - {BigInt(row.purchaseAmount || 0n)} - - - - - - - - {t.common("words.manage")} - - - - - - - - {t.common("words.edit")} - - - -
- ))} -
-
- ) : listList.isLoading ? ( - - ) : ( -
-

{t.settings("lists.noLists")}

-
- )} -
-
-
- ) -} - const formSchema = z.object({ view: z.string(), title: z.string().min(1), @@ -199,7 +58,8 @@ const formSchema = z.object({ fee: z.number().min(0), }) -const ListCreationModalContent = ({ dismiss, id }: { dismiss: () => void; id?: string }) => { +export const ListCreationModalContent = ({ id }: { id?: string }) => { + const { dismiss } = useCurrentModal() const { t } = useTranslation(["settings", "common"]) const list = useListById(id) @@ -333,6 +193,7 @@ const ListCreationModalContent = ({ dismiss, id }: { dismiss: () => void; id?: s field.onChange(value.target.valueAsNumber)} /> diff --git a/apps/renderer/src/pages/(external)/redirect.tsx b/apps/renderer/src/pages/(external)/redirect.tsx index 7338cc140..5d45b0b7f 100644 --- a/apps/renderer/src/pages/(external)/redirect.tsx +++ b/apps/renderer/src/pages/(external)/redirect.tsx @@ -26,6 +26,9 @@ export function Component() { } else { getCallbackUrl().then((url) => { window.open(url, "_top") + + // If you are in development, you can use the following code to open the app + console.info("Open in development app", url.replace(DEEPLINK_SCHEME, "follow-dev://")) }) } }, []) @@ -34,9 +37,9 @@ export function Component() {

- {t("redirect.successMessage", { APP_NAME })}
+ {t("redirect.successMessage", { app_name: APP_NAME })}

- {t("redirect.instruction", { APP_NAME })} + {t("redirect.instruction", { app_name: APP_NAME })}

diff --git a/apps/renderer/src/pages/settings/(settings)/actions.tsx b/apps/renderer/src/pages/settings/(settings)/actions.tsx index 95858cdd4..83e5ef56f 100644 --- a/apps/renderer/src/pages/settings/(settings)/actions.tsx +++ b/apps/renderer/src/pages/settings/(settings)/actions.tsx @@ -4,6 +4,7 @@ import { useTranslation } from "react-i18next" import { toast } from "sonner" import { Button } from "~/components/ui/button" +import { LoadingWithIcon } from "~/components/ui/loading" import { useAuthQuery } from "~/hooks/common" import { apiClient } from "~/lib/api-fetch" import { toastFetchError } from "~/lib/error-parser" @@ -58,6 +59,18 @@ export function Component() { }, }) + if (actions.isPending) { + return ( +
+ } + size="large" + className="-translate-y-full" + /> +
+ ) + } + return ( <> diff --git a/apps/renderer/src/providers/context-menu-provider.tsx b/apps/renderer/src/providers/context-menu-provider.tsx index 34e3451fa..12bf1747f 100644 --- a/apps/renderer/src/providers/context-menu-provider.tsx +++ b/apps/renderer/src/providers/context-menu-provider.tsx @@ -20,6 +20,7 @@ import { useSwitchHotKeyScope } from "~/hooks/common" import { nextFrame } from "~/lib/dom" import type { NativeMenuItem } from "~/lib/native-menu" import { CONTEXT_MENU_SHOW_EVENT_KEY } from "~/lib/native-menu" +import { cn } from "~/lib/utils" export const ContextMenuProvider: Component = ({ children }) => ( <> @@ -76,6 +77,7 @@ const Handler = () => { ) } + // eslint-disable-next-line @eslint-react/web-api/no-leaked-event-listener document.addEventListener(CONTEXT_MENU_SHOW_EVENT_KEY, handler) return () => { document.removeEventListener(CONTEXT_MENU_SHOW_EVENT_KEY, handler) @@ -108,6 +110,10 @@ const Item = memo(({ item }: { item: NativeMenuItem }) => { preventDefault: true, }) + if (item.hide) { + return null + } + switch (item.type) { case "separator": { return @@ -120,17 +126,20 @@ const Item = memo(({ item }: { item: NativeMenuItem }) => { : ContextMenuItem const Sub = item.submenu ? ContextMenuSub : Fragment + return ( - {item.icon} - {item.label} + {item.icon && ( + {item.icon} + )} + {item.label} {!!item.shortcut && (
diff --git a/locales/app/en.json b/locales/app/en.json index dfe3ec562..0b76cd327 100644 --- a/locales/app/en.json +++ b/locales/app/en.json @@ -203,6 +203,7 @@ "sidebar.feed_actions.copy_feed_url": "Copy feed URL", "sidebar.feed_actions.copy_list_id": "Copy list ID", "sidebar.feed_actions.copy_list_url": "Copy list URL", + "sidebar.feed_actions.create_list": "Create New list", "sidebar.feed_actions.edit": "Edit", "sidebar.feed_actions.edit_feed": "Edit feed", "sidebar.feed_actions.edit_inbox": "Edit inbox", diff --git a/locales/app/zh-CN.json b/locales/app/zh-CN.json index b40b68f12..6a4496864 100644 --- a/locales/app/zh-CN.json +++ b/locales/app/zh-CN.json @@ -203,6 +203,7 @@ "sidebar.feed_actions.copy_feed_url": "复制链接", "sidebar.feed_actions.copy_list_id": "复制列表 ID", "sidebar.feed_actions.copy_list_url": "复制列表链接", + "sidebar.feed_actions.create_list": "创建列表", "sidebar.feed_actions.edit": "编辑", "sidebar.feed_actions.edit_feed": "编辑订阅", "sidebar.feed_actions.edit_inbox": "编辑收件箱", diff --git a/locales/app/zh-HK.json b/locales/app/zh-HK.json index 6c0497265..d1349b442 100644 --- a/locales/app/zh-HK.json +++ b/locales/app/zh-HK.json @@ -203,6 +203,7 @@ "sidebar.feed_actions.copy_feed_url": "複製訂閱源 URL", "sidebar.feed_actions.copy_list_id": "複製清單 ID", "sidebar.feed_actions.copy_list_url": "複製清單 URL", + "sidebar.feed_actions.create_list": "建立清單", "sidebar.feed_actions.edit": "編輯", "sidebar.feed_actions.edit_feed": "編輯訂閱源", "sidebar.feed_actions.edit_inbox": "編輯收件箱", diff --git a/locales/app/zh-TW.json b/locales/app/zh-TW.json index 0be29628b..a574efe1e 100644 --- a/locales/app/zh-TW.json +++ b/locales/app/zh-TW.json @@ -203,6 +203,7 @@ "sidebar.feed_actions.copy_feed_url": "複製摘要 URL", "sidebar.feed_actions.copy_list_id": "複製列表 ID", "sidebar.feed_actions.copy_list_url": "複製列表連結", + "sidebar.feed_actions.create_list": "建立列表", "sidebar.feed_actions.edit": "編輯", "sidebar.feed_actions.edit_feed": "編輯摘要", "sidebar.feed_actions.edit_inbox": "編輯收件匣", diff --git a/locales/settings/zh-CN.json b/locales/settings/zh-CN.json index a2a340b65..6a6ea6c1e 100644 --- a/locales/settings/zh-CN.json +++ b/locales/settings/zh-CN.json @@ -153,7 +153,7 @@ "invitation.tableHeaders.creationTime": "创建时间", "invitation.tableHeaders.usedBy": "使用者", "invitation.title": "邀请码", - "lists.create": "创建新列表", + "lists.create": "创建列表", "lists.created.error": "创建列表失败", "lists.created.success": "创建列表成功", "lists.description": "描述",