Tab Stack Data in the "vivaldi" Object
-
I have created an automatic tab stacking mod.
It is working well as far as I have tested it for a few hours.code
(async () => { 'use strict'; const config = { // デフォルト以外のワークスペースで自動タブスタックを使用する (true: 有効, false: 無効) // Use automatic tab stacking in non-default workspaces (true: enable, false: disable) workspace: false, // サブドメインごとにタブをスタックする (true: 有効, false: 無効) // Stack tabs by subdomain (true: enable, false: disable) subdomain: true, // タブスタック名を自動的に変更する (0: 無効, 1: ホスト名を使用, 2: ベースドメインから生成) // Automatically change tab stack names (0: disabled, 1: use hostname, 2: generated from base domain) stackname: 0, // 自動タブスタックの対象とするホストのルール (完全一致もしくは正規表現) // Rules for hosts to be included in the automatic tab stack (exact match or regular expression) includes: [], // 自動タブスタックから除外するホストのルール (完全一致もしくは正規表現) // Rules for hosts to be excluded from the automatic tab stack (exact match or regular expression) excludes: [ // 'www.example.com', // /^(.+\.)?example\.net$/, ], }; const addTabGroup = async (tabId, groupId) => { const tab = await chrome.tabs.get(tabId); const extData = JSON.parse(tab.vivExtData); extData.group = groupId; await chrome.tabs.update(tabId, { vivExtData: JSON.stringify(extData) }); }; const getUrlFragments = (url) => vivaldi.utilities.getUrlFragments(url); const getBaseDomain = (url) => { const urlFragments = getUrlFragments(url); return urlFragments.host.match(`([^.]+\\.${ urlFragments.tld })$`)?.[1] || urlFragments.host; }; const getHostname = (url) => config.subdomain ? getUrlFragments(url).host : getBaseDomain(url); const matchHostRule = (url, rule) => { const hostname = getUrlFragments(url).host; return rule instanceof RegExp ? rule.test(hostname) : hostname === rule; }; const getTabInfo = async (tabId) => { const tab = await chrome.tabs.get(tabId); if (tab.id !== -1) { tab.vivExtData = JSON.parse(tab.vivExtData); return tab; } }; const getTabStore = async () => { const tabStore = {}; const tabs = (await chrome.tabs.query({ currentWindow: true })) .filter(tab => tab.id !== -1) .map(tab => Object.assign(tab, { vivExtData: JSON.parse(tab.vivExtData) })) .filter(tab => !tab.pinned) .filter(tab => !tab.vivExtData.panelId) .filter(tab => !config.includes.length ? true : config.includes.find(rule => matchHostRule(tab.url, rule))) .filter(tab => !config.excludes.find(rule => matchHostRule(tab.url, rule))); const workspaces = Object.groupBy(tabs, (tab) => tab.vivExtData.workspaceId); for (const [workspaceId, tabs] of Object.entries(workspaces)) { tabStore[workspaceId] = Object.groupBy(tabs, (tab) => tab.vivExtData.group); } return tabStore; }; const getTabGroupMap = (tabStore) => { const tabGroupMap = {}; for (const [workspaceId, groups] of Object.entries(tabStore)) { tabGroupMap[workspaceId] = {}; for (const [groupId, tabs] of Object.entries(groups)) { const hostnames = Object.keys(Object.groupBy(tabs, (tab) => getHostname(tab.url))); if (hostnames.length === 1 && groupId && groupId !== 'undefined') { tabGroupMap[workspaceId][hostnames[0]] ??= []; tabGroupMap[workspaceId][hostnames[0]].push(groupId); } } } return tabGroupMap; }; const groupingTabs = async (targetTab) => { const tabStore = await getTabStore(); const tabGroupMap = getTabGroupMap(tabStore); for (const [workspaceId, groups] of Object.entries(tabStore)) { if (!config.workspace && workspaceId !== 'undefined') continue; if (String(targetTab.vivExtData.workspaceId) !== workspaceId) continue; const tabGroups = {}; for (const tabs of Object.values(groups)) { for (const tab of tabs) { const hostname = getHostname(tab.url); tabGroupMap[workspaceId][hostname] ??= [crypto.randomUUID()]; const groupId = tabGroupMap[workspaceId][hostname].sort()[0]; tabGroups[groupId] ??= []; tabGroups[groupId].push(tab); } } for (const [groupId, tabs] of Object.entries(tabGroups)) { if (getHostname(targetTab.url) === getHostname(tabs[0].url)) { let tabIndex = (await getTabInfo(tabs[0].id)).index; if (config.stackname) { const stackNameMap = await vivaldi.prefs.get('vivaldi.tabs.stacking.name_map'); let stackname; switch (config.stackname) { case 1: stackname = getHostname(targetTab.url); break; case 2: stackname = getBaseDomain(targetTab.url).split('.')[0]; stackname = stackname.charAt(0).toUpperCase() + stackname.slice(1); break; } await vivaldi.prefs.set({ path: 'vivaldi.tabs.stacking.name_map', value: Object.assign(stackNameMap, { [groupId]: stackname }), }); } for (const tab of tabs) { if (tab.vivExtData.group !== groupId) { addTabGroup(tab.id, groupId); } chrome.tabs.move(tab.id, { index: tabIndex }); tabIndex++; } } } } } chrome.webNavigation.onCommitted.addListener(async (details) => { const tab = await getTabInfo(details.tabId); if (tab && !tab.pinned && !tab.vivExtData.panelId && details.frameType === 'outermost_frame') { setTimeout(() => { groupingTabs(tab); }, 100); } }); })();
-
-
Thank you very much.
Could you add to work in same host but different subdomains?
This links:
https://huellalibrosicc.blogspot.com
-
For your fantastic Mod to be used by more people please open a new Thread, called "Group tabs by domain".
Thanks again.
-
@barbudo2005
I have modified the code to add a new setting.
Changingsubdomain: true
tosubdomain: false
will stack tabs for different subdomains of the same host. -
Hi,
You can add a clone or own's version to the Top of the Code to provide Mod's Info / Tracking:/* * Site Security Box Favicons (a mod for Vivaldi) * https://forum.vivaldi.net/topic/23813/site-security-box-favicons-mod * Written by LonM, kichrot * No Copyright Reserved * This mod takes the favicon from a tab and places it into the address bar site info box * Assumes presence of both the tab bar and the address bar */
/* Window Border | unMaximised version -------------------------- HTML | CSS | FORMS | JAVA | jQUERY - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Description: Window Border | unMaximised version Filename: xxxxxx.css Author: slausage Topic: https://forum.vivaldi.net/topic/5603/border-around-browser-window/20 ----------------------------------------------------------------- */
/* ---------------------------------------------------------------- HTML | CSS | FORMS | JAVA | jQUERY - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Description: Name [Project] Filename: xxxxxx.css Author: Topic: ----------------------------------------------------------------- */
Thx
-
You came at the right time to save us from the monotony of not having the operation automatically, as it should always be. Thank you very much again.
What does it mean?:
workspace: false
-
@barbudo2005
This setting enables or disables automatic tab stacking within a workspace. -
Can you please give me an example to clarify what is involved?
-
@barbudo2005
The automatic tab stack will work even if you switch to a non-default workspace.Sorry. It is difficult to explain using machine translation.
Please actually change the settings and try it out. -
I used Otto tabs until Vivaldi version 6.7 and I still have it installed for removing duplicate tabs.
Could you add this feature and make the Mod even more fantastic?
-
@barbudo2005
I don't need that functionality and would not implement that functionality in a mod if it could be accomplished with existing extensions. -
Ok. You are absolutely right and it was a mistake to ask you.
-
@nafumofu
Thank you for your efforts.
You are a legend.
Btw, I wonder why but auto-stack doesn't work in workspace.
Where was my mistake?このModを有難うございます!
個別のワークスペースだと、自動スタックがされないのですが、
設定を何か間違えているのでしょうか? -
Sorry, I just forgot to overwrite a file...
-
@nafumofu
Added the ability to use regular expressions in exclusion lists. -
@Aaron mentioned in another post that it would be interesting to include a list of "Included", in such a way to group certain hosts that the user considers that they should be grouped:
For example:
gitlab.com and github.com
Does it seem reasonable to you and do you think it is possible to include such a list?
-
@nafumofu Thank you for the wonderful script! It works perfectly
If you are still open to suggestions, I would love if it had an option to automatically rename the tab stacks based on the domain with the first letter being capitalized, like renaming the tab stack to "Youtube" for the youtube.com domain
-
@barbudo2005
We will consider whether the feature is feasible. -
We have noticed a problem with the current subdomain handling not detecting top-level domains such as
.co.jp
correctly.@Moondane
For now, we have added the ability to set the hostname to the tab stack name.
It is difficult to realize the requested functionality due to the problem of top-level domain detection.