mirror of
https://github.com/vale981/tridactyl
synced 2025-03-04 09:01:39 -05:00
Add tab group commands
Functionality implemented: - :tgroupcreate to create a tab group - :tgroupswitch to switch to or create a tab group - :tgrouplast to switch to the previously active tab group - :tgrouprename to rename the current tab group - :tgroupclose to close all tabs in the current tab group - :tgroupmove to move the current tab to another tab group - :tgroupabort to destroy all tab group information and show all tabs - :tabgroup... aliases for the above commands - Completion for :tgroupswitch and :tgroupmove - Restoring tab group setup on firefox start (seems to just work without any extra code) - Basic mode indicator - Setting for initial urls for to use newly created tab groups (after first) - Setting for whether :tab shows hidden tabs. Edge cases handled - Opening tabs outside of tridactyl mechanisms (e.g. firefox gui, C-t, etc.) - Switching to a hidden tab - Detaching and attaching tabs - Pinned tabs (they can't be hidden; the commands ignore them) - Various other edge cases Fixes #811.
This commit is contained in:
parent
d1eec89c7a
commit
988ec5b770
11 changed files with 752 additions and 7 deletions
|
@ -59,5 +59,7 @@ Since Tridactyl aims to provide all the features Vimperator and Pentadactyl had,
|
|||
- This allows us to use Firefox's built-in find-in-page API, for, for example, allowing you to bind find-next and find-previous to `n` and `N`.
|
||||
- Monitor extension usage and manage themes:
|
||||
- Tridactyl needs this to integrate with and avoid conflicts with other extensions. For example, Tridactyl's contextual identity features use this to cooperate with the Multi-Account Containers extension.
|
||||
- Hide tabs:
|
||||
- Tridactyl needs this for tab group commands, which allow associating names with different groups of tabs and showing the tabs from only of those groups at a time.
|
||||
|
||||
[betas]: https://tridactyl.cmcaine.co.uk/betas/?sort=time&order=desc
|
||||
|
|
|
@ -122,6 +122,7 @@ export function enableCompletions() {
|
|||
RssCompletionSource,
|
||||
SessionsCompletionSource,
|
||||
SettingsCompletionSource,
|
||||
TabGroupCompletionSource,
|
||||
WindowCompletionSource,
|
||||
ExtensionsCompletionSource,
|
||||
]
|
||||
|
@ -392,6 +393,7 @@ Messaging.addListener("commandline_frame", Messaging.attributeCaller(SELF))
|
|||
|
||||
import { getCommandlineFns } from "@src/lib/commandline_cmds"
|
||||
import { KeyEventLike } from "./lib/keyseq"
|
||||
import { TabGroupCompletionSource } from "./completions/TabGroup"
|
||||
commandline_state.fns = getCommandlineFns(commandline_state)
|
||||
Messaging.addListener(
|
||||
"commandline_cmd",
|
||||
|
|
|
@ -168,9 +168,19 @@ export class BufferCompletionSource extends Completions.CompletionSourceFuse {
|
|||
}
|
||||
|
||||
private async fillOptions() {
|
||||
const tabs: browser.tabs.Tab[] = await browserBg.tabs.query({
|
||||
currentWindow: true,
|
||||
})
|
||||
let tabs: browser.tabs.Tab[]
|
||||
|
||||
if (config.get("tabshowhidden") === "true") {
|
||||
tabs = await browserBg.tabs.query({
|
||||
currentWindow: true
|
||||
})
|
||||
} else {
|
||||
tabs = await browserBg.tabs.query({
|
||||
currentWindow: true,
|
||||
hidden: false
|
||||
})
|
||||
}
|
||||
|
||||
const options = []
|
||||
// Get alternative tab, defined as last accessed tab.
|
||||
tabs.sort((a, b) => b.lastAccessed - a.lastAccessed)
|
||||
|
|
70
src/completions/TabGroup.ts
Normal file
70
src/completions/TabGroup.ts
Normal file
|
@ -0,0 +1,70 @@
|
|||
import * as Completions from "@src/completions"
|
||||
import { tgroups, windowTgroup, tgroupTabs } from "@src/lib/tab_groups"
|
||||
|
||||
class TabGroupCompletionOption extends Completions.CompletionOptionHTML
|
||||
implements Completions.CompletionOptionFuse {
|
||||
public fuseKeys = []
|
||||
|
||||
constructor(group: string, tabCount: number) {
|
||||
super()
|
||||
this.value = group
|
||||
this.fuseKeys.push(group)
|
||||
this.html = html`<tr class="TabGroupCompletionOption option">
|
||||
<td class="title">${group}</td>
|
||||
<td class="tabcount">${tabCount} tab${
|
||||
tabCount !== 1 ? "s" : ""
|
||||
}</td>
|
||||
</tr>`
|
||||
}
|
||||
}
|
||||
|
||||
export class TabGroupCompletionSource extends Completions.CompletionSourceFuse {
|
||||
public options: TabGroupCompletionOption[]
|
||||
|
||||
constructor(private _parent: any) {
|
||||
super(
|
||||
["tgroupswitch", "tgroupmove"],
|
||||
"TabGroupCompletionSource",
|
||||
"Tab Groups",
|
||||
)
|
||||
|
||||
this.updateOptions()
|
||||
this._parent.appendChild(this.node)
|
||||
}
|
||||
|
||||
async onInput(_: string) {}
|
||||
|
||||
async filter(exstr: string) {
|
||||
this.lastExstr = exstr
|
||||
const [prefix] = this.splitOnPrefix(exstr)
|
||||
|
||||
// Hide self and stop if prefixes don't match
|
||||
if (prefix) {
|
||||
// Show self if prefix and currently hidden
|
||||
if (this.state === "hidden") {
|
||||
this.state = "normal"
|
||||
}
|
||||
} else {
|
||||
this.state = "hidden"
|
||||
return
|
||||
}
|
||||
|
||||
return this.updateDisplay()
|
||||
}
|
||||
|
||||
private async updateOptions() {
|
||||
const currentGroup = await windowTgroup()
|
||||
const otherGroups = [...(await tgroups())].filter(
|
||||
group => group !== currentGroup,
|
||||
)
|
||||
this.options = await Promise.all(
|
||||
otherGroups.map(async group => {
|
||||
const tabCount = (await tgroupTabs(group)).length
|
||||
const o = new TabGroupCompletionOption(group, tabCount)
|
||||
o.state = "normal"
|
||||
return o
|
||||
}),
|
||||
)
|
||||
return this.updateDisplay()
|
||||
}
|
||||
}
|
|
@ -155,6 +155,7 @@ import * as urlutils from "@src/lib/url_util"
|
|||
import * as scrolling from "@src/content/scrolling"
|
||||
import * as R from "ramda"
|
||||
import * as visual from "@src/lib/visual"
|
||||
import { tabTgroup } from "./lib/tab_groups"
|
||||
/* tslint:disable:import-spacing */
|
||||
;(window as any).tri = Object.assign(Object.create(null), {
|
||||
browserBg: webext.browserBg,
|
||||
|
@ -295,16 +296,18 @@ config.getAsync("modeindicator").then(mode => {
|
|||
})
|
||||
}
|
||||
|
||||
addContentStateChangedListener((property, oldMode, oldValue, newValue) => {
|
||||
addContentStateChangedListener(async (property, oldMode, oldValue, newValue) => {
|
||||
let mode = newValue
|
||||
let group = ""
|
||||
let suffix = ""
|
||||
let result = ""
|
||||
if (property !== "mode") {
|
||||
if (property === "suffix") {
|
||||
mode = oldMode
|
||||
suffix = newValue
|
||||
} else {
|
||||
return
|
||||
} else if (property === "group") {
|
||||
mode = oldMode
|
||||
group = newValue
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -330,10 +333,17 @@ config.getAsync("modeindicator").then(mode => {
|
|||
} else {
|
||||
result = mode
|
||||
}
|
||||
|
||||
const modeindicatorshowkeys = Config.get("modeindicatorshowkeys")
|
||||
if (modeindicatorshowkeys === "true" && suffix !== "") {
|
||||
result = mode + " " + suffix
|
||||
}
|
||||
|
||||
const tabGroup = await tabTgroup()
|
||||
if (tabGroup) {
|
||||
result = result + " | " + tabGroup
|
||||
}
|
||||
|
||||
logger.debug(
|
||||
"statusindicator: ",
|
||||
result,
|
||||
|
|
|
@ -20,6 +20,8 @@ export class PrevInput {
|
|||
class ContentState {
|
||||
mode: ModeName = "normal"
|
||||
suffix = ""
|
||||
// to trigger status indicator updates
|
||||
group: string = ""
|
||||
}
|
||||
|
||||
export type ContentStateProperty =
|
||||
|
@ -27,6 +29,7 @@ export type ContentStateProperty =
|
|||
| "cmdHistory"
|
||||
| "prevInputs"
|
||||
| "suffix"
|
||||
| "group"
|
||||
|
||||
export type ContentStateChangedCallback = (
|
||||
property: ContentStateProperty,
|
||||
|
|
204
src/excmds.ts
204
src/excmds.ts
|
@ -164,6 +164,7 @@ import * as Updates from "@src/lib/updates"
|
|||
import * as Extensions from "@src/lib/extension_info"
|
||||
import * as webrequests from "@src/background/webrequests"
|
||||
import * as commandsHelper from "@src/background/commands"
|
||||
import { tgroups, tgroupActivate, setTabTgroup, setWindowTgroup, setTgroups, windowTgroup, tgroupClearOldInfo, tgroupLastTabId, tgroupTabs, clearAllTgroupInfo, tgroupActivateLast, tgroupHandleTabActivated, tgroupHandleTabCreated, tgroupHandleTabAttached, tgroupHandleTabUpdated, tgroupHandleTabRemoved, tgroupHandleTabDetached } from "./lib/tab_groups"
|
||||
|
||||
ALL_EXCMDS = {
|
||||
"": BGSELF,
|
||||
|
@ -2803,7 +2804,208 @@ export async function recontain(containerName: string) {
|
|||
}
|
||||
|
||||
// }}}
|
||||
//
|
||||
|
||||
// {{{ TAB GROUPS
|
||||
/** @hidden */
|
||||
//#background_helper
|
||||
// {
|
||||
browser.tabs.onCreated.addListener(tgroupHandleTabCreated)
|
||||
browser.tabs.onRemoved.addListener(tgroupHandleTabRemoved)
|
||||
browser.tabs.onDetached.addListener(tgroupHandleTabDetached)
|
||||
browser.tabs.onAttached.addListener(tgroupHandleTabAttached)
|
||||
browser.tabs.onActivated.addListener(tgroupHandleTabActivated)
|
||||
browser.tabs.onUpdated.addListener(tgroupHandleTabUpdated)
|
||||
// }
|
||||
|
||||
/** @hidden */
|
||||
//#content
|
||||
export function setContentStateGroup(name: string) {
|
||||
contentState.group = name
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new tab group in the current window.
|
||||
*
|
||||
* Tab groups are a way of organizing different groups of related tabs within a
|
||||
* single window. Groups allow you to have different named contexts and show
|
||||
* only the tabs for a single group at a time.
|
||||
*
|
||||
* @param name The name of the tab group to create.
|
||||
*
|
||||
* If no tab groups exist, set the tab group name for all existing tabs in the
|
||||
* window. Otherwise open a new tab and hide all tabs in the old tab group.
|
||||
*
|
||||
* Tab groups exist only for a single window.
|
||||
*
|
||||
*/
|
||||
//#background
|
||||
export async function tgroupcreate(name: string) {
|
||||
const promises = []
|
||||
const groups = await tgroups()
|
||||
|
||||
if (groups.has(name)) {
|
||||
throw new Error(`Tab group "${name}" already exists`)
|
||||
}
|
||||
|
||||
if (groups.size > 0) {
|
||||
await setWindowTgroup(name)
|
||||
const initialUrl = await config.get("tabgroupnewtaburls")[name]
|
||||
await tabopen(initialUrl)
|
||||
promises.push(tgroupTabs(name, true).then(tabs => browserBg.tabs.hide(tabs.map(tab => tab.id))))
|
||||
} else {
|
||||
promises.push(browser.tabs.query({currentWindow: true}).then((tabs) => {
|
||||
setTabTgroup(name, tabs.map(({ id }) => id))
|
||||
// trigger status line update
|
||||
setContentStateGroup(name)
|
||||
}))
|
||||
promises.push(setWindowTgroup(name))
|
||||
}
|
||||
|
||||
groups.add(name)
|
||||
promises.push(setTgroups(groups))
|
||||
return Promise.all(promises).then(() => name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch to a different tab group, hiding all other tabs.
|
||||
*
|
||||
* @param name The name of the tab group to switch to.
|
||||
*
|
||||
* If the tab group does not exist, act like tgroupcreate.
|
||||
*
|
||||
*/
|
||||
//#background
|
||||
export async function tgroupswitch(name: string) {
|
||||
if (name == await windowTgroup()) {
|
||||
throw new Error(`Already on tab group "${name}"`)
|
||||
}
|
||||
|
||||
const groups = await tgroups()
|
||||
if (groups.size > 0) {
|
||||
if (groups.has(name)) {
|
||||
return tgroupActivate(name).then(() => name)
|
||||
} else {
|
||||
return tgroupcreate(name).then(() => name)
|
||||
}
|
||||
} else {
|
||||
return tgroupcreate(name).then(() => name)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch to the previously active tab group.
|
||||
*/
|
||||
//#background
|
||||
export async function tgrouplast() {
|
||||
if ((await tgroups()).size < 2) {
|
||||
throw new Error("No last tab group")
|
||||
}
|
||||
|
||||
return tgroupActivateLast()
|
||||
}
|
||||
|
||||
/**
|
||||
* Rename the current tab group.
|
||||
*
|
||||
* @param name The new name of the tab group.
|
||||
*
|
||||
*/
|
||||
//#background
|
||||
export async function tgrouprename(name: string) {
|
||||
if ((await tgroups()).size == 0) {
|
||||
throw new Error("No tab groups exist")
|
||||
}
|
||||
|
||||
return tgroupClearOldInfo(await windowTgroup(), name).then(() => {
|
||||
// trigger status line update
|
||||
setContentStateGroup(name)
|
||||
return name
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the current tab group.
|
||||
*
|
||||
* First switch to the previously active tab group. Do nothing if there is only
|
||||
* one tab group.
|
||||
*
|
||||
*/
|
||||
//#background
|
||||
export async function tgroupclose() {
|
||||
const groups = await tgroups()
|
||||
if (groups.size == 0) {
|
||||
throw new Error("No tab groups exist")
|
||||
} else if (groups.size == 1) {
|
||||
throw new Error("This is the only tab group")
|
||||
} else if (groups.size > 1) {
|
||||
const closeGroup = await windowTgroup()
|
||||
const newTabGroup = await tgroupActivateLast()
|
||||
await tgroupTabs(closeGroup).then(tabs => {
|
||||
browser.tabs.remove(tabs.map(tab => tab.id))
|
||||
})
|
||||
return tgroupClearOldInfo(closeGroup).then(() => newTabGroup)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the current tab to another tab group.
|
||||
*
|
||||
* @param name The name of the tab group to move the tab to.
|
||||
*
|
||||
* If this is the last tab in the tab group, also switch to tab group, keeping
|
||||
* the current tab active.
|
||||
*
|
||||
*/
|
||||
//#background
|
||||
export async function tgroupmove(name: string) {
|
||||
const groups = await tgroups()
|
||||
const currentGroup = await windowTgroup()
|
||||
|
||||
if (groups.size == 0) {
|
||||
throw new Error("No tab groups exist")
|
||||
}
|
||||
if (!groups.has(name)) {
|
||||
throw new Error(`Tab group "${name}" does not exist`)
|
||||
}
|
||||
if (name == currentGroup) {
|
||||
throw new Error(`Tab is already on group "${name}"`)
|
||||
}
|
||||
|
||||
const tabCount = await tgroupTabs(currentGroup).then(tabs => tabs.length)
|
||||
|
||||
await setTabTgroup(name)
|
||||
const currentTabId = await activeTabId()
|
||||
|
||||
// switch to other group if this is the last tab in the current group
|
||||
if (tabCount == 1) {
|
||||
return Promise.all([
|
||||
tgroupClearOldInfo(currentGroup, name),
|
||||
tgroupTabs(name).then(tabs => {
|
||||
browserBg.tabs.show(tabs.map(tab => tab.id))
|
||||
}),
|
||||
]).then(() => name)
|
||||
} else {
|
||||
const lastTabId = await tgroupLastTabId(currentGroup)
|
||||
await tabSetActive(lastTabId)
|
||||
return browser.tabs.hide(currentTabId).then(() => currentGroup)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all tab group information for the current window and show all tabs.
|
||||
*
|
||||
*/
|
||||
//#background
|
||||
export async function tgroupabort() {
|
||||
if ((await tgroups()).size == 0) {
|
||||
throw new Error("No tab groups exist")
|
||||
}
|
||||
|
||||
return clearAllTgroupInfo().then(() => undefined)
|
||||
}
|
||||
|
||||
// }}}
|
||||
|
||||
// {{{ MISC
|
||||
|
||||
//#background
|
||||
|
|
|
@ -543,6 +543,13 @@ export class default_config {
|
|||
o: "open",
|
||||
w: "winopen",
|
||||
t: "tabopen",
|
||||
tabgroupabort: "tgroupabort",
|
||||
tabgroupclose: "tgroupclose",
|
||||
tabgroupcreate: "tgroupcreate",
|
||||
tabgrouplast: "tgrouplast",
|
||||
tabgroupmove: "tgroupmove",
|
||||
tabgrouprename: "tgrouprename",
|
||||
tabgroupswitch: "tgroupswitch",
|
||||
tabnew: "tabopen",
|
||||
tabm: "tabmove",
|
||||
tabo: "tabonly",
|
||||
|
@ -971,6 +978,16 @@ export class default_config {
|
|||
*/
|
||||
auconcreatecontainer: "true" | "false" = "true"
|
||||
|
||||
/**
|
||||
* Initial urls to navigate to when creating a new tab for a new tab group.
|
||||
*/
|
||||
tabgroupnewtaburls = {}
|
||||
|
||||
/**
|
||||
* Whether :tab shows completions for hidden tabs (e.g. tabs in other tab groups).
|
||||
*/
|
||||
tabshowhidden: "true" | "false" = "false"
|
||||
|
||||
/**
|
||||
* Number of most recent results to ask Firefox for. We display the top 20 or so most frequently visited ones.
|
||||
*/
|
||||
|
|
413
src/lib/tab_groups.ts
Normal file
413
src/lib/tab_groups.ts
Normal file
|
@ -0,0 +1,413 @@
|
|||
import {
|
||||
activeTabId,
|
||||
activeWindowId,
|
||||
browserBg,
|
||||
removeActiveWindowValue,
|
||||
} from "./webext"
|
||||
|
||||
/**
|
||||
* Return a set of the current window's tab groups (empty if there are none).
|
||||
*
|
||||
*/
|
||||
export async function tgroups() {
|
||||
const groups = await browserBg.sessions.getWindowValue(
|
||||
await activeWindowId(),
|
||||
"tridactyl-tgroups",
|
||||
)
|
||||
return new Set<string>(groups as string[])
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current window's tab groups.
|
||||
*
|
||||
* @param groups The set of groups.
|
||||
*
|
||||
*/
|
||||
export async function setTgroups(groups: Set<string>) {
|
||||
return browserBg.sessions.setWindowValue(
|
||||
await activeWindowId(),
|
||||
"tridactyl-tgroups",
|
||||
[...groups],
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the current window's tab groups.
|
||||
*
|
||||
*/
|
||||
export function clearTgroups() {
|
||||
removeActiveWindowValue("tridactyl-tgroups")
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the active tab group for the a window or undefined.
|
||||
*
|
||||
* @param id The id of the window. Use the current window if not specified.
|
||||
*
|
||||
*/
|
||||
export async function windowTgroup(id?: number) {
|
||||
if (id === undefined) {
|
||||
id = await activeWindowId()
|
||||
}
|
||||
return (browserBg.sessions.getWindowValue(
|
||||
id,
|
||||
"tridactyl-active-tgroup",
|
||||
) as unknown) as string
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the active tab group for a window.
|
||||
*
|
||||
* @param name The name of the new active tab group.
|
||||
* @param id The id of the window. Use the current window if not specified.
|
||||
*
|
||||
*/
|
||||
export async function setWindowTgroup(name: string, id?: number) {
|
||||
if (id === undefined) {
|
||||
id = await activeWindowId()
|
||||
}
|
||||
return browserBg.sessions.setWindowValue(
|
||||
id,
|
||||
"tridactyl-active-tgroup",
|
||||
name,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the active tab group for the current window.
|
||||
*
|
||||
*/
|
||||
export function clearWindowTgroup() {
|
||||
return removeActiveWindowValue("tridactyl-active-tgroup")
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a tab's tab group.
|
||||
*
|
||||
* @param id The id of the tab. Use the current tab if not specified.
|
||||
*
|
||||
*/
|
||||
export async function tabTgroup(id?: number) {
|
||||
if (id === undefined) {
|
||||
id = await activeTabId()
|
||||
}
|
||||
return (browserBg.sessions.getTabValue(
|
||||
id,
|
||||
"tridactyl-tgroup",
|
||||
) as unknown) as string
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of tab ids.
|
||||
*
|
||||
* @param id An id, a list of ids, or undefined.
|
||||
*
|
||||
* If id is undefined, return a list of the current tab id.
|
||||
*
|
||||
*/
|
||||
async function tabIdsOrCurrent(ids?: number | number[]): Promise<number[]> {
|
||||
if (!ids) {
|
||||
ids = [await activeTabId()]
|
||||
} else if (!Array.isArray(ids)) {
|
||||
ids = [ids]
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the a tab's tab group.
|
||||
*
|
||||
* @param name The name of the tab group.
|
||||
* @param id The id(s) of the tab(s). Use the current tab if not specified.
|
||||
*
|
||||
*/
|
||||
export async function setTabTgroup(name: string, id?: number | number[]) {
|
||||
const ids = await tabIdsOrCurrent(id)
|
||||
return Promise.all(
|
||||
ids.map(id => {
|
||||
browserBg.sessions.setTabValue(id, "tridactyl-tgroup", name)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all the tab groups.
|
||||
*
|
||||
* @param id The id(s) of the tab(s). Use the current tab if not specified.
|
||||
*
|
||||
*/
|
||||
export async function clearTabTgroup(id?: number | number[]) {
|
||||
const ids = await tabIdsOrCurrent(id)
|
||||
return Promise.all(
|
||||
ids.map(id => {
|
||||
browserBg.sessions.removeTabValue(id, "tridactyl-tgroup")
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of all tabs in a tab group.
|
||||
*
|
||||
* @param name The name of the tab group.
|
||||
* @param other Whether to return the tabs not in the tab group instead.
|
||||
* @param id The id of the window. Use the current window if not specified.
|
||||
*
|
||||
*/
|
||||
export async function tgroupTabs(
|
||||
name: string,
|
||||
other: boolean = false,
|
||||
id?: number,
|
||||
): Promise<browser.tabs.Tab[]> {
|
||||
if (id === undefined) {
|
||||
id = await activeWindowId()
|
||||
}
|
||||
return browserBg.tabs.query({ windowId: id }).then(async tabs => {
|
||||
const sameGroupIndices = await Promise.all(
|
||||
tabs.map(async ({ id }) => {
|
||||
const groupMatched = (await tabTgroup(id)) == name
|
||||
return other ? !groupMatched : groupMatched
|
||||
}),
|
||||
)
|
||||
tabs = tabs.filter((_, index) => sameGroupIndices[index])
|
||||
return tabs
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the id of the last selected tab in a tab group.
|
||||
*
|
||||
* @param name The name of the tab group.
|
||||
* @param previous Whether to return the tab selected before the last tab.
|
||||
*
|
||||
*/
|
||||
export async function tgroupLastTabId(name: string, previous: boolean = false) {
|
||||
const tabs = await tgroupTabs(name)
|
||||
tabs.sort((a, b) => b.lastAccessed - a.lastAccessed)
|
||||
if (previous) {
|
||||
return tabs[1].id
|
||||
} else {
|
||||
return tabs[0].id
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear stored information for a tab group.
|
||||
*
|
||||
* @param name The name of the tab group.
|
||||
* @param newName A name to rename the group to.
|
||||
* @param id The id of the window. Use the current window if not specified.
|
||||
*
|
||||
* If newName is specified, add it as a stored group (if it doesn't already
|
||||
* exist), set the current window group to it, and set the group for all tabs in
|
||||
* the old group to it.
|
||||
*
|
||||
*/
|
||||
export async function tgroupClearOldInfo(
|
||||
oldName: string,
|
||||
newName?: string,
|
||||
id?: number,
|
||||
) {
|
||||
const promises = []
|
||||
const groups = await tgroups()
|
||||
groups.delete(oldName)
|
||||
if (newName) {
|
||||
groups.add(newName)
|
||||
}
|
||||
promises.push(setTgroups(groups))
|
||||
|
||||
if (id === undefined) {
|
||||
id = await activeWindowId()
|
||||
}
|
||||
|
||||
if (newName) {
|
||||
promises.push(setWindowTgroup(newName, id))
|
||||
promises.push(
|
||||
tgroupTabs(oldName, false, id).then(tabs => {
|
||||
setTabTgroup(
|
||||
newName,
|
||||
tabs.map(tab => tab.id),
|
||||
)
|
||||
}),
|
||||
)
|
||||
}
|
||||
return Promise.all(promises)
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate the previously active tab in a tab group.
|
||||
*
|
||||
* @param name The name of the tab group to switch to.
|
||||
*
|
||||
*/
|
||||
export async function tgroupActivate(name: string) {
|
||||
const lastActiveTabId = await tgroupLastTabId(name)
|
||||
// this will trigger tgroupHandleTabActivated
|
||||
return browserBg.tabs.update(lastActiveTabId, { active: true })
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate the last active tab in the last active tab group.
|
||||
*
|
||||
* Return the name of activated tab group.
|
||||
*
|
||||
*/
|
||||
export async function tgroupActivateLast() {
|
||||
const otherGroupsTabs = await tgroupTabs(await windowTgroup(), true)
|
||||
otherGroupsTabs.sort((a, b) => b.lastAccessed - a.lastAccessed)
|
||||
const lastTabId = otherGroupsTabs[0].id
|
||||
return browserBg.tabs
|
||||
.update(lastTabId, { active: true })
|
||||
.then(() => tabTgroup(lastTabId))
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all stored tab group information.
|
||||
*
|
||||
*/
|
||||
export async function clearAllTgroupInfo() {
|
||||
return Promise.all([
|
||||
clearTgroups(),
|
||||
clearWindowTgroup(),
|
||||
browser.tabs.query({ currentWindow: true }).then(async tabs => {
|
||||
const ids = tabs.map(tab => tab.id)
|
||||
await browser.tabs.show(ids)
|
||||
return clearTabTgroup(ids)
|
||||
}),
|
||||
])
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the tab's tab group to its window's active tab group if there is one.
|
||||
*
|
||||
* Do nothing if the tab is already associated with a tab group.
|
||||
*
|
||||
* @param tab The tab that was just created.
|
||||
*
|
||||
*/
|
||||
export async function tgroupHandleTabCreated(tab: browser.tabs.Tab) {
|
||||
const windowGroup = await windowTgroup(tab.windowId)
|
||||
|
||||
if (windowGroup) {
|
||||
const tabGroup = await tabTgroup(tab.id)
|
||||
if (!tabGroup) {
|
||||
return setTabTgroup(windowGroup, tab.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the tab's tab group to its window's active tab group if there is one.
|
||||
*
|
||||
* @param tabId The id of the tab that was just attached to this window.
|
||||
*
|
||||
*/
|
||||
export async function tgroupHandleTabAttached(tabId: number, attachInfo) {
|
||||
// NOTE this doesn't need to worry about a tab on another window previously
|
||||
// being pinned because tabs become unpinned when you move them between
|
||||
// windows
|
||||
const windowGroup = await windowTgroup(attachInfo.newWindowId)
|
||||
if (windowGroup) {
|
||||
return setTabTgroup(windowGroup, tabId)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle tab activation, possibly switching tab groups.
|
||||
*
|
||||
* If the new tab is from a different tab group, set it as the new tab group,
|
||||
* show all its tabs, and hide all tabs from the old tab group.
|
||||
*/
|
||||
export async function tgroupHandleTabActivated(activeInfo) {
|
||||
const windowGroup = await windowTgroup(activeInfo.windowId)
|
||||
const tabGroup = await tabTgroup(activeInfo.tab)
|
||||
const promises = []
|
||||
if (windowGroup && tabGroup && windowGroup != tabGroup) {
|
||||
await setWindowTgroup(tabGroup, activeInfo.windowId)
|
||||
|
||||
promises.push(
|
||||
tgroupTabs(tabGroup, false, activeInfo.windowId).then(tabs => {
|
||||
return browserBg.tabs.show(tabs.map(tab => tab.id))
|
||||
}),
|
||||
)
|
||||
promises.push(
|
||||
tgroupTabs(tabGroup, true, activeInfo.windowId).then(tabs => {
|
||||
return browserBg.tabs.hide(tabs.map(tab => tab.id))
|
||||
}),
|
||||
)
|
||||
}
|
||||
return Promise.all(promises)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set or clear a tab's group if it was pinned or unpinned respectively.
|
||||
*
|
||||
* @param tabId The id of the tab that was just updated.
|
||||
*
|
||||
*/
|
||||
export async function tgroupHandleTabUpdated(
|
||||
tabId: number,
|
||||
changeInfo,
|
||||
tab: browser.tabs.Tab,
|
||||
) {
|
||||
if (changeInfo.pinned !== undefined) {
|
||||
const windowGroup = await windowTgroup(tab.windowId)
|
||||
if (windowGroup) {
|
||||
if (changeInfo.pinned) {
|
||||
return clearTabTgroup(tabId)
|
||||
} else {
|
||||
return setTabTgroup(windowGroup, tabId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the last tab in a tab group being closed.
|
||||
*
|
||||
* Clear its information.
|
||||
*
|
||||
*/
|
||||
export async function tgroupHandleTabRemoved(_tabId: number, removeInfo) {
|
||||
if (!removeInfo.isWindowClosing) {
|
||||
const windowGroup = await windowTgroup(removeInfo.windowId)
|
||||
const tabCount = await tgroupTabs(
|
||||
windowGroup,
|
||||
false,
|
||||
removeInfo.windowId,
|
||||
).then(tabs => tabs.length)
|
||||
if (tabCount == 0) {
|
||||
return tgroupClearOldInfo(
|
||||
windowGroup,
|
||||
undefined,
|
||||
removeInfo.windowId,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the last tab in a tab group being moved to another window.
|
||||
*
|
||||
* Clear its information.
|
||||
*
|
||||
*/
|
||||
export async function tgroupHandleTabDetached(tabId: number, detachInfo) {
|
||||
// clear tab's stored group; will automatically be changed if there are
|
||||
// groups on the other window but otherwise it will still show up in the
|
||||
// mode indicator
|
||||
clearTabTgroup(tabId)
|
||||
|
||||
const windowGroup = await windowTgroup(detachInfo.oldWindowId)
|
||||
const tabCount = await tgroupTabs(
|
||||
windowGroup,
|
||||
false,
|
||||
detachInfo.oldWindowId,
|
||||
).then(tabs => tabs.length)
|
||||
if (tabCount == 0) {
|
||||
return tgroupClearOldInfo(
|
||||
windowGroup,
|
||||
undefined,
|
||||
detachInfo.oldWindowId,
|
||||
)
|
||||
}
|
||||
}
|
|
@ -61,6 +61,21 @@ export async function activeTabId() {
|
|||
return (await activeTab()).id
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the active window's id.
|
||||
*
|
||||
*/
|
||||
export async function activeWindowId() {
|
||||
return (await browserBg.windows.getCurrent()).id
|
||||
}
|
||||
|
||||
export async function removeActiveWindowValue(value) {
|
||||
browserBg.sessions.removeWindowValue(
|
||||
await activeWindowId(),
|
||||
value,
|
||||
)
|
||||
}
|
||||
|
||||
export async function activeTabContainerId() {
|
||||
return (await activeTab()).cookieStoreId
|
||||
}
|
||||
|
|
|
@ -90,6 +90,7 @@
|
|||
"search",
|
||||
"sessions",
|
||||
"storage",
|
||||
"tabHide",
|
||||
"tabs",
|
||||
"topSites",
|
||||
"management",
|
||||
|
|
Loading…
Add table
Reference in a new issue