From 5988a6aefdf97179817662641aef95fcef235921 Mon Sep 17 00:00:00 2001 From: web96lol Date: Wed, 15 Oct 2025 05:38:44 -0700 Subject: [PATCH 1/3] Remove remaining retention configuration --- duplicate-tabs-closer-master/README.md | 41 ++-- .../_locales/en/messages.json | 52 ----- .../_locales/fr/messages.json | 52 ----- .../_locales/ja/messages.json | 52 ----- .../_locales/ru/messages.json | 52 ----- .../_locales/uk/messages.json | 52 ----- .../_locales/zh_CN/messages.json | 63 ------ duplicate-tabs-closer-master/background.js | 184 +++++------------- .../optionPage/optionPage.html | 102 ++-------- .../optionPage/optionPage.js | 9 +- duplicate-tabs-closer-master/popup/popup.html | 98 ++-------- duplicate-tabs-closer-master/popup/popup.js | 26 +-- 12 files changed, 114 insertions(+), 669 deletions(-) diff --git a/duplicate-tabs-closer-master/README.md b/duplicate-tabs-closer-master/README.md index 73d429a..57d6e65 100644 --- a/duplicate-tabs-closer-master/README.md +++ b/duplicate-tabs-closer-master/README.md @@ -14,34 +14,21 @@ deDup2Activate detects and closes duplicate tabs. * **Close tab automatically** *(default)*: automatically closes the detected duplicate tab. * **Do nothing**: monitor tabs and update the badge icon to indicate the number of duplicate tabs detected. -#### On remaining tab: +#### Whitelist: (Used with option *Close tab automatically*) -* **Activate** *(default)*: once the duplicate tab is closed, the remaining tab is activated. -* **Do nothing**: nothing is done after the duplicate tab is closed. -* **Apply opening tab behavior**: once the duplicate tab is closed, depending on the default tab behavior, the remaining tab will be moved to the position of the closed tab and activated if needed. - -#### Whiltelist": -(Used with option *Close tab automatically*) -List of urls to not close automatically. Duplicate tabs skipped will be notified in badge. -Wildcards and RegExp are supported. - - -### Priority: -(Used with option *Close tab automatically* and *Close all duplicate tabs* button) -* **Keep older tab** *(default)*: Keep the already existing tab. -* **Keep newer tab**: Keep the newer tab. -* **Keep tab with https** *(default on)*: Ignore the scheme part during comparison and keep the tab with the https scheme. -* **Keep pinned tab** *(default on)*: Keep the pinned tab. - - -### matchingRules: - -* **Ignore case in URL** *(default on)* -* **Ignore 'www' in URL domain name** *(default on)* -* **Ignore hash part in URL** *(default off)* -* **Ignore search part in URL** *(default off)* -* **Ignore path part in URL** *(default off)* -* **Compare with tab title** *(default off)* +List of urls to not close automatically. Duplicate tabs skipped will be notified in badge. +Wildcards and RegExp are supported. + + +### Duplicate resolution behavior +(Used with option *Close tab automatically* and the *Close all duplicate tabs* button) +The extension always keeps pinned tabs, prefers the HTTPS version of a page, and retains the older tab when deciding which duplicate to close. These rules are now built-in and no longer configurable. + + +### matchingRules: + +URL comparisons always normalize links by forcing HTTPS, ignoring a leading `www`, comparing in lowercase, and keeping the full path, search, and hash segments. The remaining optional rule is: +* **Compare with tab title** *(default off)* ### Scope: diff --git a/duplicate-tabs-closer-master/_locales/en/messages.json b/duplicate-tabs-closer-master/_locales/en/messages.json index 0a23e5d..8e93146 100644 --- a/duplicate-tabs-closer-master/_locales/en/messages.json +++ b/duplicate-tabs-closer-master/_locales/en/messages.json @@ -19,66 +19,14 @@ "message": "Show panel", "description": "Show panel" }, - "tabPriority": { - "message": "Priority", - "description": "Priority" - }, - "keepNewerTab": { - "message": "Keep newer tab", - "description": "Keep newer tab" - }, - "keepOlderTab": { - "message": "Keep older tab", - "description": "Keep older tab" - }, "keepTabWithHistory": { "message": "Keep tab with history", "description": "Keep tab with history" }, - "keeptabwithHttps": { - "message": "Keep tab with https", - "description": "Keep tab with https" - }, - "keepPinnedTab": { - "message": "Keep pinned tab", - "description": "Keep pinned tab" - }, - "onRemainingTab": { - "message": "On remaining tab", - "description": "On remaining tab" - }, - "activateKeptTab": { - "message": "Activate", - "description": "Activate" - }, - "defaultTabBehavior": { - "message": "Default tab behaviour", - "description": "Default tab behaviour" - }, "matchingRules": { "message": "Matching rules", "description": "Matching rules" }, - "ignoreHashPart": { - "message": "Ignore hash part in URL", - "description": "Ignore hash part in URL" - }, - "ignoreSearchPart": { - "message": "Ignore search part in URL", - "description": "Ignore search part in URL" - }, - "ignorePathPart": { - "message": "Ignore path part in URL", - "description": "Ignore path part in URL" - }, - "ignore3w": { - "message": "Ignore 'www' in URL domain name", - "description": "Ignore 'www' in URL domain name" - }, - "caseInsensitive": { - "message": "Ignore case in URL", - "description": "Ignore case in URL" - }, "compareWithTitle": { "message": "Compare with title", "description": "Compare with title" diff --git a/duplicate-tabs-closer-master/_locales/fr/messages.json b/duplicate-tabs-closer-master/_locales/fr/messages.json index 100ed48..00bc867 100644 --- a/duplicate-tabs-closer-master/_locales/fr/messages.json +++ b/duplicate-tabs-closer-master/_locales/fr/messages.json @@ -19,66 +19,14 @@ "message": "Afficher le panneau", "description": "Show panel" }, - "tabPriority": { - "message": "Priorité", - "description": "Priority" - }, - "keepNewerTab": { - "message": "Conserver le nouvel onglet", - "description": "Keep newer tab" - }, - "keepOlderTab": { - "message": "Conserver l'ancien onglet", - "description": "Keep older tab" - }, "keepTabWithHistory": { "message": "Conserver l'onglet ayant un historique", "description": "Keep tab with history" }, - "keeptabwithHttps": { - "message": "Conserver l'onglet avec https", - "description": "Keep tab with https" - }, - "keepPinnedTab": { - "message": "Conserver l'onglet épinglé", - "description": "Keep pinned tab" - }, - "onRemainingTab": { - "message": "Sur l'onglet restant", - "description": "On remaining tab" - }, - "activateKeptTab": { - "message": "Activer", - "description": "Activate" - }, - "defaultTabBehavior": { - "message": "Comportement par défaut de l'onglet", - "description": "Default tab behaviour" - }, "matchingRules": { "message": "Règle de correspondance", "description": "Règle de correspondance" }, - "ignoreHashPart": { - "message": "Ignorer la partie « hachage » de l'URL", - "description": "Ignore hash part in URL" - }, - "ignoreSearchPart": { - "message": "Ignorer la partie « recherche » de l'URL", - "description": "Ignore search part in URL" - }, - "ignorePathPart": { - "message": "Ignorer la partie « chemin » de l'URL", - "description": "Ignore path part in URL" - }, - "ignore3w": { - "message": "Ignorer 'www' dans le nom de domaine de l'URL", - "description": "Ignorer 'www' dans le nom de domaine de l'URL" - }, - "caseInsensitive": { - "message": "Ignorer la casse de l'URL", - "description": "Ignorer la casse de l'URL" - }, "compareWithTitle": { "message": "Comparer avec le titre", "description": "Compare with title" diff --git a/duplicate-tabs-closer-master/_locales/ja/messages.json b/duplicate-tabs-closer-master/_locales/ja/messages.json index aa4d5d5..68752fe 100644 --- a/duplicate-tabs-closer-master/_locales/ja/messages.json +++ b/duplicate-tabs-closer-master/_locales/ja/messages.json @@ -19,66 +19,14 @@ "message": "パネルを表示する", "description": "Show panel" }, - "tabPriority": { - "message": "優先順位", - "description": "Priority" - }, - "keepNewerTab": { - "message": "新しい方のタブを残す", - "description": "Keep newer tab" - }, - "keepOlderTab": { - "message": "古い方のタブを残す", - "description": "Keep older tab" - }, "keepTabWithHistory": { "message": "タブの履歴を維持する", "description": "Keep tab with history" }, - "keeptabwithHttps": { - "message": "HTTPSのタブを残す", - "description": "Keep tab with https" - }, - "keepPinnedTab": { - "message": "ピン留めされたタブを残す", - "description": "Keep pinned tab" - }, - "onRemainingTab": { - "message": "残されたタブがある場合", - "description": "On remaining tab" - }, - "activateKeptTab": { - "message": "アクティブにする", - "description": "Activate" - }, - "defaultTabBehavior": { - "message": "タブの既定の挙動に任せる", - "description": "Default tab behaviour" - }, "matchingRules": { "message": "Matching rules", "description": "Matching rules" }, - "ignoreHashPart": { - "message": "URLのハッシュ部分(#~)を無視する", - "description": "Ignore hash part in URL" - }, - "ignoreSearchPart": { - "message": "URLのクエリー部分(?~)を無視する", - "description": "Ignore search part in URL" - }, - "ignorePathPart": { - "message": "URLのパス部分を無視する", - "description": "Ignore path part in URL" - }, - "ignore3w": { - "message": "Ignore 'www' in URL domain name", - "description": "Ignore 'www' in URL domain name" - }, - "caseInsensitive": { - "message": "Ignore case in URL", - "description": "Ignore case in URL" - }, "compareWithTitle": { "message": "タブのタイトルを比較する", "description": "Compare with title" diff --git a/duplicate-tabs-closer-master/_locales/ru/messages.json b/duplicate-tabs-closer-master/_locales/ru/messages.json index bc3c231..8980a7d 100644 --- a/duplicate-tabs-closer-master/_locales/ru/messages.json +++ b/duplicate-tabs-closer-master/_locales/ru/messages.json @@ -19,66 +19,14 @@ "message": "Показать панель", "description": "Show panel" }, - "tabPriority": { - "message": "Приоритет", - "description": "Priority" - }, - "keepNewerTab": { - "message": "Сохранить новейшую вкладку", - "description": "Keep newer tab" - }, - "keepOlderTab": { - "message": "Сохранить старейшую вкладку", - "description": "Keep older tab" - }, "keepTabWithHistory": { "message": "Сохранить вкладку с историей", "description": "Keep tab with history" }, - "keeptabwithHttps": { - "message": "Сохранить вкладку с https", - "description": "Keep tab with https" - }, - "keepPinnedTab": { - "message": "Сохранить закрепленную вкладку", - "description": "Keep pinned tab" - }, - "onRemainingTab": { - "message": "Оставшуюся вкладку...", - "description": "On remaining tab" - }, - "activateKeptTab": { - "message": "Сделать активной", - "description": "Activate" - }, - "defaultTabBehavior": { - "message": "Поведение вкладки по умолчанию", - "description": "Default tab behaviour" - }, "matchingRules": { "message": "Matching rules", "description": "Matching rules" }, - "ignoreHashPart": { - "message": "Игнорировать хэш-часть в URL вкладки", - "description": "Ignore hash part in URL" - }, - "ignoreSearchPart": { - "message": "Игнорировать часть поиска в URL вкладки", - "description": "Ignore search part in URL" - }, - "ignorePathPart": { - "message": "Игнорировать часть пути в URL вкладки", - "description": "Ignore path part in URL" - }, - "ignore3w": { - "message": "Ignore 'www' in URL domain name", - "description": "Ignore 'www' in URL domain name" - }, - "caseInsensitive": { - "message": "Ignore case in URL", - "description": "Ignore case in URL" - }, "compareWithTitle": { "message": "Сравнить заголовки вкладок", "description": "Compare with title" diff --git a/duplicate-tabs-closer-master/_locales/uk/messages.json b/duplicate-tabs-closer-master/_locales/uk/messages.json index 6c79e8e..bf27b63 100644 --- a/duplicate-tabs-closer-master/_locales/uk/messages.json +++ b/duplicate-tabs-closer-master/_locales/uk/messages.json @@ -19,66 +19,14 @@ "message": "Показати панель", "description": "Show panel" }, - "tabPriority": { - "message": "Пріоритет", - "description": "Priority" - }, - "keepNewerTab": { - "message": "Зберегти новішу вкладку", - "description": "Keep newer tab" - }, - "keepOlderTab": { - "message": "Зберегти старішу вкладку", - "description": "Keep older tab" - }, "keepTabWithHistory": { "message": "Зберегти вкладку з історією", "description": "Keep tab with history" }, - "keeptabwithHttps": { - "message": "Зберегти вкладку з https", - "description": "Keep tab with https" - }, - "keepPinnedTab": { - "message": "Зберегти закріплену вкладку", - "description": "Keep pinned tab" - }, - "onRemainingTab": { - "message": "Вкладку, що залишилася...", - "description": "On remaining tab" - }, - "activateKeptTab": { - "message": "Зробити активною", - "description": "Activate" - }, - "defaultTabBehavior": { - "message": "Поведінка вкладки за замовчуванням", - "description": "Default tab behaviour" - }, "matchingRules": { "message": "Matching rules", "description": "Matching rules" }, - "ignoreHashPart": { - "message": "Ігнорувати хеш-частину в URL вкладки", - "description": "Ignore hash part in URL" - }, - "ignoreSearchPart": { - "message": "Ігнорувати частину пошуку в URL вкладки", - "description": "Ignore search part in URL" - }, - "ignorePathPart": { - "message": "Ігнорувати частину шляху в URL вкладки", - "description": "Ignore path part in URL" - }, - "ignore3w": { - "message": "Ignore 'www' in URL domain name", - "description": "Ignore 'www' in URL domain name" - }, - "caseInsensitive": { - "message": "Ignore case in URL", - "description": "Ignore case in URL" - }, "compareWithTitle": { "message": "Порівняти заголовки вкладок", "description": "Compare with title" diff --git a/duplicate-tabs-closer-master/_locales/zh_CN/messages.json b/duplicate-tabs-closer-master/_locales/zh_CN/messages.json index 055a3a4..c1dc8ac 100644 --- a/duplicate-tabs-closer-master/_locales/zh_CN/messages.json +++ b/duplicate-tabs-closer-master/_locales/zh_CN/messages.json @@ -24,79 +24,16 @@ "description": "Show panel", "hash": "3519be2a903e741db8c653b0e7e341e8" }, - "tabPriority": { - "message": "优先级", - "description": "Priority", - "hash": "a47af5c8632e36b75d0299167f2b38e0" - }, - "keepNewerTab": { - "message": "保留新的标签页", - "description": "Keep newer tab", - "hash": "a3aa6ddd27496953c1d39e89d344f609" - }, - "keepOlderTab": { - "message": "保留旧的标签页", - "description": "Keep older tab", - "hash": "17c1cecef3fb5c4c822c709e32428724" - }, "keepTabWithHistory": { "message": "保留有历史记录的标签页", "description": "Keep tab with history", "hash": "23668f9ee2e1380d81db5eedce7e778b" }, - "keeptabwithHttps": { - "message": "保留 https 的标签页", - "description": "Keep tab with https", - "hash": "158afd64eff0d0d42ec5fcdc69456b39" - }, - "keepPinnedTab": { - "message": "保留固定的标签页", - "description": "Keep pinned tab", - "hash": "eeded9a172a2db461ba5b29f7a4c9fce" - }, - "onRemainingTab": { - "message": "剩余标签页", - "description": "On remaining tab", - "hash": "4d7dbd19c1c07d94821411eeecbe04af" - }, - "activateKeptTab": { - "message": "激活", - "description": "Activate", - "hash": "1ca475dc17fb953961e503e67fab7000" - }, - "defaultTabBehavior": { - "message": "默认标签页行为", - "description": "Default tab behaviour", - "hash": "bb896c5652054de371ffb4e161f7bc90" - }, "matchingRules": { "message": "过滤", "description": "MatchingRules", "hash": "e86eed212e60224faadd11386632e100" }, - "ignoreHashPart": { - "message": "忽略标签页 URL 中的井字部分", - "description": "Ignore hash part in URL", - "hash": "bbeeddb75b5f0c1d72685cf06108c3e3" - }, - "ignoreSearchPart": { - "message": "忽略标签页 URL 中的问号部分", - "description": "Ignore search part in URL", - "hash": "719a708f82ce50153de4e90a4b697a84" - }, - "ignorePathPart": { - "message": "忽略标签页 URL 中的路径部分", - "description": "Ignore path part in URL", - "hash": "c05dc772b761f094ec2b99f692c26148" - }, - "ignore3w": { - "message": "Ignore 'www' in URL domain name", - "description": "Ignore 'www' in URL domain name" - }, - "caseInsensitive": { - "message": "Ignore case in URL", - "description": "Ignore case in URL" - }, "compareWithTitle": { "message": "比较依据标签页标题", "description": "Compare with title", diff --git a/duplicate-tabs-closer-master/background.js b/duplicate-tabs-closer-master/background.js index 32464ec..2a87b30 100644 --- a/duplicate-tabs-closer-master/background.js +++ b/duplicate-tabs-closer-master/background.js @@ -93,49 +93,19 @@ const defaultOptions = { }, onDuplicateTabDetected: { value: "A" - }, - onRemainingTab: { - value: "A" - }, - keepTabBasedOnAge: { - value: "O" // "O" (older) or "N" (newer) }, - keepTabWithHttps: { - value: true - }, - keepPinnedTab: { - value: true - }, keepTabWithHistory: { value: false }, scope: { value: "C" }, - ignoreHashPart: { - value: false - }, - ignoreSearchPart: { - value: false - }, - ignorePathPart: { - value: false - }, - ignore3w: { - value: true - }, - caseInsensitive: { - value: true - }, compareWithTitle: { value: false }, onDuplicateTabDetectedPinned: { value: true }, - tabPriorityPinned: { - value: true - }, matchingRulesPinned: { value: true }, @@ -226,18 +196,8 @@ const setStoredOption = async (name, value, refresh) => { const options = {}; const setOptions = (storedOptions) => { - options.autoCloseTab = storedOptions.onDuplicateTabDetected.value === "A"; - options.defaultTabBehavior = storedOptions.onRemainingTab.value === "B"; - options.activateKeptTab = storedOptions.onRemainingTab.value === "A"; - options.keepNewerTab = storedOptions.keepTabBasedOnAge.value === "N"; - options.keepTabWithHttps = storedOptions.keepTabWithHttps.value; - options.keepPinnedTab = storedOptions.keepPinnedTab.value; - options.ignoreHashPart = storedOptions.ignoreHashPart.value; - options.ignoreSearchPart = storedOptions.ignoreSearchPart.value; - options.ignorePathPart = storedOptions.ignorePathPart.value; - options.compareWithTitle = storedOptions.compareWithTitle.value; - options.ignore3w = storedOptions.ignore3w.value; - options.caseInsensitive = storedOptions.caseInsensitive.value; + options.autoCloseTab = storedOptions.onDuplicateTabDetected.value === "A"; + options.compareWithTitle = storedOptions.compareWithTitle.value; options.searchInAllWindows = storedOptions.scope.value === "A" || storedOptions.scope.value === "CA"; options.searchPerContainer = storedOptions.scope.value === "CC" || storedOptions.scope.value === "CA"; options.whiteList = whiteListToPattern(storedOptions.whiteList.value); @@ -314,49 +274,27 @@ const isHttps = (url) => { // eslint-disable-next-line no-unused-vars const getMatchingURL = (url) => { if (!isValidURL(url)) return url; - let matchingURL = url; - if (options.ignorePathPart) { - const uri = new URL(matchingURL); - matchingURL = uri.origin; - } - else if (options.ignoreSearchPart) { - matchingURL = matchingURL.split("?")[0]; - } - else if (options.ignoreHashPart) { - matchingURL = matchingURL.split("#")[0]; - } - if (options.keepTabWithHttps) { - matchingURL = matchingURL.replace(/^http:\/\//i, "https://"); - } - if (options.ignore3w) { - matchingURL = matchingURL.replace("://www.", "://"); - } - if (options.caseInsensitive) { - matchingURL = matchingURL.toLowerCase(); - } - matchingURL = matchingURL.replace(/\/$/, ""); - return matchingURL; -}; - -// eslint-disable-next-line no-unused-vars -const getMatchPatternURL = (url) => { - let urlPattern = null; - if (isValidURL(url)) { - const uri = new URL(url); - urlPattern = `*://${uri.hostname}`; - if (options.ignorePathPart) { - urlPattern += "/*"; - } - else { - urlPattern += uri.pathname; - if (uri.search || uri.hash) { - urlPattern += "*"; - } - } - } - else if (isBrowserURL(url)) { - urlPattern = `${url}*`; - } + let matchingURL = url; + matchingURL = matchingURL.replace(/^http:\/\//i, "https://"); + matchingURL = matchingURL.toLowerCase(); + matchingURL = matchingURL.replace("://www.", "://"); + matchingURL = matchingURL.replace(/\/$/, ""); + return matchingURL; +}; + +// eslint-disable-next-line no-unused-vars +const getMatchPatternURL = (url) => { + let urlPattern = null; + if (isValidURL(url)) { + const uri = new URL(url); + urlPattern = `*://${uri.hostname}${uri.pathname}`; + if (uri.search || uri.hash) { + urlPattern += "*"; + } + } + else if (isBrowserURL(url)) { + urlPattern = `${url}*`; + } return urlPattern; };"use strict"; @@ -430,44 +368,32 @@ const matchTitle = (tab1, tab2) => { return false; }; -const getHttpsTabId = (observedTab, observedTabUrl, openedTab) => { - if (options.keepTabWithHttps) { - const regex = /^https:\/\//i; - const match1 = regex.test(observedTabUrl); - const match2 = regex.test(openedTab.url); - if (match1) { - return match2 ? null : observedTab.id; - } else { - return match2 ? openedTab.id : null; - } - } - return null; -}; - -const getPinnedTabId = (tab1, tab2) => { - if (options.keepPinnedTab) { - if (tab1.pinned) { - return tab2.pinned ? null : tab1.id; - } else { - return tab2.pinned ? tab2.id : null; - } - } - return null; -}; - -const getLastUpdatedTabId = (observedTab, openedTab) => { - const observedTabLastUpdate = tabsInfo.getLastComplete(observedTab.id); - const openedTabLastUpdate = tabsInfo.getLastComplete(openedTab.id); - if (options.keepNewerTab) { - if (observedTabLastUpdate === null) return observedTab.id; - if (openedTabLastUpdate === null) return openedTab.id; - return (observedTabLastUpdate > openedTabLastUpdate) ? observedTab.id : openedTab.id; - } else { - if (observedTabLastUpdate === null) return openedTab.id; - if (openedTabLastUpdate === null) return observedTab.id; - return (observedTabLastUpdate < openedTabLastUpdate) ? observedTab.id : openedTab.id; - } -}; +const getHttpsTabId = (observedTab, observedTabUrl, openedTab) => { + const regex = /^https:\/\//i; + const match1 = regex.test(observedTabUrl); + const match2 = regex.test(openedTab.url); + if (match1) { + return match2 ? null : observedTab.id; + } else { + return match2 ? openedTab.id : null; + } +}; + +const getPinnedTabId = (tab1, tab2) => { + if (tab1.pinned) { + return tab2.pinned ? null : tab1.id; + } else { + return tab2.pinned ? tab2.id : null; + } +}; + +const getLastUpdatedTabId = (observedTab, openedTab) => { + const observedTabLastUpdate = tabsInfo.getLastComplete(observedTab.id); + const openedTabLastUpdate = tabsInfo.getLastComplete(openedTab.id); + if (observedTabLastUpdate === null) return openedTab.id; + if (openedTabLastUpdate === null) return observedTab.id; + return (observedTabLastUpdate < openedTabLastUpdate) ? observedTab.id : openedTab.id; +}; const getFocusedTab = (observedTab, openedTab, activeWindowId, retainedTabId) => { if (retainedTabId === observedTab.id) { @@ -569,13 +495,11 @@ const closeDuplicateTab = async (tabToCloseId, remainingTabInfo) => { }; const _handleRemainingTab = async (details) => { - if (!tabsInfo.hasTab(details.tabId)) return; - if (options.defaultTabBehavior && details.observedTabClosed) { - if (details.tabIndex > 0) moveTab(details.tabId, { index: details.tabIndex }); - if (details.active) activateTab(details.tabId); - } else if (options.activateKeptTab) { - focusTab(details.tabId, details.windowId); - } + if (!tabsInfo.hasTab(details.tabId)) return; + if (!details.observedTabClosed) return; + + if (details.tabIndex > 0) moveTab(details.tabId, { index: details.tabIndex }); + if (details.active) activateTab(details.tabId); }; const handleRemainingTab = debounce(_handleRemainingTab, 500); diff --git a/duplicate-tabs-closer-master/optionPage/optionPage.html b/duplicate-tabs-closer-master/optionPage/optionPage.html index 7bceb1a..ad70c03 100644 --- a/duplicate-tabs-closer-master/optionPage/optionPage.html +++ b/duplicate-tabs-closer-master/optionPage/optionPage.html @@ -35,92 +35,26 @@ -
- - -
-
- - -
+
+ + +
-
  • - - - -
  • -
  • -
    - -
    -
    - -
    -
    - -
    -
    -
  • -
  • - - - -
  • -
  • -
    -
    - -
    -
    - -
    -
    - -
    -
    - -
    -
    - -
    -
    - +
  • + + + +
  • +
  • +
    +
    +
  • diff --git a/duplicate-tabs-closer-master/optionPage/optionPage.js b/duplicate-tabs-closer-master/optionPage/optionPage.js index 2921af1..9679143 100644 --- a/duplicate-tabs-closer-master/optionPage/optionPage.js +++ b/duplicate-tabs-closer-master/optionPage/optionPage.js @@ -80,11 +80,10 @@ const cleanUpWhiteList = (whiteList) => { }; /* Show/Hide the AutoClose option */ -const changeAutoCloseOptionState = (state, resize) => { - $("#onRemainingTabGroup").toggleClass("hidden", state !== "A"); - $("#whiteListGroup").toggleClass("hidden", state !== "A"); - if (resize) resizeDuplicateTabsPanel(); -}; +const changeAutoCloseOptionState = (state, resize) => { + $("#whiteListGroup").toggleClass("hidden", state !== "A"); + if (resize) resizeDuplicateTabsPanel(); +}; const toggleExpendGroup = (groupId, checkbox, resize) => { if (checkbox) { diff --git a/duplicate-tabs-closer-master/popup/popup.html b/duplicate-tabs-closer-master/popup/popup.html index c9d1d4b..d60eb4d 100644 --- a/duplicate-tabs-closer-master/popup/popup.html +++ b/duplicate-tabs-closer-master/popup/popup.html @@ -42,92 +42,24 @@ -
    - - -
    - - - -
    -
  • - - -
  • + + +
    +
  • + + +
  • -
  • -
    - -
    -
    - -
    -
    - -
    -
    -
  • -
    -
    -
  • - - - -
  • -
  • -
    -
    - -
    -
    - -
    -
    - -
    -
    - -
    -
    - -
    -
    - +
  • +
    +
    +
  • diff --git a/duplicate-tabs-closer-master/popup/popup.js b/duplicate-tabs-closer-master/popup/popup.js index 42e99c6..47a024a 100644 --- a/duplicate-tabs-closer-master/popup/popup.js +++ b/duplicate-tabs-closer-master/popup/popup.js @@ -5,15 +5,9 @@ let lastDuplicateTabs = {}; let closePopup = false; let environment = ""; -/* Show/Hide the AutoClose option */ -const changeAutoCloseOptionState = (state, resize) => { - $("#onRemainingTabGroup").toggleClass("hidden", state !== "A"); - if (resize) resizeDuplicateTabsPanel(); -}; - -const toggleShrunkMode = (checked) => { - $(".list-group-form").toggleClass("shrunk", checked); -}; +const toggleShrunkMode = (checked) => { + $(".list-group-form").toggleClass("shrunk", checked); +}; const toggleExpendOptions = (resize) => { $("#optionHeader").toggleClass("collapsed"); @@ -106,11 +100,10 @@ const setPanelOptions = async () => { } // combobox else { - $(`#${storedOption} option[value='${value}']`).prop("selected", true); - if (storedOption === "onDuplicateTabDetected") changeAutoCloseOptionState(value, false); - } - if (isLockedKey) $(`#${storedOption}`).prop("disabled", true); - } + $(`#${storedOption} option[value='${value}']`).prop("selected", true); + } + if (isLockedKey) $(`#${storedOption}`).prop("disabled", true); + } } if (collapseOptions) toggleExpendOptions(false); }; @@ -143,9 +136,8 @@ const loadListenerEvents = () => { $(".list-group select").on("change", function (event) { event.stopPropagation(); const refresh = this.id === "scope"; - saveOption(this.id, this.value, refresh); - if (this.id === "onDuplicateTabDetected") changeAutoCloseOptionState(this.value, true); - }); + saveOption(this.id, this.value, refresh); + }); /* Open Option tab */ $(".fa-cog").on("click", (event) => { From 918c246cd8835eb47f80a4b11fd03959fa805c1c Mon Sep 17 00:00:00 2001 From: web96lol Date: Wed, 15 Oct 2025 10:42:36 -0700 Subject: [PATCH 2/3] Enforce automatic duplicate handling defaults --- duplicate-tabs-closer-master/README.md | 18 +-- .../_locales/en/messages.json | 20 ++- .../_locales/fr/messages.json | 20 ++- .../_locales/ja/messages.json | 20 ++- .../_locales/ru/messages.json | 20 ++- .../_locales/uk/messages.json | 20 ++- .../_locales/zh_CN/messages.json | 23 ++-- duplicate-tabs-closer-master/background.js | 129 +++++++++--------- .../optionPage/optionPage.css | 6 +- .../optionPage/optionPage.html | 27 ++-- .../optionPage/optionPage.js | 24 ++-- duplicate-tabs-closer-master/popup/popup.css | 4 - duplicate-tabs-closer-master/popup/popup.html | 17 --- 13 files changed, 139 insertions(+), 209 deletions(-) diff --git a/duplicate-tabs-closer-master/README.md b/duplicate-tabs-closer-master/README.md index 57d6e65..34b4209 100644 --- a/duplicate-tabs-closer-master/README.md +++ b/duplicate-tabs-closer-master/README.md @@ -9,19 +9,13 @@ deDup2Activate detects and closes duplicate tabs. ## Options: -### On duplicate tab detected: - -* **Close tab automatically** *(default)*: automatically closes the detected duplicate tab. -* **Do nothing**: monitor tabs and update the badge icon to indicate the number of duplicate tabs detected. - -#### Whitelist: -(Used with option *Close tab automatically*) -List of urls to not close automatically. Duplicate tabs skipped will be notified in badge. -Wildcards and RegExp are supported. - - +### Automatic duplicate handling + +Duplicate tabs are always closed automatically. Use the whitelist to keep specific URLs open. Wildcards and RegExp are supported, and skipped duplicates are still reflected in the badge count. + + ### Duplicate resolution behavior -(Used with option *Close tab automatically* and the *Close all duplicate tabs* button) +(Used when the extension closes duplicates automatically or when you use the *Close all duplicate tabs* button) The extension always keeps pinned tabs, prefers the HTTPS version of a page, and retains the older tab when deciding which duplicate to close. These rules are now built-in and no longer configurable. diff --git a/duplicate-tabs-closer-master/_locales/en/messages.json b/duplicate-tabs-closer-master/_locales/en/messages.json index 8e93146..32906c7 100644 --- a/duplicate-tabs-closer-master/_locales/en/messages.json +++ b/duplicate-tabs-closer-master/_locales/en/messages.json @@ -3,18 +3,14 @@ "message": "Options", "description": "Options" }, - "onDuplicateTabDetected": { - "message": "On duplicate tab detected", - "description": "On duplicate tab detected" - }, - "doNothing": { - "message": "Do nothing", - "description": "Do nothing" - }, - "autoCloseTab": { - "message": "Close tab automatically", - "description": "Close tab automatically" - }, + "automaticHandling": { + "message": "Automatic duplicate handling", + "description": "Section title for automatic duplicate handling" + }, + "automaticHandlingDescription": { + "message": "Duplicate tabs close automatically. Use the whitelist to keep specific URLs open.", + "description": "Explains that duplicates are always closed and how to use the whitelist." + }, "showPanel": { "message": "Show panel", "description": "Show panel" diff --git a/duplicate-tabs-closer-master/_locales/fr/messages.json b/duplicate-tabs-closer-master/_locales/fr/messages.json index 00bc867..c64ae94 100644 --- a/duplicate-tabs-closer-master/_locales/fr/messages.json +++ b/duplicate-tabs-closer-master/_locales/fr/messages.json @@ -3,18 +3,14 @@ "message": "Options", "description": "Options" }, - "onDuplicateTabDetected": { - "message": "À la détection d'un doublon", - "description": "On duplicate tab detected" - }, - "doNothing": { - "message": "Ne rien faire", - "description": "Do nothing" - }, - "autoCloseTab": { - "message": "Fermer l'onglet automatiquement", - "description": "Close tab automatically" - }, + "automaticHandling": { + "message": "Gestion automatique des doublons", + "description": "Titre pour la gestion automatique des doublons" + }, + "automaticHandlingDescription": { + "message": "Les onglets en double se ferment automatiquement. Utilisez la liste blanche pour garder certaines URL ouvertes.", + "description": "Explique la fermeture automatique et la liste blanche." + }, "showPanel": { "message": "Afficher le panneau", "description": "Show panel" diff --git a/duplicate-tabs-closer-master/_locales/ja/messages.json b/duplicate-tabs-closer-master/_locales/ja/messages.json index 68752fe..53fcda0 100644 --- a/duplicate-tabs-closer-master/_locales/ja/messages.json +++ b/duplicate-tabs-closer-master/_locales/ja/messages.json @@ -3,18 +3,14 @@ "message": "オプション", "description": "Options" }, - "onDuplicateTabDetected": { - "message": "タブの重複を検知したとき", - "description": "On duplicate tab detected" - }, - "doNothing": { - "message": "何もしない", - "description": "Do nothing" - }, - "autoCloseTab": { - "message": "自動的にタブを閉じる", - "description": "Close tab automatically" - }, + "automaticHandling": { + "message": "重複タブの自動処理", + "description": "Section title for automatic duplicate handling" + }, + "automaticHandlingDescription": { + "message": "重複したタブは自動的に閉じられます。特定の URL を保持したい場合はホワイトリストを利用してください。", + "description": "Explains automatic closing and whitelist usage." + }, "showPanel": { "message": "パネルを表示する", "description": "Show panel" diff --git a/duplicate-tabs-closer-master/_locales/ru/messages.json b/duplicate-tabs-closer-master/_locales/ru/messages.json index 8980a7d..faa8a5a 100644 --- a/duplicate-tabs-closer-master/_locales/ru/messages.json +++ b/duplicate-tabs-closer-master/_locales/ru/messages.json @@ -3,18 +3,14 @@ "message": "Параметры", "description": "Options" }, - "onDuplicateTabDetected": { - "message": "Если обнаружен дубликат вкладки", - "description": "On duplicate tab detected" - }, - "doNothing": { - "message": "Ничего не делать", - "description": "Do nothing" - }, - "autoCloseTab": { - "message": "Закрыть вкладку автоматически", - "description": "Close tab automatically" - }, + "automaticHandling": { + "message": "Автоматическая обработка дубликатов", + "description": "Заголовок раздела для автоматического удаления дубликатов" + }, + "automaticHandlingDescription": { + "message": "Дубликаты вкладок закрываются автоматически. Используйте белый список, чтобы оставить нужные URL открытыми.", + "description": "Пояснение про автоматическое закрытие и белый список." + }, "showPanel": { "message": "Показать панель", "description": "Show panel" diff --git a/duplicate-tabs-closer-master/_locales/uk/messages.json b/duplicate-tabs-closer-master/_locales/uk/messages.json index bf27b63..e2b3adf 100644 --- a/duplicate-tabs-closer-master/_locales/uk/messages.json +++ b/duplicate-tabs-closer-master/_locales/uk/messages.json @@ -3,18 +3,14 @@ "message": "Опції", "description": "Options" }, - "onDuplicateTabDetected": { - "message": "Якщо виявлено дублікат вкладки", - "description": "On duplicate tab detected" - }, - "doNothing": { - "message": "Нічого не робити", - "description": "Do nothing" - }, - "autoCloseTab": { - "message": "Закрити вкладку автоматично", - "description": "Close tab automatically" - }, + "automaticHandling": { + "message": "Автоматична обробка дублікатів", + "description": "Заголовок розділу для автоматичної обробки дублікатів" + }, + "automaticHandlingDescription": { + "message": "Дублікати вкладок закриваються автоматично. Використовуйте білий список, щоб залишити потрібні URL відкритими.", + "description": "Пояснення про автоматичне закриття та білий список." + }, "showPanel": { "message": "Показати панель", "description": "Show panel" diff --git a/duplicate-tabs-closer-master/_locales/zh_CN/messages.json b/duplicate-tabs-closer-master/_locales/zh_CN/messages.json index c1dc8ac..e0edf80 100644 --- a/duplicate-tabs-closer-master/_locales/zh_CN/messages.json +++ b/duplicate-tabs-closer-master/_locales/zh_CN/messages.json @@ -4,21 +4,14 @@ "description": "Options", "hash": "531dad0078a72193602b18495f0ad4e9" }, - "onDuplicateTabDetected": { - "message": "检测到重复标签页时", - "description": "On duplicate tab detected", - "hash": "4193a78ce76ecf58828768b4c6fabc7f" - }, - "doNothing": { - "message": "无操作", - "description": "Do nothing", - "hash": "a0a28b9a09a44d3e69599b6500e47a0e" - }, - "autoCloseTab": { - "message": "自动关闭标签页", - "description": "Close tab automatically", - "hash": "6096376eef1def5290ffc9e691152903" - }, + "automaticHandling": { + "message": "重复标签的自动处理", + "description": "Section title for automatic duplicate handling" + }, + "automaticHandlingDescription": { + "message": "重复的标签页会自动关闭。如需保留特定网址,请将其加入白名单。", + "description": "Explains automatic closing and whitelist usage." + }, "showPanel": { "message": "显示面板", "description": "Show panel", diff --git a/duplicate-tabs-closer-master/background.js b/duplicate-tabs-closer-master/background.js index 2a87b30..641b226 100644 --- a/duplicate-tabs-closer-master/background.js +++ b/duplicate-tabs-closer-master/background.js @@ -91,24 +91,21 @@ const defaultOptions = { shrunkMode: { value: false }, - onDuplicateTabDetected: { - value: "A" + keepTabWithHistory: { + value: false + }, + scope: { + value: "C" + }, + compareWithTitle: { + value: false + }, + automaticHandlingPinned: { + value: true + }, + matchingRulesPinned: { + value: true }, - keepTabWithHistory: { - value: false - }, - scope: { - value: "C" - }, - compareWithTitle: { - value: false - }, - onDuplicateTabDetectedPinned: { - value: true - }, - matchingRulesPinned: { - value: true - }, scopePinned: { value: true }, @@ -186,17 +183,15 @@ const setStoredOption = async (name, value, refresh) => { const options = await getStoredOptions(); const storedOptions = options.storedOptions; storedOptions[name].value = value; - saveStoredOptions(storedOptions); - setOptions(storedOptions); - if (refresh) refreshGlobalDuplicateTabsInfo(); - else if (name === "onDuplicateTabDetected") setBadgeIcon(); - else if (name === "showBadgeIfNoDuplicateTabs" || name === "badgeColorNoDuplicateTabs" || name === "badgeColorDuplicateTabs") updateBadgeStyle(); + saveStoredOptions(storedOptions); + setOptions(storedOptions); + if (refresh) refreshGlobalDuplicateTabsInfo(); + else if (name === "showBadgeIfNoDuplicateTabs" || name === "badgeColorNoDuplicateTabs" || name === "badgeColorDuplicateTabs") updateBadgeStyle(); }; const options = {}; const setOptions = (storedOptions) => { - options.autoCloseTab = storedOptions.onDuplicateTabDetected.value === "A"; options.compareWithTitle = storedOptions.compareWithTitle.value; options.searchInAllWindows = storedOptions.scope.value === "A" || storedOptions.scope.value === "CA"; options.searchPerContainer = storedOptions.scope.value === "CC" || storedOptions.scope.value === "CA"; @@ -301,7 +296,7 @@ const getMatchPatternURL = (url) => { // eslint-disable-next-line no-unused-vars const setBadgeIcon = () => { - chrome.action.setIcon({ path: options.autoCloseTab ? "images/auto_close_16.png" : "images/manual_close_16.png" }); + chrome.action.setIcon({ path: "images/auto_close_16.png" }); if (environment.isFirefox) browser.action.setBadgeTextColor({ color: "white" }); }; @@ -658,54 +653,54 @@ chrome.runtime.onMessage.addListener(handleMessage);"use strict"; const onCreatedTab = (tab) => { tabsInfo.setNewTab(tab.id); - if (tab.status === "complete" && !isBlankURL(tab.url)) { - options.autoCloseTab ? searchForDuplicateTabsToClose(tab, true) : refreshDuplicateTabsInfo(tab.windowId); - } -}; - -const onBeforeNavigate = async (details) => { - if (options.autoCloseTab && (details.frameId == 0) && (details.tabId !== -1) && !isBlankURL(details.url)) { - if (tabsInfo.isIgnoredTab(details.tabId)) return; - const tab = await getTab(details.tabId); - if (tab) { - tabsInfo.resetTab(tab.id); - searchForDuplicateTabsToClose(tab, true, details.url); - } - } -}; - -const onCompletedTab = async (details) => { - if ((details.frameId == 0) && (details.tabId !== -1)) { - if (tabsInfo.isIgnoredTab(details.tabId)) return; - const tab = await getTab(details.tabId); - if (tab) { - tabsInfo.updateTab(tab); - options.autoCloseTab ? searchForDuplicateTabsToClose(tab) : refreshDuplicateTabsInfo(tab.windowId); - } - } -}; + if (tab.status === "complete" && !isBlankURL(tab.url)) { + searchForDuplicateTabsToClose(tab, true); + } +}; + +const onBeforeNavigate = async (details) => { + if ((details.frameId == 0) && (details.tabId !== -1) && !isBlankURL(details.url)) { + if (tabsInfo.isIgnoredTab(details.tabId)) return; + const tab = await getTab(details.tabId); + if (tab) { + tabsInfo.resetTab(tab.id); + searchForDuplicateTabsToClose(tab, true, details.url); + } + } +}; + +const onCompletedTab = async (details) => { + if ((details.frameId == 0) && (details.tabId !== -1)) { + if (tabsInfo.isIgnoredTab(details.tabId)) return; + const tab = await getTab(details.tabId); + if (tab) { + tabsInfo.updateTab(tab); + searchForDuplicateTabsToClose(tab); + } + } +}; const onUpdatedTab = (tabId, changeInfo, tab) => { if (tabsInfo.isIgnoredTab(tabId)) return; if (Object.prototype.hasOwnProperty.call(changeInfo, "status") && changeInfo.status === "complete") { if (Object.prototype.hasOwnProperty.call(changeInfo, "url") && (changeInfo.url !== tab.url)) { - if (isBlankURL(tab.url) || !tab.favIconUrl || !tabsInfo.hasUrlChanged(tab)) return; - tabsInfo.updateTab(tab); - options.autoCloseTab ? searchForDuplicateTabsToClose(tab) : refreshDuplicateTabsInfo(tab.windowId); - } - else if (isChromeURL(tab.url)) { - tabsInfo.updateTab(tab); - options.autoCloseTab ? searchForDuplicateTabsToClose(tab) : refreshDuplicateTabsInfo(tab.windowId); - } - } -}; - -const onAttached = async (tabId) => { - const tab = await getTab(tabId); - if (tab) { - options.autoCloseTab ? searchForDuplicateTabsToClose(tab) : refreshDuplicateTabsInfo(tab.windowId); - } -}; + if (isBlankURL(tab.url) || !tab.favIconUrl || !tabsInfo.hasUrlChanged(tab)) return; + tabsInfo.updateTab(tab); + searchForDuplicateTabsToClose(tab); + } + else if (isChromeURL(tab.url)) { + tabsInfo.updateTab(tab); + searchForDuplicateTabsToClose(tab); + } + } +}; + +const onAttached = async (tabId) => { + const tab = await getTab(tabId); + if (tab) { + searchForDuplicateTabsToClose(tab); + } +}; const onRemovedTab = (removedTabId, removeInfo) => { tabsInfo.removeTab(removedTabId); diff --git a/duplicate-tabs-closer-master/optionPage/optionPage.css b/duplicate-tabs-closer-master/optionPage/optionPage.css index 9a6e633..a85b394 100644 --- a/duplicate-tabs-closer-master/optionPage/optionPage.css +++ b/duplicate-tabs-closer-master/optionPage/optionPage.css @@ -124,9 +124,9 @@ body { color: rgb(72, 80, 92); } -#onDuplicateTabDetectedBody { - margin-bottom: 8px; -} +#automaticHandlingBody { + margin-bottom: 8px; +} .checkboxes label { margin: 0px; diff --git a/duplicate-tabs-closer-master/optionPage/optionPage.html b/duplicate-tabs-closer-master/optionPage/optionPage.html index ad70c03..9c86619 100644 --- a/duplicate-tabs-closer-master/optionPage/optionPage.html +++ b/duplicate-tabs-closer-master/optionPage/optionPage.html @@ -22,25 +22,22 @@
    +
  • diff --git a/duplicate-tabs-closer-master/optionPage/optionPage.js b/duplicate-tabs-closer-master/optionPage/optionPage.js index 9679143..a1f582e 100644 --- a/duplicate-tabs-closer-master/optionPage/optionPage.js +++ b/duplicate-tabs-closer-master/optionPage/optionPage.js @@ -20,12 +20,11 @@ const loadPopupEvents = () => { }); /* Save combobox settings */ - $(".list-group select").on("change", function (event) { - event.stopPropagation(); - const refresh = this.id === "scope"; - saveOption(this.id, this.value, refresh); - if (this.id === "onDuplicateTabDetected") changeAutoCloseOptionState(this.value, true); - }); + $(".list-group select").on("change", function (event) { + event.stopPropagation(); + const refresh = this.id === "scope"; + saveOption(this.id, this.value, refresh); + }); /* Save badge color settings */ $(".list-group input[type='color']").on("change", function () { @@ -79,12 +78,6 @@ const cleanUpWhiteList = (whiteList) => { return Array.from(whiteListCleaned).join("\n"); }; -/* Show/Hide the AutoClose option */ -const changeAutoCloseOptionState = (state, resize) => { - $("#whiteListGroup").toggleClass("hidden", state !== "A"); - if (resize) resizeDuplicateTabsPanel(); -}; - const toggleExpendGroup = (groupId, checkbox, resize) => { if (checkbox) { const thumbChecked = $(`#${groupId}`).prop("checked"); @@ -169,10 +162,9 @@ const setPanelOption = (details) => { // badge color value $(`#${storedOption}`).prop("value", value); } - else { - $(`#${storedOption} option[value='${value}']`).prop("selected", true); - if (storedOption === "onDuplicateTabDetected") changeAutoCloseOptionState(value, resize); - } + else { + $(`#${storedOption} option[value='${value}']`).prop("selected", true); + } if (isLockedKey) $(`#${storedOption}`).prop("disabled", true); } }; diff --git a/duplicate-tabs-closer-master/popup/popup.css b/duplicate-tabs-closer-master/popup/popup.css index ce16dd2..02e24cb 100644 --- a/duplicate-tabs-closer-master/popup/popup.css +++ b/duplicate-tabs-closer-master/popup/popup.css @@ -166,10 +166,6 @@ body { margin: 4px 2px; } -#onDuplicateTabDetectedBody { - margin-bottom: 6px; -} - #matchingRulesBody { margin-top: -4px; } diff --git a/duplicate-tabs-closer-master/popup/popup.html b/duplicate-tabs-closer-master/popup/popup.html index d60eb4d..a0e81f4 100644 --- a/duplicate-tabs-closer-master/popup/popup.html +++ b/duplicate-tabs-closer-master/popup/popup.html @@ -28,23 +28,6 @@
      -
      -
    • - - - -
    • -
    • -
      - -
      -
    • -
    • From dff3fe71429d4e0092d714cd31c13d168b3686f5 Mon Sep 17 00:00:00 2001 From: web96lol Date: Wed, 15 Oct 2025 10:42:58 -0700 Subject: [PATCH 3/3] Add context menu trigger for closing duplicates --- duplicate-tabs-closer-master/README.md | 54 ++- .../_locales/en/messages.json | 72 +++- .../_locales/fr/messages.json | 72 +++- .../_locales/ja/messages.json | 72 +++- .../_locales/ru/messages.json | 72 +++- .../_locales/uk/messages.json | 72 +++- .../_locales/zh_CN/messages.json | 87 ++++- duplicate-tabs-closer-master/background.js | 357 ++++++++++++------ duplicate-tabs-closer-master/manifest-c.json | 1 + duplicate-tabs-closer-master/manifest-f.json | 1 + .../optionPage/optionPage.css | 6 +- .../optionPage/optionPage.html | 129 +++++-- .../optionPage/optionPage.js | 25 +- duplicate-tabs-closer-master/popup/popup.css | 4 + duplicate-tabs-closer-master/popup/popup.html | 109 +++++- duplicate-tabs-closer-master/popup/popup.js | 26 +- 16 files changed, 928 insertions(+), 231 deletions(-) diff --git a/duplicate-tabs-closer-master/README.md b/duplicate-tabs-closer-master/README.md index 34b4209..0652d12 100644 --- a/duplicate-tabs-closer-master/README.md +++ b/duplicate-tabs-closer-master/README.md @@ -3,26 +3,46 @@ deDup2Activate detects and closes duplicate tabs. -* Use the WebExtensions API -* Support [Firefox](https://addons.mozilla.org/en-US/firefox/addon/duplicate-tabs-closer) and [Chrome](https://chrome.google.com/webstore/detail/duplicate-tabs-closer/gnmdbogfankgjepgglmmfmbnimcmcjle) -* Firefox Container Tab feature is supported. +* Use the WebExtensions API +* Support [Firefox](https://addons.mozilla.org/en-US/firefox/addon/duplicate-tabs-closer) and [Chrome](https://chrome.google.com/webstore/detail/duplicate-tabs-closer/gnmdbogfankgjepgglmmfmbnimcmcjle) +* Firefox Container Tab feature is supported. +* Includes a toolbar context menu item **Duplicate Tab Closer** to trigger a manual duplicate sweep. ## Options: -### Automatic duplicate handling - -Duplicate tabs are always closed automatically. Use the whitelist to keep specific URLs open. Wildcards and RegExp are supported, and skipped duplicates are still reflected in the badge count. - - -### Duplicate resolution behavior -(Used when the extension closes duplicates automatically or when you use the *Close all duplicate tabs* button) -The extension always keeps pinned tabs, prefers the HTTPS version of a page, and retains the older tab when deciding which duplicate to close. These rules are now built-in and no longer configurable. - - -### matchingRules: - -URL comparisons always normalize links by forcing HTTPS, ignoring a leading `www`, comparing in lowercase, and keeping the full path, search, and hash segments. The remaining optional rule is: -* **Compare with tab title** *(default off)* +### On duplicate tab detected: + +* **Close tab automatically** *(default)*: automatically closes the detected duplicate tab. +* **Do nothing**: monitor tabs and update the badge icon to indicate the number of duplicate tabs detected. + +#### On remaining tab: +(Used with option *Close tab automatically*) +* **Activate** *(default)*: once the duplicate tab is closed, the remaining tab is activated. +* **Do nothing**: nothing is done after the duplicate tab is closed. +* **Apply opening tab behavior**: once the duplicate tab is closed, depending on the default tab behavior, the remaining tab will be moved to the position of the closed tab and activated if needed. + +#### Whiltelist": +(Used with option *Close tab automatically*) +List of urls to not close automatically. Duplicate tabs skipped will be notified in badge. +Wildcards and RegExp are supported. + + +### Priority: +(Used with option *Close tab automatically* and *Close all duplicate tabs* button) +* **Keep older tab** *(default)*: Keep the already existing tab. +* **Keep newer tab**: Keep the newer tab. +* **Keep tab with https** *(default on)*: Ignore the scheme part during comparison and keep the tab with the https scheme. +* **Keep pinned tab** *(default on)*: Keep the pinned tab. + + +### matchingRules: + +* **Ignore case in URL** *(default on)* +* **Ignore 'www' in URL domain name** *(default on)* +* **Ignore hash part in URL** *(default off)* +* **Ignore search part in URL** *(default off)* +* **Ignore path part in URL** *(default off)* +* **Compare with tab title** *(default off)* ### Scope: diff --git a/duplicate-tabs-closer-master/_locales/en/messages.json b/duplicate-tabs-closer-master/_locales/en/messages.json index 32906c7..ccc1066 100644 --- a/duplicate-tabs-closer-master/_locales/en/messages.json +++ b/duplicate-tabs-closer-master/_locales/en/messages.json @@ -3,26 +3,86 @@ "message": "Options", "description": "Options" }, - "automaticHandling": { - "message": "Automatic duplicate handling", - "description": "Section title for automatic duplicate handling" + "onDuplicateTabDetected": { + "message": "On duplicate tab detected", + "description": "On duplicate tab detected" + }, + "doNothing": { + "message": "Do nothing", + "description": "Do nothing" + }, + "autoCloseTab": { + "message": "Close tab automatically", + "description": "Close tab automatically" }, - "automaticHandlingDescription": { - "message": "Duplicate tabs close automatically. Use the whitelist to keep specific URLs open.", - "description": "Explains that duplicates are always closed and how to use the whitelist." + "contextMenuCloseDuplicates": { + "message": "Duplicate Tab Closer", + "description": "Context menu item that closes duplicate tabs" }, "showPanel": { "message": "Show panel", "description": "Show panel" }, + "tabPriority": { + "message": "Priority", + "description": "Priority" + }, + "keepNewerTab": { + "message": "Keep newer tab", + "description": "Keep newer tab" + }, + "keepOlderTab": { + "message": "Keep older tab", + "description": "Keep older tab" + }, "keepTabWithHistory": { "message": "Keep tab with history", "description": "Keep tab with history" }, + "keeptabwithHttps": { + "message": "Keep tab with https", + "description": "Keep tab with https" + }, + "keepPinnedTab": { + "message": "Keep pinned tab", + "description": "Keep pinned tab" + }, + "onRemainingTab": { + "message": "On remaining tab", + "description": "On remaining tab" + }, + "activateKeptTab": { + "message": "Activate", + "description": "Activate" + }, + "defaultTabBehavior": { + "message": "Default tab behaviour", + "description": "Default tab behaviour" + }, "matchingRules": { "message": "Matching rules", "description": "Matching rules" }, + "ignoreHashPart": { + "message": "Ignore hash part in URL", + "description": "Ignore hash part in URL" + }, + "ignoreSearchPart": { + "message": "Ignore search part in URL", + "description": "Ignore search part in URL" + }, + "ignorePathPart": { + "message": "Ignore path part in URL", + "description": "Ignore path part in URL" + }, + "ignore3w": { + "message": "Ignore 'www' in URL domain name", + "description": "Ignore 'www' in URL domain name" + }, + "caseInsensitive": { + "message": "Ignore case in URL", + "description": "Ignore case in URL" + }, "compareWithTitle": { "message": "Compare with title", "description": "Compare with title" diff --git a/duplicate-tabs-closer-master/_locales/fr/messages.json b/duplicate-tabs-closer-master/_locales/fr/messages.json index c64ae94..5f9cb5f 100644 --- a/duplicate-tabs-closer-master/_locales/fr/messages.json +++ b/duplicate-tabs-closer-master/_locales/fr/messages.json @@ -3,26 +3,86 @@ "message": "Options", "description": "Options" }, - "automaticHandling": { - "message": "Gestion automatique des doublons", - "description": "Titre pour la gestion automatique des doublons" + "onDuplicateTabDetected": { + "message": "À la détection d'un doublon", + "description": "On duplicate tab detected" + }, + "doNothing": { + "message": "Ne rien faire", + "description": "Do nothing" + }, + "autoCloseTab": { + "message": "Fermer l'onglet automatiquement", + "description": "Close tab automatically" }, - "automaticHandlingDescription": { - "message": "Les onglets en double se ferment automatiquement. Utilisez la liste blanche pour garder certaines URL ouvertes.", - "description": "Explique la fermeture automatique et la liste blanche." + "contextMenuCloseDuplicates": { + "message": "Fermer les onglets dupliqués", + "description": "Context menu item that closes duplicate tabs" }, "showPanel": { "message": "Afficher le panneau", "description": "Show panel" }, + "tabPriority": { + "message": "Priorité", + "description": "Priority" + }, + "keepNewerTab": { + "message": "Conserver le nouvel onglet", + "description": "Keep newer tab" + }, + "keepOlderTab": { + "message": "Conserver l'ancien onglet", + "description": "Keep older tab" + }, "keepTabWithHistory": { "message": "Conserver l'onglet ayant un historique", "description": "Keep tab with history" }, + "keeptabwithHttps": { + "message": "Conserver l'onglet avec https", + "description": "Keep tab with https" + }, + "keepPinnedTab": { + "message": "Conserver l'onglet épinglé", + "description": "Keep pinned tab" + }, + "onRemainingTab": { + "message": "Sur l'onglet restant", + "description": "On remaining tab" + }, + "activateKeptTab": { + "message": "Activer", + "description": "Activate" + }, + "defaultTabBehavior": { + "message": "Comportement par défaut de l'onglet", + "description": "Default tab behaviour" + }, "matchingRules": { "message": "Règle de correspondance", "description": "Règle de correspondance" }, + "ignoreHashPart": { + "message": "Ignorer la partie « hachage » de l'URL", + "description": "Ignore hash part in URL" + }, + "ignoreSearchPart": { + "message": "Ignorer la partie « recherche » de l'URL", + "description": "Ignore search part in URL" + }, + "ignorePathPart": { + "message": "Ignorer la partie « chemin » de l'URL", + "description": "Ignore path part in URL" + }, + "ignore3w": { + "message": "Ignorer 'www' dans le nom de domaine de l'URL", + "description": "Ignorer 'www' dans le nom de domaine de l'URL" + }, + "caseInsensitive": { + "message": "Ignorer la casse de l'URL", + "description": "Ignorer la casse de l'URL" + }, "compareWithTitle": { "message": "Comparer avec le titre", "description": "Compare with title" diff --git a/duplicate-tabs-closer-master/_locales/ja/messages.json b/duplicate-tabs-closer-master/_locales/ja/messages.json index 53fcda0..8109bbe 100644 --- a/duplicate-tabs-closer-master/_locales/ja/messages.json +++ b/duplicate-tabs-closer-master/_locales/ja/messages.json @@ -3,26 +3,86 @@ "message": "オプション", "description": "Options" }, - "automaticHandling": { - "message": "重複タブの自動処理", - "description": "Section title for automatic duplicate handling" + "onDuplicateTabDetected": { + "message": "タブの重複を検知したとき", + "description": "On duplicate tab detected" + }, + "doNothing": { + "message": "何もしない", + "description": "Do nothing" + }, + "autoCloseTab": { + "message": "自動的にタブを閉じる", + "description": "Close tab automatically" }, - "automaticHandlingDescription": { - "message": "重複したタブは自動的に閉じられます。特定の URL を保持したい場合はホワイトリストを利用してください。", - "description": "Explains automatic closing and whitelist usage." + "contextMenuCloseDuplicates": { + "message": "重複タブを閉じる", + "description": "Context menu item that closes duplicate tabs" }, "showPanel": { "message": "パネルを表示する", "description": "Show panel" }, + "tabPriority": { + "message": "優先順位", + "description": "Priority" + }, + "keepNewerTab": { + "message": "新しい方のタブを残す", + "description": "Keep newer tab" + }, + "keepOlderTab": { + "message": "古い方のタブを残す", + "description": "Keep older tab" + }, "keepTabWithHistory": { "message": "タブの履歴を維持する", "description": "Keep tab with history" }, + "keeptabwithHttps": { + "message": "HTTPSのタブを残す", + "description": "Keep tab with https" + }, + "keepPinnedTab": { + "message": "ピン留めされたタブを残す", + "description": "Keep pinned tab" + }, + "onRemainingTab": { + "message": "残されたタブがある場合", + "description": "On remaining tab" + }, + "activateKeptTab": { + "message": "アクティブにする", + "description": "Activate" + }, + "defaultTabBehavior": { + "message": "タブの既定の挙動に任せる", + "description": "Default tab behaviour" + }, "matchingRules": { "message": "Matching rules", "description": "Matching rules" }, + "ignoreHashPart": { + "message": "URLのハッシュ部分(#~)を無視する", + "description": "Ignore hash part in URL" + }, + "ignoreSearchPart": { + "message": "URLのクエリー部分(?~)を無視する", + "description": "Ignore search part in URL" + }, + "ignorePathPart": { + "message": "URLのパス部分を無視する", + "description": "Ignore path part in URL" + }, + "ignore3w": { + "message": "Ignore 'www' in URL domain name", + "description": "Ignore 'www' in URL domain name" + }, + "caseInsensitive": { + "message": "Ignore case in URL", + "description": "Ignore case in URL" + }, "compareWithTitle": { "message": "タブのタイトルを比較する", "description": "Compare with title" diff --git a/duplicate-tabs-closer-master/_locales/ru/messages.json b/duplicate-tabs-closer-master/_locales/ru/messages.json index faa8a5a..eacfec2 100644 --- a/duplicate-tabs-closer-master/_locales/ru/messages.json +++ b/duplicate-tabs-closer-master/_locales/ru/messages.json @@ -3,26 +3,86 @@ "message": "Параметры", "description": "Options" }, - "automaticHandling": { - "message": "Автоматическая обработка дубликатов", - "description": "Заголовок раздела для автоматического удаления дубликатов" + "onDuplicateTabDetected": { + "message": "Если обнаружен дубликат вкладки", + "description": "On duplicate tab detected" + }, + "doNothing": { + "message": "Ничего не делать", + "description": "Do nothing" + }, + "autoCloseTab": { + "message": "Закрыть вкладку автоматически", + "description": "Close tab automatically" }, - "automaticHandlingDescription": { - "message": "Дубликаты вкладок закрываются автоматически. Используйте белый список, чтобы оставить нужные URL открытыми.", - "description": "Пояснение про автоматическое закрытие и белый список." + "contextMenuCloseDuplicates": { + "message": "Закрыть дубликаты вкладок", + "description": "Context menu item that closes duplicate tabs" }, "showPanel": { "message": "Показать панель", "description": "Show panel" }, + "tabPriority": { + "message": "Приоритет", + "description": "Priority" + }, + "keepNewerTab": { + "message": "Сохранить новейшую вкладку", + "description": "Keep newer tab" + }, + "keepOlderTab": { + "message": "Сохранить старейшую вкладку", + "description": "Keep older tab" + }, "keepTabWithHistory": { "message": "Сохранить вкладку с историей", "description": "Keep tab with history" }, + "keeptabwithHttps": { + "message": "Сохранить вкладку с https", + "description": "Keep tab with https" + }, + "keepPinnedTab": { + "message": "Сохранить закрепленную вкладку", + "description": "Keep pinned tab" + }, + "onRemainingTab": { + "message": "Оставшуюся вкладку...", + "description": "On remaining tab" + }, + "activateKeptTab": { + "message": "Сделать активной", + "description": "Activate" + }, + "defaultTabBehavior": { + "message": "Поведение вкладки по умолчанию", + "description": "Default tab behaviour" + }, "matchingRules": { "message": "Matching rules", "description": "Matching rules" }, + "ignoreHashPart": { + "message": "Игнорировать хэш-часть в URL вкладки", + "description": "Ignore hash part in URL" + }, + "ignoreSearchPart": { + "message": "Игнорировать часть поиска в URL вкладки", + "description": "Ignore search part in URL" + }, + "ignorePathPart": { + "message": "Игнорировать часть пути в URL вкладки", + "description": "Ignore path part in URL" + }, + "ignore3w": { + "message": "Ignore 'www' in URL domain name", + "description": "Ignore 'www' in URL domain name" + }, + "caseInsensitive": { + "message": "Ignore case in URL", + "description": "Ignore case in URL" + }, "compareWithTitle": { "message": "Сравнить заголовки вкладок", "description": "Compare with title" diff --git a/duplicate-tabs-closer-master/_locales/uk/messages.json b/duplicate-tabs-closer-master/_locales/uk/messages.json index e2b3adf..d4eff0c 100644 --- a/duplicate-tabs-closer-master/_locales/uk/messages.json +++ b/duplicate-tabs-closer-master/_locales/uk/messages.json @@ -3,26 +3,86 @@ "message": "Опції", "description": "Options" }, - "automaticHandling": { - "message": "Автоматична обробка дублікатів", - "description": "Заголовок розділу для автоматичної обробки дублікатів" + "onDuplicateTabDetected": { + "message": "Якщо виявлено дублікат вкладки", + "description": "On duplicate tab detected" + }, + "doNothing": { + "message": "Нічого не робити", + "description": "Do nothing" + }, + "autoCloseTab": { + "message": "Закрити вкладку автоматично", + "description": "Close tab automatically" }, - "automaticHandlingDescription": { - "message": "Дублікати вкладок закриваються автоматично. Використовуйте білий список, щоб залишити потрібні URL відкритими.", - "description": "Пояснення про автоматичне закриття та білий список." + "contextMenuCloseDuplicates": { + "message": "Закрити дублікати вкладок", + "description": "Context menu item that closes duplicate tabs" }, "showPanel": { "message": "Показати панель", "description": "Show panel" }, + "tabPriority": { + "message": "Пріоритет", + "description": "Priority" + }, + "keepNewerTab": { + "message": "Зберегти новішу вкладку", + "description": "Keep newer tab" + }, + "keepOlderTab": { + "message": "Зберегти старішу вкладку", + "description": "Keep older tab" + }, "keepTabWithHistory": { "message": "Зберегти вкладку з історією", "description": "Keep tab with history" }, + "keeptabwithHttps": { + "message": "Зберегти вкладку з https", + "description": "Keep tab with https" + }, + "keepPinnedTab": { + "message": "Зберегти закріплену вкладку", + "description": "Keep pinned tab" + }, + "onRemainingTab": { + "message": "Вкладку, що залишилася...", + "description": "On remaining tab" + }, + "activateKeptTab": { + "message": "Зробити активною", + "description": "Activate" + }, + "defaultTabBehavior": { + "message": "Поведінка вкладки за замовчуванням", + "description": "Default tab behaviour" + }, "matchingRules": { "message": "Matching rules", "description": "Matching rules" }, + "ignoreHashPart": { + "message": "Ігнорувати хеш-частину в URL вкладки", + "description": "Ignore hash part in URL" + }, + "ignoreSearchPart": { + "message": "Ігнорувати частину пошуку в URL вкладки", + "description": "Ignore search part in URL" + }, + "ignorePathPart": { + "message": "Ігнорувати частину шляху в URL вкладки", + "description": "Ignore path part in URL" + }, + "ignore3w": { + "message": "Ignore 'www' in URL domain name", + "description": "Ignore 'www' in URL domain name" + }, + "caseInsensitive": { + "message": "Ignore case in URL", + "description": "Ignore case in URL" + }, "compareWithTitle": { "message": "Порівняти заголовки вкладок", "description": "Compare with title" diff --git a/duplicate-tabs-closer-master/_locales/zh_CN/messages.json b/duplicate-tabs-closer-master/_locales/zh_CN/messages.json index e0edf80..35ea996 100644 --- a/duplicate-tabs-closer-master/_locales/zh_CN/messages.json +++ b/duplicate-tabs-closer-master/_locales/zh_CN/messages.json @@ -4,29 +4,104 @@ "description": "Options", "hash": "531dad0078a72193602b18495f0ad4e9" }, - "automaticHandling": { - "message": "重复标签的自动处理", - "description": "Section title for automatic duplicate handling" + "onDuplicateTabDetected": { + "message": "检测到重复标签页时", + "description": "On duplicate tab detected", + "hash": "4193a78ce76ecf58828768b4c6fabc7f" + }, + "doNothing": { + "message": "无操作", + "description": "Do nothing", + "hash": "a0a28b9a09a44d3e69599b6500e47a0e" + }, + "autoCloseTab": { + "message": "自动关闭标签页", + "description": "Close tab automatically", + "hash": "6096376eef1def5290ffc9e691152903" }, - "automaticHandlingDescription": { - "message": "重复的标签页会自动关闭。如需保留特定网址,请将其加入白名单。", - "description": "Explains automatic closing and whitelist usage." + "contextMenuCloseDuplicates": { + "message": "关闭重复标签页", + "description": "Context menu item that closes duplicate tabs", + "hash": "1f2e3d4c5b6a79808796a5b4c3d2e1f0" }, "showPanel": { "message": "显示面板", "description": "Show panel", "hash": "3519be2a903e741db8c653b0e7e341e8" }, + "tabPriority": { + "message": "优先级", + "description": "Priority", + "hash": "a47af5c8632e36b75d0299167f2b38e0" + }, + "keepNewerTab": { + "message": "保留新的标签页", + "description": "Keep newer tab", + "hash": "a3aa6ddd27496953c1d39e89d344f609" + }, + "keepOlderTab": { + "message": "保留旧的标签页", + "description": "Keep older tab", + "hash": "17c1cecef3fb5c4c822c709e32428724" + }, "keepTabWithHistory": { "message": "保留有历史记录的标签页", "description": "Keep tab with history", "hash": "23668f9ee2e1380d81db5eedce7e778b" }, + "keeptabwithHttps": { + "message": "保留 https 的标签页", + "description": "Keep tab with https", + "hash": "158afd64eff0d0d42ec5fcdc69456b39" + }, + "keepPinnedTab": { + "message": "保留固定的标签页", + "description": "Keep pinned tab", + "hash": "eeded9a172a2db461ba5b29f7a4c9fce" + }, + "onRemainingTab": { + "message": "剩余标签页", + "description": "On remaining tab", + "hash": "4d7dbd19c1c07d94821411eeecbe04af" + }, + "activateKeptTab": { + "message": "激活", + "description": "Activate", + "hash": "1ca475dc17fb953961e503e67fab7000" + }, + "defaultTabBehavior": { + "message": "默认标签页行为", + "description": "Default tab behaviour", + "hash": "bb896c5652054de371ffb4e161f7bc90" + }, "matchingRules": { "message": "过滤", "description": "MatchingRules", "hash": "e86eed212e60224faadd11386632e100" }, + "ignoreHashPart": { + "message": "忽略标签页 URL 中的井字部分", + "description": "Ignore hash part in URL", + "hash": "bbeeddb75b5f0c1d72685cf06108c3e3" + }, + "ignoreSearchPart": { + "message": "忽略标签页 URL 中的问号部分", + "description": "Ignore search part in URL", + "hash": "719a708f82ce50153de4e90a4b697a84" + }, + "ignorePathPart": { + "message": "忽略标签页 URL 中的路径部分", + "description": "Ignore path part in URL", + "hash": "c05dc772b761f094ec2b99f692c26148" + }, + "ignore3w": { + "message": "Ignore 'www' in URL domain name", + "description": "Ignore 'www' in URL domain name" + }, + "caseInsensitive": { + "message": "Ignore case in URL", + "description": "Ignore case in URL" + }, "compareWithTitle": { "message": "比较依据标签页标题", "description": "Compare with title", diff --git a/duplicate-tabs-closer-master/background.js b/duplicate-tabs-closer-master/background.js index 641b226..676b811 100644 --- a/duplicate-tabs-closer-master/background.js +++ b/duplicate-tabs-closer-master/background.js @@ -84,28 +84,64 @@ class TabsInfo { } -// eslint-disable-next-line no-unused-vars -const tabsInfo = new TabsInfo();"use strict"; +// eslint-disable-next-line no-unused-vars +const tabsInfo = new TabsInfo(); + +const CONTEXT_MENU_CLOSE_DUPLICATES_ID = "close-duplicate-tabs"; +"use strict"; const defaultOptions = { shrunkMode: { value: false }, - keepTabWithHistory: { - value: false - }, - scope: { - value: "C" - }, - compareWithTitle: { - value: false + onDuplicateTabDetected: { + value: "A" + }, + onRemainingTab: { + value: "A" + }, + keepTabBasedOnAge: { + value: "O" // "O" (older) or "N" (newer) }, - automaticHandlingPinned: { + keepTabWithHttps: { + value: true + }, + keepPinnedTab: { + value: true + }, + keepTabWithHistory: { + value: false + }, + scope: { + value: "C" + }, + ignoreHashPart: { + value: false + }, + ignoreSearchPart: { + value: false + }, + ignorePathPart: { + value: false + }, + ignore3w: { value: true }, - matchingRulesPinned: { + caseInsensitive: { value: true }, + compareWithTitle: { + value: false + }, + onDuplicateTabDetectedPinned: { + value: true + }, + tabPriorityPinned: { + value: true + }, + matchingRulesPinned: { + value: true + }, scopePinned: { value: true }, @@ -183,16 +219,28 @@ const setStoredOption = async (name, value, refresh) => { const options = await getStoredOptions(); const storedOptions = options.storedOptions; storedOptions[name].value = value; - saveStoredOptions(storedOptions); - setOptions(storedOptions); - if (refresh) refreshGlobalDuplicateTabsInfo(); - else if (name === "showBadgeIfNoDuplicateTabs" || name === "badgeColorNoDuplicateTabs" || name === "badgeColorDuplicateTabs") updateBadgeStyle(); + saveStoredOptions(storedOptions); + setOptions(storedOptions); + if (refresh) refreshGlobalDuplicateTabsInfo(); + else if (name === "onDuplicateTabDetected") setBadgeIcon(); + else if (name === "showBadgeIfNoDuplicateTabs" || name === "badgeColorNoDuplicateTabs" || name === "badgeColorDuplicateTabs") updateBadgeStyle(); }; const options = {}; const setOptions = (storedOptions) => { - options.compareWithTitle = storedOptions.compareWithTitle.value; + options.autoCloseTab = storedOptions.onDuplicateTabDetected.value === "A"; + options.defaultTabBehavior = storedOptions.onRemainingTab.value === "B"; + options.activateKeptTab = storedOptions.onRemainingTab.value === "A"; + options.keepNewerTab = storedOptions.keepTabBasedOnAge.value === "N"; + options.keepTabWithHttps = storedOptions.keepTabWithHttps.value; + options.keepPinnedTab = storedOptions.keepPinnedTab.value; + options.ignoreHashPart = storedOptions.ignoreHashPart.value; + options.ignoreSearchPart = storedOptions.ignoreSearchPart.value; + options.ignorePathPart = storedOptions.ignorePathPart.value; + options.compareWithTitle = storedOptions.compareWithTitle.value; + options.ignore3w = storedOptions.ignore3w.value; + options.caseInsensitive = storedOptions.caseInsensitive.value; options.searchInAllWindows = storedOptions.scope.value === "A" || storedOptions.scope.value === "CA"; options.searchPerContainer = storedOptions.scope.value === "CC" || storedOptions.scope.value === "CA"; options.whiteList = whiteListToPattern(storedOptions.whiteList.value); @@ -269,34 +317,56 @@ const isHttps = (url) => { // eslint-disable-next-line no-unused-vars const getMatchingURL = (url) => { if (!isValidURL(url)) return url; - let matchingURL = url; - matchingURL = matchingURL.replace(/^http:\/\//i, "https://"); - matchingURL = matchingURL.toLowerCase(); - matchingURL = matchingURL.replace("://www.", "://"); - matchingURL = matchingURL.replace(/\/$/, ""); - return matchingURL; -}; - -// eslint-disable-next-line no-unused-vars -const getMatchPatternURL = (url) => { - let urlPattern = null; - if (isValidURL(url)) { - const uri = new URL(url); - urlPattern = `*://${uri.hostname}${uri.pathname}`; - if (uri.search || uri.hash) { - urlPattern += "*"; - } - } - else if (isBrowserURL(url)) { - urlPattern = `${url}*`; - } + let matchingURL = url; + if (options.ignorePathPart) { + const uri = new URL(matchingURL); + matchingURL = uri.origin; + } + else if (options.ignoreSearchPart) { + matchingURL = matchingURL.split("?")[0]; + } + else if (options.ignoreHashPart) { + matchingURL = matchingURL.split("#")[0]; + } + if (options.keepTabWithHttps) { + matchingURL = matchingURL.replace(/^http:\/\//i, "https://"); + } + if (options.ignore3w) { + matchingURL = matchingURL.replace("://www.", "://"); + } + if (options.caseInsensitive) { + matchingURL = matchingURL.toLowerCase(); + } + matchingURL = matchingURL.replace(/\/$/, ""); + return matchingURL; +}; + +// eslint-disable-next-line no-unused-vars +const getMatchPatternURL = (url) => { + let urlPattern = null; + if (isValidURL(url)) { + const uri = new URL(url); + urlPattern = `*://${uri.hostname}`; + if (options.ignorePathPart) { + urlPattern += "/*"; + } + else { + urlPattern += uri.pathname; + if (uri.search || uri.hash) { + urlPattern += "*"; + } + } + } + else if (isBrowserURL(url)) { + urlPattern = `${url}*`; + } return urlPattern; };"use strict"; // eslint-disable-next-line no-unused-vars const setBadgeIcon = () => { - chrome.action.setIcon({ path: "images/auto_close_16.png" }); + chrome.action.setIcon({ path: options.autoCloseTab ? "images/auto_close_16.png" : "images/manual_close_16.png" }); if (environment.isFirefox) browser.action.setBadgeTextColor({ color: "white" }); }; @@ -363,32 +433,44 @@ const matchTitle = (tab1, tab2) => { return false; }; -const getHttpsTabId = (observedTab, observedTabUrl, openedTab) => { - const regex = /^https:\/\//i; - const match1 = regex.test(observedTabUrl); - const match2 = regex.test(openedTab.url); - if (match1) { - return match2 ? null : observedTab.id; - } else { - return match2 ? openedTab.id : null; - } -}; - -const getPinnedTabId = (tab1, tab2) => { - if (tab1.pinned) { - return tab2.pinned ? null : tab1.id; - } else { - return tab2.pinned ? tab2.id : null; - } -}; - -const getLastUpdatedTabId = (observedTab, openedTab) => { - const observedTabLastUpdate = tabsInfo.getLastComplete(observedTab.id); - const openedTabLastUpdate = tabsInfo.getLastComplete(openedTab.id); - if (observedTabLastUpdate === null) return openedTab.id; - if (openedTabLastUpdate === null) return observedTab.id; - return (observedTabLastUpdate < openedTabLastUpdate) ? observedTab.id : openedTab.id; -}; +const getHttpsTabId = (observedTab, observedTabUrl, openedTab) => { + if (options.keepTabWithHttps) { + const regex = /^https:\/\//i; + const match1 = regex.test(observedTabUrl); + const match2 = regex.test(openedTab.url); + if (match1) { + return match2 ? null : observedTab.id; + } else { + return match2 ? openedTab.id : null; + } + } + return null; +}; + +const getPinnedTabId = (tab1, tab2) => { + if (options.keepPinnedTab) { + if (tab1.pinned) { + return tab2.pinned ? null : tab1.id; + } else { + return tab2.pinned ? tab2.id : null; + } + } + return null; +}; + +const getLastUpdatedTabId = (observedTab, openedTab) => { + const observedTabLastUpdate = tabsInfo.getLastComplete(observedTab.id); + const openedTabLastUpdate = tabsInfo.getLastComplete(openedTab.id); + if (options.keepNewerTab) { + if (observedTabLastUpdate === null) return observedTab.id; + if (openedTabLastUpdate === null) return openedTab.id; + return (observedTabLastUpdate > openedTabLastUpdate) ? observedTab.id : openedTab.id; + } else { + if (observedTabLastUpdate === null) return openedTab.id; + if (openedTabLastUpdate === null) return observedTab.id; + return (observedTabLastUpdate < openedTabLastUpdate) ? observedTab.id : openedTab.id; + } +}; const getFocusedTab = (observedTab, openedTab, activeWindowId, retainedTabId) => { if (retainedTabId === observedTab.id) { @@ -490,11 +572,13 @@ const closeDuplicateTab = async (tabToCloseId, remainingTabInfo) => { }; const _handleRemainingTab = async (details) => { - if (!tabsInfo.hasTab(details.tabId)) return; - if (!details.observedTabClosed) return; - - if (details.tabIndex > 0) moveTab(details.tabId, { index: details.tabIndex }); - if (details.active) activateTab(details.tabId); + if (!tabsInfo.hasTab(details.tabId)) return; + if (options.defaultTabBehavior && details.observedTabClosed) { + if (details.tabIndex > 0) moveTab(details.tabId, { index: details.tabIndex }); + if (details.active) activateTab(details.tabId); + } else if (options.activateKeptTab) { + focusTab(details.tabId, details.windowId); + } }; const handleRemainingTab = debounce(_handleRemainingTab, 500); @@ -565,8 +649,44 @@ const searchForDuplicateTabs = async (windowId, closeTabs) => { } }; -// eslint-disable-next-line no-unused-vars -const closeDuplicateTabs = (windowId) => searchForDuplicateTabs(windowId, true); +// eslint-disable-next-line no-unused-vars +const closeDuplicateTabs = (windowId) => searchForDuplicateTabs(windowId, true); + +const getContextMenusApi = () => { + if (chrome.contextMenus) return chrome.contextMenus; + if (typeof browser !== "undefined" && browser.contextMenus) return browser.contextMenus; + return null; +}; + +const createContextMenus = () => { + const contextMenus = getContextMenusApi(); + if (!contextMenus) return; + contextMenus.removeAll(() => { + const title = chrome.i18n.getMessage("contextMenuCloseDuplicates") || "Duplicate Tab Closer"; + const contexts = ["action"]; + try { + contextMenus.create({ + id: CONTEXT_MENU_CLOSE_DUPLICATES_ID, + title: title, + contexts: contexts + }); + } catch (error) { + console.error("createContextMenus error:", error); + } + }); +}; + +const handleContextMenuClick = async (info, tab) => { + if (info.menuItemId !== CONTEXT_MENU_CLOSE_DUPLICATES_ID) return; + let windowId = typeof info.windowId !== "undefined" ? info.windowId : undefined; + if (typeof windowId === "undefined" && tab && typeof tab.windowId !== "undefined") { + windowId = tab.windowId; + } + if (typeof windowId === "undefined") { + windowId = await getActiveWindowId(); + } + closeDuplicateTabs(windowId); +}; const setDuplicateTabPanel = async (duplicateTab, duplicateTabs) => { let containerColor = ""; @@ -649,58 +769,62 @@ const handleMessage = (message, sender, response) => { } }; -chrome.runtime.onMessage.addListener(handleMessage);"use strict"; +chrome.runtime.onMessage.addListener(handleMessage); + +const contextMenusApi = getContextMenusApi(); +if (contextMenusApi) contextMenusApi.onClicked.addListener(handleContextMenuClick); +"use strict"; const onCreatedTab = (tab) => { tabsInfo.setNewTab(tab.id); - if (tab.status === "complete" && !isBlankURL(tab.url)) { - searchForDuplicateTabsToClose(tab, true); - } -}; - -const onBeforeNavigate = async (details) => { - if ((details.frameId == 0) && (details.tabId !== -1) && !isBlankURL(details.url)) { - if (tabsInfo.isIgnoredTab(details.tabId)) return; - const tab = await getTab(details.tabId); - if (tab) { - tabsInfo.resetTab(tab.id); - searchForDuplicateTabsToClose(tab, true, details.url); - } - } -}; - -const onCompletedTab = async (details) => { - if ((details.frameId == 0) && (details.tabId !== -1)) { - if (tabsInfo.isIgnoredTab(details.tabId)) return; - const tab = await getTab(details.tabId); - if (tab) { - tabsInfo.updateTab(tab); - searchForDuplicateTabsToClose(tab); - } - } -}; + if (tab.status === "complete" && !isBlankURL(tab.url)) { + options.autoCloseTab ? searchForDuplicateTabsToClose(tab, true) : refreshDuplicateTabsInfo(tab.windowId); + } +}; + +const onBeforeNavigate = async (details) => { + if (options.autoCloseTab && (details.frameId == 0) && (details.tabId !== -1) && !isBlankURL(details.url)) { + if (tabsInfo.isIgnoredTab(details.tabId)) return; + const tab = await getTab(details.tabId); + if (tab) { + tabsInfo.resetTab(tab.id); + searchForDuplicateTabsToClose(tab, true, details.url); + } + } +}; + +const onCompletedTab = async (details) => { + if ((details.frameId == 0) && (details.tabId !== -1)) { + if (tabsInfo.isIgnoredTab(details.tabId)) return; + const tab = await getTab(details.tabId); + if (tab) { + tabsInfo.updateTab(tab); + options.autoCloseTab ? searchForDuplicateTabsToClose(tab) : refreshDuplicateTabsInfo(tab.windowId); + } + } +}; const onUpdatedTab = (tabId, changeInfo, tab) => { if (tabsInfo.isIgnoredTab(tabId)) return; if (Object.prototype.hasOwnProperty.call(changeInfo, "status") && changeInfo.status === "complete") { if (Object.prototype.hasOwnProperty.call(changeInfo, "url") && (changeInfo.url !== tab.url)) { - if (isBlankURL(tab.url) || !tab.favIconUrl || !tabsInfo.hasUrlChanged(tab)) return; - tabsInfo.updateTab(tab); - searchForDuplicateTabsToClose(tab); - } - else if (isChromeURL(tab.url)) { - tabsInfo.updateTab(tab); - searchForDuplicateTabsToClose(tab); - } - } -}; - -const onAttached = async (tabId) => { - const tab = await getTab(tabId); - if (tab) { - searchForDuplicateTabsToClose(tab); - } -}; + if (isBlankURL(tab.url) || !tab.favIconUrl || !tabsInfo.hasUrlChanged(tab)) return; + tabsInfo.updateTab(tab); + options.autoCloseTab ? searchForDuplicateTabsToClose(tab) : refreshDuplicateTabsInfo(tab.windowId); + } + else if (isChromeURL(tab.url)) { + tabsInfo.updateTab(tab); + options.autoCloseTab ? searchForDuplicateTabsToClose(tab) : refreshDuplicateTabsInfo(tab.windowId); + } + } +}; + +const onAttached = async (tabId) => { + const tab = await getTab(tabId); + if (tab) { + options.autoCloseTab ? searchForDuplicateTabsToClose(tab) : refreshDuplicateTabsInfo(tab.windowId); + } +}; const onRemovedTab = (removedTabId, removeInfo) => { tabsInfo.removeTab(removedTabId); @@ -728,6 +852,7 @@ const start = async () => { await initializeOptions(); setBadgeIcon(); await refreshGlobalDuplicateTabsInfo(); + createContextMenus(); chrome.tabs.onCreated.addListener(onCreatedTab); chrome.webNavigation.onBeforeNavigate.addListener(onBeforeNavigate); chrome.tabs.onAttached.addListener(onAttached); diff --git a/duplicate-tabs-closer-master/manifest-c.json b/duplicate-tabs-closer-master/manifest-c.json index ed4c5e0..153105f 100644 --- a/duplicate-tabs-closer-master/manifest-c.json +++ b/duplicate-tabs-closer-master/manifest-c.json @@ -22,6 +22,7 @@ "permissions": [ "tabs", "webNavigation", + "contextMenus", "storage" ], "options_ui": { diff --git a/duplicate-tabs-closer-master/manifest-f.json b/duplicate-tabs-closer-master/manifest-f.json index a6ac457..216b16f 100644 --- a/duplicate-tabs-closer-master/manifest-f.json +++ b/duplicate-tabs-closer-master/manifest-f.json @@ -33,6 +33,7 @@ "cookies", "tabs", "webNavigation", + "contextMenus", "storage", "contextualIdentities" ], diff --git a/duplicate-tabs-closer-master/optionPage/optionPage.css b/duplicate-tabs-closer-master/optionPage/optionPage.css index a85b394..9a6e633 100644 --- a/duplicate-tabs-closer-master/optionPage/optionPage.css +++ b/duplicate-tabs-closer-master/optionPage/optionPage.css @@ -124,9 +124,9 @@ body { color: rgb(72, 80, 92); } -#automaticHandlingBody { - margin-bottom: 8px; -} +#onDuplicateTabDetectedBody { + margin-bottom: 8px; +} .checkboxes label { margin: 0px; diff --git a/duplicate-tabs-closer-master/optionPage/optionPage.html b/duplicate-tabs-closer-master/optionPage/optionPage.html index 9c86619..7bceb1a 100644 --- a/duplicate-tabs-closer-master/optionPage/optionPage.html +++ b/duplicate-tabs-closer-master/optionPage/optionPage.html @@ -22,36 +22,105 @@
      • -
      • - - - -
      • -
      • -
        -

        -
        - - -
        -
        -
      • -
      • - - - -
      • -
      • -
        -
        - +
      • + + + +
      • +
      • +
        + +
        + + +
        +
        + + +
        +
        +
      • +
      • + + + +
      • +
      • +
        + +
        +
        + +
        +
        + +
        +
        +
      • +
      • + + + +
      • +
      • +
        +
        + +
        +
        + +
        +
        + +
        +
        + +
        +
        + +
        +
        +
      • diff --git a/duplicate-tabs-closer-master/optionPage/optionPage.js b/duplicate-tabs-closer-master/optionPage/optionPage.js index a1f582e..2921af1 100644 --- a/duplicate-tabs-closer-master/optionPage/optionPage.js +++ b/duplicate-tabs-closer-master/optionPage/optionPage.js @@ -20,11 +20,12 @@ const loadPopupEvents = () => { }); /* Save combobox settings */ - $(".list-group select").on("change", function (event) { - event.stopPropagation(); - const refresh = this.id === "scope"; - saveOption(this.id, this.value, refresh); - }); + $(".list-group select").on("change", function (event) { + event.stopPropagation(); + const refresh = this.id === "scope"; + saveOption(this.id, this.value, refresh); + if (this.id === "onDuplicateTabDetected") changeAutoCloseOptionState(this.value, true); + }); /* Save badge color settings */ $(".list-group input[type='color']").on("change", function () { @@ -78,6 +79,13 @@ const cleanUpWhiteList = (whiteList) => { return Array.from(whiteListCleaned).join("\n"); }; +/* Show/Hide the AutoClose option */ +const changeAutoCloseOptionState = (state, resize) => { + $("#onRemainingTabGroup").toggleClass("hidden", state !== "A"); + $("#whiteListGroup").toggleClass("hidden", state !== "A"); + if (resize) resizeDuplicateTabsPanel(); +}; + const toggleExpendGroup = (groupId, checkbox, resize) => { if (checkbox) { const thumbChecked = $(`#${groupId}`).prop("checked"); @@ -162,9 +170,10 @@ const setPanelOption = (details) => { // badge color value $(`#${storedOption}`).prop("value", value); } - else { - $(`#${storedOption} option[value='${value}']`).prop("selected", true); - } + else { + $(`#${storedOption} option[value='${value}']`).prop("selected", true); + if (storedOption === "onDuplicateTabDetected") changeAutoCloseOptionState(value, resize); + } if (isLockedKey) $(`#${storedOption}`).prop("disabled", true); } }; diff --git a/duplicate-tabs-closer-master/popup/popup.css b/duplicate-tabs-closer-master/popup/popup.css index 02e24cb..ce16dd2 100644 --- a/duplicate-tabs-closer-master/popup/popup.css +++ b/duplicate-tabs-closer-master/popup/popup.css @@ -166,6 +166,10 @@ body { margin: 4px 2px; } +#onDuplicateTabDetectedBody { + margin-bottom: 6px; +} + #matchingRulesBody { margin-top: -4px; } diff --git a/duplicate-tabs-closer-master/popup/popup.html b/duplicate-tabs-closer-master/popup/popup.html index a0e81f4..c9d1d4b 100644 --- a/duplicate-tabs-closer-master/popup/popup.html +++ b/duplicate-tabs-closer-master/popup/popup.html @@ -28,21 +28,106 @@
        -
        -
      • - - -
      • + + + +
      • +
      • +
        + +
        + + +
        +
        +
      • +
        +
        +
      • + + +
      • -
      • -
        -
        - +
      • +
        + +
        +
        + +
        +
        + +
        +
        +
      • +
        +
        +
      • + + + +
      • +
      • +
        +
        + +
        +
        + +
        +
        + +
        +
        + +
        +
        + +
        +
        +
      • diff --git a/duplicate-tabs-closer-master/popup/popup.js b/duplicate-tabs-closer-master/popup/popup.js index 47a024a..42e99c6 100644 --- a/duplicate-tabs-closer-master/popup/popup.js +++ b/duplicate-tabs-closer-master/popup/popup.js @@ -5,9 +5,15 @@ let lastDuplicateTabs = {}; let closePopup = false; let environment = ""; -const toggleShrunkMode = (checked) => { - $(".list-group-form").toggleClass("shrunk", checked); -}; +/* Show/Hide the AutoClose option */ +const changeAutoCloseOptionState = (state, resize) => { + $("#onRemainingTabGroup").toggleClass("hidden", state !== "A"); + if (resize) resizeDuplicateTabsPanel(); +}; + +const toggleShrunkMode = (checked) => { + $(".list-group-form").toggleClass("shrunk", checked); +}; const toggleExpendOptions = (resize) => { $("#optionHeader").toggleClass("collapsed"); @@ -100,10 +106,11 @@ const setPanelOptions = async () => { } // combobox else { - $(`#${storedOption} option[value='${value}']`).prop("selected", true); - } - if (isLockedKey) $(`#${storedOption}`).prop("disabled", true); - } + $(`#${storedOption} option[value='${value}']`).prop("selected", true); + if (storedOption === "onDuplicateTabDetected") changeAutoCloseOptionState(value, false); + } + if (isLockedKey) $(`#${storedOption}`).prop("disabled", true); + } } if (collapseOptions) toggleExpendOptions(false); }; @@ -136,8 +143,9 @@ const loadListenerEvents = () => { $(".list-group select").on("change", function (event) { event.stopPropagation(); const refresh = this.id === "scope"; - saveOption(this.id, this.value, refresh); - }); + saveOption(this.id, this.value, refresh); + if (this.id === "onDuplicateTabDetected") changeAutoCloseOptionState(this.value, true); + }); /* Open Option tab */ $(".fa-cog").on("click", (event) => {