mirror of
https://github.com/zen-browser/components.git
synced 2025-07-07 21:49:59 +02:00
Merge branch 'zen-browser:main' into main
This commit is contained in:
commit
00f50176c9
6 changed files with 496 additions and 87 deletions
|
@ -79,7 +79,7 @@ var gZenBrowserManagerSidebar = {
|
|||
// relative to avoid the top margin
|
||||
// 20px is the padding
|
||||
let parentRelativeHeight =
|
||||
parent.getBoundingClientRect().height - parent.getBoundingClientRect().top + sidebar.getBoundingClientRect().top - 20;
|
||||
parent.getBoundingClientRect().height - parent.getBoundingClientRect().top + sidebar.getBoundingClientRect().top;
|
||||
let minHeight = parseInt(computedStyle.getPropertyValue('min-height').replace('px', ''));
|
||||
if (!this._isDragging) {
|
||||
// Prevent multiple resizes
|
||||
|
@ -304,7 +304,6 @@ var gZenBrowserManagerSidebar = {
|
|||
let data = this.sidebarData;
|
||||
let newPos = [];
|
||||
for (let element of this.__dragingElement.parentNode.children) {
|
||||
console.log(element);
|
||||
let panelId = element.getAttribute('zen-sidebar-id');
|
||||
newPos.push(panelId);
|
||||
}
|
||||
|
|
98
src/ZenThemesCommon.mjs
Normal file
98
src/ZenThemesCommon.mjs
Normal file
|
@ -0,0 +1,98 @@
|
|||
var ZenThemesCommon = {
|
||||
kZenOSToSmallName: {
|
||||
WINNT: 'windows',
|
||||
Darwin: 'macos',
|
||||
Linux: 'linux',
|
||||
},
|
||||
|
||||
kZenColors: ['#aac7ff', '#74d7cb', '#a0d490', '#dec663', '#ffb787', '#dec1b1', '#ffb1c0', '#ddbfc3', '#f6b0ea', '#d4bbff'],
|
||||
|
||||
get currentOperatingSystem() {
|
||||
let os = Services.appinfo.OS;
|
||||
return this.kZenOSToSmallName[os];
|
||||
},
|
||||
|
||||
get browsers() {
|
||||
return Services.wm.getEnumerator('navigator:browser');
|
||||
},
|
||||
|
||||
get currentBrowser() {
|
||||
return Services.wm.getMostRecentWindow('navigator:browser');
|
||||
},
|
||||
|
||||
get themesRootPath() {
|
||||
return PathUtils.join(PathUtils.profileDir, 'chrome', 'zen-themes');
|
||||
},
|
||||
|
||||
get themesDataFile() {
|
||||
return PathUtils.join(PathUtils.profileDir, 'zen-themes.json');
|
||||
},
|
||||
|
||||
getThemeFolder(themeId) {
|
||||
return PathUtils.join(this.themesRootPath, themeId);
|
||||
},
|
||||
|
||||
async getThemes() {
|
||||
if (!this.themes) {
|
||||
if (!(await IOUtils.exists(this.themesDataFile))) {
|
||||
await IOUtils.writeJSON(this.themesDataFile, {});
|
||||
}
|
||||
|
||||
this.themes = await IOUtils.readJSON(this.themesDataFile);
|
||||
}
|
||||
return this.themes;
|
||||
},
|
||||
|
||||
async getThemePreferences(theme) {
|
||||
const themePath = PathUtils.join(this.themesRootPath, theme.id, 'preferences.json');
|
||||
if (!(await IOUtils.exists(themePath)) || !theme.preferences) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const preferences = await IOUtils.readJSON(themePath);
|
||||
|
||||
// compat mode for old preferences, all of them can only be checkboxes
|
||||
if (typeof preferences === 'object' && !Array.isArray(preferences)) {
|
||||
console.warn(
|
||||
`[ZenThemes]: Warning, ${theme.name} uses legacy preferences, please migrate them to the new preferences style, as legacy preferences might be removed at a future release. More information at: https://docs.zen-browser.app/themes-store/themes-marketplace-preferences`
|
||||
);
|
||||
const newThemePreferences = [];
|
||||
|
||||
for (let [entry, label] of Object.entries(preferences)) {
|
||||
const [_, negation = '', os = '', property] = /(!?)(?:(macos|windows|linux):)?([A-z0-9-_.]+)/g.exec(entry);
|
||||
const isNegation = negation === '!';
|
||||
|
||||
if (
|
||||
(isNegation && os === this.currentOperatingSystem) ||
|
||||
(os !== '' && os !== this.currentOperatingSystem && !isNegation)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
newThemePreferences.push({
|
||||
property,
|
||||
label,
|
||||
type: 'checkbox',
|
||||
disabledOn: os !== '' ? [os] : [],
|
||||
});
|
||||
}
|
||||
|
||||
return newThemePreferences;
|
||||
}
|
||||
|
||||
return preferences.filter(({ disabledOn = [] }) => !disabledOn.includes(this.currentOperatingSystem));
|
||||
},
|
||||
|
||||
throttle(mainFunction, delay) {
|
||||
let timerFlag = null;
|
||||
|
||||
return (...args) => {
|
||||
if (timerFlag === null) {
|
||||
mainFunction(...args);
|
||||
timerFlag = setTimeout(() => {
|
||||
timerFlag = null;
|
||||
}, delay);
|
||||
}
|
||||
};
|
||||
},
|
||||
};
|
|
@ -8,14 +8,22 @@ const kZenStylesheetThemeHeader = `
|
|||
const kenStylesheetFooter = `
|
||||
/* End of Zen Themes */
|
||||
`;
|
||||
|
||||
var gZenStylesheetManager = {
|
||||
async writeStylesheet(path, themes) {
|
||||
let content = kZenStylesheetThemeHeader;
|
||||
|
||||
for (let theme of themes) {
|
||||
if (theme.enabled !== undefined && !theme.enabled) {
|
||||
continue;
|
||||
}
|
||||
content += this.getThemeCSS(theme);
|
||||
}
|
||||
|
||||
content += kenStylesheetFooter;
|
||||
let buffer = new TextEncoder().encode(content);
|
||||
|
||||
const buffer = new TextEncoder().encode(content);
|
||||
|
||||
await IOUtils.write(path, buffer);
|
||||
},
|
||||
|
||||
|
@ -36,8 +44,22 @@ var gZenThemeImporter = new (class {
|
|||
constructor() {
|
||||
console.info('ZenThemeImporter: Initiating Zen theme importer');
|
||||
try {
|
||||
window.SessionStore.promiseInitialized.then(() => {
|
||||
window.SessionStore.promiseInitialized.then(async () => {
|
||||
this.insertStylesheet();
|
||||
|
||||
const themesWithPreferences = await Promise.all(
|
||||
Object.values(await ZenThemesCommon.getThemes()).map(async (theme) => {
|
||||
const preferences = await ZenThemesCommon.getThemePreferences(theme);
|
||||
|
||||
return {
|
||||
name: theme.name,
|
||||
enabled: theme.enabled,
|
||||
preferences,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
this.writeToDom(themesWithPreferences);
|
||||
});
|
||||
console.info('ZenThemeImporter: Zen theme imported');
|
||||
} catch (e) {
|
||||
|
@ -57,31 +79,9 @@ var gZenThemeImporter = new (class {
|
|||
return PathUtils.join(PathUtils.profileDir, 'chrome', 'zen-themes.css');
|
||||
}
|
||||
|
||||
get themesRootPath() {
|
||||
return PathUtils.join(PathUtils.profileDir, 'chrome', 'zen-themes');
|
||||
}
|
||||
|
||||
get themesDataFile() {
|
||||
return PathUtils.join(PathUtils.profileDir, 'zen-themes.json');
|
||||
}
|
||||
|
||||
getThemeFolder(theme) {
|
||||
return PathUtils.join(this.themesRootPath, theme.id);
|
||||
}
|
||||
|
||||
async getThemes() {
|
||||
if (!this._themes) {
|
||||
if (!(await IOUtils.exists(this.themesDataFile))) {
|
||||
await IOUtils.writeJSON(this.themesDataFile, {});
|
||||
}
|
||||
this._themes = await IOUtils.readJSON(this.themesDataFile);
|
||||
}
|
||||
return this._themes;
|
||||
}
|
||||
|
||||
rebuildThemeStylesheet() {
|
||||
this._themes = null;
|
||||
this.updateStylesheet();
|
||||
async rebuildThemeStylesheet() {
|
||||
ZenThemesCommon.themes = null;
|
||||
await this.updateStylesheet();
|
||||
}
|
||||
|
||||
get styleSheetURI() {
|
||||
|
@ -92,32 +92,155 @@ var gZenThemeImporter = new (class {
|
|||
}
|
||||
|
||||
getStylesheetURIForTheme(theme) {
|
||||
return Services.io.newFileURI(new FileUtils.File(PathUtils.join(this.getThemeFolder(theme), 'chrome.css')));
|
||||
return Services.io.newFileURI(new FileUtils.File(PathUtils.join(ZenThemesCommon.getThemeFolder(theme.id), 'chrome.css')));
|
||||
}
|
||||
|
||||
insertStylesheet() {
|
||||
if (IOUtils.exists(this.styleSheetPath)) {
|
||||
this.sss.loadAndRegisterSheet(this.styleSheetURI, this.sss.AGENT_SHEET);
|
||||
async insertStylesheet() {
|
||||
if (await IOUtils.exists(this.styleSheetPath)) {
|
||||
await this.sss.loadAndRegisterSheet(this.styleSheetURI, this.sss.AGENT_SHEET);
|
||||
}
|
||||
}
|
||||
|
||||
removeStylesheet() {
|
||||
this.sss.unregisterSheet(this.styleSheetURI, this.sss.AGENT_SHEET);
|
||||
async removeStylesheet() {
|
||||
await this.sss.unregisterSheet(this.styleSheetURI, this.sss.AGENT_SHEET);
|
||||
}
|
||||
|
||||
async updateStylesheet() {
|
||||
this.removeStylesheet();
|
||||
await this.writeStylesheet();
|
||||
this.insertStylesheet();
|
||||
await this.removeStylesheet();
|
||||
|
||||
const themes = Object.values(await ZenThemesCommon.getThemes());
|
||||
await this.writeStylesheet(themes);
|
||||
|
||||
const themesWithPreferences = await Promise.all(
|
||||
themes.map(async (theme) => {
|
||||
const preferences = await ZenThemesCommon.getThemePreferences(theme);
|
||||
|
||||
return {
|
||||
name: theme.name,
|
||||
enabled: theme.enabled,
|
||||
preferences,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
this.setDefaults(themesWithPreferences);
|
||||
this.writeToDom(themesWithPreferences);
|
||||
|
||||
await this.insertStylesheet();
|
||||
}
|
||||
|
||||
async writeStylesheet() {
|
||||
setDefaults(themesWithPreferences) {
|
||||
for (const { preferences, enabled } of themesWithPreferences) {
|
||||
if (enabled !== undefined && !enabled) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const { type, property, defaultValue } of preferences) {
|
||||
if (defaultValue === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case 'checkbox': {
|
||||
const value = Services.prefs.getBoolPref(property, false);
|
||||
if (typeof defaultValue !== 'boolean') {
|
||||
console.log(`[ZenThemesImporter]: Warning, invalid data type received for expected type boolean, skipping.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
Services.prefs.setBoolPref(property, defaultValue);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
const value = Services.prefs.getStringPref(property, 'zen-property-no-saved');
|
||||
|
||||
if (typeof defaultValue !== 'string' && typeof defaultValue !== 'number') {
|
||||
console.log(`[ZenThemesImporter]: Warning, invalid data type received (${typeof defaultValue}), skipping.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (value === 'zen-property-no-saved') {
|
||||
Services.prefs.setStringPref(property, defaultValue.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
writeToDom(themesWithPreferences) {
|
||||
const browser = ZenThemesCommon.currentBrowser;
|
||||
|
||||
for (const { enabled, preferences, name } of themesWithPreferences) {
|
||||
const sanitizedName = `theme-${name?.replaceAll(/\s/g, '-')?.replaceAll(/[^A-z_-]+/g, '')}`;
|
||||
|
||||
if (enabled !== undefined && !enabled) {
|
||||
const element = browser.document.getElementById(sanitizedName);
|
||||
|
||||
if (element) {
|
||||
element.remove();
|
||||
}
|
||||
|
||||
for (const { property } of preferences.filter(({ type }) => type !== 'checkbox')) {
|
||||
const sanitizedProperty = property?.replaceAll(/\./g, '-');
|
||||
|
||||
browser.document.querySelector(':root').style.removeProperty(`--${sanitizedProperty}`);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const { property, type } of preferences) {
|
||||
const value = Services.prefs.getStringPref(property, '');
|
||||
const sanitizedProperty = property?.replaceAll(/\./g, '-');
|
||||
|
||||
switch (type) {
|
||||
case 'dropdown': {
|
||||
if (value !== '') {
|
||||
let element = browser.document.getElementById(sanitizedName);
|
||||
|
||||
if (!element) {
|
||||
element = browser.document.createElement('div');
|
||||
|
||||
element.style.display = 'none';
|
||||
element.setAttribute('id', sanitizedName);
|
||||
|
||||
browser.document.body.appendChild(element);
|
||||
}
|
||||
|
||||
element.setAttribute(sanitizedProperty, value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'string': {
|
||||
if (value === '') {
|
||||
browser.document.querySelector(':root').style.removeProperty(`--${sanitizedProperty}`);
|
||||
} else {
|
||||
browser.document.querySelector(':root').style.setProperty(`--${sanitizedProperty}`, value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async writeStylesheet(themeList) {
|
||||
const themes = [];
|
||||
this._themes = null;
|
||||
for (let theme of Object.values(await this.getThemes())) {
|
||||
ZenThemesCommon.themes = null;
|
||||
|
||||
for (let theme of themeList) {
|
||||
theme._chromeURL = this.getStylesheetURIForTheme(theme).spec;
|
||||
themes.push(theme);
|
||||
}
|
||||
|
||||
await gZenStylesheetManager.writeStylesheet(this.styleSheetPath, themes);
|
||||
}
|
||||
})();
|
||||
|
|
|
@ -536,8 +536,9 @@ var gZenViewSplitter = new (class {
|
|||
if (this.currentView < 0) return;
|
||||
const currentTab = window.gBrowser.selectedTab;
|
||||
const tabs = this._data[this.currentView].tabs;
|
||||
for (const tab of tabs) {
|
||||
this.handleTabClose({ target: tab, forUnsplit: true });
|
||||
// note: This MUST be an index loop, as we are removing tabs from the array
|
||||
for (let i = tabs.length - 1; i >= 0; i--) {
|
||||
this.handleTabClose({ target: tabs[i], forUnsplit: true });
|
||||
}
|
||||
window.gBrowser.selectedTab = currentTab;
|
||||
this.updateSplitViewButton(true);
|
||||
|
@ -562,7 +563,7 @@ var gZenViewSplitter = new (class {
|
|||
this.unsplitCurrentView();
|
||||
return;
|
||||
}
|
||||
const tabs = gBrowser.tabs;
|
||||
const tabs = gBrowser.visibleTabs;
|
||||
if (tabs.length < 2) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
|
||||
var ZenWorkspaces = {
|
||||
async init() {
|
||||
let docElement = document.documentElement;
|
||||
if (
|
||||
docElement.getAttribute('chromehidden').includes('toolbar') ||
|
||||
docElement.getAttribute('chromehidden').includes('menubar') ||
|
||||
docElement.hasAttribute('privatebrowsingmode')
|
||||
) {
|
||||
/**
|
||||
* Stores workspace IDs and their last selected tabs.
|
||||
*/
|
||||
_lastSelectedWorkspaceTabs: {},
|
||||
|
||||
init() {
|
||||
if (!this.shouldHaveWorkspaces) {
|
||||
console.warn('ZenWorkspaces: !!! ZenWorkspaces is disabled in hidden windows !!!');
|
||||
return; // We are in a hidden window, don't initialize ZenWorkspaces
|
||||
}
|
||||
|
@ -16,8 +17,29 @@ var ZenWorkspaces = {
|
|||
});
|
||||
},
|
||||
|
||||
get shouldShowIconStrip() {
|
||||
delete this.shouldShowIconStrip;
|
||||
this.shouldShowIconStrip = Services.prefs.getBoolPref('zen.workspaces.show-icon-strip', true);
|
||||
return this.shouldShowIconStrip;
|
||||
},
|
||||
|
||||
get shouldHaveWorkspaces() {
|
||||
delete this.shouldHaveWorkspaces;
|
||||
let docElement = document.documentElement;
|
||||
this.shouldHaveWorkspaces = !(docElement.hasAttribute('privatebrowsingmode')
|
||||
|| docElement.getAttribute('chromehidden').includes('toolbar')
|
||||
|| docElement.getAttribute('chromehidden').includes('menubar'));
|
||||
return this.shouldHaveWorkspaces;
|
||||
},
|
||||
|
||||
get workspaceEnabled() {
|
||||
return Services.prefs.getBoolPref('zen.workspaces.enabled', false);
|
||||
delete this.workspaceEnabled;
|
||||
this.workspaceEnabled = Services.prefs.getBoolPref('zen.workspaces.enabled', false) && this.shouldHaveWorkspaces;
|
||||
return this.workspaceEnabled;
|
||||
},
|
||||
|
||||
getActiveWorkspaceFromCache() {
|
||||
return this._workspaceCache.workspaces.find((workspace) => workspace.used);
|
||||
},
|
||||
|
||||
// Wrorkspaces saving/loading
|
||||
|
@ -47,15 +69,25 @@ var ZenWorkspaces = {
|
|||
}
|
||||
},
|
||||
|
||||
async onWorkspacesIconStripChanged() {
|
||||
this.shouldShowIconStrip = Services.prefs.getBoolPref('zen.workspaces.show-icon-strip', true);
|
||||
await this._expandWorkspacesStrip();
|
||||
},
|
||||
|
||||
async initializeWorkspaces() {
|
||||
Services.prefs.addObserver('zen.workspaces.enabled', this.onWorkspacesEnabledChanged.bind(this));
|
||||
this.initializeWorkspacesButton();
|
||||
Services.prefs.addObserver('zen.workspaces.show-icon-strip', this.onWorkspacesIconStripChanged.bind(this));
|
||||
let file = new FileUtils.File(this._storeFile);
|
||||
if (!file.exists()) {
|
||||
await IOUtils.writeJSON(this._storeFile, {});
|
||||
}
|
||||
await this.initializeWorkspacesButton();
|
||||
if (this.workspaceEnabled) {
|
||||
this._initializeWorkspaceCreationIcons();
|
||||
this._initializeWorkspaceEditIcons();
|
||||
this._initializeWorkspaceTabContextMenus();
|
||||
window.addEventListener('TabClose', this.handleTabClose.bind(this));
|
||||
window.addEventListener('TabBrowserInserted', this.onTabBrowserInserted.bind(this));
|
||||
let workspaces = await this._workspaces();
|
||||
if (workspaces.workspaces.length === 0) {
|
||||
await this.createAndSaveWorkspace('Default Workspace', true);
|
||||
|
@ -73,9 +105,6 @@ var ZenWorkspaces = {
|
|||
}
|
||||
this.changeWorkspace(activeWorkspace, true);
|
||||
}
|
||||
this._initializeWorkspaceCreationIcons();
|
||||
this._initializeWorkspaceEditIcons();
|
||||
this._initializeWorkspaceTabContextMenus();
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -91,12 +120,12 @@ var ZenWorkspaces = {
|
|||
if (tabs.length === 1) {
|
||||
this._createNewTabForWorkspace({ uuid: workspaceID });
|
||||
// We still need to close other tabs in the workspace
|
||||
this.changeWorkspace({ uuid: workspaceID });
|
||||
this.changeWorkspace({ uuid: workspaceID }, true);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_kIcons: ['🏠', '📄', '💹', '💼', '📧', '✅', '👥'],
|
||||
_kIcons: JSON.parse(Services.prefs.getStringPref("zen.workspaces.icons")).map((icon) => icon),
|
||||
|
||||
_initializeWorkspaceCreationIcons() {
|
||||
let container = document.getElementById('PanelUI-zen-workspaces-create-icons-container');
|
||||
|
@ -160,6 +189,7 @@ var ZenWorkspaces = {
|
|||
console.info('ZenWorkspaces: Removing workspace', windowID);
|
||||
await this.changeWorkspace(json.workspaces.find((workspace) => workspace.uuid !== windowID));
|
||||
this._deleteAllTabsInWorkspace(windowID);
|
||||
delete this._lastSelectedWorkspaceTabs[windowID];
|
||||
json.workspaces = json.workspaces.filter((workspace) => workspace.uuid !== windowID);
|
||||
await this.unsafeSaveWorkspaces(json);
|
||||
await this._propagateWorkspaceData();
|
||||
|
@ -219,20 +249,41 @@ var ZenWorkspaces = {
|
|||
return workspace.name[0].toUpperCase();
|
||||
},
|
||||
|
||||
async _propagateWorkspaceData() {
|
||||
async _propagateWorkspaceData({
|
||||
ignoreStrip = false
|
||||
} = {}) {
|
||||
let currentContainer = document.getElementById('PanelUI-zen-workspaces-current-info');
|
||||
let workspaceList = document.getElementById('PanelUI-zen-workspaces-list');
|
||||
if (!ignoreStrip) {
|
||||
await this._expandWorkspacesStrip();
|
||||
}
|
||||
const createWorkspaceElement = (workspace) => {
|
||||
let element = document.createXULElement('toolbarbutton');
|
||||
element.className = 'subviewbutton';
|
||||
element.setAttribute('tooltiptext', workspace.name);
|
||||
element.setAttribute('zen-workspace-id', workspace.uuid);
|
||||
//element.setAttribute("context", "zenWorkspaceActionsMenu");
|
||||
if (workspace.used) {
|
||||
element.setAttribute('active', 'true');
|
||||
}
|
||||
if (workspace.default) {
|
||||
element.setAttribute('default', 'true');
|
||||
}
|
||||
const containerGroup = ContextualIdentityService.getPublicIdentities().find(
|
||||
(container) => container.userContextId === workspace.containerTabId
|
||||
);
|
||||
if (containerGroup) {
|
||||
element.classList.add('identity-color-' + containerGroup.color);
|
||||
element.setAttribute('data-usercontextid', containerGroup.userContextId);
|
||||
}
|
||||
let childs = window.MozXULElement.parseXULToFragment(`
|
||||
<div class="zen-workspace-icon">
|
||||
</div>
|
||||
<div class="zen-workspace-name">
|
||||
</div>
|
||||
<vbox>
|
||||
<div class="zen-workspace-name">
|
||||
</div>
|
||||
<div class="zen-workspace-container" ${containerGroup ? '' : 'hidden="true"'}>
|
||||
</div>
|
||||
</vbox>
|
||||
<toolbarbutton closemenu="none" class="toolbarbutton-1 zen-workspace-actions">
|
||||
<image class="toolbarbutton-icon" id="zen-workspace-actions-menu-icon"></image>
|
||||
</toolbarbutton>
|
||||
|
@ -241,6 +292,11 @@ var ZenWorkspaces = {
|
|||
// use text content instead of innerHTML to avoid XSS
|
||||
childs.querySelector('.zen-workspace-icon').textContent = this.getWorkspaceIcon(workspace);
|
||||
childs.querySelector('.zen-workspace-name').textContent = workspace.name;
|
||||
if (containerGroup) {
|
||||
childs.querySelector('.zen-workspace-container').textContent = ContextualIdentityService.getUserContextLabel(
|
||||
containerGroup.userContextId
|
||||
);
|
||||
}
|
||||
|
||||
childs.querySelector('.zen-workspace-actions').addEventListener('command', (event) => {
|
||||
let button = event.target;
|
||||
|
@ -290,14 +346,16 @@ var ZenWorkspaces = {
|
|||
}
|
||||
let target = event.target;
|
||||
let panel = document.getElementById('PanelUI-zen-workspaces');
|
||||
await this._propagateWorkspaceData();
|
||||
await this._propagateWorkspaceData({
|
||||
ignoreStrip: true
|
||||
});
|
||||
PanelMultiView.openPopup(panel, target, {
|
||||
position: 'bottomright topright',
|
||||
triggerEvent: event,
|
||||
}).catch(console.error);
|
||||
},
|
||||
|
||||
initializeWorkspacesButton() {
|
||||
async initializeWorkspacesButton() {
|
||||
if (!this.workspaceEnabled) {
|
||||
return;
|
||||
} else if (document.getElementById('zen-workspaces-button')) {
|
||||
|
@ -305,14 +363,60 @@ var ZenWorkspaces = {
|
|||
button.removeAttribute('hidden');
|
||||
return;
|
||||
}
|
||||
let browserTabs = document.getElementById('tabbrowser-tabs');
|
||||
let button = document.createElement('toolbarbutton');
|
||||
button.id = 'zen-workspaces-button';
|
||||
button.className = 'toolbarbutton-1 chromeclass-toolbar-additional';
|
||||
button.setAttribute('label', 'Workspaces');
|
||||
button.setAttribute('tooltiptext', 'Workspaces');
|
||||
button.onclick = this.openWorkspacesDialog.bind(this);
|
||||
browserTabs.insertAdjacentElement('beforebegin', button);
|
||||
const nextSibling = document.getElementById('zen-sidepanel-button');
|
||||
const wrapper = document.createXULElement('toolbarbutton');
|
||||
wrapper.id = 'zen-workspaces-button';
|
||||
nextSibling.after(wrapper);
|
||||
await this._expandWorkspacesStrip();
|
||||
},
|
||||
|
||||
async _expandWorkspacesStrip() {
|
||||
let workspaces = await this._workspaces();
|
||||
let workspaceList = document.getElementById('zen-workspaces-button');
|
||||
const newWorkspacesButton = document.createXULElement(this.shouldShowIconStrip ? 'hbox' : 'toolbarbutton');
|
||||
newWorkspacesButton.id = 'zen-workspaces-button';
|
||||
newWorkspacesButton.setAttribute('removable', 'false');
|
||||
newWorkspacesButton.setAttribute('showInPrivateBrowsing', 'false');
|
||||
newWorkspacesButton.setAttribute('tooltiptext', 'Workspaces');
|
||||
|
||||
if (this.shouldShowIconStrip) {
|
||||
for (let workspace of workspaces.workspaces) {
|
||||
let button = document.createXULElement('toolbarbutton');
|
||||
button.className = 'subviewbutton';
|
||||
button.setAttribute('tooltiptext', workspace.name);
|
||||
button.setAttribute('zen-workspace-id', workspace.uuid);
|
||||
if (workspace.used) {
|
||||
button.setAttribute('active', 'true');
|
||||
}
|
||||
if (workspace.default) {
|
||||
button.setAttribute('default', 'true');
|
||||
}
|
||||
button.onclick = (async (_, event) => {
|
||||
// Make sure it's not a context menu event
|
||||
if (event.button !== 0) {
|
||||
return;
|
||||
}
|
||||
await this.changeWorkspace(workspace);
|
||||
}).bind(this, workspace);
|
||||
let icon = document.createXULElement('div');
|
||||
icon.className = 'zen-workspace-icon';
|
||||
icon.textContent = this.getWorkspaceIcon(workspace);
|
||||
button.appendChild(icon);
|
||||
newWorkspacesButton.appendChild(button);
|
||||
}
|
||||
// Listen for context menu events and open the all workspaces dialog
|
||||
newWorkspacesButton.addEventListener('contextmenu', (event) => {
|
||||
event.preventDefault();
|
||||
this.openWorkspacesDialog(event);
|
||||
});
|
||||
}
|
||||
|
||||
workspaceList.after(newWorkspacesButton);
|
||||
workspaceList.remove();
|
||||
|
||||
if (!this.shouldShowIconStrip) {
|
||||
await this._updateWorkspacesButton();
|
||||
}
|
||||
},
|
||||
|
||||
async _updateWorkspacesButton() {
|
||||
|
@ -322,20 +426,33 @@ var ZenWorkspaces = {
|
|||
}
|
||||
let activeWorkspace = (await this._workspaces()).workspaces.find((workspace) => workspace.used);
|
||||
if (activeWorkspace) {
|
||||
button.innerHTML = `
|
||||
<div class="zen-workspace-sidebar-icon">
|
||||
</div>
|
||||
<div class="zen-workspace-sidebar-name">
|
||||
</div>
|
||||
`;
|
||||
button.setAttribute('as-button', 'true');
|
||||
button.classList.add('toolbarbutton-1', 'zen-sidebar-action-button');
|
||||
|
||||
button.addEventListener('click', this.openWorkspacesDialog.bind(this));
|
||||
|
||||
const wrapper = document.createXULElement('hbox');
|
||||
wrapper.className = 'zen-workspace-sidebar-wrapper';
|
||||
|
||||
const icon = document.createElement('div');
|
||||
icon.className = 'zen-workspace-sidebar-icon';
|
||||
icon.textContent = this.getWorkspaceIcon(activeWorkspace);
|
||||
|
||||
|
||||
// use text content instead of innerHTML to avoid XSS
|
||||
button.querySelector('.zen-workspace-sidebar-name').textContent = activeWorkspace.name;
|
||||
button.querySelector('.zen-workspace-sidebar-icon').textContent = this.getWorkspaceIcon(activeWorkspace);
|
||||
const name = document.createElement('div');
|
||||
name.className = 'zen-workspace-sidebar-name';
|
||||
name.textContent = activeWorkspace.name;
|
||||
|
||||
if (!this.workspaceHasIcon(activeWorkspace)) {
|
||||
button.querySelector('.zen-workspace-sidebar-icon').setAttribute('no-icon', 'true');
|
||||
icon.setAttribute('no-icon', 'true');
|
||||
}
|
||||
|
||||
wrapper.appendChild(icon);
|
||||
wrapper.appendChild(name);
|
||||
|
||||
button.innerHTML = '';
|
||||
button.appendChild(wrapper);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -442,24 +559,39 @@ var ZenWorkspaces = {
|
|||
button.removeAttribute('disabled');
|
||||
},
|
||||
|
||||
get _shouldAllowPinTab() {
|
||||
return Services.prefs.getBoolPref('zen.workspaces.individual-pinned-tabs');
|
||||
},
|
||||
|
||||
get tabContainer() {
|
||||
delete this.tabContainer;
|
||||
return (this.tabContainer = document.getElementById("tabbrowser-tabs"));
|
||||
},
|
||||
|
||||
async changeWorkspace(window, onInit = false) {
|
||||
if (!this.workspaceEnabled) {
|
||||
return;
|
||||
}
|
||||
this.tabContainer._invalidateCachedTabs();
|
||||
const shouldAllowPinnedTabs = this._shouldAllowPinTab;
|
||||
let firstTab = undefined;
|
||||
let workspaces = await this._workspaces();
|
||||
for (let workspace of workspaces.workspaces) {
|
||||
if (workspace.uuid === window.uuid && workspace.used) {
|
||||
// If the workspace is already active, do nothing
|
||||
return;
|
||||
}
|
||||
workspace.used = workspace.uuid === window.uuid;
|
||||
}
|
||||
this.unsafeSaveWorkspaces(workspaces);
|
||||
await this.unsafeSaveWorkspaces(workspaces);
|
||||
console.info('ZenWorkspaces: Changing workspace to', window.uuid);
|
||||
for (let tab of gBrowser.tabs) {
|
||||
if ((tab.getAttribute('zen-workspace-id') === window.uuid && !tab.pinned) || !tab.hasAttribute('zen-workspace-id')) {
|
||||
if ((tab.getAttribute('zen-workspace-id') === window.uuid && !(tab.pinned && !shouldAllowPinnedTabs)) || !tab.hasAttribute('zen-workspace-id')) {
|
||||
if (!firstTab) {
|
||||
firstTab = tab;
|
||||
} else if (gBrowser.selectedTab === tab) {
|
||||
// If the selected tab is already in the workspace, we don't want to change it
|
||||
firstTab = undefined;
|
||||
firstTab = null; // note: Do not add "undefined" here, a new tab would be created
|
||||
}
|
||||
gBrowser.showTab(tab);
|
||||
if (!tab.hasAttribute('zen-workspace-id')) {
|
||||
|
@ -470,21 +602,26 @@ var ZenWorkspaces = {
|
|||
}
|
||||
}
|
||||
if (firstTab) {
|
||||
gBrowser.selectedTab = firstTab;
|
||||
gBrowser.selectedTab = this._lastSelectedWorkspaceTabs[window.uuid] ?? firstTab;
|
||||
}
|
||||
if (typeof firstTab === 'undefined' && !onInit) {
|
||||
this._createNewTabForWorkspace(window);
|
||||
}
|
||||
for (let tab of gBrowser.tabs) {
|
||||
if (tab.getAttribute('zen-workspace-id') !== window.uuid) {
|
||||
gBrowser.hideTab(tab);
|
||||
// FOR UNLOADING TABS:
|
||||
// gBrowser.discardBrowser(tab, true);
|
||||
gBrowser.hideTab(tab, undefined, shouldAllowPinnedTabs);
|
||||
}
|
||||
}
|
||||
this.tabContainer._invalidateCachedTabs();
|
||||
document.documentElement.setAttribute('zen-workspace-id', window.uuid);
|
||||
await this.saveWorkspaces();
|
||||
await this._updateWorkspacesButton();
|
||||
await this._propagateWorkspaceData();
|
||||
await this._updateWorkspacesChangeContextMenu();
|
||||
|
||||
document.getElementById('tabbrowser-tabs')._positionPinnedTabs();
|
||||
},
|
||||
|
||||
async _updateWorkspacesChangeContextMenu() {
|
||||
|
@ -530,6 +667,19 @@ var ZenWorkspaces = {
|
|||
await this.changeWorkspace(workspaceData);
|
||||
},
|
||||
|
||||
async onTabBrowserInserted(event) {
|
||||
let tab = event.originalTarget;
|
||||
if (tab.getAttribute('zen-workspace-id') || !this.workspaceEnabled) {
|
||||
return;
|
||||
}
|
||||
let workspaces = await this._workspaces();
|
||||
let activeWorkspace = workspaces.workspaces.find((workspace) => workspace.used);
|
||||
if (!activeWorkspace) {
|
||||
return;
|
||||
}
|
||||
tab.setAttribute('zen-workspace-id', activeWorkspace.uuid);
|
||||
},
|
||||
|
||||
async onLocationChange(browser) {
|
||||
let tab = gBrowser.getTabForBrowser(browser);
|
||||
let workspaceID = tab.getAttribute('zen-workspace-id');
|
||||
|
@ -540,7 +690,9 @@ var ZenWorkspaces = {
|
|||
return;
|
||||
}
|
||||
tab.setAttribute('zen-workspace-id', activeWorkspace.uuid);
|
||||
workspaceID = activeWorkspace.uuid;
|
||||
}
|
||||
this._lastSelectedWorkspaceTabs[workspaceID] = tab;
|
||||
},
|
||||
|
||||
// Context menu management
|
||||
|
@ -575,6 +727,15 @@ var ZenWorkspaces = {
|
|||
}
|
||||
},
|
||||
|
||||
async contextChangeContainerTab(event) {
|
||||
let workspaces = await this._workspaces();
|
||||
let workspace = workspaces.workspaces.find((workspace) => workspace.uuid === this._contextMenuId);
|
||||
let userContextId = parseInt(event.target.getAttribute('data-usercontextid'));
|
||||
workspace.containerTabId = userContextId;
|
||||
await this.saveWorkspace(workspace);
|
||||
await this._propagateWorkspaceData();
|
||||
},
|
||||
|
||||
onContextMenuClose() {
|
||||
let target = document.querySelector(
|
||||
`#PanelUI-zen-workspaces [zen-workspace-id="${this._contextMenuId}"] .zen-workspace-actions`
|
||||
|
@ -637,12 +798,37 @@ var ZenWorkspaces = {
|
|||
|
||||
async changeTabWorkspace(workspaceID) {
|
||||
const tabs = TabContextMenu.contextTab.multiselected ? gBrowser.selectedTabs : [TabContextMenu.contextTab];
|
||||
const previousWorkspaceID = document.documentElement.getAttribute('zen-workspace-id');
|
||||
for (let tab of tabs) {
|
||||
tab.setAttribute('zen-workspace-id', workspaceID);
|
||||
if (this._lastSelectedWorkspaceTabs[previousWorkspaceID] === tab) {
|
||||
// This tab is no longer the last selected tab in the previous workspace because it's being moved to
|
||||
// the current workspace
|
||||
delete this._lastSelectedWorkspaceTabs[previousWorkspaceID];
|
||||
}
|
||||
}
|
||||
const workspaces = await this._workspaces();
|
||||
await this.changeWorkspace(workspaces.workspaces.find((workspace) => workspace.uuid === workspaceID));
|
||||
},
|
||||
|
||||
// Tab browser utilities
|
||||
createContainerTabMenu(event) {
|
||||
let window = event.target.ownerGlobal;
|
||||
const workspace = this._workspaceCache.workspaces.find((workspace) => this._contextMenuId === workspace.uuid);
|
||||
let containerTabId = workspace.containerTabId;
|
||||
return window.createUserContextMenu(event, {
|
||||
isContextMenu: true,
|
||||
excludeUserContextId: containerTabId,
|
||||
showDefaultTab: true,
|
||||
});
|
||||
},
|
||||
|
||||
getContextIdIfNeeded(userContextId) {
|
||||
if (typeof userContextId !== 'undefined' || !this.workspaceEnabled) {
|
||||
return [userContextId, false];
|
||||
}
|
||||
const activeWorkspace = this.getActiveWorkspaceFromCache();
|
||||
return [activeWorkspace?.containerTabId, true];
|
||||
},
|
||||
};
|
||||
|
||||
ZenWorkspaces.init();
|
||||
|
|
|
@ -8,6 +8,7 @@ export class ZenThemeMarketplaceParent extends JSWindowActorParent {
|
|||
case 'ZenThemeMarketplace:InstallTheme': {
|
||||
console.info('ZenThemeMarketplaceParent: Updating themes');
|
||||
const theme = message.data.theme;
|
||||
theme.enabled = true;
|
||||
const themes = await this.getThemes();
|
||||
themes[theme.id] = theme;
|
||||
this.updateThemes(themes);
|
||||
|
@ -73,6 +74,7 @@ export class ZenThemeMarketplaceParent extends JSWindowActorParent {
|
|||
}
|
||||
if (!this.compareversion(themeInfo.version, theme.version || '0.0.0') && themeInfo.version != theme.version) {
|
||||
console.info('ZenThemeMarketplaceParent: Theme update found', theme.id, theme.version, themeInfo.version);
|
||||
themeInfo.enabled = theme.enabled;
|
||||
updates.push(themeInfo);
|
||||
await this.removeTheme(theme.id, false);
|
||||
this._themes[themeInfo.id] = themeInfo;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue