mirror of
https://github.com/zen-browser/components.git
synced 2025-07-07 21:09:58 +02:00
feat(essentials): Add Zen Essentials Toolbar
This commit introduces a new "Zen Essentials" toolbar to the sidebar, providing quick access to frequently used features. - The toolbar is based on the `PlacesViewBase` class and uses the `PlacesUtils` API to manage bookmarks. - It includes a dedicated folder for Zen Essentials bookmarks, ensuring they're organized and easily accessible. - The toolbar's visibility can be controlled by the `zen.essentials.enabled` preference. - The toolbar supports drag-and-drop functionality for organizing bookmarks. - The toolbar items are hidden if they are in another workspace. - The toolbar supports hover interactions, highlighting the active bookmark on mouseover. - The toolbar items can be opened in a glance by pressing `Ctrl`, `Alt`, `Shift`, or `Meta` depending on the user's preference. - The bookmark toolbar automatically updates if the workspace changes.
This commit is contained in:
parent
f49c256194
commit
49a83678ba
4 changed files with 982 additions and 102 deletions
806
src/ZenEssentialsToolbar.mjs
Normal file
806
src/ZenEssentialsToolbar.mjs
Normal file
|
@ -0,0 +1,806 @@
|
||||||
|
|
||||||
|
class ZenEssentialsToolbar extends PlacesViewBase {
|
||||||
|
constructor(placesUrl, rootElt, viewElt) {
|
||||||
|
// We'll initialize the places URL after ensuring the folder exists
|
||||||
|
super(null, rootElt, viewElt);
|
||||||
|
// Do initialization of properties that don't depend on Places
|
||||||
|
this._init();
|
||||||
|
this._initPlacesFolder();
|
||||||
|
}
|
||||||
|
|
||||||
|
get _accordionHeader() {
|
||||||
|
return document.getElementById("essentials-accordion-header");
|
||||||
|
}
|
||||||
|
|
||||||
|
async _initPlacesFolder() {
|
||||||
|
try {
|
||||||
|
const ESSENTIALS_GUID = "pfgqteRgY-Wr"; // Fixed GUID for the folder
|
||||||
|
|
||||||
|
// First try to fetch by GUID since it's more efficient
|
||||||
|
let folder = await PlacesUtils.bookmarks.fetch(ESSENTIALS_GUID );
|
||||||
|
|
||||||
|
if (!folder) {
|
||||||
|
// If not found by GUID, try creating with our specific GUID
|
||||||
|
// try {
|
||||||
|
folder = await PlacesUtils.bookmarks.insert({
|
||||||
|
type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
||||||
|
guid: ESSENTIALS_GUID,
|
||||||
|
title: "Zen Essentials",
|
||||||
|
parentGuid: PlacesUtils.bookmarks.menuGuid
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the folder is in the right place with the right title
|
||||||
|
if (folder.parentGuid !== PlacesUtils.bookmarks.menuGuid ||
|
||||||
|
folder.title !== "Zen Essentials") {
|
||||||
|
await PlacesUtils.bookmarks.update({
|
||||||
|
guid: folder.guid,
|
||||||
|
title: "Zen Essentials",
|
||||||
|
parentGuid: PlacesUtils.bookmarks.menuGuid,
|
||||||
|
index: folder.index
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!folder) {
|
||||||
|
console.error("Failed to initialize ZenEssentials folder");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now that we have the folder, set up the places URL
|
||||||
|
this.place = `place:parent=${folder.guid}`;
|
||||||
|
|
||||||
|
// Initialize view event listeners and setup
|
||||||
|
this._setupEventListeners();
|
||||||
|
|
||||||
|
} catch (ex) {
|
||||||
|
console.error("Failed to initialize ZenEssentials folder:", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateEssentialsVisibility() {
|
||||||
|
// Get current preference value
|
||||||
|
this.isEssentialsEnabled = Services.prefs.getBoolPref("zen.essentials.enabled", false);
|
||||||
|
|
||||||
|
// Update visibility
|
||||||
|
if (this._rootElt) {
|
||||||
|
this._rootElt.hidden = !this.isEssentialsEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._accordionHeader) {
|
||||||
|
this._accordionHeader.style.display = this.isEssentialsEnabled ? "flex" : "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called by PlacesViewBase during initialization
|
||||||
|
_init() {
|
||||||
|
this._prefObserver = (() => {
|
||||||
|
this._updateEssentialsVisibility();
|
||||||
|
}).bind(this);
|
||||||
|
|
||||||
|
// Get initial preference value and set up observer
|
||||||
|
Services.prefs.addObserver("zen.essentials.enabled", this._prefObserver);
|
||||||
|
|
||||||
|
// Initialize visibility based on current pref value
|
||||||
|
this.isEssentialsEnabled = Services.prefs.getBoolPref("zen.essentials.enabled", false);
|
||||||
|
this._updateEssentialsVisibility();
|
||||||
|
this._overFolder = {
|
||||||
|
elt: null,
|
||||||
|
openTimer: null,
|
||||||
|
hoverTime: 350,
|
||||||
|
closeTimer: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
let thisView = this;
|
||||||
|
[
|
||||||
|
["_dropIndicator", "EssentialsToolbarDropIndicator"],
|
||||||
|
].forEach(function (elementGlobal) {
|
||||||
|
let [name, id] = elementGlobal;
|
||||||
|
thisView.__defineGetter__(name, function () {
|
||||||
|
let element = document.getElementById(id);
|
||||||
|
if (!element) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
delete thisView[name];
|
||||||
|
return (thisView[name] = element);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize base properties
|
||||||
|
this._viewElt._placesView = this;
|
||||||
|
this._dragRoot = this._viewElt;
|
||||||
|
}
|
||||||
|
|
||||||
|
_setupEventListeners() {
|
||||||
|
// Add standard event listeners
|
||||||
|
this._addEventListeners(this._dragRoot, this._cbEvents, false);
|
||||||
|
this._addEventListeners(this._rootElt, ["popupshowing", "popuphidden"], true);
|
||||||
|
this._addEventListeners(window, ["unload"], false);
|
||||||
|
}
|
||||||
|
|
||||||
|
_cbEvents = [
|
||||||
|
"dragstart",
|
||||||
|
"dragover",
|
||||||
|
"dragleave",
|
||||||
|
"dragend",
|
||||||
|
"drop",
|
||||||
|
"mousemove",
|
||||||
|
"mouseover",
|
||||||
|
"mouseout",
|
||||||
|
"mousedown",
|
||||||
|
];
|
||||||
|
|
||||||
|
QueryInterface = ChromeUtils.generateQI([
|
||||||
|
"nsINamed",
|
||||||
|
"nsITimerCallback",
|
||||||
|
...PlacesViewBase.interfaces,
|
||||||
|
]);
|
||||||
|
|
||||||
|
uninit() {
|
||||||
|
if (this._prefObserver) {
|
||||||
|
Services.prefs.removeObserver("zen.essentials.enabled", this._prefObserver);
|
||||||
|
this._prefObserver = null;
|
||||||
|
}
|
||||||
|
if (this._dragRoot) {
|
||||||
|
this._removeEventListeners(this._dragRoot, this._cbEvents, false);
|
||||||
|
}
|
||||||
|
this._removeEventListeners(
|
||||||
|
this._rootElt,
|
||||||
|
["popupshowing", "popuphidden"],
|
||||||
|
true
|
||||||
|
);
|
||||||
|
this._removeEventListeners(window, ["unload"], false);
|
||||||
|
|
||||||
|
super.uninit();
|
||||||
|
}
|
||||||
|
|
||||||
|
_allowPopupShowing = true;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
get _isAlive() {
|
||||||
|
return this._resultNode && this._rootElt;
|
||||||
|
}
|
||||||
|
|
||||||
|
async _rebuild() {
|
||||||
|
if (this._overFolder.elt) {
|
||||||
|
this._clearOverFolder();
|
||||||
|
}
|
||||||
|
|
||||||
|
while (this._rootElt.hasChildNodes()) {
|
||||||
|
this._rootElt.firstChild.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
let cc = this._resultNode.childCount;
|
||||||
|
if (cc > 0) {
|
||||||
|
for (let i = 0; i < cc; i++) {
|
||||||
|
this._insertNewItem(this._resultNode.getChild(i), this._rootElt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_insertNewItem(aChild, aInsertionNode, aBefore = null) {
|
||||||
|
this._domNodes.delete(aChild);
|
||||||
|
|
||||||
|
let type = aChild.type;
|
||||||
|
let button;
|
||||||
|
if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR) {
|
||||||
|
button = document.createXULElement("toolbarseparator");
|
||||||
|
} else {
|
||||||
|
button = document.createXULElement("toolbarbutton");
|
||||||
|
button.className = "bookmark-item";
|
||||||
|
button.setAttribute("label", aChild.title || "");
|
||||||
|
|
||||||
|
if (PlacesUtils.containerTypes.includes(type)) {
|
||||||
|
button.setAttribute("type", "menu");
|
||||||
|
button.setAttribute("container", "true");
|
||||||
|
|
||||||
|
if (PlacesUtils.nodeIsQuery(aChild)) {
|
||||||
|
button.setAttribute("query", "true");
|
||||||
|
if (PlacesUtils.nodeIsTagQuery(aChild)) {
|
||||||
|
button.setAttribute("tagContainer", "true");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let popup = document.createXULElement("menupopup", {
|
||||||
|
is: "places-popup",
|
||||||
|
});
|
||||||
|
popup.setAttribute("placespopup", "true");
|
||||||
|
popup.classList.add("toolbar-menupopup");
|
||||||
|
button.appendChild(popup);
|
||||||
|
popup._placesNode = PlacesUtils.asContainer(aChild);
|
||||||
|
popup.setAttribute("context", "placesContext");
|
||||||
|
|
||||||
|
this._domNodes.set(aChild, popup);
|
||||||
|
} else if (PlacesUtils.nodeIsURI(aChild)) {
|
||||||
|
button.setAttribute(
|
||||||
|
"scheme",
|
||||||
|
PlacesUIUtils.guessUrlSchemeForUI(aChild.uri)
|
||||||
|
);
|
||||||
|
button.hidden = ZenWorkspaces.isBookmarkInAnotherWorkspace(aChild);
|
||||||
|
button.addEventListener("command", gZenGlanceManager.openGlanceForBookmark.bind(gZenGlanceManager));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button._placesNode = aChild;
|
||||||
|
let { icon } = button._placesNode;
|
||||||
|
if (icon) {
|
||||||
|
button.setAttribute("image", icon);
|
||||||
|
}
|
||||||
|
if (!this._domNodes.has(aChild)) {
|
||||||
|
this._domNodes.set(aChild, button);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aBefore) {
|
||||||
|
aInsertionNode.insertBefore(button, aBefore);
|
||||||
|
} else {
|
||||||
|
aInsertionNode.appendChild(button);
|
||||||
|
}
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleEvent(aEvent) {
|
||||||
|
switch (aEvent.type) {
|
||||||
|
case "unload":
|
||||||
|
this.uninit();
|
||||||
|
break;
|
||||||
|
case "dragstart":
|
||||||
|
this._onDragStart(aEvent);
|
||||||
|
break;
|
||||||
|
case "dragover":
|
||||||
|
this._onDragOver(aEvent);
|
||||||
|
break;
|
||||||
|
case "dragleave":
|
||||||
|
this._onDragLeave(aEvent);
|
||||||
|
break;
|
||||||
|
case "dragend":
|
||||||
|
this._onDragEnd(aEvent);
|
||||||
|
break;
|
||||||
|
case "drop":
|
||||||
|
this._onDrop(aEvent);
|
||||||
|
break;
|
||||||
|
case "mouseover":
|
||||||
|
this._onMouseOver(aEvent);
|
||||||
|
break;
|
||||||
|
case "mousemove":
|
||||||
|
this._onMouseMove(aEvent);
|
||||||
|
break;
|
||||||
|
case "mouseout":
|
||||||
|
this._onMouseOut(aEvent);
|
||||||
|
break;
|
||||||
|
case "mousedown":
|
||||||
|
this._onMouseDown(aEvent);
|
||||||
|
break;
|
||||||
|
case "popupshowing":
|
||||||
|
this._onPopupShowing(aEvent);
|
||||||
|
break;
|
||||||
|
case "popuphidden":
|
||||||
|
this._onPopupHidden(aEvent);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error("Trying to handle unexpected event.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
nodeInserted(aParentPlacesNode, aPlacesNode, aIndex) {
|
||||||
|
let parentElt = this._getDOMNodeForPlacesNode(aParentPlacesNode);
|
||||||
|
if (parentElt == this._rootElt) {
|
||||||
|
let children = this._rootElt.children;
|
||||||
|
if (aIndex > children.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._insertNewItem(
|
||||||
|
aPlacesNode,
|
||||||
|
this._rootElt,
|
||||||
|
children[aIndex] || null
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
super.nodeInserted(aParentPlacesNode, aPlacesNode, aIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeRemoved(aParentPlacesNode, aPlacesNode, aIndex) {
|
||||||
|
let parentElt = this._getDOMNodeForPlacesNode(aParentPlacesNode);
|
||||||
|
if (parentElt == this._rootElt) {
|
||||||
|
let elt = this._getDOMNodeForPlacesNode(aPlacesNode, true);
|
||||||
|
if (!elt) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elt.localName == "menupopup") {
|
||||||
|
elt = elt.parentNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._removeChild(elt);
|
||||||
|
if (this._resultNode.childCount > this._rootElt.children.length) {
|
||||||
|
this._insertNewItem(
|
||||||
|
this._resultNode.getChild(this._rootElt.children.length),
|
||||||
|
this._rootElt
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
super.nodeRemoved(aParentPlacesNode, aPlacesNode, aIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeMoved(aPlacesNode, aOldParentPlacesNode, aOldIndex, aNewParentPlacesNode, aNewIndex) {
|
||||||
|
let parentElt = this._getDOMNodeForPlacesNode(aNewParentPlacesNode);
|
||||||
|
if (parentElt == this._rootElt) {
|
||||||
|
let elt = this._getDOMNodeForPlacesNode(aPlacesNode, true);
|
||||||
|
if (elt) {
|
||||||
|
if (elt.localName == "menupopup") {
|
||||||
|
elt = elt.parentNode;
|
||||||
|
}
|
||||||
|
this._removeChild(elt);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._insertNewItem(
|
||||||
|
aPlacesNode,
|
||||||
|
this._rootElt,
|
||||||
|
this._rootElt.children[aNewIndex]
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
super.nodeMoved(
|
||||||
|
aPlacesNode,
|
||||||
|
aOldParentPlacesNode,
|
||||||
|
aOldIndex,
|
||||||
|
aNewParentPlacesNode,
|
||||||
|
aNewIndex
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeTitleChanged(aPlacesNode, aNewTitle) {
|
||||||
|
let elt = this._getDOMNodeForPlacesNode(aPlacesNode, true);
|
||||||
|
if (!elt || elt == this._rootElt) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
super.nodeTitleChanged(aPlacesNode, aNewTitle);
|
||||||
|
|
||||||
|
if (elt.localName == "menupopup") {
|
||||||
|
elt = elt.parentNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
invalidateContainer(aPlacesNode) {
|
||||||
|
let elt = this._getDOMNodeForPlacesNode(aPlacesNode, true);
|
||||||
|
// Nothing to do if it's a never-visible node.
|
||||||
|
if (!elt) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elt == this._rootElt) {
|
||||||
|
// Container is the toolbar itself.
|
||||||
|
let instance = (this._rebuildingInstance = {});
|
||||||
|
if (!this._rebuilding) {
|
||||||
|
this._rebuilding = Promise.withResolvers();
|
||||||
|
}
|
||||||
|
this._rebuild()
|
||||||
|
.catch(console.error)
|
||||||
|
.finally(() => {
|
||||||
|
if (instance == this._rebuildingInstance) {
|
||||||
|
this._rebuilding.resolve();
|
||||||
|
this._rebuilding = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
super.invalidateContainer(aPlacesNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
_clearOverFolder() {
|
||||||
|
// The mouse is no longer dragging over the stored menubutton.
|
||||||
|
// Close the menubutton, clear out drag styles, and clear all
|
||||||
|
// timers for opening/closing it.
|
||||||
|
if (this._overFolder.elt && this._overFolder.elt.menupopup) {
|
||||||
|
if (!this._overFolder.elt.menupopup.hasAttribute("dragover")) {
|
||||||
|
this._overFolder.elt.menupopup.hidePopup();
|
||||||
|
}
|
||||||
|
this._overFolder.elt.removeAttribute("dragover");
|
||||||
|
this._overFolder.elt = null;
|
||||||
|
}
|
||||||
|
if (this._overFolder.openTimer) {
|
||||||
|
this._overFolder.openTimer.cancel();
|
||||||
|
this._overFolder.openTimer = null;
|
||||||
|
}
|
||||||
|
if (this._overFolder.closeTimer) {
|
||||||
|
this._overFolder.closeTimer.cancel();
|
||||||
|
this._overFolder.closeTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines the drop target while dragging over the vertical toolbar.
|
||||||
|
*
|
||||||
|
* @param {object} aEvent
|
||||||
|
* The drag event.
|
||||||
|
* @returns {object}
|
||||||
|
* - ip: The insertion point for the bookmarks service.
|
||||||
|
* - beforeIndex: Child index to drop before, for the drop indicator.
|
||||||
|
* - folderElt: The folder to drop into, if applicable.
|
||||||
|
*/
|
||||||
|
_getDropPoint(aEvent) {
|
||||||
|
if (!PlacesUtils.nodeIsFolderOrShortcut(this._resultNode)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dropPoint = { ip: null, beforeIndex: null, folderElt: null };
|
||||||
|
let elt = aEvent.target;
|
||||||
|
|
||||||
|
// If we're not dragging over a child element, handle dropping at the end
|
||||||
|
if (!elt._placesNode || elt == this._rootElt || elt.localName == "menupopup") {
|
||||||
|
dropPoint.ip = new PlacesInsertionPoint({
|
||||||
|
parentGuid: PlacesUtils.getConcreteItemGuid(this._resultNode),
|
||||||
|
orientation: Ci.nsITreeView.DROP_BEFORE
|
||||||
|
});
|
||||||
|
dropPoint.beforeIndex = -1;
|
||||||
|
|
||||||
|
// Find the closest child based on vertical position
|
||||||
|
for (let i = 0; i < this._rootElt.children.length; i++) {
|
||||||
|
let childRect = this._rootElt.children[i].getBoundingClientRect();
|
||||||
|
if (aEvent.clientY <= childRect.top) {
|
||||||
|
dropPoint.beforeIndex = i;
|
||||||
|
dropPoint.ip.index = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dropPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get target element's position info
|
||||||
|
let eltRect = elt.getBoundingClientRect();
|
||||||
|
let eltIndex = Array.prototype.indexOf.call(this._rootElt.children, elt);
|
||||||
|
|
||||||
|
// Handle dropping on folders
|
||||||
|
if (PlacesUtils.nodeIsFolderOrShortcut(elt._placesNode) &&
|
||||||
|
!PlacesUIUtils.isFolderReadOnly(elt._placesNode)) {
|
||||||
|
|
||||||
|
// Define drop zones: top 25%, middle 50%, bottom 25%
|
||||||
|
let topThreshold = eltRect.top + (eltRect.height * 0.25);
|
||||||
|
let bottomThreshold = eltRect.bottom - (eltRect.height * 0.25);
|
||||||
|
|
||||||
|
if (aEvent.clientY < topThreshold) {
|
||||||
|
// Drop before folder
|
||||||
|
dropPoint.ip = new PlacesInsertionPoint({
|
||||||
|
parentGuid: PlacesUtils.getConcreteItemGuid(this._resultNode),
|
||||||
|
index: eltIndex,
|
||||||
|
orientation: Ci.nsITreeView.DROP_BEFORE
|
||||||
|
});
|
||||||
|
dropPoint.beforeIndex = eltIndex;
|
||||||
|
} else if (aEvent.clientY > bottomThreshold) {
|
||||||
|
// Drop after folder
|
||||||
|
let beforeIndex = eltIndex == this._rootElt.children.length - 1 ? -1 : eltIndex + 1;
|
||||||
|
dropPoint.ip = new PlacesInsertionPoint({
|
||||||
|
parentGuid: PlacesUtils.getConcreteItemGuid(this._resultNode),
|
||||||
|
index: beforeIndex,
|
||||||
|
orientation: Ci.nsITreeView.DROP_BEFORE
|
||||||
|
});
|
||||||
|
dropPoint.beforeIndex = beforeIndex;
|
||||||
|
} else {
|
||||||
|
// Drop inside folder
|
||||||
|
let tagName = PlacesUtils.nodeIsTagQuery(elt._placesNode)
|
||||||
|
? elt._placesNode.title
|
||||||
|
: null;
|
||||||
|
dropPoint.ip = new PlacesInsertionPoint({
|
||||||
|
parentGuid: PlacesUtils.getConcreteItemGuid(elt._placesNode),
|
||||||
|
tagName
|
||||||
|
});
|
||||||
|
dropPoint.beforeIndex = eltIndex;
|
||||||
|
dropPoint.folderElt = elt;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Handle dropping around non-folder items
|
||||||
|
let midPoint = eltRect.top + (eltRect.height / 2);
|
||||||
|
|
||||||
|
if (aEvent.clientY < midPoint) {
|
||||||
|
// Drop before item
|
||||||
|
dropPoint.ip = new PlacesInsertionPoint({
|
||||||
|
parentGuid: PlacesUtils.getConcreteItemGuid(this._resultNode),
|
||||||
|
index: eltIndex,
|
||||||
|
orientation: Ci.nsITreeView.DROP_BEFORE
|
||||||
|
});
|
||||||
|
dropPoint.beforeIndex = eltIndex;
|
||||||
|
} else {
|
||||||
|
// Drop after item
|
||||||
|
let beforeIndex = eltIndex == this._rootElt.children.length - 1 ? -1 : eltIndex + 1;
|
||||||
|
dropPoint.ip = new PlacesInsertionPoint({
|
||||||
|
parentGuid: PlacesUtils.getConcreteItemGuid(this._resultNode),
|
||||||
|
index: beforeIndex,
|
||||||
|
orientation: Ci.nsITreeView.DROP_BEFORE
|
||||||
|
});
|
||||||
|
dropPoint.beforeIndex = beforeIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dropPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
_setTimer(aTime) {
|
||||||
|
let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||||
|
timer.initWithCallback(this, aTime, timer.TYPE_ONE_SHOT);
|
||||||
|
return timer;
|
||||||
|
}
|
||||||
|
|
||||||
|
get name() {
|
||||||
|
return "ZenEssentialsToolbar";
|
||||||
|
}
|
||||||
|
|
||||||
|
notify(aTimer) {
|
||||||
|
if (aTimer == this._overFolder.openTimer) {
|
||||||
|
this._overFolder.elt.menupopup.setAttribute("autoopened", "true");
|
||||||
|
this._overFolder.elt.open = true;
|
||||||
|
this._overFolder.openTimer = null;
|
||||||
|
} else if (aTimer == this._overFolder.closeTimer) {
|
||||||
|
let currentPlacesNode = PlacesControllerDragHelper.currentDropTarget;
|
||||||
|
let inHierarchy = false;
|
||||||
|
while (currentPlacesNode) {
|
||||||
|
if (currentPlacesNode == this._rootElt) {
|
||||||
|
inHierarchy = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
currentPlacesNode = currentPlacesNode.parentNode;
|
||||||
|
}
|
||||||
|
if (inHierarchy) {
|
||||||
|
this._overFolder.elt = null;
|
||||||
|
}
|
||||||
|
this._clearOverFolder();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_onMouseOver(aEvent) {
|
||||||
|
let button = aEvent.target;
|
||||||
|
if (
|
||||||
|
button.parentNode == this._rootElt &&
|
||||||
|
button._placesNode &&
|
||||||
|
PlacesUtils.nodeIsURI(button._placesNode)
|
||||||
|
) {
|
||||||
|
window.XULBrowserWindow.setOverLink(aEvent.target._placesNode.uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_onMouseOut() {
|
||||||
|
window.XULBrowserWindow.setOverLink("");
|
||||||
|
}
|
||||||
|
|
||||||
|
_onMouseDown(aEvent) {
|
||||||
|
let target = aEvent.target;
|
||||||
|
if (
|
||||||
|
aEvent.button == 0 &&
|
||||||
|
target.localName == "toolbarbutton" &&
|
||||||
|
target.getAttribute("type") == "menu"
|
||||||
|
) {
|
||||||
|
|
||||||
|
|
||||||
|
let modifKey = aEvent.shiftKey || aEvent.getModifierState("Accel");
|
||||||
|
if (modifKey) {
|
||||||
|
// Do not open the popup since BEH_onClick is about to
|
||||||
|
// open all child uri nodes in tabs.
|
||||||
|
this._allowPopupShowing = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PlacesUIUtils.maybeSpeculativeConnectOnMouseDown(aEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
_cleanupDragDetails() {
|
||||||
|
// Called on dragend and drop.
|
||||||
|
PlacesControllerDragHelper.currentDropTarget = null;
|
||||||
|
this._draggedElt = null;
|
||||||
|
this._dropIndicator.collapsed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_onDragStart(aEvent) {
|
||||||
|
// Sub menus have their own d&d handlers.
|
||||||
|
let draggedElt = aEvent.target;
|
||||||
|
if (draggedElt.parentNode != this._rootElt || !draggedElt._placesNode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
draggedElt.localName == "toolbarbutton" &&
|
||||||
|
draggedElt.getAttribute("type") == "menu"
|
||||||
|
) {
|
||||||
|
// If the drag gesture on a container is toward down we open instead
|
||||||
|
// of dragging.
|
||||||
|
let translateY = this._cachedMouseMoveEvent.clientY - aEvent.clientY;
|
||||||
|
let translateX = this._cachedMouseMoveEvent.clientX - aEvent.clientX;
|
||||||
|
if (translateY >= Math.abs(translateX / 2)) {
|
||||||
|
// Don't start the drag.
|
||||||
|
aEvent.preventDefault();
|
||||||
|
// Open the menu.
|
||||||
|
draggedElt.open = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the menu is open, close it.
|
||||||
|
if (draggedElt.open) {
|
||||||
|
draggedElt.menupopup.hidePopup();
|
||||||
|
draggedElt.open = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Activate the view and cache the dragged element.
|
||||||
|
this._draggedElt = draggedElt._placesNode;
|
||||||
|
this._rootElt.focus();
|
||||||
|
|
||||||
|
this._controller.setDataTransfer(aEvent);
|
||||||
|
aEvent.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
_onDragOver(aEvent) {
|
||||||
|
// Cache the dataTransfer
|
||||||
|
PlacesControllerDragHelper.currentDropTarget = aEvent.target;
|
||||||
|
let dt = aEvent.dataTransfer;
|
||||||
|
|
||||||
|
let dropPoint = this._getDropPoint(aEvent);
|
||||||
|
if (
|
||||||
|
!dropPoint ||
|
||||||
|
!dropPoint.ip ||
|
||||||
|
!PlacesControllerDragHelper.canDrop(dropPoint.ip, dt)
|
||||||
|
) {
|
||||||
|
this._dropIndicator.collapsed = true;
|
||||||
|
aEvent.stopPropagation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dropPoint.folderElt) {
|
||||||
|
let overElt = dropPoint.folderElt;
|
||||||
|
if (this._overFolder.elt != overElt) {
|
||||||
|
this._clearOverFolder();
|
||||||
|
this._overFolder.elt = overElt;
|
||||||
|
this._overFolder.openTimer = this._setTimer(this._overFolder.hoverTime);
|
||||||
|
}
|
||||||
|
if (!this._overFolder.elt.hasAttribute("dragover")) {
|
||||||
|
this._overFolder.elt.setAttribute("dragover", "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
this._dropIndicator.collapsed = true;
|
||||||
|
} else {
|
||||||
|
// Dragging over a normal toolbarbutton,
|
||||||
|
// show indicator bar and move it to the appropriate drop point.
|
||||||
|
let ind = this._dropIndicator;
|
||||||
|
ind.parentNode.collapsed = false;
|
||||||
|
let halfInd = ind.clientWidth / 2;
|
||||||
|
let translateX;
|
||||||
|
if (this.isRTL) {
|
||||||
|
halfInd = Math.ceil(halfInd);
|
||||||
|
translateX = 0 - this._rootElt.getBoundingClientRect().right - halfInd;
|
||||||
|
if (this._rootElt.firstElementChild) {
|
||||||
|
if (dropPoint.beforeIndex == -1) {
|
||||||
|
translateX +=
|
||||||
|
this._rootElt.lastElementChild.getBoundingClientRect().left;
|
||||||
|
} else {
|
||||||
|
translateX +=
|
||||||
|
this._rootElt.children[
|
||||||
|
dropPoint.beforeIndex
|
||||||
|
].getBoundingClientRect().right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
halfInd = Math.floor(halfInd);
|
||||||
|
translateX = 0 - this._rootElt.getBoundingClientRect().left + halfInd;
|
||||||
|
if (this._rootElt.firstElementChild) {
|
||||||
|
if (dropPoint.beforeIndex == -1) {
|
||||||
|
translateX +=
|
||||||
|
this._rootElt.lastElementChild.getBoundingClientRect().right;
|
||||||
|
} else {
|
||||||
|
translateX +=
|
||||||
|
this._rootElt.children[
|
||||||
|
dropPoint.beforeIndex
|
||||||
|
].getBoundingClientRect().left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ind.style.transform = "translate(" + Math.round(translateX) + "px)";
|
||||||
|
ind.style.marginInlineStart = -ind.clientWidth + "px";
|
||||||
|
ind.collapsed = false;
|
||||||
|
|
||||||
|
// Clear out old folder information.
|
||||||
|
this._clearOverFolder();
|
||||||
|
}
|
||||||
|
|
||||||
|
aEvent.preventDefault();
|
||||||
|
aEvent.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
_onDrop(aEvent) {
|
||||||
|
PlacesControllerDragHelper.currentDropTarget = aEvent.target;
|
||||||
|
|
||||||
|
let dropPoint = this._getDropPoint(aEvent);
|
||||||
|
if (dropPoint && dropPoint.ip) {
|
||||||
|
PlacesControllerDragHelper.onDrop(
|
||||||
|
dropPoint.ip,
|
||||||
|
aEvent.dataTransfer
|
||||||
|
).catch(console.error);
|
||||||
|
aEvent.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
this._cleanupDragDetails();
|
||||||
|
aEvent.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
_onDragLeave() {
|
||||||
|
PlacesControllerDragHelper.currentDropTarget = null;
|
||||||
|
|
||||||
|
this._dropIndicator.collapsed = true;
|
||||||
|
|
||||||
|
// If we hovered over a folder, close it now.
|
||||||
|
if (this._overFolder.elt) {
|
||||||
|
this._overFolder.closeTimer = this._setTimer(this._overFolder.hoverTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_onDragEnd() {
|
||||||
|
this._cleanupDragDetails();
|
||||||
|
}
|
||||||
|
|
||||||
|
_onPopupShowing(aEvent) {
|
||||||
|
if (!this._allowPopupShowing) {
|
||||||
|
this._allowPopupShowing = true;
|
||||||
|
aEvent.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let parent = aEvent.target.parentNode;
|
||||||
|
if (parent.localName == "toolbarbutton") {
|
||||||
|
this._openedMenuButton = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
super._onPopupShowing(aEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
_onPopupHidden(aEvent) {
|
||||||
|
let popup = aEvent.target;
|
||||||
|
let placesNode = popup._placesNode;
|
||||||
|
// Avoid handling popuphidden of inner views
|
||||||
|
if (
|
||||||
|
placesNode &&
|
||||||
|
PlacesUIUtils.getViewForNode(popup) == this &&
|
||||||
|
// UI performance: folder queries are cheap, keep the resultnode open
|
||||||
|
// so we don't rebuild its contents whenever the popup is reopened.
|
||||||
|
!PlacesUtils.nodeIsFolderOrShortcut(placesNode)
|
||||||
|
) {
|
||||||
|
placesNode.containerOpen = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let parent = popup.parentNode;
|
||||||
|
if (parent.localName == "toolbarbutton") {
|
||||||
|
this._openedMenuButton = null;
|
||||||
|
// Clear the dragover attribute if present, if we are dragging into a
|
||||||
|
// folder in the hierachy of current opened popup we don't clear
|
||||||
|
// this attribute on clearOverFolder. See Notify for closeTimer.
|
||||||
|
if (parent.hasAttribute("dragover")) {
|
||||||
|
parent.removeAttribute("dragover");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_onMouseMove(aEvent) {
|
||||||
|
// Used in dragStart to prevent dragging folders when dragging down.
|
||||||
|
this._cachedMouseMoveEvent = aEvent;
|
||||||
|
|
||||||
|
if (
|
||||||
|
this._openedMenuButton == null ||
|
||||||
|
PlacesControllerDragHelper.getSession()
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let target = aEvent.originalTarget;
|
||||||
|
if (
|
||||||
|
this._openedMenuButton != target &&
|
||||||
|
target.localName == "toolbarbutton" &&
|
||||||
|
target.type == "menu"
|
||||||
|
) {
|
||||||
|
this._openedMenuButton.open = false;
|
||||||
|
target.open = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -294,6 +294,38 @@
|
||||||
});
|
});
|
||||||
}, 300);
|
}, 300);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
openGlanceForBookmark(event) {
|
||||||
|
const activationMethod = Services.prefs.getStringPref('zen.glance.activation-method', 'ctrl');
|
||||||
|
|
||||||
|
if (activationMethod === 'ctrl' && !event.ctrlKey) {
|
||||||
|
return;
|
||||||
|
} else if (activationMethod === 'alt' && !event.altKey) {
|
||||||
|
return;
|
||||||
|
} else if (activationMethod === 'shift' && !event.shiftKey) {
|
||||||
|
return;
|
||||||
|
} else if (activationMethod === 'meta' && !event.metaKey) {
|
||||||
|
return;
|
||||||
|
}else if (activationMethod === 'mantain' || typeof activationMethod === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
const rect = event.target.getBoundingClientRect();
|
||||||
|
const data = {
|
||||||
|
url: event.target._placesNode.uri,
|
||||||
|
x: rect.left,
|
||||||
|
y: rect.top,
|
||||||
|
width: rect.width,
|
||||||
|
height: rect.height,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.openGlance(data);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.gZenGlanceManager = new ZenGlanceManager();
|
window.gZenGlanceManager = new ZenGlanceManager();
|
||||||
|
|
|
@ -783,6 +783,27 @@ class ZenBrowserManagerSidebar extends ZenDOMOperatedFeature {
|
||||||
const url = gContextMenu.linkURL || gContextMenu.target.ownerDocument.location.href;
|
const url = gContextMenu.linkURL || gContextMenu.target.ownerDocument.location.href;
|
||||||
this._createNewPanel(url);
|
this._createNewPanel(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleEssentialsAccordion(header) {
|
||||||
|
const content = document.getElementById('EssentialsToolbarItems');
|
||||||
|
const isExpanded = header.hasAttribute('expanded');
|
||||||
|
|
||||||
|
if (isExpanded) {
|
||||||
|
// Collapse
|
||||||
|
header.removeAttribute('expanded');
|
||||||
|
content.style.maxHeight = null;
|
||||||
|
content.style.opacity = '0';
|
||||||
|
setTimeout(() => {
|
||||||
|
content.removeAttribute('expanded');
|
||||||
|
}, 300);
|
||||||
|
} else {
|
||||||
|
// Expand
|
||||||
|
header.setAttribute('expanded', 'true');
|
||||||
|
content.setAttribute('expanded', 'true');
|
||||||
|
content.style.maxHeight = content.scrollHeight + "px";
|
||||||
|
content.style.opacity = '1';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.gZenBrowserManagerSidebar = new ZenBrowserManagerSidebar();
|
window.gZenBrowserManagerSidebar = new ZenBrowserManagerSidebar();
|
||||||
|
|
|
@ -941,6 +941,17 @@ var ZenWorkspaces = new (class extends ZenMultiWindowFeature {
|
||||||
for (let listener of this._changeListeners ?? []) {
|
for (let listener of this._changeListeners ?? []) {
|
||||||
listener(window);
|
listener(window);
|
||||||
}
|
}
|
||||||
|
// reset bookmark toolbars
|
||||||
|
const placesToolbar = document.getElementById("PlacesToolbar");
|
||||||
|
if(placesToolbar?._placesView) {
|
||||||
|
placesToolbar._placesView.invalidateContainer(placesToolbar._placesView._resultNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
const essentialsToolbar = document.getElementById("EssentialsToolbar");
|
||||||
|
if(essentialsToolbar?._placesView) {
|
||||||
|
essentialsToolbar._placesView.invalidateContainer(essentialsToolbar._placesView._resultNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
this._inChangingWorkspace = false;
|
this._inChangingWorkspace = false;
|
||||||
}
|
}
|
||||||
|
@ -1194,4 +1205,14 @@ var ZenWorkspaces = new (class extends ZenMultiWindowFeature {
|
||||||
const workspaceToSwitch = workspaces.workspaces[index];
|
const workspaceToSwitch = workspaces.workspaces[index];
|
||||||
await this.changeWorkspace(workspaceToSwitch);
|
await this.changeWorkspace(workspaceToSwitch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isBookmarkInAnotherWorkspace(bookmark) {
|
||||||
|
let tags = bookmark.tags;
|
||||||
|
// if any tag starts with "_workspace_id" and the workspace id doesnt match the active workspace id, return null
|
||||||
|
if (tags) {
|
||||||
|
for (let tag of tags.split(",")) {
|
||||||
|
return !!(tag.startsWith("zen_workspace_") && this.getActiveWorkspaceFromCache()?.uuid !== tag.split("_")[2]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
})();
|
})();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue