refactor: Remove unused type declarations and package.json dependencies

This commit is contained in:
Mauro Balades 2024-08-06 20:05:53 +02:00
parent de5090fe9c
commit 828ecbbf10
10 changed files with 1326 additions and 2546 deletions

5
@types/common.d.ts vendored
View file

@ -1,5 +0,0 @@
export interface Config {
debug: boolean;
browserName: string;
};

525
@types/gecko.d.ts vendored
View file

@ -1,525 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// https://github.com/Floorp-Projects/Floorp-core/blob/95ce9eb44c06a682e9d6379eaa4a5d1bbd5db6cd/%40types/gecko.d.ts#L198
/**
* Namespace anything that has its types mocked out here. These definitions are
* only "good enough" to get the type checking to pass in this directory.
* Eventually some more structured solution should be found. This namespace is
* global and makes sure that all the definitions inside do not clash with
* naming.
*/
declare namespace MockedExports {
/**
* This interface teaches ChromeUtils.import how to find modules.
*/
interface KnownModules {
"resource://gre/modules/FileUtils.sys.mjs": FileUtils;
"resource://gre/modules/FileUtils.jsm": FileUtils;
Services: Services;
"resource://gre/modules/Services.jsm": Services;
"resource://gre/modules/AppConstants.sys.mjs": typeof AppConstantsSYSMJS;
"resource:///modules/CustomizableUI.sys.mjs": typeof CustomizableUISYSMJS;
"resource:///modules/CustomizableWidgets.sys.mjs": typeof CustomizableWidgetsSYSMJS;
"resource://devtools/shared/loader/Loader.sys.mjs": typeof LoaderESM;
//"resource://devtools/client/performance-new/shared/background.jsm.js": typeof import("resource://devtools/client/performance-new/shared/background.jsm.js");
//"resource://devtools/client/performance-new/shared/symbolication.jsm.js": typeof import("resource://devtools/client/performance-new/shared/symbolication.jsm.js");
"resource://devtools/shared/loader/browser-loader.js": any;
//"resource://devtools/client/performance-new/popup/menu-button.jsm.js": typeof import("resource://devtools/client/performance-new/popup/menu-button.jsm.js");
//"resource://devtools/client/performance-new/shared/typescript-lazy-load.jsm.js": typeof import("resource://devtools/client/performance-new/shared/typescript-lazy-load.jsm.js");
//"resource://devtools/client/performance-new/popup/logic.jsm.js": typeof import("resource://devtools/client/performance-new/popup/logic.jsm.js");
"resource:///modules/PanelMultiView.sys.mjs": typeof PanelMultiViewSYSMJS;
}
type Services = {
Services: import("firefox").Services;
};
type FileUtils = {
FileUtils: IFileUtils;
};
interface IFileUtils {
MODE_RDONLY: number;
MODE_WRONLY: number;
MODE_RDWR: number;
MODE_CREATE: number;
MODE_APPEND: number;
MODE_TRUNCATE: number;
PERMS_FILE: number;
PERMS_DIRECTORY: number;
/**
* Gets a file at the specified hierarchy under a nsIDirectoryService key.
* @param key
* The Directory Service Key to start from
* @param pathArray
* An array of path components to locate beneath the directory
* specified by |key|. The last item in this array must be the
* leaf name of a file.
* @return nsIFile object for the file specified. The file is NOT created
* if it does not exist, however all required directories along
* the way are if pathArray has more than one item.
*/
getFile: (key: any, pathArray: any) => any;
/**
* Gets a directory at the specified hierarchy under a nsIDirectoryService
* key.
* @param key
* The Directory Service Key to start from
* @param pathArray
* An array of path components to locate beneath the directory
* specified by |key|
* @param shouldCreate
* true if the directory hierarchy specified in |pathArray|
* should be created if it does not exist, false otherwise.
* @return nsIFile object for the location specified.
*/
getDir: (key, pathArray, shouldCreate) => any;
/**
* Opens a file output stream for writing.
* @param file
* The file to write to.
* @param modeFlags
* (optional) File open flags. Can be undefined.
* @returns nsIFileOutputStream to write to.
* @note The stream is initialized with the DEFER_OPEN behavior flag.
* See nsIFileOutputStream.
*/
openFileOutputStream: (file, modeFlags) => any;
/**
* Opens an atomic file output stream for writing.
* @param file
* The file to write to.
* @param modeFlags
* (optional) File open flags. Can be undefined.
* @returns nsIFileOutputStream to write to.
* @note The stream is initialized with the DEFER_OPEN behavior flag.
* See nsIFileOutputStream.
* OpeanAtomicFileOutputStream is generally better than openSafeFileOutputStream
* baecause flushing is not needed in most of the issues.
*/
openAtomicFileOutputStream: (file, modeFlags) => any;
/**
* Opens a safe file output stream for writing.
* @param file
* The file to write to.
* @param modeFlags
* (optional) File open flags. Can be undefined.
* @returns nsIFileOutputStream to write to.
* @note The stream is initialized with the DEFER_OPEN behavior flag.
* See nsIFileOutputStream.
*/
openSafeFileOutputStream: (file, modeFlags) => any;
_initFileOutputStream: (fos, file, modeFlags) => any;
/**
* Closes an atomic file output stream.
* @param stream
* The stream to close.
*/
closeAtomicFileOutputStream: <
Stream extends
import("firefox").Components_Interfaces["nsISafeOutputStream"] & any,
>(
stream: Stream
) => void;
/**
* Closes a safe file output stream.
* @param stream
* The stream to close.
*/
closeSafeFileOutputStream: <
Stream extends
import("firefox").Components_Interfaces["nsISafeOutputStream"] & any,
>(
stream: Stream
) => void;
File: any;
}
//TODO: add to window
interface MozXULElement {
/**
* Allows eager deterministic construction of XUL elements with XBL attached, by
* parsing an element tree and returning a DOM fragment to be inserted in the
* document before any of the inner elements is referenced by JavaScript.
*
* This process is required instead of calling the createElement method directly
* because bindings get attached when:
*
* 1. the node gets a layout frame constructed, or
* 2. the node gets its JavaScript reflector created, if it's in the document,
*
* whichever happens first. The createElement method would return a JavaScript
* reflector, but the element wouldn't be in the document, so the node wouldn't
* get XBL attached. After that point, even if the node is inserted into a
* document, it won't get XBL attached until either the frame is constructed or
* the reflector is garbage collected and the element is touched again.
*
* @param {string} str
* String with the XML representation of XUL elements.
* @param {string[]} [entities]
* An array of DTD URLs containing entity definitions.
*
* @return {DocumentFragment} `DocumentFragment` instance containing
* the corresponding element tree, including element nodes
* but excluding any text node.
*/
parseXULToFragment: (str: string, entities: string[]) => DocumentFragment;
}
interface ChromeUtils {
/**
* This function reads the KnownModules and resolves which import to use.
* If you are getting the TS2345 error:
*
* Argument of type '"resource:///.../file.jsm"' is not assignable to parameter
* of type
*
* Then add the file path to the KnownModules above.
*/
import: <S extends keyof KnownModules>(module: S) => KnownModules[S];
importESModule: <S extends keyof KnownModules>(
module: S
) => KnownModules[S];
defineModuleGetter: (target: any, variable: string, path: string) => void;
defineESModuleGetters: (target: any, mappings: any) => void;
}
interface MessageManager {
loadFrameScript(url: string, flag: boolean): void;
sendAsyncMessage: (event: string, data: any) => void;
addMessageListener: (event: string, listener: (event: any) => void) => void;
}
interface Browser extends HTMLElement {
addWebTab: (url: string, options: any) => BrowserTab;
contentPrincipal: any;
selectedTab: BrowserTab;
selectedBrowser?: ChromeBrowser;
messageManager: MessageManager;
ownerDocument?: ChromeDocument;
}
interface BrowserTab extends HTMLElement {
linkedBrowser: Browser;
}
interface ChromeWindow {
gBrowser: Browser;
focus(): void;
openWebLinkIn(
url: string,
where: "current" | "tab" | "window",
options: Partial<{
// Not all possible options are present, please add more if/when needed.
userContextId: number;
forceNonPrivate: boolean;
resolveOnContentBrowserCreated: (
contentBrowser: ChromeBrowser
) => unknown;
}>
): void;
}
interface ChromeBrowser {
browsingContext?: BrowsingContext;
}
interface BrowsingContext {
/**
* A unique identifier for the browser element that is hosting this
* BrowsingContext tree. Every BrowsingContext in the element's tree will
* return the same ID in all processes and it will remain stable regardless of
* process changes. When a browser element's frameloader is switched to
* another browser element this ID will remain the same but hosted under the
* under the new browser element.
* We are using this identifier for getting the active tab ID and passing to
* the profiler back-end. See `getActiveBrowserID` for the usage.
*/
browserId: number;
}
type GetPref<T> = (prefName: string, defaultValue?: T) => T;
type SetPref<T> = (prefName: string, value?: T) => T;
type PrefObserverFunction = (
aSubject: import("./firefox/modules/libpref/nsIPrefBranch").nsIPrefBranch,
aTopic: "nsPref:changed",
aData: string
) => unknown;
type PrefObserver = PrefObserverFunction | { observe: PrefObserverFunction };
interface SharedLibrary {
start: number;
end: number;
offset: number;
name: string;
path: string;
debugName: string;
debugPath: string;
breakpadId: string;
arch: string;
}
interface ProfileGenerationAdditionalInformation {
sharedLibraries: SharedLibrary[];
}
interface ProfileAndAdditionalInformation {
profile: ArrayBuffer;
additionalInformation?: ProfileGenerationAdditionalInformation;
}
const EventEmitter: {
decorate: (target: object) => void;
};
const AppConstantsSYSMJS: {
AppConstants: {
platform: string;
};
};
interface BrowsingContextStub {}
interface PrincipalStub {}
interface WebChannelTarget {
browsingContext: BrowsingContextStub;
browser: Browser;
eventTarget: null;
principal: PrincipalStub;
}
// TS-TODO
const CustomizableUISYSMJS: any;
const CustomizableWidgetsSYSMJS: any;
const PanelMultiViewSYSMJS: any;
const LoaderESM: {
require: (path: string) => any;
};
//const Services: Services;
// This class is needed by the Cc importing mechanism. e.g.
// Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
//class nsIFilePicker {}
interface FilePicker {
init: (window: Window, title: string, mode: number) => void;
open: (callback: (rv: number) => unknown) => void;
// The following are enum values.
modeGetFolder: number;
returnOK: number;
file: {
path: string;
};
}
// interface Cc {
// "@mozilla.org/filepicker;1": {
// createInstance(instance: nsIFilePicker): FilePicker;
// };
// }
// interface Ci {
// nsIFilePicker: nsIFilePicker;
// }
// interface Cu {
// /**
// * This function reads the KnownModules and resolves which import to use.
// * If you are getting the TS2345 error:
// *
// * Argument of type '"resource:///.../file.jsm"' is not assignable to parameter
// * of type
// *
// * Then add the file path to the KnownModules above.
// */
// import: <S extends keyof KnownModules>(module: S) => KnownModules[S];
// exportFunction: (fn: Function, scope: object, options?: object) => void;
// cloneInto: (value: any, scope: object, options?: object) => void;
// isInAutomation: boolean;
// }
interface FluentLocalization {
/**
* This function sets the attributes data-l10n-id and possibly data-l10n-args
* on the element.
*/
setAttributes(
target: Element,
id?: string,
args?: Record<string, string>
): void;
}
}
interface PathUtilsInterface {
split: (path: string) => string[];
isAbsolute: (path: string) => boolean;
}
// export module "resource://devtools/client/shared/vendor/react.js" {
// import * as React from "react";
// export = React;
// }
// declare module "resource://devtools/client/shared/vendor/react-dom-factories.js" {
// import * as ReactDomFactories from "react-dom-factories";
// export = ReactDomFactories;
// }
// declare module "resource://devtools/client/shared/vendor/redux.js" {
// import * as Redux from "redux";
// export = Redux;
// }
// declare module "resource://devtools/client/shared/vendor/react-redux.js" {
// import * as ReactRedux from "react-redux";
// export = ReactRedux;
// }
// declare module "resource://devtools/shared/event-emitter2.js" {
// export = MockedExports.EventEmitter;
// }
// declare module "Services" {
// export = Services;
// }
// declare module "ChromeUtils" {
// export = ChromeUtils;
// }
// declare module "resource://gre/modules/AppConstants.sys.mjs" {
// export = MockedExports.AppConstantsSYSMJS;
// }
// declare module "resource://devtools/client/performance-new/shared/background.jsm.js" {
// import * as Background from "devtools/client/performance-new/shared/background.jsm.js";
// export = Background;
// }
// declare module "resource://devtools/client/performance-new/shared/symbolication.jsm.js" {
// import * as PerfSymbolication from "devtools/client/performance-new/shared/symbolication.jsm.js";
// export = PerfSymbolication;
// }
// declare module "resource:///modules/CustomizableUI.sys.mjs" {
// export = MockedExports.CustomizableUISYSMJS;
// }
// declare module "resource:///modules/CustomizableWidgets.sys.mjs" {
// export = MockedExports.CustomizableWidgetsSYSMJS;
// }
// declare module "resource:///modules/PanelMultiView.sys.mjs" {
// export = MockedExports.PanelMultiViewSYSMJS;
// }
// declare module "resource://devtools/shared/loader/Loader.sys.mjs" {
// export = MockedExports.LoaderESM;
// }
declare var ChromeUtils: MockedExports.ChromeUtils;
declare var PathUtils: PathUtilsInterface;
/**
* This is a variant on the normal Document, as it contains chrome-specific properties.
*/
declare interface ChromeDocument extends Document {
/**
* Create a XUL element of a specific type. Right now this function
* only refines iframes, but more tags could be added.
*/
createXULElement: ((type: "iframe") => XULIframeElement) &
((type: string) => XULElement);
/**
* This is a fluent instance connected to this document.
*/
l10n: MockedExports.FluentLocalization;
}
/**
* This is a variant on the HTMLElement, as it contains chrome-specific properties.
*/
declare interface ChromeHTMLElement extends HTMLElement {
ownerDocument: ChromeDocument;
}
declare interface XULElement extends HTMLElement {
ownerDocument: ChromeDocument;
}
declare interface XULIframeElement extends XULElement {
contentWindow: ChromeWindow;
src: string;
}
declare interface ChromeWindow extends Window {
openWebLinkIn: (
url: string,
where: "current" | "tab" | "tabshifted" | "window" | "save",
// TS-TODO
params?: unknown
) => void;
openTrustedLinkIn: (
url: string,
where: "current" | "tab" | "tabshifted" | "window" | "save",
// TS-TODO
params?: unknown
) => void;
}
declare class ChromeWorker extends Worker {}
declare interface MenuListElement extends XULElement {
value: string;
disabled: boolean;
}
declare interface XULCommandEvent extends Event {
target: XULElement;
}
declare interface XULElementWithCommandHandler {
addEventListener: (
type: "command",
handler: (event: XULCommandEvent) => void,
isCapture?: boolean
) => void;
removeEventListener: (
type: "command",
handler: (event: XULCommandEvent) => void,
isCapture?: boolean
) => void;
}
//declare type nsIPrefBranch = MockedExports.nsIPrefBranch;
// chrome context-only DOM isInstance method
// XXX: This hackishly extends Function because there is no way to extend DOM constructors.
// Callers should manually narrow the type when needed.
// See also https://github.com/microsoft/TypeScript-DOM-lib-generator/issues/222
interface Function {
isInstance(obj: any): boolean;
}
declare module "resource://gre/modules/FileUtils.sys.mjs" {
const FileUtils: MockedExports.IFileUtils;
export { FileUtils };
}
// Extend "window" to extend "ChromeWindow"
declare global {
interface Window extends ChromeWindow {}
}

View file

@ -1,13 +0,0 @@
declare type SplitType = 'horizontal' | 'vertical' | 'grid';
declare interface SplitViewConfig extends Config {
splitIndicator: string; // e.g. "split-tab='true'"
defaultSplitView: SplitType;
};
declare interface SplitView {
type: SplitType;
tabs: MockedExports.BrowserTab[];
id: number;
};

1551
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,16 +0,0 @@
{
"name": "components",
"version": "1.0.0",
"description": "Some components used by @zen-browser and @Floorp-Projects as an attempt to make firefox forks a better place",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"check": "tsc"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"typescript": "^5.5.4"
}
}

551
src/ZenSidebarManager.mjs Normal file
View file

@ -0,0 +1,551 @@
export var gZenBrowserManagerSidebar = {
_sidebarElement: null,
_currentPanel: null,
_lastOpenedPanel: null,
_hasChangedConfig: true,
_splitterElement: null,
_hSplitterElement: null,
_hasRegisteredPinnedClickOutside: false,
_isDragging: false,
contextTab: null,
DEFAULT_MOBILE_USER_AGENT: "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36 Edg/114.0.1823.79",
MAX_SIDEBAR_PANELS: 8, // +1 for the add panel button
MAX_RUNS: 3,
init() {
this.update();
this.close(); // avoid caching
this.listenForPrefChanges();
this.insertIntoContextMenu();
},
get sidebarData() {
let services = Services.prefs.getStringPref("zen.sidebar.data");
if (services === "") {
return {};
}
return JSON.parse(services);
},
get shouldCloseOnBlur() {
return Services.prefs.getBoolPref("zen.sidebar.close-on-blur");
},
listenForPrefChanges() {
Services.prefs.addObserver("zen.sidebar.data", this.handleEvent.bind(this));
Services.prefs.addObserver("zen.sidebar.enabled", this.handleEvent.bind(this));
let sidebar = document.getElementById("zen-sidebar-web-panel");
this.splitterElement.addEventListener("mousedown", (function(event) {
let computedStyle = window.getComputedStyle(sidebar);
let maxWidth = parseInt(computedStyle.getPropertyValue("max-width").replace("px", ""));
let minWidth = parseInt(computedStyle.getPropertyValue("min-width").replace("px", ""));
if (!this._isDragging) { // Prevent multiple resizes
this._isDragging = true;
let sidebarWidth = sidebar.getBoundingClientRect().width;
let startX = event.clientX;
let startWidth = sidebarWidth;
let mouseMove = (function(e) {
let newWidth = startWidth + e.clientX - startX;
if (newWidth <= minWidth+10) {
newWidth = minWidth+1;
} else if (newWidth >= maxWidth-10) {
newWidth = maxWidth-1;
}
sidebar.style.width = `${newWidth}px`;
});
let mouseUp = (function() {
this.handleEvent();
this._isDragging = false;
document.removeEventListener("mousemove", mouseMove);
document.removeEventListener("mouseup", mouseUp);
}).bind(this);
document.addEventListener("mousemove", mouseMove);
document.addEventListener("mouseup", mouseUp);
}
}).bind(this));
this.hSplitterElement.addEventListener("mousedown", (function(event) {
let computedStyle = window.getComputedStyle(sidebar);
const parent = sidebar.parentElement;
// relative to avoid the top margin
// 20px is the padding
let parentRelativeHeight = parent.getBoundingClientRect().height - parent.getBoundingClientRect().top + 20;
let minHeight = parseInt(computedStyle.getPropertyValue("min-height").replace("px", ""));
if (!this._isDragging) { // Prevent multiple resizes
this._isDragging = true;
let sidebarHeight = sidebar.getBoundingClientRect().height;
let startY = event.clientY;
let startHeight = sidebarHeight;
let mouseMove = (function(e) {
let newHeight = startHeight + e.clientY - startY;
if (newHeight <= minHeight+10) {
newHeight = minHeight+1;
} else if (newHeight >= parentRelativeHeight) { // 10px is the padding
newHeight = parentRelativeHeight;
}
sidebar.style.height = `${newHeight}px`;
});
let mouseUp = (function() {
this.handleEvent();
this._isDragging = false;
document.removeEventListener("mousemove", mouseMove);
document.removeEventListener("mouseup", mouseUp);
}).bind(this);
document.addEventListener("mousemove", mouseMove);
document.addEventListener("mouseup", mouseUp);
}
}).bind(this));
this.handleEvent();
},
get isFloating() {
return document.getElementById("zen-sidebar-web-panel").hasAttribute("pinned");
},
handleEvent() {
this._hasChangedConfig = true;
this.update();
this._hasChangedConfig = false;
// https://stackoverflow.com/questions/11565471/removing-event-listener-which-was-added-with-bind
var clickOutsideHandler = this._handleClickOutside.bind(this);
let isFloating = this.isFloating;
if (isFloating && !this._hasRegisteredPinnedClickOutside) {
document.addEventListener("mouseup", clickOutsideHandler);
this._hasRegisteredPinnedClickOutside = true;
} else if (!isFloating && this._hasRegisteredPinnedClickOutside) {
document.removeEventListener("mouseup", clickOutsideHandler);
this._hasRegisteredPinnedClickOutside = false;
}
const button = document.getElementById("zen-sidepanel-button");
if (Services.prefs.getBoolPref("zen.sidebar.enabled")) {
button.removeAttribute("hidden");
} else {
button.setAttribute("hidden", "true");
this._closeSidebarPanel();
return;
}
},
_handleClickOutside(event) {
let sidebar = document.getElementById("zen-sidebar-web-panel");
if (!sidebar.hasAttribute("pinned") || this._isDragging || !this.shouldCloseOnBlur) {
return;
}
let target = event.target;
const closestSelector = [
"#zen-sidebar-web-panel",
"#zen-sidebar-panels-wrapper",
"#zenWebPanelContextMenu",
"#zen-sidebar-web-panel-splitter",
"#contentAreaContextMenu"
].join(", ");
if (target.closest(closestSelector)) {
return;
}
this.close();
},
toggle() {
if (!this._currentPanel) {
this._currentPanel = this._lastOpenedPanel;
}
if (document.getElementById("zen-sidebar-web-panel").hasAttribute("hidden")) {
this.open();
return;
}
this.close();
},
open() {
let sidebar = document.getElementById("zen-sidebar-web-panel");
sidebar.removeAttribute("hidden");
this.update();
},
update() {
this._updateWebPanels();
this._updateSidebarButton();
this._updateWebPanel();
this._updateButtons();
},
_updateSidebarButton() {
let button = document.getElementById("zen-sidepanel-button");
if (!document.getElementById("zen-sidebar-web-panel").hasAttribute("hidden")) {
button.setAttribute("open", "true");
} else {
button.removeAttribute("open");
}
},
_updateWebPanels() {
if (Services.prefs.getBoolPref("zen.sidebar.enabled")) {
this.sidebarElement.removeAttribute("hidden");
} else {
this.sidebarElement.setAttribute("hidden", "true");
this._closeSidebarPanel();
return;
}
let data = this.sidebarData;
if (!data.data || !data.index) {
return;
}
this.sidebarElement.innerHTML = "";
for (let site of data.index) {
let panel = data.data[site];
if (!panel || !panel.url) {
continue;
}
let button = document.createXULElement("toolbarbutton");
button.classList.add("zen-sidebar-panel-button", "toolbarbutton-1", "chromeclass-toolbar-additional");
button.setAttribute("flex", "1");
button.setAttribute("zen-sidebar-id", site);
button.setAttribute("context", "zenWebPanelContextMenu");
this._getWebPanelIcon(panel.url, button);
button.addEventListener("click", this._handleClick.bind(this));
this.sidebarElement.appendChild(button);
}
const addButton = document.getElementById("zen-sidebar-add-panel-button");
if (data.index.length < this.MAX_SIDEBAR_PANELS) {
addButton.removeAttribute("hidden");
} else {
addButton.setAttribute("hidden", "true");
}
},
async _openAddPanelDialog() {
let dialogURL = "chrome://browser/content/places/zenNewWebPanel.xhtml";
let features = "centerscreen,chrome,modal,resizable=no";
let aParentWindow = Services.wm.getMostRecentWindow("navigator:browser");
if (aParentWindow?.gDialogBox) {
await aParentWindow.gDialogBox.open(dialogURL, {});
} else {
aParentWindow.openDialog(dialogURL, "", features, {});
}
},
_setPinnedToElements() {
let sidebar = document.getElementById("zen-sidebar-web-panel");
sidebar.setAttribute("pinned", "true");
document.getElementById("zen-sidebar-web-panel-pinned").setAttribute("pinned", "true");
},
_removePinnedFromElements() {
let sidebar = document.getElementById("zen-sidebar-web-panel");
sidebar.removeAttribute("pinned");
document.getElementById("zen-sidebar-web-panel-pinned").removeAttribute("pinned");
},
_closeSidebarPanel() {
let sidebar = document.getElementById("zen-sidebar-web-panel");
sidebar.setAttribute("hidden", "true");
this._lastOpenedPanel = this._currentPanel;
this._currentPanel = null;
},
_handleClick(event) {
let target = event.target;
let panelId = target.getAttribute("zen-sidebar-id");
if (this._currentPanel === panelId) {
return;
}
this._currentPanel = panelId;
this._updateWebPanel();
},
_createNewPanel(url) {
let data = this.sidebarData;
let newName = "p" + new Date().getTime();
data.index.push(newName);
data.data[newName] = {
url: url,
ua: false,
};
Services.prefs.setStringPref("zen.sidebar.data", JSON.stringify(data));
this._currentPanel = newName;
this.open();
},
_updateButtons() {
for (let button of this.sidebarElement.querySelectorAll(".zen-sidebar-panel-button")) {
if (button.getAttribute("zen-sidebar-id") === this._currentPanel) {
button.setAttribute("selected", "true");
} else {
button.removeAttribute("selected");
}
}
},
_hideAllWebPanels() {
let sidebar = document.getElementById("zen-sidebar-web-panel");
for (let browser of sidebar.querySelectorAll("browser[zen-sidebar-id]")) {
browser.setAttribute("hidden", "true");
browser.docShellIsActive = false;
}
},
get introductionPanel() {
return document.getElementById("zen-sidebar-introduction-panel");
},
_updateWebPanel() {
this._updateButtons();
let sidebar = document.getElementById("zen-sidebar-web-panel");
this._hideAllWebPanels();
if (!this._currentPanel) {
this.introductionPanel.removeAttribute("hidden");
return;
}
this.introductionPanel.setAttribute("hidden", "true");
let existantWebview = this._getCurrentBrowser();
if (existantWebview) {
existantWebview.docShellIsActive = true;
existantWebview.removeAttribute("hidden");
document.getElementById("zen-sidebar-web-panel-title").textContent = existantWebview.contentTitle;
return;
}
let data = this._getWebPanelData(this._currentPanel);
let browser = this._createWebPanelBrowser(data);
let browserContainers = document.getElementById("zen-sidebar-web-panel-browser-containers");
browserContainers.appendChild(browser);
if (data.ua) {
browser.browsingContext.customUserAgent = this.DEFAULT_MOBILE_USER_AGENT;
}
browser.docShellIsActive = true;
},
_getWebPanelData(id) {
let data = this.sidebarData;
let panel = data.data[id];
if (!panel || !panel.url) {
return {};
}
return {
id: id,
...panel,
};
},
_createWebPanelBrowser(data) {
const titleContainer = document.getElementById("zen-sidebar-web-panel-title");
titleContainer.textContent = "Loading...";
let browser = gBrowser.createBrowser({});
browser.setAttribute("disablefullscreen", "true");
browser.setAttribute("src", data.url);
browser.setAttribute("zen-sidebar-id", data.id);
browser.setAttribute("disableglobalhistory", "true");
browser.setAttribute("autoscroll", "false");
browser.setAttribute("autocompletepopup", "PopupAutoComplete");
browser.setAttribute("contextmenu", "contentAreaContextMenu");
browser.setAttribute("disablesecurity", "true");
browser.addEventListener("pagetitlechanged", (function(event) {
let browser = event.target;
let title = browser.contentTitle;
if (!title) {
return;
}
let id = browser.getAttribute("zen-sidebar-id");
if (id === this._currentPanel) {
titleContainer.textContent = title;
}
}).bind(this));
return browser;
},
_getWebPanelIcon(url, element) {
let { preferredURI } = Services.uriFixup.getFixupURIInfo(url);
element.setAttribute("image", `page-icon:${preferredURI.spec}`);
fetch(`https://s2.googleusercontent.com/s2/favicons?domain_url=${preferredURI.spec}`).then(async response => {
if (response.ok) {
let blob = await response.blob();
let reader = new FileReader();
reader.onload = function() {
element.setAttribute("image", reader.result);
};
reader.readAsDataURL(blob);
}
});
},
_getBrowserById(id) {
let sidebar = document.getElementById("zen-sidebar-web-panel");
return sidebar.querySelector(`browser[zen-sidebar-id="${id}"]`);
},
_getCurrentBrowser() {
return this._getBrowserById(this._currentPanel);
},
reload() {
let browser = this._getCurrentBrowser();
if (browser) {
browser.reload();
}
},
forward() {
let browser = this._getCurrentBrowser();
if (browser) {
browser.goForward();
}
},
back() {
let browser = this._getCurrentBrowser();
if (browser) {
browser.goBack();
}
},
home() {
let browser = this._getCurrentBrowser();
if (browser) {
browser.gotoIndex();
}
},
close() {
this._hideAllWebPanels();
this._closeSidebarPanel();
this._updateSidebarButton();
},
togglePinned(elem) {
let sidebar = document.getElementById("zen-sidebar-web-panel");
if (sidebar.hasAttribute("pinned")) {
this._removePinnedFromElements();
} else {
this._setPinnedToElements();
}
this.update();
},
get sidebarElement() {
if (!this._sidebarElement) {
this._sidebarElement = document.getElementById("zen-sidebar-panels-sites");
}
return this._sidebarElement;
},
get splitterElement() {
if (!this._splitterElement) {
this._splitterElement = document.getElementById("zen-sidebar-web-panel-splitter");
}
return this._splitterElement;
},
get hSplitterElement() {
if (!this._hSplitterElement) {
this._hSplitterElement = document.getElementById("zen-sidebar-web-panel-hsplitter");
}
return this._hSplitterElement;
},
// Context menu
updateContextMenu(aPopupMenu) {
let panel =
aPopupMenu.triggerNode &&
(aPopupMenu.triggerNode || aPopupMenu.triggerNode.closest("toolbarbutton[zen-sidebar-id]"));
if (!panel) {
return;
}
let id = panel.getAttribute("zen-sidebar-id");
this.contextTab = id;
let data = this._getWebPanelData(id);
let browser = this._getBrowserById(id);
let isMuted = browser && browser.audioMuted;
let mutedContextItem = document.getElementById("context_zenToggleMuteWebPanel");
document.l10n.setAttributes(mutedContextItem,
!isMuted ? "zen-web-side-panel-context-mute-panel" : "zen-web-side-panel-context-unmute-panel");
if (!isMuted) {
mutedContextItem.setAttribute("muted", "true");
} else {
mutedContextItem.removeAttribute("muted");
}
document.l10n.setAttributes(document.getElementById("context_zenToogleUAWebPanel"),
data.ua ? "zen-web-side-panel-context-disable-ua" : "zen-web-side-panel-context-enable-ua");
if (!browser) {
document.getElementById("context_zenUnloadWebPanel").setAttribute("disabled", "true");
} else {
document.getElementById("context_zenUnloadWebPanel").removeAttribute("disabled");
}
},
contextOpenNewTab() {
let browser = this._getBrowserById(this.contextTab);
let data = this.sidebarData;
let panel = data.data[this.contextTab];
let url = (browser == null) ? panel.url : browser.currentURI.spec;
gZenUIManager.openAndChangeToTab(url);
this.close();
},
contextToggleMuteAudio() {
let browser = this._getBrowserById(this.contextTab);
if (browser.audioMuted) {
browser.unmute();
} else {
browser.mute();
}
},
contextToggleUserAgent() {
let browser = this._getBrowserById(this.contextTab);
browser.browsingContext.customUserAgent = browser.browsingContext.customUserAgent ? null : this.DEFAULT_MOBILE_USER_AGENT;
let data = this.sidebarData;
data.data[this.contextTab].ua = !data.data[this.contextTab].ua;
Services.prefs.setStringPref("zen.sidebar.data", JSON.stringify(data));
browser.reload();
},
contextDelete() {
let data = this.sidebarData;
delete data.data[this.contextTab];
data.index = data.index.filter(id => id !== this.contextTab);
let browser = this._getBrowserById(this.contextTab);
if (browser) {
browser.remove();
}
this._currentPanel = null;
this._lastOpenedPanel = null;
this.update();
Services.prefs.setStringPref("zen.sidebar.data", JSON.stringify(data));
},
contextUnload() {
let browser = this._getBrowserById(this.contextTab);
browser.remove();
this._closeSidebarPanel();
this.close();
this._lastOpenedPanel = null;
},
insertIntoContextMenu() {
const sibling = document.getElementById("context-stripOnShareLink");
const menuitem = document.createXULElement("menuitem");
menuitem.setAttribute("id", "context-zenAddToWebPanel");
menuitem.setAttribute("hidden", "true");
menuitem.setAttribute("oncommand", "gZenBrowserManagerSidebar.addPanelFromContextMenu();");
menuitem.setAttribute("data-l10n-id", "zen-web-side-panel-context-add-to-panel");
sibling.insertAdjacentElement("afterend", menuitem);
},
addPanelFromContextMenu() {
const url = gContextMenu.linkURL || gContextMenu.target.ownerDocument.location.href;
this._createNewPanel(url);
},
};
gZenBrowserManagerSidebar.init();

349
src/ZenViewSplitter.mjs Normal file
View file

@ -0,0 +1,349 @@
export var gZenViewSplitter = {
/**
* [
* {
* tabs: [
* tab1,
* tab2,
* tab3,
* ],
* gridType: "vsep" | "hsep" | "grid",
* }
* ]
*/
_data: [],
currentView: -1,
init() {
Services.prefs.setBoolPref("zen.splitView.working", false);
window.addEventListener("TabClose", this);
this.initializeUI();
console.log("ZenViewSplitter initialized");
},
initializeUI() {
this.insertIntoContextMenu();
this.initializeUpdateContextMenuItems();
this.initializeTabContextMenu();
this.initializeZenSplitBox();
},
initializeZenSplitBox() {
const fragment = window.MozXULElement.parseXULToFragment(`
<hbox id="zen-split-views-box"
hidden="true"
role="button"
class="urlbar-page-action"
onclick="gZenViewSplitter.openSplitViewPanel(event);">
<image id="zen-split-views-button"
class="urlbar-icon"/>
</hbox>`);
document.getElementById("star-button-box").after(fragment);
},
initializeTabContextMenu() {
const fragment = window.MozXULElement.parseXULToFragment(`
<menuseparator/>
<menuitem id="context_zenSplitTabs"
data-lazy-l10n-id="tab-zen-split-tabs"
oncommand="gZenViewSplitter.contextSplitTabs();"/>
`);
document.getElementById("tabContextMenu").appendChild(fragment);
},
initializeUpdateContextMenuItems() {
const contentAreaContextMenu = document.getElementById("tabContextMenu");
contentAreaContextMenu.addEventListener("popupshowing", () => {
const tabCountInfo = JSON.stringify({
tabCount: window.gBrowser.selectedTabs.length,
});
document
.getElementById("context_zenSplitTabs")
.setAttribute("data-l10n-args", tabCountInfo);
document.getElementById("context_zenSplitTabs").disabled =
!this.contextCanSplitTabs();
});
},
handleEvent(event) {
switch (event.type) {
case "TabClose":
this.onTabClose(event);
}
},
insertIntoContextMenu() {
const sibling = document.getElementById("context-stripOnShareLink");
const menuitem = document.createXULElement("menuitem");
menuitem.setAttribute("id", "context-zenSplitLink");
menuitem.setAttribute("hidden", "true");
menuitem.setAttribute("oncommand", "gZenViewSplitter.contextSplitLink();");
menuitem.setAttribute("data-l10n-id", "zen-split-link");
const separator = document.createXULElement("menuseparator");
sibling.insertAdjacentElement("afterend", menuitem);
sibling.insertAdjacentElement("afterend", separator);
},
get tabBrowserPanel() {
if (!this._tabBrowserPanel) {
this._tabBrowserPanel = document.getElementById("tabbrowser-tabpanels");
}
return this._tabBrowserPanel;
},
onTabClose(event) {
const tab = event.target;
let index = this._data.findIndex((group) => group.tabs.includes(tab));
if (index < 0) {
return;
}
let dataTab = this._data[index].tabs;
dataTab.splice(dataTab.indexOf(tab), 1);
tab._zenSplitted = false;
tab.linkedBrowser.zenModeActive = false;
let container = tab.linkedBrowser.closest(".browserSidebarContainer");
container.removeAttribute("zen-split");
if (!event.forUnsplit) {
tab.linkedBrowser.docShellIsActive = false;
container.style.display = "none";
} else {
container.style.gridArea = "1 / 1";
}
if (dataTab.length < 2) {
this._data.splice(index, 1);
if (this.currentView == index) {
console.assert(dataTab.length == 1, "Data tab length is not 1");
this.currentView = -1;
this.tabBrowserPanel.removeAttribute("zen-split-view");
this.tabBrowserPanel.style.gridTemplateAreas = "";
this.tabBrowserPanel.style.gridGap = "0px";
Services.prefs.setBoolPref("zen.splitView.working", false);
for (const tab of dataTab) {
let container = tab.linkedBrowser.closest(".browserSidebarContainer");
container.removeAttribute("zen-split");
container.style.gridArea = "1 / 1";
tab._zenSplitted = false;
}
}
return;
}
let lastTab = dataTab[dataTab.length - 1];
this._showSplitView(lastTab);
},
contextSplitLink() {
const url = gContextMenu.linkURL || gContextMenu.target.ownerDocument.location.href;
const tab = gBrowser.selectedTab;
const newTab = gZenUIManager.openAndChangeToTab(url);
this.splitTabs([tab, newTab]);
},
onLocationChange(browser) {
let tab = gBrowser.getTabForBrowser(browser);
this.updateSplitViewButton(!(tab && tab._zenSplitted));
if (!tab) {
return;
}
this._showSplitView(tab);
},
splitTabs(tabs) {
if (tabs.length < 2) {
return;
}
// Check if any tab is already split
for (const tab of tabs) {
if (tab._zenSplitted) {
let index = this._data.findIndex((group) => group.tabs.includes(tab));
if (index < 0) {
return;
}
this._showSplitView(tab);
return;
}
}
this._data.push({
tabs,
gridType: "grid",
});
gBrowser.selectedTab = tabs[0];
this._showSplitView(tabs[0]);
},
_showSplitView(tab) {
const splitData = this._data.find((group) => group.tabs.includes(tab));
function modifyDecks(tabs, add) {
for (const tab of tabs) {
tab.linkedBrowser.zenModeActive = add;
tab.linkedBrowser.docShellIsActive = add;
let browser = tab.linkedBrowser.closest(".browserSidebarContainer");
if (add) {
browser.setAttribute("zen-split", "true");
continue;
}
browser.removeAttribute("zen-split");
}
}
const handleClick = (tab) => {
return ((event) => {
gBrowser.selectedTab = tab;
})
};
if (!splitData || (this.currentView >= 0 && !this._data[this.currentView].tabs.includes(tab))) {
this.updateSplitViewButton(true);
if (this.currentView < 0) {
return;
}
for (const tab of this._data[this.currentView].tabs) {
//tab._zenSplitted = false;
let container = tab.linkedBrowser.closest(".browserSidebarContainer");
container.removeAttribute("zen-split-active");
container.classList.remove("deck-selected");
console.assert(container, "No container found for tab");
container.removeEventListener("click", handleClick(tab));
container.style.gridArea = "";
}
this.tabBrowserPanel.removeAttribute("zen-split-view");
this.tabBrowserPanel.style.gridTemplateAreas = "";
Services.prefs.setBoolPref("zen.splitView.working", false);
modifyDecks(this._data[this.currentView].tabs, false);
// console.log("Setting the active tab to be active", gBrowser.selectedTab);
gBrowser.selectedTab.linkedBrowser.docShellIsActive = true; // Make sure the active tab is active
this.currentView = -1;
if (!splitData) {
return;
}
}
this.tabBrowserPanel.setAttribute("zen-split-view", "true");
Services.prefs.setBoolPref("zen.splitView.working", true);
this.currentView = this._data.indexOf(splitData);
let gridType = splitData.gridType || "grid"; // TODO: let user decide the grid type
let i = 0;
// 2 rows, infinite columns
let currentRowGridArea = ["", ""/* first row, second row */];
let numberOfRows = 0;
for (const _tab of splitData.tabs) {
_tab._zenSplitted = true;
let container = _tab.linkedBrowser.closest(".browserSidebarContainer");
console.assert(container, "No container found for tab");
container.removeAttribute("zen-split-active");
if (_tab == tab) {
container.setAttribute("zen-split-active", "true");
}
container.setAttribute("zen-split-anim", "true");
container.addEventListener("click", handleClick(_tab));
// Set the grid type for the container. If the grid type is not "grid", then set the grid type contain
// each column or row. If it's "grid", then try to create
if (gridType == "grid") {
// Each 2 tabs, create a new row
if (i % 2 == 0) {
currentRowGridArea[0] += ` tab${i + 1}`;
} else {
currentRowGridArea[1] += ` tab${i + 1}`;
numberOfRows++;
}
container.style.gridArea = `tab${i + 1}`;
}
i++;
}
if (gridType == "grid") {
if ((numberOfRows < splitData.tabs.length / 2) && (splitData.tabs.length != 2)) {
// Make the last tab occupy the last row
currentRowGridArea[1] += ` tab${i}`;
}
if (gridType == "grid" && (splitData.tabs.length === 2)) {
currentRowGridArea[0] = `tab1 tab2`;
currentRowGridArea[1] = "";
}
this.tabBrowserPanel.style.gridTemplateAreas = `'${currentRowGridArea[0]}'`;
if (currentRowGridArea[1] != "") {
this.tabBrowserPanel.style.gridTemplateAreas += ` '${currentRowGridArea[1]}'`;
}
} else if (gridType == "vsep") {
this.tabBrowserPanel.style.gridTemplateAreas = `'${splitData.tabs.map((_, i) => `tab${i + 1}`).join(" ")}'`;
} else if (gridType == "hsep") {
this.tabBrowserPanel.style.gridTemplateAreas = `${splitData.tabs.map((_, i) => `'tab${i + 1}'`).join(" ")}`;
}
modifyDecks(splitData.tabs, true);
this.updateSplitViewButton(false);
},
contextSplitTabs() {
let tabs = gBrowser.selectedTabs;
this.splitTabs(tabs);
},
contextCanSplitTabs() {
if (gBrowser.selectedTabs.length < 2) {
return false;
}
// Check if any tab is already split
for (const tab of gBrowser.selectedTabs) {
if (tab._zenSplitted) {
return false;
}
}
return true;
},
// Panel and url button
updateSplitViewButton(hidden) {
let button = document.getElementById("zen-split-views-box");
if (hidden) {
button.setAttribute("hidden", "true");
return;
}
button.removeAttribute("hidden");
},
get _modifierElement() {
if (!this.__modifierElement) {
let wrapper = document.getElementById("template-zen-split-view-modifier");
const panel = wrapper.content.firstElementChild;
wrapper.replaceWith(wrapper.content);
this.__modifierElement = panel;
}
return this.__modifierElement;
},
async openSplitViewPanel(event) {
let panel = this._modifierElement;
let target = event.target.parentNode;
for (const gridType of ["hsep", "vsep", "grid", "unsplit"]) {
let selector = panel.querySelector(`.zen-split-view-modifier-preview.${gridType}`);
selector.classList.remove("active");
if (this.currentView >= 0 && this._data[this.currentView].gridType == gridType) {
selector.classList.add("active");
}
if (this.__hasSetMenuListener) {
continue;
}
selector.addEventListener("click", ((gridType) => {
if (gridType === "unsplit") {
let currentTab = gBrowser.selectedTab;
let tabs = this._data[this.currentView].tabs;
for (const tab of tabs) {
this.onTabClose({ target: tab, forUnsplit: true });
}
gBrowser.selectedTab = currentTab;
panel.hidePopup();
this.updateSplitViewButton(true);
return;
}
this._data[this.currentView].gridType = gridType;
this._showSplitView(gBrowser.selectedTab);
panel.hidePopup();
}).bind(this, gridType));
}
this.__hasSetMenuListener = true;
PanelMultiView.openPopup(panel, target, {
position: "bottomright topright",
triggerEvent: event,
}).catch(console.error);
},
};
gZenViewSplitter.init();

426
src/ZenWorkspaces.mjs Normal file
View file

@ -0,0 +1,426 @@
export var ZenWorkspaces = {
async init() {
let docElement = document.documentElement;
if (docElement.getAttribute("chromehidden").includes("toolbar")
|| docElement.getAttribute("chromehidden").includes("menubar")
|| docElement.hasAttribute("privatebrowsingmode")) {
console.warn("ZenWorkspaces: !!! ZenWorkspaces is disabled in hidden windows !!!");
return; // We are in a hidden window, don't initialize ZenWorkspaces
}
console.log("ZenWorkspaces: Initializing ZenWorkspaces...");
await this.initializeWorkspaces();
console.log("ZenWorkspaces: ZenWorkspaces initialized");
},
get workspaceEnabled() {
return Services.prefs.getBoolPref("zen.workspaces.enabled", false);
},
// Wrorkspaces saving/loading
get _storeFile() {
return PathUtils.join(
PathUtils.profileDir,
"zen-workspaces",
"Workspaces.json",
);
},
async _workspaces() {
if (!this._workspaceCache) {
this._workspaceCache = await IOUtils.readJSON(this._storeFile);
if (!this._workspaceCache.workspaces) {
this._workspaceCache.workspaces = [];
}
}
return this._workspaceCache;
},
onWorkspacesEnabledChanged() {
if (this.workspaceEnabled) {
this.initializeWorkspaces();
} else {
this._workspaceCache = null;
document.getElementById("zen-workspaces-button")?.remove();
for (let tab of gBrowser.tabs) {
gBrowser.showTab(tab);
}
}
},
async initializeWorkspaces() {
Services.prefs.addObserver("zen.workspaces.enabled", this.onWorkspacesEnabledChanged.bind(this));
this.initializeWorkspacesButton();
let file = new FileUtils.File(this._storeFile);
if (!file.exists()) {
await IOUtils.writeJSON(this._storeFile, {});
}
if (this.workspaceEnabled) {
let workspaces = await this._workspaces();
if (workspaces.workspaces.length === 0) {
await this.createAndSaveWorkspace("Default Workspace", true);
} else {
let activeWorkspace = workspaces.workspaces.find(workspace => workspace.default);
if (!activeWorkspace) {
activeWorkspace = workspaces.workspaces.find(workspace => workspace.used);
activeWorkspace.used = true;
await this.saveWorkspaces();
}
if (!activeWorkspace) {
activeWorkspace = workspaces.workspaces[0];
activeWorkspace.used = true;
await this.saveWorkspaces();
}
await this.changeWorkspace(activeWorkspace);
}
this._initializeWorkspaceIcons();
}
},
_initializeWorkspaceIcons() {
const kIcons = ["🏠", "📄", "💹", "💼", "📧", "✅", "👥"];
let container = document.getElementById("PanelUI-zen-workspaces-create-icons-container");
for (let icon of kIcons) {
let button = document.createXULElement("toolbarbutton");
button.className = "toolbarbutton-1";
button.setAttribute("label", icon);
button.onclick = ((event) => {
for (let button of container.children) {
button.removeAttribute("selected");
}
button.setAttribute("selected", "true");
}).bind(this, button);
container.appendChild(button);
}
},
async saveWorkspace(workspaceData) {
let json = await IOUtils.readJSON(this._storeFile);
if (typeof json.workspaces === "undefined") {
json.workspaces = [];
}
json.workspaces.push(workspaceData);
console.log("ZenWorkspaces: Saving workspace", workspaceData);
await IOUtils.writeJSON(this._storeFile, json);
this._workspaceCache = null;
},
async removeWorkspace(windowID) {
let json = await this._workspaces();
console.log("ZenWorkspaces: Removing workspace", windowID);
await this.changeWorkspace(json.workspaces.find(workspace => workspace.uuid !== windowID));
this._deleteAllTabsInWorkspace(windowID);
json.workspaces = json.workspaces.filter(workspace => workspace.uuid !== windowID);
await this.unsafeSaveWorkspaces(json);
await this._propagateWorkspaceData();
},
async saveWorkspaces() {
await IOUtils.writeJSON(this._storeFile, await this._workspaces());
this._workspaceCache = null;
},
async unsafeSaveWorkspaces(workspaces) {
await IOUtils.writeJSON(this._storeFile, workspaces);
this._workspaceCache = null;
},
// Workspaces dialog UI management
openSaveDialog() {
let parentPanel = document.getElementById("PanelUI-zen-workspaces-multiview");
PanelUI.showSubView("PanelUI-zen-workspaces-create", parentPanel);
},
cancelWorkspaceCreation() {
let parentPanel = document.getElementById("PanelUI-zen-workspaces-multiview");
parentPanel.goBack();
},
workspaceHasIcon(workspace) {
return typeof workspace.icon !== "undefined" && workspace.icon !== "";
},
getWorkspaceIcon(workspace) {
if (this.workspaceHasIcon(workspace)) {
return workspace.icon;
}
return workspace.name[0].toUpperCase();
},
async _propagateWorkspaceData() {
let currentContainer = document.getElementById("PanelUI-zen-workspaces-current-info");
let workspaceList = document.getElementById("PanelUI-zen-workspaces-list");
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");
let childs = window.MozXULElement.parseXULToFragment(`
<div class="zen-workspace-icon">
${this.getWorkspaceIcon(workspace)}
</div>
<div class="zen-workspace-name">
${workspace.name}
</div>
<toolbarbutton closemenu="none" class="toolbarbutton-1 zen-workspace-actions">
<image class="toolbarbutton-icon" id="zen-workspace-actions-menu-icon"></image>
</toolbarbutton>
`);
childs.querySelector(".zen-workspace-actions").addEventListener("command", ((event) => {
let button = event.target;
this._contextMenuId = button.closest("toolbarbutton[zen-workspace-id]").getAttribute("zen-workspace-id");
const popup = button.ownerDocument.getElementById(
"zenWorkspaceActionsMenu"
);
popup.openPopup(button, "after_end");
}).bind(this));
element.appendChild(childs);
element.onclick = (async () => {
if (event.target.closest(".zen-workspace-actions")) {
return; // Ignore clicks on the actions button
}
await this.changeWorkspace(workspace)
let panel = document.getElementById("PanelUI-zen-workspaces");
PanelMultiView.hidePopup(panel);
}).bind(this, workspace);
return element;
}
let workspaces = await this._workspaces();
let activeWorkspace = workspaces.workspaces.find(workspace => workspace.used);
currentContainer.innerHTML = "";
workspaceList.innerHTML = "";
workspaceList.parentNode.style.display = "flex";
if (workspaces.workspaces.length - 1 <= 0) {
workspaceList.innerHTML = "No workspaces available";
workspaceList.setAttribute("empty", "true");
} else {
workspaceList.removeAttribute("empty");
}
if (activeWorkspace) {
let currentWorkspace = createWorkspaceElement(activeWorkspace);
currentContainer.appendChild(currentWorkspace);
}
for (let workspace of workspaces.workspaces) {
if (workspace.used) {
continue;
}
let workspaceElement = createWorkspaceElement(workspace);
workspaceList.appendChild(workspaceElement);
}
},
async openWorkspacesDialog(event) {
if (!this.workspaceEnabled) {
return;
}
let target = event.target;
let panel = document.getElementById("PanelUI-zen-workspaces");
await this._propagateWorkspaceData();
PanelMultiView.openPopup(panel, target, {
position: "bottomright topright",
triggerEvent: event,
}).catch(console.error);
},
initializeWorkspacesButton() {
if (!this.workspaceEnabled) {
return;
} else if (document.getElementById("zen-workspaces-button")) {
let button = document.getElementById("zen-workspaces-button");
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);
},
async _updateWorkspacesButton() {
let button = document.getElementById("zen-workspaces-button");
if (!button) {
return;
}
let activeWorkspace = (await this._workspaces()).workspaces.find(workspace => workspace.used);
if (activeWorkspace) {
button.innerHTML = `
<div class="zen-workspace-sidebar-icon">
${this.getWorkspaceIcon(activeWorkspace)}
</div>
<div class="zen-workspace-sidebar-name">
${activeWorkspace.name}
</div>
`;
if (!this.workspaceHasIcon(activeWorkspace)) {
button.querySelector(".zen-workspace-sidebar-icon").setAttribute("no-icon", "true");
}
}
},
// Workspaces management
get _workspaceInput() {
return document.getElementById("PanelUI-zen-workspaces-create-input");
},
_deleteAllTabsInWorkspace(workspaceID) {
for (let tab of gBrowser.tabs) {
if (tab.getAttribute("zen-workspace-id") === workspaceID) {
gBrowser.removeTab(tab, {
animate: true,
skipSessionStore: true,
closeWindowWithLastTab: false,
});
}
}
},
_prepareNewWorkspace(window) {
document.documentElement.setAttribute("zen-workspace-id", window.uuid);
let tabCount = 0;
for (let tab of gBrowser.tabs) {
if (!tab.hasAttribute("zen-workspace-id")) {
tab.setAttribute("zen-workspace-id", window.uuid);
tabCount++;
}
}
if (tabCount === 0) {
this._createNewTabForWorkspace(window);
}
},
_createNewTabForWorkspace(window) {
let tab = gZenUIManager.openAndChangeToTab(Services.prefs.getStringPref("browser.startup.homepage"));
tab.setAttribute("zen-workspace-id", window.uuid);
},
async saveWorkspaceFromInput() {
// Go to the next view
let parentPanel = document.getElementById("PanelUI-zen-workspaces-multiview");
PanelUI.showSubView("PanelUI-zen-workspaces-create-icons", parentPanel);
},
async saveWorkspaceFromIcon() {
let workspaceName = this._workspaceInput.value;
if (!workspaceName) {
return;
}
this._workspaceInput.value = "";
let icon = document.querySelector("#PanelUI-zen-workspaces-create-icons-container [selected]");
icon?.removeAttribute("selected");
await this.createAndSaveWorkspace(workspaceName, false, icon?.label);
document.getElementById("PanelUI-zen-workspaces").hidePopup(true);
},
onWorkspaceNameChange(event) {
let button = document.getElementById("PanelUI-zen-workspaces-create-save");
if (this._workspaceInput.value === "") {
button.setAttribute("disabled", "true");
return;
}
button.removeAttribute("disabled");
},
async changeWorkspace(window) {
if (!this.workspaceEnabled) {
return;
}
let firstTab = undefined;
let workspaces = await this._workspaces();
for (let workspace of workspaces.workspaces) {
workspace.used = workspace.uuid === window.uuid;
}
this.unsafeSaveWorkspaces(workspaces);
console.log("ZenWorkspaces: Changing workspace to", window.uuid);
for (let tab of gBrowser.tabs) {
if (tab.getAttribute("zen-workspace-id") === window.uuid && !tab.pinned) {
if (!firstTab) {
firstTab = tab;
gBrowser.selectedTab = firstTab;
}
gBrowser.showTab(tab);
}
}
if (typeof firstTab === "undefined") {
this._createNewTabForWorkspace(window);
}
for (let tab of gBrowser.tabs) {
if (tab.getAttribute("zen-workspace-id") !== window.uuid) {
gBrowser.hideTab(tab);
}
}
document.documentElement.setAttribute("zen-workspace-id", window.uuid);
await this.saveWorkspaces();
await this._updateWorkspacesButton();
await this._propagateWorkspaceData();
},
_createWorkspaceData(name, isDefault, icon) {
let window = {
uuid: gZenUIManager.generateUuidv4(),
default: isDefault,
used: true,
icon: icon,
name: name,
};
this._prepareNewWorkspace(window);
return window;
},
async createAndSaveWorkspace(name = "New Workspace", isDefault = false, icon = undefined) {
if (!this.workspaceEnabled) {
return;
}
let workspaceData = this._createWorkspaceData(name, isDefault, icon);
await this.saveWorkspace(workspaceData);
await this.changeWorkspace(workspaceData);
},
async onLocationChange(browser) {
let tab = gBrowser.getTabForBrowser(browser);
let workspaceID = tab.getAttribute("zen-workspace-id");
if (!workspaceID) {
let workspaces = await this._workspaces();
let activeWorkspace = workspaces.workspaces.find(workspace => workspace.used);
if (!activeWorkspace) {
return;
}
tab.setAttribute("zen-workspace-id", activeWorkspace.uuid);
}
},
// Context menu management
_contextMenuId: null,
async updateContextMenu(_) {
console.assert(this._contextMenuId, "No context menu ID set");
document.querySelector(`#PanelUI-zen-workspaces [zen-workspace-id="${this._contextMenuId}"] .zen-workspace-actions`).setAttribute("active", "true");
const workspaces = await this._workspaces();
let deleteMenuItem = document.getElementById("context_zenDeleteWorkspace");
if (workspaces.workspaces.length <= 1 || workspaces.workspaces.find(workspace => workspace.uuid === this._contextMenuId).default) {
deleteMenuItem.setAttribute("disabled", "true");
} else {
deleteMenuItem.removeAttribute("disabled");
}
},
onContextMenuClose() {
let target = document.querySelector(`#PanelUI-zen-workspaces [zen-workspace-id="${this._contextMenuId}"] .zen-workspace-actions`);
if (target) {
target.removeAttribute("active");
}
this._contextMenuId = null;
},
async contextDelete() {
await this.removeWorkspace(this._contextMenuId);
}
};
ZenWorkspaces.init();

View file

@ -1,330 +0,0 @@
class SplitViewsUtils {
/**
* @returns {HTMLDivElement}
*/
get tabBrowser() {
if (!this._tabBrowser) {
this._tabBrowser = document.getElementById('tabbrowser-tabpanels');
}
// @ts-ignore
return this._tabBrowser;
}
}
class SplitViewsBase extends SplitViewsUtils {
/**
* @type {SplitView[]}
*/
data;
/**
* @param {SplitViewConfig} config
*/
constructor(config) {
super();
this.config = config;
this.data = [];
this.currentView = -1;
this.globalIdCounter = 0;
// Added to "navigator-toolbox" element
this.parentSplitIndicator = this.config.splitIndicator + '-view';
this.log('SplitViewsBase initialized');
}
/**
* @param {string} message
* @protected
*/
log(message) {
console.log(`SplitViews: ${message}`);
}
get isActivated() {
return this.currentView !== -1;
}
get activeView() {
if (!this.isActivated) {
throw new Error('No active view');
}
return this.data[this.currentView];
}
/**
* @param {MockedExports.BrowserTab} tab
*/
getTabView(tab) {
return this.data.find(view => view.tabs.includes(tab));
}
/**
* @param {MockedExports.BrowserTab} tab
*/
isTabSplit(tab) {
return tab.hasAttribute(this.config.splitIndicator);
}
/**
* @param {MockedExports.BrowserTab} tab
* @param {SplitType} type
* @param {MockedExports.BrowserTab[]} tabs
*/
changeSplitViewBase(tab, type, tabs) {
let view = this.getTabView(tab);
if (!view) {
return -1;
}
view.type = type;
view.tabs.push(...tabs.filter(t => !view.tabs.includes(t)));
return view.id;
}
/**
* @param {MockedExports.BrowserTab[]} tabs
* @param {SplitType} type
*/
createSplitViewBase(tabs, type) {
let view = {
id: this.globalIdCounter++,
type,
tabs,
};
this.data.push(view);
this.currentView = this.data.length - 1;
return view.id;
}
/**
* Applies the grid layout to the tabs.
*
* @param {MockedExports.BrowserTab[]} tabs - The tabs to apply the grid layout to.
* @param {string} gridType - The type of grid layout.
* @param {MockedExports.BrowserTab} activeTab - The active tab.
*/
applyGridLayout(tabs, gridType, activeTab) {
const gridAreas = this.calculateGridAreas(tabs, gridType);
this.tabBrowser.style.gridTemplateAreas = gridAreas;
tabs.forEach((tab, index) => {
tab.setAttribute(this.config.splitIndicator, "true");
const container = tab.linkedBrowser.closest(".browserSidebarContainer");
if (!container) {
throw new Error("Container not found");
}
this.styleContainer(container, tab === activeTab, index, gridType);
});
}
/**
* Styles the container for a tab.
*
* @param {Element} container - The container element.
* @param {boolean} isActive - Indicates if the tab is active.
* @param {number} index - The index of the tab.
* @param {string} gridType - The type of grid layout.
*/
styleContainer(container, isActive, index, gridType) {
container.removeAttribute("split-active");
container.setAttribute(this.config.splitIndicator, "true");
if (isActive) {
container.setAttribute("split-active", "true");
}
container.setAttribute("split-anim", "true");
container.addEventListener("click", this.handleTabClick);
if (gridType === "grid") {
// @ts-ignore
container.style.gridArea = `tab${index + 1}`;
}
}
/**
* Calculates the grid areas for the tabs.
*
* @param {MockedExports.BrowserTab[]} tabs - The tabs.
* @param {string} gridType - The type of grid layout.
* @returns {string} The calculated grid areas.
*/
calculateGridAreas(tabs, gridType) {
if (gridType === "grid") {
return this.calculateGridAreasForGrid(tabs);
}
if (gridType === "vsep") {
return `'${tabs.map((_, j) => `tab${j + 1}`).join(" ")}'`;
}
if (gridType === "hsep") {
return tabs.map((_, j) => `'tab${j + 1}'`).join(" ");
}
return "";
}
/**
* Handles the tab click event.
*
* @param {Event} event - The click event.
*/
handleTabClick(event) {
const container = event.currentTarget;
// @ts-ignore
const tab = window.gBrowser.tabs.find(
// @ts-ignore
t => t.linkedBrowser.closest(".browserSidebarContainer") === container
);
if (tab) {
// @ts-ignore
window.gBrowser.selectedTab = tab;
}
};
/**
* Calculates the grid areas for the tabs in a grid layout.
*
* @param {MockedExports.BrowserTab[]} tabs - The tabs.
* @returns {string} The calculated grid areas.
*/
calculateGridAreasForGrid(tabs) {
const rows = ["", ""];
tabs.forEach((_, i) => {
if (i % 2 === 0) {
rows[0] += ` tab${i + 1}`;
} else {
rows[1] += ` tab${i + 1}`;
}
});
if (tabs.length === 2) {
return "'tab1 tab2'";
}
if (tabs.length % 2 !== 0) {
rows[1] += ` tab${tabs.length}`;
}
return `'${rows[0].trim()}' '${rows[1].trim()}'`;
}
/**
* @param {number} viewId
* @protected
*/
updateSplitView(viewId) {
let view = this.data.find(view => view.id === viewId);
this.log(`updateSplitView: ${viewId}`);
this.currentView = viewId;
if (!view) {
this.tabBrowser.removeAttribute(this.parentSplitIndicator);
throw new Error('TODO: Remove split view');
return;
}
this.tabBrowser.setAttribute(this.parentSplitIndicator, "true");
this.applyGridLayout(view.tabs, view.type, view.tabs[0]);
}
/**
* @param {MockedExports.BrowserTab[]} tabs
* @param {SplitType} type
* @protected
*/
createOrChangeSplitView(tabs, type) {
let activeTab = tabs.find(tab => this.isTabSplit(tab));
this.log(`createOrChangeSplitView: ${type}`);
let viewId = -1;
if (activeTab) {
viewId = this.changeSplitViewBase(activeTab, type, tabs);
} else {
viewId = this.createSplitViewBase(tabs, type);
}
this.updateSplitView(viewId);
}
}
// Public API exposed by the module
export class SplitViews extends SplitViewsBase {
/**
* @param {SplitViewConfig} config
*/
constructor(config) {
super(config);
this.addEventListeners();
}
addEventListeners() {
window.addEventListener('TabClose', this);
}
/**
* @param {Event} event
*/
handleEvent(event) {
switch (event.type) {
case 'TabClose':
this.onTabClose(event);
break;
}
}
/**
* @param {Event} event
*/
// @ts-ignore
// @ts-ignore
onTabClose(event) {
}
/**
* @param {MockedExports.Browser} browser
*/
// @ts-ignore
// @ts-ignore
onLocationChange(browser) {
this.log('onLocationChange');
}
/**
* @param {SplitType} type
*/
// @ts-ignore
// @ts-ignore
tileCurrentView(type) {
this.log('tileCurrentView');
}
closeCurrentView() {
this.log('closeCurrentView');
}
/**
* @param {MockedExports.BrowserTab} tab
*/
// @ts-ignore
// @ts-ignore
tabIsInActiveView(tab) {
this.log('tabIsInActiveView');
return false;
}
getActiveViewTabs() {
this.log('getActiveViewTabs');
return [];
}
getActiveViewType() {
if (!this.isActivated) {
return undefined;
}
return this.activeView.type;
}
/**
* @param {MockedExports.BrowserTab[]} tabs
* @param {SplitType} type
* @public
*/
createSplitView(tabs, type = this.config.defaultSplitView) {
if (tabs.length < 2) {
return;
}
this.createOrChangeSplitView(tabs, type);
}
};

View file

@ -1,106 +0,0 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Projects */
// "incremental": true, /* Enable incremental compilation */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
// "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
// Make the type checking as strict as possible.
// TypeScript will check JS files only if they have a @ts-check comment in them.
"allowJs": true,
// Allow esnext syntax. Otherwise the default is ES5 only.
"target": "esnext",
"lib": ["esnext", "dom"],
/* Modules */
"module": "CommonJS", /* Specify what module code is generated. */
"rootDir": "./src", /* Specify the root folder within your source files. */
// "moduleResolution": "NodeNext", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
"typeRoots": ["./@types"], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "resolveJsonModule": true, /* Enable importing .json files */
// "noResolve": true, /* Disallow `import`s, `require`s or `<reference>`s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
"checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
/* Emit */
"declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
"emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
"sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "outFile": "./dist/components.js", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
"outDir": "./dist", /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
/* Type Checking */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
// "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
// "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}