diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5a13eaba..9b410321 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ jobs: ci: runs-on: ubuntu-latest steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: node-version: 24.11.1 diff --git a/.github/workflows/create-release-pull-request.yml b/.github/workflows/create-release-pull-request.yml index 14566aed..4b563dd7 100644 --- a/.github/workflows/create-release-pull-request.yml +++ b/.github/workflows/create-release-pull-request.yml @@ -12,7 +12,7 @@ jobs: pull-requests: write runs-on: ubuntu-latest steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - uses: ruby/setup-ruby@d5126b9b3579e429dd52e51e68624dda2e05be25 # v1.267.0 with: ruby-version: 3.4.7 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 899246bc..e1d34767 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,7 +12,7 @@ jobs: if: github.event.pull_request.merged == true runs-on: ubuntu-latest steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Install jq run: sudo apt-get install -y jq - name: Get version from manifest.json @@ -31,7 +31,7 @@ jobs: needs: create_tag_and_release_note runs-on: ubuntu-latest steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: node-version: 24.11.1 diff --git a/.github/workflows/validate-release.yml b/.github/workflows/validate-release.yml index 27985893..a110b069 100644 --- a/.github/workflows/validate-release.yml +++ b/.github/workflows/validate-release.yml @@ -9,7 +9,7 @@ jobs: check_manifest_version_update: runs-on: ubuntu-latest steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Install jq run: sudo apt-get install jq - name: Get latest tag from main branch diff --git a/src/platform/repository/TabGroupRepository.ts b/src/platform/repository/TabGroupRepository.ts index b32fa4d4..d45bc3b8 100644 --- a/src/platform/repository/TabGroupRepository.ts +++ b/src/platform/repository/TabGroupRepository.ts @@ -4,8 +4,14 @@ import type { TabGroup, TabGroupColor, } from "../../model/TabContainer"; -import type { WindowId } from "../../model/Window"; +import { + findGroupsByName, + flatTabsInWindows, + type Window, + type WindowId, +} from "../../model/Window"; import { ChromeLocalStorage } from "../storage/ChromeLocalStorage"; +import { addTabsToGroup } from "./TabsRepository"; export const collapseTabGroup = async (groupId: number): Promise => { await chrome.tabGroups.update(groupId, { collapsed: true }); @@ -31,6 +37,24 @@ export const createGroupWithTabs = async (name: string, tabIds: number[]) => { await chrome.tabGroups.update(groupId, { title: name }); }; +export const groupTabsBySearchKeyword = async ( + keyword: string, + windows: Window[], + tabIds: number[], +) => { + const pinnedTabs = flatTabsInWindows(windows).filter((tab) => tab.pinned); + const tabIdsExcludingPinned = tabIds.filter( + (tabId) => !pinnedTabs.some((tab) => tab.id === tabId), + ); + + const sameNameGroups = findGroupsByName(keyword, windows); + if (sameNameGroups.length > 0) { + await addTabsToGroup(tabIdsExcludingPinned, sameNameGroups[0].id); + } + + await createGroupWithTabs(keyword, tabIdsExcludingPinned); +}; + export const moveTabGroup = async ( groupId: number, currentWindowId: number, diff --git a/src/platform/repository/TabsRepository.ts b/src/platform/repository/TabsRepository.ts index 98793931..c57cf1fa 100644 --- a/src/platform/repository/TabsRepository.ts +++ b/src/platform/repository/TabsRepository.ts @@ -1,5 +1,14 @@ -import type { Tab, TabId, TabStatus } from "../../model/Tab"; -import type { WindowId } from "../../model/Window"; +import { + isSamePageTabs, + type Tab, + type TabId, + type TabStatus, +} from "../../model/Tab"; +import { + flatTabsInWindows, + type Window, + type WindowId, +} from "../../model/Window"; import { ChromeLocalStorage } from "../storage/ChromeLocalStorage"; import { ChromeSessionStorage } from "../storage/ChromeSessionStorage"; @@ -24,6 +33,31 @@ export const closeTabs = async (tabIds: number[]) => { await chrome.tabs.remove(tabIds); }; +export const moveTabFromRootToPinned = async ( + tabId: number, + windowId: number, + index: number, +) => { + await moveTab(tabId, windowId, -1); + await pinTab(tabId); + await moveTab(tabId, windowId, index); +}; + +export const moveTabFromPinnedToPinned = async ( + tabId: number, + sourceWindowId: number, + destWindowId: number, + index: number, +) => { + if (sourceWindowId === destWindowId) { + await moveTab(tabId, sourceWindowId, index); + } else { + await moveTab(tabId, destWindowId, -1); + await pinTab(tabId); + await moveTab(tabId, destWindowId, index); + } +}; + export const updateTabLastActivatedAt = async ( tabId: number, options?: { @@ -164,6 +198,18 @@ export const unpinAllTabs = async (tabs: Tab[]) => { } }; +export const resolveDuplicatedTabs = async ( + windows: Window[], + targetTab: Tab, +) => { + const allTabs = flatTabsInWindows(windows); + const duplicateTabs = allTabs.filter( + (tab) => tab.id !== targetTab.id && isSamePageTabs(tab, targetTab), + ); + const duplicateTabIds = duplicateTabs.map((t) => t.id); + await closeTabs(duplicateTabIds); +}; + export const getRecentActiveTabs = async (): Promise => { const recentActiveTabs = await ChromeSessionStorage.getRecentActiveTabs(); if (!recentActiveTabs) return []; diff --git a/src/platform/repository/WindowsRepository.ts b/src/platform/repository/WindowsRepository.ts index eff871bf..9515dcac 100644 --- a/src/platform/repository/WindowsRepository.ts +++ b/src/platform/repository/WindowsRepository.ts @@ -129,6 +129,29 @@ export const closeWindow = async (window: Window): Promise => { return getWindows(); }; +export const mergeWindow = async ( + destWindowId: number, + sourceWindow: Window, +) => { + for (const child of sourceWindow.children) { + if (isPinned(child)) { + for (const tab of child.children) { + await chrome.tabs.move(tab.id, { windowId: destWindowId, index: -1 }); + await chrome.tabs.update(tab.id, { pinned: true }); + } + } + if (isTabGroup(child)) { + await chrome.tabGroups.move(child.id, { + windowId: destWindowId, + index: -1, + }); + } + if (isTab(child)) { + await chrome.tabs.move(child.id, { windowId: destWindowId, index: -1 }); + } + } +}; + const applyLastActivatedAtToTabs = async ( windows: Window[], ): Promise => { diff --git a/src/ui/components/ActionMenu.tsx b/src/ui/components/ActionMenu.tsx index 017912d9..6d373b43 100644 --- a/src/ui/components/ActionMenu.tsx +++ b/src/ui/components/ActionMenu.tsx @@ -59,12 +59,12 @@ import { } from "../../platform/repository/TabsRepository"; import { closeWindow, + mergeWindow, removeStoredWindow, restoreWindow, saveWindow, saveWindows, } from "../../platform/repository/WindowsRepository"; -import mergeWindow from "../functions/mergeWindow"; type ActionMenuItemAttrs = | { diff --git a/src/ui/components/DragAndDropContext.tsx b/src/ui/components/DragAndDropContext.tsx index 0cd74f76..5bec2d27 100644 --- a/src/ui/components/DragAndDropContext.tsx +++ b/src/ui/components/DragAndDropContext.tsx @@ -44,6 +44,8 @@ import { } from "../../platform/repository/TabGroupRepository"; import { moveTab, + moveTabFromPinnedToPinned, + moveTabFromRootToPinned, moveTabOutOfGroup, moveTabToOtherWindow, unpinTab, @@ -53,8 +55,6 @@ import { addWindowWithTabGroup, } from "../../platform/repository/WindowsRepository"; import { WindowsContext } from "../contexts/WindowsContext"; -import moveTabFromPinnedToPinned from "../functions/moveTabFromPinnedToPinned"; -import moveTabFromRootToPinned from "../functions/moveTabFromRootToPinned"; import TabGroupContainer from "./TabGroupContainer"; import TabItem from "./TabItem"; diff --git a/src/ui/components/TabItem.tsx b/src/ui/components/TabItem.tsx index 682546c2..bf8ce616 100644 --- a/src/ui/components/TabItem.tsx +++ b/src/ui/components/TabItem.tsx @@ -28,9 +28,12 @@ import { hasDuplicatedTabs, type Window, } from "../../model/Window"; -import { closeTab, focusTab } from "../../platform/repository/TabsRepository"; +import { + closeTab, + focusTab, + resolveDuplicatedTabs, +} from "../../platform/repository/TabsRepository"; import { WindowsContext } from "../contexts/WindowsContext"; -import resolveDuplicatedTabs from "../functions/resolveDuplicatedTabs"; import { TabItemActionMenu } from "./ActionMenu"; import TabFavicon from "./TabFavicon"; diff --git a/src/ui/functions/groupTabsBySearchKeyword.ts b/src/ui/functions/groupTabsBySearchKeyword.ts deleted file mode 100644 index e293eaa1..00000000 --- a/src/ui/functions/groupTabsBySearchKeyword.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { - findGroupsByName, - flatTabsInWindows, - type Window, -} from "../../model/Window"; -import { createGroupWithTabs } from "../../platform/repository/TabGroupRepository"; -import { addTabsToGroup } from "../../platform/repository/TabsRepository"; - -const groupTabsBySearchKeyword = async ( - keyword: string, - windows: Window[], - tabIds: number[], -) => { - const pinnedTabs = flatTabsInWindows(windows).filter((tab) => tab.pinned); - const tabIdsExcludingPinned = tabIds.filter( - (tabId) => !pinnedTabs.some((tab) => tab.id === tabId), - ); - - const sameNameGroups = findGroupsByName(keyword, windows); - if (sameNameGroups.length > 0) { - await addTabsToGroup(tabIdsExcludingPinned, sameNameGroups[0].id); - } - - await createGroupWithTabs(keyword, tabIdsExcludingPinned); -}; - -export default groupTabsBySearchKeyword; diff --git a/src/ui/functions/mergeWindow.ts b/src/ui/functions/mergeWindow.ts deleted file mode 100644 index 8ab84a7b..00000000 --- a/src/ui/functions/mergeWindow.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { isPinned, isTab, isTabGroup } from "../../model/TabContainer"; -import type { Window } from "../../model/Window"; -import { moveTabGroup } from "../../platform/repository/TabGroupRepository"; -import { moveTab, pinTab } from "../../platform/repository/TabsRepository"; - -const mergeWindow = async (destWindowId: number, sourceWindow: Window) => { - for (const child of sourceWindow.children) { - if (isPinned(child)) { - for (const tab of child.children) { - await moveTab(tab.id, destWindowId, -1); - await pinTab(tab.id); - } - } - if (isTabGroup(child)) { - await moveTabGroup(child.id, sourceWindow.id, destWindowId, -1); - } - if (isTab(child)) { - await moveTab(child.id, destWindowId, -1); - } - } -}; - -export default mergeWindow; diff --git a/src/ui/functions/moveTabFromPinnedToPinned.ts b/src/ui/functions/moveTabFromPinnedToPinned.ts deleted file mode 100644 index cd733cb1..00000000 --- a/src/ui/functions/moveTabFromPinnedToPinned.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { moveTab, pinTab } from "../../platform/repository/TabsRepository"; - -const moveTabFromPinnedToPinned = async ( - tabId: number, - sourceWindowId: number, - destWindowId: number, - index: number, -) => { - if (sourceWindowId === destWindowId) { - await moveTab(tabId, sourceWindowId, index); - } else { - await moveTab(tabId, destWindowId, -1); - await pinTab(tabId); - await moveTab(tabId, destWindowId, index); - } -}; - -export default moveTabFromPinnedToPinned; diff --git a/src/ui/functions/moveTabFromRootToPinned.ts b/src/ui/functions/moveTabFromRootToPinned.ts deleted file mode 100644 index 91b2097c..00000000 --- a/src/ui/functions/moveTabFromRootToPinned.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { moveTab, pinTab } from "../../platform/repository/TabsRepository"; - -const moveTabFromRootToPinned = async ( - tabId: number, - windowId: number, - index: number, -) => { - await moveTab(tabId, windowId, -1); - await pinTab(tabId); - await moveTab(tabId, windowId, index); -}; - -export default moveTabFromRootToPinned; diff --git a/src/ui/functions/resolveDuplicatedTabs.ts b/src/ui/functions/resolveDuplicatedTabs.ts deleted file mode 100644 index 37e55b4b..00000000 --- a/src/ui/functions/resolveDuplicatedTabs.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { isSamePageTabs, type Tab } from "../../model/Tab"; -import { flatTabsInWindows, type Window } from "../../model/Window"; -import { closeTabs } from "../../platform/repository/TabsRepository"; - -const resolveDuplicatedTabs = async (windows: Window[], targetTab: Tab) => { - const allTabs = flatTabsInWindows(windows); - const duplicateTabs = allTabs.filter( - (tab) => tab.id !== targetTab.id && isSamePageTabs(tab, targetTab), - ); - const duplicateTabIds = duplicateTabs.map((t) => t.id); - await closeTabs(duplicateTabIds); -}; - -export default resolveDuplicatedTabs; diff --git a/src/ui/options/TabSearchForm.tsx b/src/ui/options/TabSearchForm.tsx index 3362bd30..903cff24 100644 --- a/src/ui/options/TabSearchForm.tsx +++ b/src/ui/options/TabSearchForm.tsx @@ -21,13 +21,13 @@ import { useCallback, useContext, useEffect, useRef, useState } from "react"; import t from "../../i18n/Translations"; import type { Tab } from "../../model/Tab"; import { findTabsByTitleOrUrl } from "../../model/Window"; +import { groupTabsBySearchKeyword } from "../../platform/repository/TabGroupRepository"; import { focusTab, getRecentActiveTabs, } from "../../platform/repository/TabsRepository"; import TabItem from "../components/TabItem"; import { WindowsContext, WindowsProvider } from "../contexts/WindowsContext"; -import groupTabsBySearchKeyword from "../functions/groupTabsBySearchKeyword"; const smoothScrollTo = (container: HTMLElement, targetScrollTop: number) => { const startScrollTop = container.scrollTop; diff --git a/src/ui/popup/SearchResult.tsx b/src/ui/popup/SearchResult.tsx index 90e46ca7..037795c0 100644 --- a/src/ui/popup/SearchResult.tsx +++ b/src/ui/popup/SearchResult.tsx @@ -11,13 +11,13 @@ import { useContext, useEffect, useRef, useState } from "react"; import t from "../../i18n/Translations"; import type { Tab } from "../../model/Tab"; import { findTabsByTitleOrUrl } from "../../model/Window"; +import { groupTabsBySearchKeyword } from "../../platform/repository/TabGroupRepository"; import { focusTab, getRecentActiveTabs, } from "../../platform/repository/TabsRepository"; import TabItem from "../components/TabItem"; import { WindowsContext } from "../contexts/WindowsContext"; -import groupTabsBySearchKeyword from "../functions/groupTabsBySearchKeyword"; type SearchResultProps = { searchText: string;