diff --git a/README.md b/README.md index 12b6cfb..254ff25 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ - 支持分类、标签组、标签页管理,包括一键收集保存、恢复、星标、锁定、增删改查、拖拽排序等功能。 - 分类支持展开/收起,支持创建分类和标签组,方便移动其他标签组/标签页到新分类/新标签组。 - 支持多种插件格式的 **导入/导出** 功能,支持导出到本地。目前支持 `NiceTab`、`OneTab` 格式的交叉导入导出(比如:可选择导入OneTab格式并导出为NiceTab格式;或者将NiceTab格式导出为OneTab格式),后续可根据需求增加其他插件格式的导入导出功能。 -- 支持**远程同步**,您可根据需求将标签页同步到自己的 github 和 gitee 账号,只需要配置自己的 access token 即可,(注意 token 权限只勾选 gists 操作)。 +- 支持**远程同步**,您可根据需求将标签页同步到自己的 github 和 gitee 账号,只需要配置自己的 access token 即可(注意 token 权限只勾选 gists 操作)。 - 支持**皮肤主题切换**,目前暂时设置了有限的几种主题色提供选择,后续可根据需求扩大选择范围。 - 支持**多语言**,目前暂时支持中英文切换 (非地道英语,期待英语大佬帮忙校正)。 - 支持**回收站功能**,回收站中的标签页可还原到标签列表或者彻底删除。标签列表和回收站支持根据分类和标签组归类合并,方便管理。 @@ -110,6 +110,7 @@ - 打开**管理后台 > 标签列表**页,查看已发送的标签页列表,支持分类和标签组管理。 - 打开**管理后台 > 设置**页,可保存扩展的偏好设置。 - 打开**管理后台 > 导入导出**页,可进行NiceTab和OneTab格式的标签页导入导出操作。 +- 打开**管理后台 > 同步**页,可根据需求将标签页同步到自己的 github 和 gitee 账号,只需要配置自己的 access token 即可(注意 token 权限只勾选 gists 操作)。 - 打开**管理后台 > 回收站**页,可查看和管理从标签列表页中删除的分类、标签组、标签页,并进行还原和删除操作。 ## 更新计划 diff --git a/entrypoints/common/components/StickyBox.tsx b/entrypoints/common/components/StickyBox.tsx index 0650182..d73c87d 100644 --- a/entrypoints/common/components/StickyBox.tsx +++ b/entrypoints/common/components/StickyBox.tsx @@ -1,4 +1,4 @@ -import { ReactNode, useEffect, useState, useRef, useCallback } from 'react'; +import { ReactNode, useEffect, useState, useRef } from 'react'; import styled from 'styled-components'; import { classNames } from '~/entrypoints/common/utils'; @@ -74,7 +74,7 @@ export function StickyBox({ children, topGap = 0, fullWidth = false, bgColor = ' ` + position: relative; + width: 100%; + padding: 0 ${(props) => props.$paddingX || 0}px; + background: ${(props) => props.$bgColor || '#fff'}; + &.fixed { + position: fixed; + width: ${(props) => props.$width ? `${props.$width}px` : '100%'}; + left: ${(props) => `${props.$left || 0}px`}; + bottom: ${(props) => `${props.$bottom || 0}px`}; + box-shadow: 0 -2px 12px 1px rgba(0, 0, 0, 0.1); + z-index: 10; + } +`; + +interface StickyFooterProps { + children: ReactNode; + bottomGap?: number; + fullWidth?: boolean; + bgColor?: string; + paddingX?: number | string; +} + +export default function StickyFooter({ + children, + bottomGap = 0, + fullWidth = false, + bgColor = '#fff', + paddingX = 0, +}: StickyFooterProps) { + const wrapperRef = useRef(null); + const innerRef = useRef(null); + const [wrapperBounds, setWrapperBounds] = useState({ + bottom: 0, + left: 0, + width: 0, + height: 0, + }); + + useEffect(() => { + const wrapper = wrapperRef.current; + const inner = innerRef.current; + if (!wrapper || !inner) return; + const { left, width } = wrapper.getBoundingClientRect(); + const { height } = inner.getBoundingClientRect(); + wrapper.style.height = `${height}px`; + setWrapperBounds({ bottom: 0, left, width, height }); + }, []); + + return ( +
+ + {children} + +
+ ); +} diff --git a/entrypoints/common/constants.ts b/entrypoints/common/constants.ts index 4453c61..ec2a5ca 100644 --- a/entrypoints/common/constants.ts +++ b/entrypoints/common/constants.ts @@ -10,7 +10,7 @@ import { magenta, gold, } from '@ant-design/colors'; -import type { LanguageTypes, SettingsProps, TabEvents } from '~/entrypoints/types'; +import type { LanguageTypes, SettingsProps, TabEvents, SyncType } from '~/entrypoints/types'; export const GITHUB_URL = 'https://github.com/web-dahuyou/NiceTab'; @@ -91,6 +91,15 @@ export const IS_GROUP_SUPPORT = 'group' in browser.tabs && 'tabGroups' in brows export const SUCCESS_KEY = 'success'; export const FAILED_KEY = 'failed'; +// 同步方式枚举 +export const syncTypeMap: Record = { + AUTO: 'auto', + MANUAL_PULL_MERGE: 'manual-pull-merge', + MANUAL_PULL_FORCE: 'manual-pull-force', + MANUAL_PUSH_MERGE: 'manual-push-merge', + MANUAL_PUSH_FORCE: 'manual-push-force', +}; + export default { ENUM_COLORS, THEME_COLORS, diff --git a/entrypoints/common/locale/modules/home/enUS.ts b/entrypoints/common/locale/modules/home/enUS.ts index a806e7f..294b128 100644 --- a/entrypoints/common/locale/modules/home/enUS.ts +++ b/entrypoints/common/locale/modules/home/enUS.ts @@ -13,6 +13,7 @@ export default { 'home.recoverAll': 'Recover All', 'home.emptyTip': 'No Data', 'home.searchTagAndGroup': 'Search Tag/Group', + 'home.moveAllGroupTo': 'Move All Group To ...', 'home.createTabGroup': 'Create Tab Group', 'home.helpInfo': 'Help Info', 'home.removeTitle': 'Remove Reminder', diff --git a/entrypoints/common/locale/modules/home/zhCN.ts b/entrypoints/common/locale/modules/home/zhCN.ts index 7d215fb..3793f23 100644 --- a/entrypoints/common/locale/modules/home/zhCN.ts +++ b/entrypoints/common/locale/modules/home/zhCN.ts @@ -13,6 +13,7 @@ export default { 'home.recoverAll': '全部还原', 'home.emptyTip': '暂无数据', 'home.searchTagAndGroup': '搜索分类/标签组', + 'home.moveAllGroupTo': '移动所有标签组到...', 'home.createTabGroup': '创建标签组', 'home.helpInfo': '帮助信息', 'home.removeTitle': '删除提醒', diff --git a/entrypoints/common/locale/modules/settings/enUS.ts b/entrypoints/common/locale/modules/settings/enUS.ts index a153863..afdf68a 100644 --- a/entrypoints/common/locale/modules/settings/enUS.ts +++ b/entrypoints/common/locale/modules/settings/enUS.ts @@ -17,7 +17,7 @@ export default { 'settings.closeTabsAfterSendTabs': 'When sending tabs - whether to automatically close the tabs ?', 'settings.closeTabsAfterSendTabs.yes': 'Automatically close', 'settings.closeTabsAfterSendTabs.no': 'Do not', - 'settings.deleteAfterRestore': 'When restoring tabs - whether to remove the tabs ?', + 'settings.deleteAfterRestore': 'When opening tabs or tab groups - whether to remove the tabs ?', 'settings.deleteAfterRestore.yes': 'Remove (pinned tabs remain)', 'settings.deleteAfterRestore.no': 'All remain (recommended)', 'settings.deleteUnlockedEmptyGroup': 'When clearing tabs - whether to remove the empty group ?', diff --git a/entrypoints/common/locale/modules/settings/zhCN.ts b/entrypoints/common/locale/modules/settings/zhCN.ts index e0c51a1..2f979e4 100644 --- a/entrypoints/common/locale/modules/settings/zhCN.ts +++ b/entrypoints/common/locale/modules/settings/zhCN.ts @@ -17,7 +17,7 @@ export default { 'settings.closeTabsAfterSendTabs': '发送标签页时-是否自动关闭标签页{mark}', 'settings.closeTabsAfterSendTabs.yes': '自动关闭标签页', 'settings.closeTabsAfterSendTabs.no': '不要自动关闭标签页', - 'settings.deleteAfterRestore': '恢复标签页/标签组时-是否自动删除标签页{mark}', + 'settings.deleteAfterRestore': '打开标签页/标签组时-是否自动删除标签页{mark}', 'settings.deleteAfterRestore.yes': '从NiceTab列表中删除(仍保留固定标签页)', 'settings.deleteAfterRestore.no': '保留在NiceTab列表中(推荐)', 'settings.deleteUnlockedEmptyGroup': '清空标签页时-是否自动删除该标签组{mark}', diff --git a/entrypoints/common/locale/modules/sync/enUS.ts b/entrypoints/common/locale/modules/sync/enUS.ts index 5d7cd66..8208ea6 100644 --- a/entrypoints/common/locale/modules/sync/enUS.ts +++ b/entrypoints/common/locale/modules/sync/enUS.ts @@ -6,6 +6,8 @@ export default { 'sync.noGithubToken': 'No github access token', 'sync.noGiteeToken': 'No gitee access token', 'sync.syncType.auto': 'auto', + 'sync.syncType.manualPullMerge': 'menual pull (merge)', + 'sync.syncType.manualPullForce': 'menual pull (overwrite)', 'sync.syncType.manualPushMerge': 'menual push (merge)', 'sync.syncType.manualPushForce': 'menual push (force)', 'sync.lastSyncTime': 'Last Sync Time', @@ -15,6 +17,8 @@ export default { 'sync.syncType': 'Sync Type', 'sync.syncResult': 'Sync Result', 'sync.tip.auto': 'Auto Sync by mergeing push type', + 'sync.tip.manualPullMerge': 'ull remote data, merge to local data.', + 'sync.tip.manualPullForce': 'Pull remote data, force overwrite local data.', 'sync.tip.manualPushMerge': 'Pull remote data, merge to local data, and then push to remote.', 'sync.tip.manualPushForce': 'Push local data to remote (force overwrite).', 'sync.tip.tokenChange': 'Change the access token will clear the local {type} sync history', diff --git a/entrypoints/common/locale/modules/sync/zhCN.ts b/entrypoints/common/locale/modules/sync/zhCN.ts index df99a30..b342d31 100644 --- a/entrypoints/common/locale/modules/sync/zhCN.ts +++ b/entrypoints/common/locale/modules/sync/zhCN.ts @@ -6,6 +6,8 @@ export default { 'sync.noGithubToken': '未设置 github access token', 'sync.noGiteeToken': '未设置 gitee access token', 'sync.syncType.auto': '自动同步', + 'sync.syncType.manualPullMerge': '手动拉取-合并到本地', + 'sync.syncType.manualPullForce': '手动拉取-覆盖本地数据', 'sync.syncType.manualPushMerge': '手动同步-合并推送', 'sync.syncType.manualPushForce': '手动同步-覆盖推送', 'sync.lastSyncTime': '上次同步时间', @@ -15,6 +17,8 @@ export default { 'sync.syncType': '同步方式', 'sync.syncResult': '同步结果', 'sync.tip.auto': '以合并推送的方式按时自动同步', + 'sync.tip.manualPullMerge': '拉取远程数据,合并到本地', + 'sync.tip.manualPullForce': '拉取远程数据,覆盖本地数据', 'sync.tip.manualPushMerge': '拉取远程数据,与本地合并,然后推送', 'sync.tip.manualPushForce': '推送本地数据到远程(强制覆盖)', 'sync.tip.tokenChange': '修改access token会清空本地{type}的同步历史记录', diff --git a/entrypoints/common/storage/syncUtils.ts b/entrypoints/common/storage/syncUtils.ts index d54604b..2699e36 100644 --- a/entrypoints/common/storage/syncUtils.ts +++ b/entrypoints/common/storage/syncUtils.ts @@ -16,7 +16,7 @@ import { fetchApi, getLocaleMessages, } from '~/entrypoints/common/utils'; -import { SUCCESS_KEY, FAILED_KEY } from '~/entrypoints/common/constants'; +import { SUCCESS_KEY, FAILED_KEY, syncTypeMap } from '~/entrypoints/common/constants'; import Store from './instanceStore'; type GistFilesProps = { @@ -231,10 +231,10 @@ export default class SyncUtils { async handleBySyncType( remoteType: SyncRemoteType, syncType: SyncType, - gistData?: GistResponseItemProps + gistData: GistResponseItemProps ) { let result: GistResponseItemProps = {} as GistResponseItemProps; - if (syncType === 'manual-push-force') { + if (syncType === syncTypeMap.MANUAL_PUSH_FORCE) { result = await this.updateGist(remoteType); } else { try { @@ -255,9 +255,17 @@ export default class SyncUtils { } else { fileContent = fileInfo?.content || ''; } + + if (syncType === syncTypeMap.MANUAL_PULL_FORCE) { + await Store.tabListUtils.clearAll(); + } const tagList = extContentImporter.niceTab(fileContent || ''); await Store.tabListUtils.importTags(tagList, 'merge'); - result = await this.updateGist(remoteType); + if (syncType === syncTypeMap.MANUAL_PUSH_MERGE) { + result = await this.updateGist(remoteType); + } else { + result = { id: gistData?.id } as GistResponseItemProps; + } } catch (e) { console.log(e); } diff --git a/entrypoints/common/storage/tabListUtils.ts b/entrypoints/common/storage/tabListUtils.ts index f3f4cd6..fabcb79 100644 --- a/entrypoints/common/storage/tabListUtils.ts +++ b/entrypoints/common/storage/tabListUtils.ts @@ -574,6 +574,62 @@ export default class TabListUtils { await this.setTagList(tagList); return { targetGroupId: undefined }; } + // 当前分类中所有标签组移动到(穿越) + async allTabGroupsMoveThrough({ + sourceTagId, + targetTagId, + autoMerge = false, + }: { + sourceTagId: Key; + targetTagId: Key; + autoMerge?: boolean; + }) { + const tagList = await this.getTagList(); + let isSourceFound = false, + isTargetFound = false, + sourceTagIndex = 0, + targetTagIndex = 0; + for (let tIndex = 0; tIndex < tagList.length; tIndex++) { + const tag = tagList[tIndex]; + if (tag.tagId === targetTagId) { + isTargetFound = true; + targetTagIndex = tIndex; + } + if (tag.tagId === sourceTagId) { + isSourceFound = true; + sourceTagIndex = tIndex; + } + + if (isSourceFound && isTargetFound) break; + } + + if (isSourceFound && isTargetFound) { + const sourceTag = tagList?.[sourceTagIndex]; + const allSourceGroups = sourceTag?.groupList?.splice(0); + + const targetTag = tagList?.[targetTagIndex]; + + // 如果开启自动合并,则同名标签组会自动合并 + if (autoMerge) { + targetTag.groupList = mergeGroupsAndTabs({ + targetList: targetTag.groupList || [], + insertList: allSourceGroups, + exceptValue: UNNAMED_GROUP, + }) + + await this.setTagList(tagList); + } else { + // 穿越操作改为往队尾插入 + targetTag.groupList = [...targetTag?.groupList, ...allSourceGroups]; + await this.setTagList(tagList); + } + + return { targetTagId: targetTag.tagId }; + } + + await this.setTagList(tagList); + return { targetTagId: undefined }; + } // 标签组排序 async groupListSort(sortType: string, tagId: Key) { const tagList = await this.getTagList(); diff --git a/entrypoints/options/App.tsx b/entrypoints/options/App.tsx index cfd95b9..f67cbbb 100644 --- a/entrypoints/options/App.tsx +++ b/entrypoints/options/App.tsx @@ -290,7 +290,7 @@ function AppLayout() { {/* 回到顶部 */} - + {/* BackTop组件自带的 tooltip 在点击按钮时会闪 */} diff --git a/entrypoints/options/home/Home.styled.tsx b/entrypoints/options/home/Home.styled.tsx index e15f9db..4569a05 100644 --- a/entrypoints/options/home/Home.styled.tsx +++ b/entrypoints/options/home/Home.styled.tsx @@ -15,6 +15,7 @@ export const StyledSidebarWrapper = styled.div<{ position: fixed; top: 100px; transition: transform 0.2s ease-in-out; + border-right: 1px solid rgba(5, 5, 5, 0.06); &.collapsed { .sidebar-inner-content { @@ -103,7 +104,7 @@ export const StyledListWrapper = styled.div<{ .content { padding-left: 40px; - border-left: 1px solid rgba(5, 5, 5, 0.06); + // border-left: 1px solid rgba(5, 5, 5, 0.06); } `; @@ -134,6 +135,14 @@ export const StyledTreeNodeItem = styled.div` } `; +export const StyledFooterWrapper = styled.div<{ $paddingLeft?: number }>` + display: flex; + align-items: center; + height: 60px; + padding-left: ${(props) => props.$paddingLeft || 0}px; + transition: padding 0.2s ease-in-out; +`; + export default { name: 'option-home-styled', }; diff --git a/entrypoints/options/home/MoveToModal.tsx b/entrypoints/options/home/MoveToModal.tsx index d7e9b9c..86d22c6 100644 --- a/entrypoints/options/home/MoveToModal.tsx +++ b/entrypoints/options/home/MoveToModal.tsx @@ -45,12 +45,16 @@ export default function MoveToModal({ // 校验级联数据 const validInfo = useMemo(() => { if (!moveData) return { valid: false, type: 'tag' }; - const { tabs } = moveData || {}; + const { tagId, groupId, tabs } = moveData || {}; if (!targetOptions?.length) { return { valid: false, type: 'tag' }; } - if (!tabs?.length) { + if (!tagId && !groupId) { + return { valid: false, type: 'tag' }; + } + + if (tagId || !tabs?.length) { return { valid: targetOptions?.length > 0, type: 'tag' }; } @@ -73,20 +77,29 @@ export default function MoveToModal({ if (!validInfo.valid) return; if (!targetOptions?.length) return; - if (validInfo.type === 'tag') { + const { tagId, groupId, tabs = [] } = moveData; + + if (tagId) { + await tabListUtils.allTabGroupsMoveThrough({ + sourceTagId: tagId, + targetTagId: targetOptions[0].value, + autoMerge, + }); + + onOk?.({ targetTagId: targetOptions[0].value }); + } else if (validInfo.type === 'tag' && groupId) { const { targetGroupId } = await tabListUtils.tabGroupMoveThrough({ - sourceGroupId: moveData?.groupId, + sourceGroupId: groupId, targetTagId: targetOptions[0].value, autoMerge, }); onOk?.({ targetTagId: targetOptions[0].value, targetGroupId }); - } else { + } else if (validInfo.type === 'tabGroup' && groupId) { const targetTagId = targetOptions[0].value; const targetGroupId = targetOptions[1].value; - const tabs = moveData?.tabs || []; await tabListUtils.tabMoveThrough({ - sourceGroupId: moveData?.groupId, + sourceGroupId: groupId, targetTagId, targetGroupId, tabs, diff --git a/entrypoints/options/home/RenderTreeNode.tsx b/entrypoints/options/home/RenderTreeNode.tsx index 868a3d5..41d0d96 100644 --- a/entrypoints/options/home/RenderTreeNode.tsx +++ b/entrypoints/options/home/RenderTreeNode.tsx @@ -1,6 +1,6 @@ import React, { useMemo, useState, useEffect, useRef } from 'react'; import { theme, Modal } from 'antd'; -import { CloseOutlined, PlusOutlined } from '@ant-design/icons'; +import { CloseOutlined, PlusOutlined, SendOutlined } from '@ant-design/icons'; import { useIntlUtls } from '~/entrypoints/common/hooks/global'; import { ENUM_COLORS, UNNAMED_TAG, UNNAMED_GROUP } from '~/entrypoints/common/constants'; import { StyledActionIconBtn } from '~/entrypoints/common/style/Common.styled'; @@ -9,6 +9,8 @@ import { RenderTreeNodeProps } from './types'; import { dndKeys } from './constants'; import EditInput from '../components/EditInput'; import DropComponent from '@/entrypoints/common/components/DropComponent'; +import MoveToModal from './MoveToModal'; +import useMoveTo from './hooks/moveTo'; const allowDropKey = dndKeys.tabItem; @@ -20,6 +22,7 @@ export default function RenderTreeNode({ refreshKey, onAction, onTabItemDrop, // 这个 onTabItemDrop 只是为了方便右侧面板的标签页拖拽到左侧树的标签组,左侧树中的 分类和标签组的拖拽由 antd 的 Tree 组件自带实现 + onMoveTo }: RenderTreeNodeProps) { const { token } = theme.useToken(); const { $fmt } = useIntlUtls(); @@ -31,6 +34,14 @@ export default function RenderTreeNode({ }); const unnamedNodeName = node.type === 'tag' ? UNNAMED_TAG : UNNAMED_GROUP; + const { + modalVisible: moveToModalVisible, + openModal: openMoveToModal, + onConfirm: onMoveToConfirm, + onClose: onMoveToClose, + moveData + } = useMoveTo(); + // 是否锁定 const isLocked = useMemo(() => { return !!node?.originData?.isLocked; @@ -118,7 +129,16 @@ export default function RenderTreeNode({ )} - {node.type === 'tag' && ( + {node.type === 'tag' && <> + openMoveToModal?.({ tagId: node.key as string })} + > + + - )} + + } {!isLocked && !isStaticTag && ( {removeDesc} )} + + {/* 移动到弹窗 */} + { moveToModalVisible && ( + { + onMoveToConfirm(() => { + onMoveTo?.({ targetData }); + }); + }} + onCancel={onMoveToClose} + /> + )} ); diff --git a/entrypoints/options/home/hooks/moveTo.ts b/entrypoints/options/home/hooks/moveTo.ts index e537a51..032757d 100644 --- a/entrypoints/options/home/hooks/moveTo.ts +++ b/entrypoints/options/home/hooks/moveTo.ts @@ -6,8 +6,8 @@ export default function useMoveTo() { const [moveData, setMoveData] = useState(); // 打开移动到弹窗 - const openModal = ({ groupId, tabs }: MoveDataProps) => { - setMoveData({ groupId, tabs }); + const openModal = ({ tagId, groupId, tabs }: MoveDataProps) => { + setMoveData({ tagId, groupId, tabs }); setModalVisible(true); }; const onConfirm = (callback?: () => void) => { diff --git a/entrypoints/options/home/index.tsx b/entrypoints/options/home/index.tsx index 8e44fed..c5187f5 100644 --- a/entrypoints/options/home/index.tsx +++ b/entrypoints/options/home/index.tsx @@ -21,16 +21,23 @@ import { } from '@ant-design/icons'; import { useIntlUtls } from '~/entrypoints/common/hooks/global'; import { classNames } from '~/entrypoints/common/utils'; +import { tabListUtils } from '@/entrypoints/common/storage'; import { StyledActionIconBtn } from '~/entrypoints/common/style/Common.styled'; -import { StyledListWrapper, StyledSidebarWrapper } from './Home.styled'; +import { + StyledListWrapper, + StyledSidebarWrapper, + StyledFooterWrapper, +} from './Home.styled'; import ToggleSidebarBtn from '../components/ToggleSidebarBtn'; import SortingBtns from './SortingBtns'; import RenderTreeNode from './RenderTreeNode'; import TabGroup from './TabGroup'; import HotkeyList from '../components/HotkeyList'; +import StickyFooter from '~/entrypoints/common/components/StickyFooter'; import type { TagItem, GroupItem, TabItem } from '~/entrypoints/types'; import type { + TreeDataNodeTag, TreeDataNodeTabGroup, TreeDataNodeUnion, MoveToCallbackProps, @@ -38,7 +45,6 @@ import type { import { useTreeData } from './hooks/treeData'; import useHotkeys from './hooks/hotkeys'; import { getTreeData } from './utils'; -import { tabListUtils } from '@/entrypoints/common/storage'; export default function Home() { const { token } = theme.useToken(); @@ -99,6 +105,7 @@ export default function Home() { setConfirmModalVisible(false); }; + // 移动单个标签组 const handleTabGroupMoveTo = async ({ moveData, targetData, @@ -111,10 +118,15 @@ export default function Home() { if (!groupId) return; // 如果是移动标签页的话,则不需要重新选择标签组 if (tabs && tabs?.length > 0) return; + if (!targetTagId) return; let group = null; for (let tag of treeData) { if (!!targetTagId && tag.key !== targetTagId) continue; + if (!targetGroupId) { + handleSelect(treeData, [targetTagId], { node: tag as TreeDataNodeTag }); + break; + } for (let g of tag.children || []) { if (g.key === targetGroupId) { group = g; @@ -127,6 +139,18 @@ export default function Home() { } }); }; + // 移动所有标签组 + const handleAllTabGroupsMoveTo = async ({ targetData }: MoveToCallbackProps) => { + refreshTreeData((treeData) => { + const { targetTagId } = targetData || {}; + for (let tag of treeData) { + if (tag.key == targetTagId) { + handleSelect(treeData, [targetTagId], { node: tag as TreeDataNodeTag }); + break; + } + } + }); + }; const onSearch: SearchProps['onSearch'] = (value) => { setSearchValue(value); @@ -306,6 +330,7 @@ export default function Home() { refreshKey={refreshKey} onAction={onTreeNodeAction} onTabItemDrop={handleTabItemDrop} + onMoveTo={handleAllTabGroupsMoveTo} > )} onExpand={(expandedKeys) => setExpandedKeys(expandedKeys)} @@ -359,6 +384,19 @@ export default function Home() { + {/* + + + + + + */} + {/* 清空全部提示 */} void; onTabItemDrop?: DndTabItemOnDropCallback; + onMoveTo?: ({moveData, targetData}: MoveToCallbackProps) => void; }; // 需要移动的数据 export interface MoveDataProps { - groupId: string; + tagId?: string; + groupId?: string; tabs?: TabItem[]; } @@ -49,7 +51,7 @@ export interface MoveTargetProps { export interface MoveToCallbackProps { moveData?: MoveDataProps; targetData: MoveTargetProps; - selected: boolean; + selected?: boolean; } // tagList 级联 option diff --git a/entrypoints/options/sync/SidebarContent.tsx b/entrypoints/options/sync/SidebarContent.tsx index 1a8edd6..38be884 100644 --- a/entrypoints/options/sync/SidebarContent.tsx +++ b/entrypoints/options/sync/SidebarContent.tsx @@ -4,6 +4,7 @@ import { SettingOutlined, SyncOutlined, ExclamationCircleOutlined, + CloudDownloadOutlined, CloudUploadOutlined, } from '@ant-design/icons'; import styled from 'styled-components'; @@ -18,6 +19,7 @@ import type { } from '~/entrypoints/types'; import { classNames } from '~/entrypoints/common/utils'; import { useIntlUtls } from '~/entrypoints/common/hooks/global'; +import { syncTypeMap } from '~/entrypoints/common/constants'; import type { RemoteOptionProps } from './types'; import { remoteOptions } from './constants'; import { StyledLabel } from './Sync.styled'; @@ -123,6 +125,18 @@ function CardItemMarkup({ , + {$fmt('sync.tip.manualPullForce')}} + > +
onAction?.(option, syncTypeMap['MANUAL_PULL_FORCE'])} + > + +
+
,
onAction?.(option, 'manual-push-merge')} + onClick={() => onAction?.(option, syncTypeMap['MANUAL_PUSH_MERGE'])} > - +
,
onAction?.(option, 'manual-push-force')} + onClick={() => onAction?.(option, syncTypeMap['MANUAL_PUSH_FORCE'])} > - +
, ] diff --git a/entrypoints/options/sync/constants.ts b/entrypoints/options/sync/constants.ts index 082ee83..b41e8a1 100644 --- a/entrypoints/options/sync/constants.ts +++ b/entrypoints/options/sync/constants.ts @@ -13,13 +13,6 @@ export const remoteOptions: RemoteOptionProps[] = [ { key: 'gitee', label: 'Gitee', pageUrl: tokenSettingsPageUrls.gitee }, ]; -// 同步方式枚举 -export const syncTypeMap: Record = { - AUTO: 'auto', - MANUAL_PUSH_MERGE: 'manual-push-merge', - MANUAL_PUSH_FORCE: 'manual-push-force', -}; - export default { name: 'option-sync-constants', }; diff --git a/entrypoints/options/sync/hooks.ts b/entrypoints/options/sync/hooks.ts index 88e989d..565ea67 100644 --- a/entrypoints/options/sync/hooks.ts +++ b/entrypoints/options/sync/hooks.ts @@ -1,19 +1,22 @@ import type { SyncType, SyncResultItemProps } from '~/entrypoints/types'; import { useIntlUtls } from '~/entrypoints/common/hooks/global'; -import { SUCCESS_KEY } from '~/entrypoints/common/constants'; -import { syncTypeMap } from './constants'; +import { SUCCESS_KEY, syncTypeMap } from '~/entrypoints/common/constants'; export function useSyncResult(resultData?: SyncResultItemProps) { const { $fmt } = useIntlUtls(); const syncTypeTextMap = { [syncTypeMap.AUTO]: $fmt('sync.syncType.auto'), + [syncTypeMap.MANUAL_PULL_MERGE]: $fmt('sync.syncType.manualPullMerge'), + [syncTypeMap.MANUAL_PULL_FORCE]: $fmt('sync.syncType.manualPullForce'), [syncTypeMap.MANUAL_PUSH_MERGE]: $fmt('sync.syncType.manualPushMerge'), [syncTypeMap.MANUAL_PUSH_FORCE]: $fmt('sync.syncType.manualPushForce'), }; const syncTypeTipMap = { [syncTypeMap.AUTO]: $fmt('sync.tip.auto'), + [syncTypeMap.MANUAL_PULL_MERGE]: $fmt('sync.tip.manualPullMerge'), + [syncTypeMap.MANUAL_PULL_FORCE]: $fmt('sync.tip.manualPullForce'), [syncTypeMap.MANUAL_PUSH_MERGE]: $fmt('sync.tip.manualPushMerge'), [syncTypeMap.MANUAL_PUSH_FORCE]: $fmt('sync.tip.manualPushForce'), }; diff --git a/entrypoints/types/sync.ts b/entrypoints/types/sync.ts index 64c7313..176a4cf 100644 --- a/entrypoints/types/sync.ts +++ b/entrypoints/types/sync.ts @@ -10,7 +10,7 @@ export type SyncConfigProps = { gitee: SyncConfigItemProps; github: SyncConfigItemProps; }; -export type SyncType = 'auto' | 'manual-push-merge' | 'manual-push-force'; +export type SyncType = 'auto' | 'manual-pull-merge' | 'manual-pull-force' | 'manual-push-merge' | 'manual-push-force'; // 单次同步结果类型 export type SyncResultItemProps = { syncTime: string; diff --git a/package.json b/package.json index aed3f9f..5fc8a61 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "nice-tab", "description": "A nice, convenient, powerful tab manager", "private": true, - "version": "2.0.0", + "version": "2.0.1", "type": "module", "scripts": { "dev": "wxt",