fix moving of browser groups and allow to store groups into session

This commit is contained in:
2026-04-09 23:38:00 +02:00
parent eaa86e3f3d
commit c5a4218da0
3 changed files with 226 additions and 21 deletions
+114 -20
View File
@@ -444,22 +444,31 @@ async function groupMove({ group, forward, backward }) {
const allTabs = await chrome.tabs.query({ windowId: groupInfo.windowId });
allTabs.sort((a, b) => a.index - b.index);
const groupTabs = allTabs.filter(t => t.groupId === groupId);
if (!groupTabs.length) throw new Error(`No tabs found in group '${group}'`);
const blocks = buildTabBlocks(allTabs);
const currentIdx = blocks.findIndex(block => block.groupId === groupId);
if (currentIdx === -1) throw new Error(`No tabs found in group '${group}'`);
const firstIdx = groupTabs[0].index;
const lastIdx = groupTabs[groupTabs.length - 1].index;
const currentBlock = blocks[currentIdx];
const currentLength = currentBlock.tabIds.length;
if (forward) {
const tabAfter = allTabs.find(t => t.index > lastIdx && t.groupId !== groupId);
if (!tabAfter) return { groupId, moved: false };
await chrome.tabs.move(groupTabs.map(t => t.id), { index: tabAfter.index + 1 });
const nextBlock = blocks[currentIdx + 1];
if (!nextBlock) return { groupId, moved: false };
const targetIndex =
nextBlock.groupId === null
? currentBlock.startIndex + 1
: nextBlock.endIndex - currentLength + 1;
await chrome.tabGroups.move(groupId, { index: targetIndex });
} else if (backward) {
const tabsBefore = allTabs.filter(t => t.index < firstIdx && t.groupId !== groupId);
const tabBefore = tabsBefore[tabsBefore.length - 1];
if (!tabBefore) return { groupId, moved: false };
await chrome.tabs.move(groupTabs.map(t => t.id), { index: tabBefore.index });
const previousBlock = blocks[currentIdx - 1];
if (!previousBlock) return { groupId, moved: false };
const targetIndex =
previousBlock.groupId === null
? currentBlock.startIndex - 1
: previousBlock.startIndex;
await chrome.tabGroups.move(groupId, { index: targetIndex });
}
return { groupId, moved: true };
}
@@ -597,28 +606,74 @@ function contentDispatch(funcName, args) {
async function sessionSave({ name }) {
const tabs = await chrome.tabs.query({});
const urls = tabs.map(t => t.url).filter(Boolean);
const groups = await chrome.tabGroups.query({});
const groupById = new Map(groups.map(group => [group.id, group]));
const sessionTabs = tabs
.filter(tab => Boolean(tab.url))
.sort((a, b) => (a.windowId - b.windowId) || (a.index - b.index))
.map(tab => {
const entry = { url: tab.url };
if (tab.groupId >= 0) {
const group = groupById.get(tab.groupId);
entry.group = {
key: `${tab.windowId}:${tab.groupId}`,
title: group?.title || "",
color: normalizeGroupColor(group?.color),
collapsed: Boolean(group?.collapsed),
};
}
return entry;
});
const sessions = await getSessions();
sessions[name] = { urls, savedAt: Date.now() };
sessions[name] = {
tabs: sessionTabs,
urls: sessionTabs.map(tab => tab.url),
savedAt: Date.now(),
};
await chrome.storage.local.set({ sessions });
return { name, tabs: urls.length };
return { name, tabs: sessionTabs.length };
}
async function sessionLoad({ name }) {
const sessions = await getSessions();
const session = sessions[name];
if (!session) throw new Error(`Session '${name}' not found`);
for (const url of session.urls) {
await chrome.tabs.create({ url, active: false });
const sessionTabs = getSessionTabs(session);
const createdTabs = [];
for (const entry of sessionTabs) {
const tab = await chrome.tabs.create({ url: entry.url, active: false });
createdTabs.push({ tabId: tab.id, entry });
}
return { name, tabs: session.urls.length };
const groups = new Map();
for (const { tabId, entry } of createdTabs) {
if (!entry.group) continue;
const key = entry.group.key || `${entry.group.title || "group"}:${groups.size}`;
if (!groups.has(key)) {
groups.set(key, { meta: entry.group, tabIds: [] });
}
groups.get(key).tabIds.push(tabId);
}
for (const { meta, tabIds } of groups.values()) {
const restoredGroupId = await chrome.tabs.group({ tabIds });
await chrome.tabGroups.update(restoredGroupId, {
title: meta.title || "",
color: normalizeGroupColor(meta.color),
collapsed: Boolean(meta.collapsed),
});
}
return { name, tabs: sessionTabs.length };
}
async function sessionList() {
const sessions = await getSessions();
return Object.entries(sessions).map(([name, s]) => ({
name,
tabs: (s.urls || []).length,
tabs: getSessionTabs(s).length,
savedAt: s.savedAt || null,
}));
}
@@ -633,8 +688,8 @@ async function sessionRemove({ name }) {
async function sessionDiff({ nameA, nameB }) {
const sessions = await getSessions();
const a = new Set((sessions[nameA]?.urls) || []);
const b = new Set((sessions[nameB]?.urls) || []);
const a = new Set(getSessionTabs(sessions[nameA]).map(tab => tab.url));
const b = new Set(getSessionTabs(sessions[nameB]).map(tab => tab.url));
return {
added: [...b].filter(u => !a.has(u)),
removed: [...a].filter(u => !b.has(u)),
@@ -692,6 +747,45 @@ async function resolveGroupId(nameOrId) {
return match.id;
}
function buildTabBlocks(tabs) {
const blocks = [];
for (const tab of tabs) {
const normalizedGroupId = tab.groupId >= 0 ? tab.groupId : null;
const lastBlock = blocks[blocks.length - 1];
if (lastBlock?.groupId === normalizedGroupId) {
lastBlock.tabIds.push(tab.id);
lastBlock.endIndex = tab.index;
continue;
}
blocks.push({
groupId: normalizedGroupId,
startIndex: tab.index,
endIndex: tab.index,
tabIds: [tab.id],
});
}
return blocks;
}
function getSessionTabs(session) {
if (!session) return [];
if (Array.isArray(session.tabs)) {
return session.tabs
.map(entry => typeof entry === "string" ? { url: entry } : entry)
.filter(entry => entry?.url);
}
if (Array.isArray(session.urls)) {
return session.urls.filter(Boolean).map(url => ({ url }));
}
return [];
}
function normalizeGroupColor(color) {
const allowed = new Set(["grey", "blue", "red", "yellow", "green", "pink", "purple", "cyan", "orange"]);
return allowed.has(color) ? color : "grey";
}
async function getAliases() {
const { windowAliases } = await chrome.storage.local.get("windowAliases");
return windowAliases || {};