mirror of
https://github.com/zen-browser/desktop.git
synced 2025-10-05 17:36:34 +00:00
Added workspace selector on bookmarks create/edit modal, added table for storing relationships between workspaces and bookmarks.
This commit is contained in:
@@ -13,6 +13,13 @@ var ZenWorkspaces = new (class extends ZenMultiWindowFeature {
|
||||
};
|
||||
_hoveringSidebar = false;
|
||||
_lastScrollTime = 0;
|
||||
bookmarkMenus = [
|
||||
"PlacesToolbar",
|
||||
"bookmarks-menu-button",
|
||||
"BMB_bookmarksToolbar",
|
||||
"BMB_unsortedBookmarks",
|
||||
"BMB_mobileBookmarks"
|
||||
];
|
||||
|
||||
async init() {
|
||||
if (!this.shouldHaveWorkspaces) {
|
||||
@@ -61,6 +68,11 @@ var ZenWorkspaces = new (class extends ZenMultiWindowFeature {
|
||||
}
|
||||
|
||||
Services.obs.addObserver(this, 'weave:engine:sync:finish');
|
||||
Services.obs.addObserver(async function observe(subject) {
|
||||
this._workspaceBookmarksCache = null;
|
||||
await this.workspaceBookmarks();
|
||||
this._invalidateBookmarkContainers();
|
||||
}.bind(this), "workspace-bookmarks-updated");
|
||||
}
|
||||
|
||||
initializeWorkspaceNavigation() {
|
||||
@@ -128,7 +140,7 @@ var ZenWorkspaces = new (class extends ZenMultiWindowFeature {
|
||||
}
|
||||
|
||||
// Change workspace based on scroll direction
|
||||
const direction = event.deltaX > 0 ? -1 : 1;
|
||||
const direction = event.deltaX > 0 ? 1 : -1;
|
||||
await this.changeWorkspaceShortcut(direction);
|
||||
this._lastScrollTime = currentTime;
|
||||
}, { passive: true });
|
||||
@@ -320,6 +332,21 @@ var ZenWorkspaces = new (class extends ZenMultiWindowFeature {
|
||||
return this._workspaceCache;
|
||||
}
|
||||
|
||||
async workspaceBookmarks() {
|
||||
if (this._workspaceBookmarksCache) {
|
||||
return this._workspaceBookmarksCache;
|
||||
}
|
||||
|
||||
const [bookmarks, lastChangeTimestamp] = await Promise.all([
|
||||
ZenWorkspaceBookmarksStorage.getBookmarkGuidsByWorkspace(),
|
||||
ZenWorkspaceBookmarksStorage.getLastChangeTimestamp(),
|
||||
]);
|
||||
|
||||
this._workspaceBookmarksCache = { bookmarks, lastChangeTimestamp };
|
||||
|
||||
return this._workspaceCache;
|
||||
}
|
||||
|
||||
async onWorkspacesEnabledChanged() {
|
||||
if (this.workspaceEnabled) {
|
||||
throw Error("Shoud've had reloaded the window");
|
||||
@@ -339,6 +366,7 @@ var ZenWorkspaces = new (class extends ZenMultiWindowFeature {
|
||||
if (this.workspaceEnabled) {
|
||||
this._initializeWorkspaceCreationIcons();
|
||||
this._initializeWorkspaceTabContextMenus();
|
||||
await this.workspaceBookmarks();
|
||||
window.addEventListener('TabBrowserInserted', this.onTabBrowserInserted.bind(this));
|
||||
await SessionStore.promiseInitialized;
|
||||
let workspaces = await this._workspaces();
|
||||
@@ -754,8 +782,10 @@ var ZenWorkspaces = new (class extends ZenMultiWindowFeature {
|
||||
|
||||
if(clearCache) {
|
||||
browser.ZenWorkspaces._workspaceCache = null;
|
||||
browser.ZenWorkspaces._bookmarkWorkspaceCache = null;
|
||||
}
|
||||
let workspaces = await browser.ZenWorkspaces._workspaces();
|
||||
await browser.ZenWorkspaces.workspaceBookmarks();
|
||||
workspaceList.innerHTML = '';
|
||||
workspaceList.parentNode.style.display = 'flex';
|
||||
if (workspaces.workspaces.length <= 0) {
|
||||
@@ -1252,16 +1282,23 @@ var ZenWorkspaces = new (class extends ZenMultiWindowFeature {
|
||||
}
|
||||
}
|
||||
|
||||
// Reset bookmarks toolbar
|
||||
const placesToolbar = document.getElementById("PlacesToolbar");
|
||||
if (placesToolbar?._placesView) {
|
||||
placesToolbar._placesView.invalidateContainer(placesToolbar._placesView._resultNode);
|
||||
}
|
||||
// Reset bookmarks
|
||||
this._invalidateBookmarkContainers();
|
||||
|
||||
// Update workspace indicator
|
||||
await this.updateWorkspaceIndicator();
|
||||
}
|
||||
|
||||
_invalidateBookmarkContainers() {
|
||||
for (let i = 0, len = this.bookmarkMenus.length; i < len; i++) {
|
||||
const element = document.getElementById(this.bookmarkMenus[i]);
|
||||
if (element && element._placesView) {
|
||||
const placesView = element._placesView;
|
||||
placesView.invalidateContainer(placesView._resultNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async updateWorkspaceIndicator() {
|
||||
// Update current workspace indicator
|
||||
const currentWorkspace = await this.getActiveWorkspace();
|
||||
@@ -1534,12 +1571,24 @@ var ZenWorkspaces = new (class extends ZenMultiWindowFeature {
|
||||
}
|
||||
|
||||
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]);
|
||||
if (!this._workspaceBookmarksCache?.bookmarks) return false;
|
||||
const bookmarkGuid = bookmark.bookmarkGuid;
|
||||
const activeWorkspaceUuid = this.activeWorkspace;
|
||||
let isInActiveWorkspace = false;
|
||||
let isInOtherWorkspace = false;
|
||||
|
||||
for (const [workspaceUuid, bookmarkGuids] of Object.entries(this._workspaceBookmarksCache.bookmarks)) {
|
||||
if (bookmarkGuids.includes(bookmarkGuid)) {
|
||||
if (workspaceUuid === activeWorkspaceUuid) {
|
||||
isInActiveWorkspace = true;
|
||||
} else {
|
||||
isInOtherWorkspace = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return true only if the bookmark is in another workspace and not in the active one
|
||||
return isInOtherWorkspace && !isInActiveWorkspace;
|
||||
}
|
||||
|
||||
})();
|
||||
|
@@ -2,6 +2,8 @@ var ZenWorkspacesStorage = {
|
||||
async init() {
|
||||
console.log('ZenWorkspacesStorage: Initializing...');
|
||||
await this._ensureTable();
|
||||
await ZenWorkspaceBookmarksStorage.init();
|
||||
ZenWorkspaces._delayedStartup();
|
||||
},
|
||||
|
||||
async _ensureTable() {
|
||||
@@ -64,7 +66,6 @@ var ZenWorkspacesStorage = {
|
||||
await ZenWorkspacesStorage.migrateWorkspacesFromJSON();
|
||||
}
|
||||
|
||||
ZenWorkspaces._delayedStartup();
|
||||
});
|
||||
},
|
||||
|
||||
@@ -405,3 +406,152 @@ var ZenWorkspacesStorage = {
|
||||
this._notifyWorkspacesChanged("zen-workspace-updated", Array.from(changedUUIDs));
|
||||
},
|
||||
};
|
||||
|
||||
// Integration of workspace-specific bookmarks into Places
|
||||
var ZenWorkspaceBookmarksStorage = {
|
||||
async init() {
|
||||
await this._ensureTable();
|
||||
},
|
||||
|
||||
async _ensureTable() {
|
||||
await PlacesUtils.withConnectionWrapper('ZenWorkspaceBookmarksStorage.init', async (db) => {
|
||||
// Create table using GUIDs instead of IDs
|
||||
await db.execute(`
|
||||
CREATE TABLE IF NOT EXISTS zen_bookmarks_workspaces (
|
||||
id INTEGER PRIMARY KEY,
|
||||
bookmark_guid TEXT NOT NULL,
|
||||
workspace_uuid TEXT NOT NULL,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL,
|
||||
UNIQUE(bookmark_guid, workspace_uuid),
|
||||
FOREIGN KEY(workspace_uuid) REFERENCES zen_workspaces(uuid) ON DELETE CASCADE,
|
||||
FOREIGN KEY(bookmark_guid) REFERENCES moz_bookmarks(guid) ON DELETE CASCADE
|
||||
)
|
||||
`);
|
||||
|
||||
// Create index for fast lookups
|
||||
await db.execute(`
|
||||
CREATE INDEX IF NOT EXISTS idx_bookmarks_workspaces_lookup
|
||||
ON zen_bookmarks_workspaces(workspace_uuid, bookmark_guid)
|
||||
`);
|
||||
|
||||
// Add changes tracking table
|
||||
await db.execute(`
|
||||
CREATE TABLE IF NOT EXISTS zen_bookmarks_workspaces_changes (
|
||||
id INTEGER PRIMARY KEY,
|
||||
bookmark_guid TEXT NOT NULL,
|
||||
workspace_uuid TEXT NOT NULL,
|
||||
change_type TEXT NOT NULL,
|
||||
timestamp INTEGER NOT NULL,
|
||||
UNIQUE(bookmark_guid, workspace_uuid),
|
||||
FOREIGN KEY(workspace_uuid) REFERENCES zen_workspaces(uuid) ON DELETE CASCADE,
|
||||
FOREIGN KEY(bookmark_guid) REFERENCES moz_bookmarks(guid) ON DELETE CASCADE
|
||||
)
|
||||
`);
|
||||
|
||||
// Create index for changes tracking
|
||||
await db.execute(`
|
||||
CREATE INDEX IF NOT EXISTS idx_bookmarks_workspaces_changes
|
||||
ON zen_bookmarks_workspaces_changes(bookmark_guid, workspace_uuid)
|
||||
`);
|
||||
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the last change timestamp in the metadata table.
|
||||
* @param {Object} db - The database connection.
|
||||
*/
|
||||
async updateLastChangeTimestamp(db) {
|
||||
const now = Date.now();
|
||||
await db.execute(`
|
||||
INSERT OR REPLACE INTO moz_meta (key, value)
|
||||
VALUES ('zen_bookmarks_workspaces_last_change', :now)
|
||||
`, { now });
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the timestamp of the last change.
|
||||
* @returns {Promise<number>} The timestamp of the last change.
|
||||
*/
|
||||
async getLastChangeTimestamp() {
|
||||
const db = await PlacesUtils.promiseDBConnection();
|
||||
const result = await db.executeCached(`
|
||||
SELECT value FROM moz_meta WHERE key = 'zen_bookmarks_workspaces_last_change'
|
||||
`);
|
||||
return result.length ? parseInt(result[0].getResultByName('value'), 10) : 0;
|
||||
},
|
||||
|
||||
async getBookmarkWorkspaces(bookmarkGuid) {
|
||||
const db = await PlacesUtils.promiseDBConnection();
|
||||
|
||||
const rows = await db.execute(`
|
||||
SELECT workspace_uuid
|
||||
FROM zen_bookmarks_workspaces
|
||||
WHERE bookmark_guid = :bookmark_guid
|
||||
`, { bookmark_guid: bookmarkGuid });
|
||||
|
||||
return rows.map(row => row.getResultByName("workspace_uuid"));
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all bookmark GUIDs organized by workspace UUID.
|
||||
* @returns {Promise<Object>} A dictionary with workspace UUIDs as keys and arrays of bookmark GUIDs as values.
|
||||
* @example
|
||||
* // Returns:
|
||||
* {
|
||||
* "workspace-uuid-1": ["bookmark-guid-1", "bookmark-guid-2"],
|
||||
* "workspace-uuid-2": ["bookmark-guid-3"]
|
||||
* }
|
||||
*/
|
||||
async getBookmarkGuidsByWorkspace() {
|
||||
const db = await PlacesUtils.promiseDBConnection();
|
||||
|
||||
const rows = await db.execute(`
|
||||
SELECT workspace_uuid, GROUP_CONCAT(bookmark_guid) as bookmark_guids
|
||||
FROM zen_bookmarks_workspaces
|
||||
GROUP BY workspace_uuid
|
||||
`);
|
||||
|
||||
const result = {};
|
||||
for (const row of rows) {
|
||||
const workspaceUuid = row.getResultByName("workspace_uuid");
|
||||
const bookmarkGuids = row.getResultByName("bookmark_guids");
|
||||
result[workspaceUuid] = bookmarkGuids ? bookmarkGuids.split(',') : [];
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all changed bookmarks with their change types.
|
||||
* @returns {Promise<Object>} An object mapping bookmark+workspace pairs to their change data.
|
||||
*/
|
||||
async getChangedIDs() {
|
||||
const db = await PlacesUtils.promiseDBConnection();
|
||||
const rows = await db.execute(`
|
||||
SELECT bookmark_guid, workspace_uuid, change_type, timestamp
|
||||
FROM zen_bookmarks_workspaces_changes
|
||||
`);
|
||||
|
||||
const changes = {};
|
||||
for (const row of rows) {
|
||||
const key = `${row.getResultByName('bookmark_guid')}:${row.getResultByName('workspace_uuid')}`;
|
||||
changes[key] = {
|
||||
type: row.getResultByName('change_type'),
|
||||
timestamp: row.getResultByName('timestamp')
|
||||
};
|
||||
}
|
||||
return changes;
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear all recorded changes.
|
||||
*/
|
||||
async clearChangedIDs() {
|
||||
await PlacesUtils.withConnectionWrapper('ZenWorkspaceBookmarksStorage.clearChangedIDs', async (db) => {
|
||||
await db.execute(`DELETE FROM zen_bookmarks_workspaces_changes`);
|
||||
});
|
||||
},
|
||||
|
||||
};
|
159
src/browser/components/places/PlacesUIUtils-sys-mjs.patch
Normal file
159
src/browser/components/places/PlacesUIUtils-sys-mjs.patch
Normal file
@@ -0,0 +1,159 @@
|
||||
diff --git a/browser/components/places/PlacesUIUtils.sys.mjs b/browser/components/places/PlacesUIUtils.sys.mjs
|
||||
index 0f79ba5dd42116d626445b86f6b24731d2fa8aad..76d692db1731e84b28d9035b03e34c176c12bd23 100644
|
||||
--- a/browser/components/places/PlacesUIUtils.sys.mjs
|
||||
+++ b/browser/components/places/PlacesUIUtils.sys.mjs
|
||||
@@ -58,6 +58,7 @@ class BookmarkState {
|
||||
info,
|
||||
tags = "",
|
||||
keyword = "",
|
||||
+ workspaces = [],
|
||||
isFolder = false,
|
||||
children = [],
|
||||
autosave = false,
|
||||
@@ -82,12 +83,18 @@ class BookmarkState {
|
||||
keyword,
|
||||
parentGuid: info.parentGuid,
|
||||
index,
|
||||
+ workspaces,
|
||||
};
|
||||
|
||||
// Edited bookmark
|
||||
this._newState = {};
|
||||
}
|
||||
|
||||
+ async _workspacesChanged(workspaces) {
|
||||
+ this._newState.workspaces = workspaces;
|
||||
+ await this._maybeSave();
|
||||
+ }
|
||||
+
|
||||
/**
|
||||
* Save edited title for the bookmark
|
||||
*
|
||||
@@ -181,6 +188,14 @@ class BookmarkState {
|
||||
"BookmarkState::createBookmark"
|
||||
);
|
||||
this._guid = results?.[0];
|
||||
+
|
||||
+ if ('workspaces' in this._newState) {
|
||||
+ try {
|
||||
+ await this.updateBookmarkWorkspaces(this._guid, this._newState.workspaces);
|
||||
+ } catch (ex) {
|
||||
+ console.error("Failed to update workspace assignments:", ex);
|
||||
+ }
|
||||
+ }
|
||||
return this._guid;
|
||||
}
|
||||
|
||||
@@ -214,6 +229,14 @@ class BookmarkState {
|
||||
"BookmarkState::save::createFolder"
|
||||
);
|
||||
this._guid = results[0];
|
||||
+
|
||||
+ if ('workspaces' in this._newState) {
|
||||
+ try {
|
||||
+ await this.updateBookmarkWorkspaces(this._guid, this._newState.workspaces);
|
||||
+ } catch (ex) {
|
||||
+ console.error("Failed to update workspace assignments:", ex);
|
||||
+ }
|
||||
+ }
|
||||
return this._guid;
|
||||
}
|
||||
|
||||
@@ -300,11 +323,97 @@ class BookmarkState {
|
||||
await lazy.PlacesTransactions.batch(transactions, "BookmarkState::save");
|
||||
}
|
||||
|
||||
+ if ('workspaces' in this._newState) {
|
||||
+ try {
|
||||
+ await this.updateBookmarkWorkspaces(this._guid, this._newState.workspaces);
|
||||
+ } catch (ex) {
|
||||
+ console.error("Failed to update workspace assignments:", ex);
|
||||
+ }
|
||||
+ }
|
||||
this._originalState = { ...this._originalState, ...this._newState };
|
||||
this._newState = {};
|
||||
return this._guid;
|
||||
}
|
||||
|
||||
+ async updateBookmarkWorkspaces(bookmarkGuid, workspaces) {
|
||||
+ await lazy.PlacesUtils.withConnectionWrapper('ZenWorkspaceBookmarksStorage.updateBookmarkWorkspaces', async (db) => {
|
||||
+ const now = Date.now();
|
||||
+
|
||||
+ await db.executeTransaction(async () => {
|
||||
+ const rows = await db.execute(`
|
||||
+ SELECT workspace_uuid
|
||||
+ FROM zen_bookmarks_workspaces
|
||||
+ WHERE bookmark_guid = :bookmark_guid
|
||||
+ `, { bookmark_guid: bookmarkGuid });
|
||||
+
|
||||
+ const currentWorkspaces = rows.map(row => row.getResultByName("workspace_uuid"));
|
||||
+ const workspacesToRemove = currentWorkspaces.filter(w => !workspaces.includes(w));
|
||||
+ const workspacesToAdd = workspaces.filter(w => !currentWorkspaces.includes(w));
|
||||
+
|
||||
+ // If there are workspaces to remove, delete only those specific associations
|
||||
+ if (workspacesToRemove.length > 0) {
|
||||
+ const placeholders = workspacesToRemove.map(() => '?').join(',');
|
||||
+ await db.execute(`
|
||||
+ DELETE FROM zen_bookmarks_workspaces
|
||||
+ WHERE bookmark_guid = :bookmark_guid
|
||||
+ AND workspace_uuid IN (${placeholders})
|
||||
+ `, [bookmarkGuid, ...workspacesToRemove]);
|
||||
+
|
||||
+ // Record removals
|
||||
+ for (const workspace of workspacesToRemove) {
|
||||
+ await this._recordChange(db, bookmarkGuid, workspace, 'removed');
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ // Add only new associations
|
||||
+ for (const workspaceUuid of workspacesToAdd) {
|
||||
+ await db.execute(`
|
||||
+ INSERT INTO zen_bookmarks_workspaces (
|
||||
+ bookmark_guid, workspace_uuid, created_at, updated_at
|
||||
+ ) VALUES (
|
||||
+ :bookmark_guid, :workspace_uuid, :now, :now
|
||||
+ )
|
||||
+ `, {
|
||||
+ bookmark_guid: bookmarkGuid,
|
||||
+ workspace_uuid: workspaceUuid,
|
||||
+ now
|
||||
+ });
|
||||
+
|
||||
+ await this._recordChange(db, bookmarkGuid, workspaceUuid, 'added');
|
||||
+ }
|
||||
+ });
|
||||
+ });
|
||||
+
|
||||
+ const changes = { bookmarkGuid, workspaces };
|
||||
+ Services.obs.notifyObservers(null, "workspace-bookmarks-updated", JSON.stringify(changes));
|
||||
+ }
|
||||
+
|
||||
+ async _recordChange(db, bookmarkGuid, workspaceUuid, changeType) {
|
||||
+ const now = Date.now();
|
||||
+ await db.execute(`
|
||||
+ INSERT OR REPLACE INTO zen_bookmarks_workspaces_changes (
|
||||
+ bookmark_guid, workspace_uuid, change_type, timestamp
|
||||
+ ) VALUES (
|
||||
+ :bookmark_guid, :workspace_uuid, :change_type, :timestamp
|
||||
+ )
|
||||
+ `, {
|
||||
+ bookmark_guid: bookmarkGuid,
|
||||
+ workspace_uuid: workspaceUuid,
|
||||
+ change_type: changeType,
|
||||
+ timestamp: Math.floor(now / 1000)
|
||||
+ });
|
||||
+
|
||||
+ await this._updateLastChangeTimestamp(db);
|
||||
+ }
|
||||
+
|
||||
+ async _updateLastChangeTimestamp(db) {
|
||||
+ const now = Date.now();
|
||||
+ await db.execute(`
|
||||
+ INSERT OR REPLACE INTO moz_meta (key, value)
|
||||
+ VALUES ('zen_bookmarks_workspaces_last_change', :now)
|
||||
+ `, { now });
|
||||
+ }
|
||||
+
|
||||
/**
|
||||
* Append transactions to update tags by given information.
|
||||
*
|
@@ -1,5 +1,5 @@
|
||||
diff --git a/browser/components/places/content/bookmarkProperties.xhtml b/browser/components/places/content/bookmarkProperties.xhtml
|
||||
index 047652a52e705d49f870399992873fce536c07b9..2932eb94e8c16eb05f172322a6ce3ea201ecd0b1 100644
|
||||
index 047652a52e705d49f870399992873fce536c07b9..8bc7d1c5e44c33d90f82fdc6f66d9e2e80c60bae 100644
|
||||
--- a/browser/components/places/content/bookmarkProperties.xhtml
|
||||
+++ b/browser/components/places/content/bookmarkProperties.xhtml
|
||||
@@ -37,6 +37,7 @@
|
||||
@@ -10,3 +10,11 @@ index 047652a52e705d49f870399992873fce536c07b9..2932eb94e8c16eb05f172322a6ce3ea2
|
||||
</linkset>
|
||||
|
||||
<stringbundleset id="stringbundleset">
|
||||
@@ -44,6 +45,7 @@
|
||||
src="chrome://browser/locale/places/bookmarkProperties.properties"/>
|
||||
</stringbundleset>
|
||||
|
||||
+ <script src="chrome://browser/content/zen-components/ZenWorkspacesStorage.mjs" />
|
||||
<script src="chrome://browser/content/places/editBookmark.js"/>
|
||||
<script src="chrome://browser/content/places/bookmarkProperties.js"/>
|
||||
<script src="chrome://global/content/globalOverlay.js"/>
|
||||
|
@@ -1,8 +1,35 @@
|
||||
diff --git a/browser/components/places/content/browserPlacesViews.js b/browser/components/places/content/browserPlacesViews.js
|
||||
index 1bfa0af16178c9b42172bc1b1e0249d28ff8e9e6..e7e76a6d548b32887c1d39053e42c5e3dafbb839 100644
|
||||
index 1bfa0af16178c9b42172bc1b1e0249d28ff8e9e6..417a9dc4e55208bdc9c1422a3bae14361a4964c5 100644
|
||||
--- a/browser/components/places/content/browserPlacesViews.js
|
||||
+++ b/browser/components/places/content/browserPlacesViews.js
|
||||
@@ -393,6 +393,7 @@ class PlacesViewBase {
|
||||
@@ -330,12 +330,23 @@ class PlacesViewBase {
|
||||
|
||||
this._cleanPopup(aPopup);
|
||||
|
||||
+ let children = [];
|
||||
let cc = resultNode.childCount;
|
||||
- if (cc > 0) {
|
||||
+ for (let i = 0; i < cc; ++i) {
|
||||
+ let child = resultNode.getChild(i);
|
||||
+ // Skip nodes that don't belong in current workspace
|
||||
+ if (PlacesUtils.nodeIsURI(child) || PlacesUtils.containerTypes.includes(child.type)) {
|
||||
+ if (ZenWorkspaces.isBookmarkInAnotherWorkspace(child)) {
|
||||
+ continue;
|
||||
+ }
|
||||
+ }
|
||||
+ children.push(child);
|
||||
+ }
|
||||
+
|
||||
+ if (children.length > 0) {
|
||||
this._setEmptyPopupStatus(aPopup, false);
|
||||
let fragment = document.createDocumentFragment();
|
||||
- for (let i = 0; i < cc; ++i) {
|
||||
- let child = resultNode.getChild(i);
|
||||
+ for (let child of children) {
|
||||
this._insertNewItemToPopup(child, fragment);
|
||||
}
|
||||
aPopup.insertBefore(fragment, aPopup._endMarker);
|
||||
@@ -393,6 +404,7 @@ class PlacesViewBase {
|
||||
"scheme",
|
||||
PlacesUIUtils.guessUrlSchemeForUI(aPlacesNode.uri)
|
||||
);
|
||||
@@ -10,16 +37,89 @@ index 1bfa0af16178c9b42172bc1b1e0249d28ff8e9e6..e7e76a6d548b32887c1d39053e42c5e3
|
||||
} else if (PlacesUtils.containerTypes.includes(type)) {
|
||||
element = document.createXULElement("menu");
|
||||
element.setAttribute("container", "true");
|
||||
@@ -1087,6 +1088,8 @@ class PlacesToolbar extends PlacesViewBase {
|
||||
@@ -981,25 +993,33 @@ class PlacesToolbar extends PlacesViewBase {
|
||||
this._rootElt.firstChild.remove();
|
||||
}
|
||||
|
||||
+ let visibleNodes = [];
|
||||
let cc = this._resultNode.childCount;
|
||||
- if (cc > 0) {
|
||||
- // There could be a lot of nodes, but we only want to build the ones that
|
||||
- // are more likely to be shown, not all of them.
|
||||
- // We also don't want to wait for reflows at every node insertion, to
|
||||
- // calculate a precise number of visible items, thus we guess a size from
|
||||
- // the first non-separator node (because separators have flexible size).
|
||||
+ for (let i = 0; i < cc; i++) {
|
||||
+ let child = this._resultNode.getChild(i);
|
||||
+ if (PlacesUtils.nodeIsURI(child) || PlacesUtils.containerTypes.includes(child.type)) {
|
||||
+ if (!ZenWorkspaces.isBookmarkInAnotherWorkspace(child)) {
|
||||
+ visibleNodes.push(child);
|
||||
+ }
|
||||
+ } else {
|
||||
+ // Always include separators
|
||||
+ visibleNodes.push(child);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ if (visibleNodes.length > 0) {
|
||||
+ // Look for the first non-separator node.
|
||||
let startIndex = 0;
|
||||
let limit = await this._runBeforeFrameRender(() => {
|
||||
if (!this._isAlive) {
|
||||
- return cc;
|
||||
+ return visibleNodes.length;
|
||||
}
|
||||
|
||||
- // Look for the first non-separator node.
|
||||
let elt;
|
||||
- while (startIndex < cc) {
|
||||
+ while (startIndex < visibleNodes.length) {
|
||||
elt = this._insertNewItem(
|
||||
- this._resultNode.getChild(startIndex),
|
||||
- this._rootElt
|
||||
+ visibleNodes[startIndex],
|
||||
+ this._rootElt
|
||||
);
|
||||
++startIndex;
|
||||
if (elt.localName != "toolbarseparator") {
|
||||
@@ -1007,15 +1027,12 @@ class PlacesToolbar extends PlacesViewBase {
|
||||
}
|
||||
}
|
||||
if (!elt) {
|
||||
- return cc;
|
||||
+ return visibleNodes.length;
|
||||
}
|
||||
|
||||
return window.promiseDocumentFlushed(() => {
|
||||
- // We assume a button with just the icon will be more or less a square,
|
||||
- // then compensate the measurement error by considering a larger screen
|
||||
- // width. Moreover the window could be bigger than the screen.
|
||||
- let size = elt.clientHeight || 1; // Sanity fallback.
|
||||
- return Math.min(cc, parseInt((window.screen.width * 1.5) / size));
|
||||
+ let size = elt.clientHeight || 1;
|
||||
+ return Math.min(visibleNodes.length, parseInt((window.screen.width * 1.5) / size));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1025,7 +1042,7 @@ class PlacesToolbar extends PlacesViewBase {
|
||||
|
||||
let fragment = document.createDocumentFragment();
|
||||
for (let i = startIndex; i < limit; ++i) {
|
||||
- this._insertNewItem(this._resultNode.getChild(i), fragment);
|
||||
+ this._insertNewItem(visibleNodes[i], fragment);
|
||||
}
|
||||
await new Promise(resolve => window.requestAnimationFrame(resolve));
|
||||
if (!this._isAlive) {
|
||||
@@ -1087,6 +1104,8 @@ class PlacesToolbar extends PlacesViewBase {
|
||||
"scheme",
|
||||
PlacesUIUtils.guessUrlSchemeForUI(aChild.uri)
|
||||
);
|
||||
+ button.hidden = ZenWorkspaces.isBookmarkInAnotherWorkspace(aChild);
|
||||
+
|
||||
+ button.addEventListener("command", gZenGlanceManager.openGlanceForBookmark.bind(gZenGlanceManager));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2235,7 +2238,7 @@ this.PlacesPanelview = class PlacesPanelview extends PlacesViewBase {
|
||||
@@ -2235,7 +2254,7 @@ this.PlacesPanelview = class PlacesPanelview extends PlacesViewBase {
|
||||
PlacesUIUtils.guessUrlSchemeForUI(placesNode.uri)
|
||||
);
|
||||
element.setAttribute("label", PlacesUIUtils.getBestTitle(placesNode));
|
||||
|
172
src/browser/components/places/content/editBookmark-js.patch
Normal file
172
src/browser/components/places/content/editBookmark-js.patch
Normal file
@@ -0,0 +1,172 @@
|
||||
diff --git a/browser/components/places/content/editBookmark.js b/browser/components/places/content/editBookmark.js
|
||||
index 9f17174fdd9cc1eaefb4330da1e10f40eeda2f31..56e504c4348422d30a1afca63ba1e5d44d66436f 100644
|
||||
--- a/browser/components/places/content/editBookmark.js
|
||||
+++ b/browser/components/places/content/editBookmark.js
|
||||
@@ -370,6 +370,10 @@ var gEditItemOverlay = {
|
||||
this._keywordField.readOnly = this.readOnly;
|
||||
}
|
||||
|
||||
+ if (showOrCollapse("workspaceRow", true, "workspace")) {
|
||||
+ await this._initWorkspaceDropdown(aInfo);
|
||||
+ }
|
||||
+
|
||||
// Collapse the tag selector if the item does not accept tags.
|
||||
if (showOrCollapse("tagsRow", isBookmark || bulkTagging, "tags")) {
|
||||
this._initTagsField();
|
||||
@@ -682,6 +686,7 @@ var gEditItemOverlay = {
|
||||
if (this._paneInfo.isBookmark) {
|
||||
options.tags = this._element("tagsField").value;
|
||||
options.keyword = this._keyword;
|
||||
+ options.workspaces = this._selectedWorkspaces;
|
||||
}
|
||||
|
||||
if (this._paneInfo.bulkTagging) {
|
||||
@@ -1232,6 +1237,138 @@ var gEditItemOverlay = {
|
||||
get bookmarkState() {
|
||||
return this._bookmarkState;
|
||||
},
|
||||
+
|
||||
+ async _initWorkspaceSelector() {
|
||||
+ if(document.documentElement.getAttribute("windowtype") === "Places:Organizer") {
|
||||
+ return;
|
||||
+ }
|
||||
+ this._workspaces = await ZenWorkspacesStorage.getWorkspaces();
|
||||
+
|
||||
+ const selectElement = this._workspaceSelect;
|
||||
+
|
||||
+ // Clear any existing options
|
||||
+ while (selectElement.firstChild) {
|
||||
+ selectElement.removeChild(selectElement.firstChild);
|
||||
+ }
|
||||
+
|
||||
+ // For each workspace, create an option element
|
||||
+ for (let workspace of this._workspaces) {
|
||||
+ const option = document.createElementNS("http://www.w3.org/1999/xhtml", "option");
|
||||
+ option.textContent = workspace.name;
|
||||
+ option.value = workspace.uuid;
|
||||
+ selectElement.appendChild(option);
|
||||
+ }
|
||||
+
|
||||
+ selectElement.disabled = this.readOnly;
|
||||
+ },
|
||||
+ async onWorkspaceSelectionChange(event) {
|
||||
+ if(document.documentElement.getAttribute("windowtype") === "Places:Organizer") {
|
||||
+ return;
|
||||
+ }
|
||||
+ event.stopPropagation();
|
||||
+
|
||||
+ // Add new workspaces uuids
|
||||
+ const checkboxes = this._workspaceList.querySelectorAll("input[type='checkbox']");
|
||||
+ const newWorkspaces = [];
|
||||
+ checkboxes.forEach(checkbox => {
|
||||
+ if (checkbox.checked) {
|
||||
+ newWorkspaces.push(checkbox.value);
|
||||
+ }
|
||||
+ });
|
||||
+
|
||||
+ this._selectedWorkspaces = [ ...newWorkspaces];
|
||||
+
|
||||
+ // Update the bookmark state
|
||||
+ if (this._bookmarkState) {
|
||||
+ await this._bookmarkState._workspacesChanged(this._selectedWorkspaces);
|
||||
+ }
|
||||
+
|
||||
+ // Update summary text
|
||||
+ const selectedCount = this._selectedWorkspaces.length;
|
||||
+ this._workspaceSummary.textContent = selectedCount
|
||||
+ ? `${selectedCount} workspace${selectedCount > 1 ? 's' : ''} selected`
|
||||
+ : "Choose Workspaces";
|
||||
+ },
|
||||
+
|
||||
+ onWorkspaceDropdownToggle(event) {
|
||||
+ if(document.documentElement.getAttribute("windowtype") === "Places:Organizer") {
|
||||
+ return;
|
||||
+ }
|
||||
+ const details = this._workspaceDropdown;
|
||||
+ const summary = this._workspaceSummary;
|
||||
+
|
||||
+ if (!details.open) {
|
||||
+ const checkboxes = this._workspaceList.querySelectorAll("input[type='checkbox']");
|
||||
+ const selectedWorkspaces = [];
|
||||
+ const selectedLabels = [];
|
||||
+
|
||||
+ checkboxes.forEach(checkbox => {
|
||||
+ if (checkbox.checked) {
|
||||
+ selectedWorkspaces.push(checkbox.value);
|
||||
+ const label = checkbox.parentNode.textContent.trim();
|
||||
+ selectedLabels.push(label);
|
||||
+ }
|
||||
+ });
|
||||
+
|
||||
+ // Update the summary text
|
||||
+ const count = selectedLabels.length;
|
||||
+ summary.textContent = count
|
||||
+ ? `${count} workspace${count > 1 ? 's' : ''} selected`
|
||||
+ : "Choose Workspaces";
|
||||
+ }
|
||||
+
|
||||
+ event.stopPropagation();
|
||||
+ },
|
||||
+
|
||||
+ async _initWorkspaceDropdown(aInfo) {
|
||||
+ if(document.documentElement.getAttribute("windowtype") === "Places:Organizer") {
|
||||
+ return;
|
||||
+ }
|
||||
+ this._workspaces = await ZenWorkspacesStorage.getWorkspaces();
|
||||
+ const workspaceList = this._workspaceList;
|
||||
+ if(aInfo.node?.bookmarkGuid) {
|
||||
+ this._selectedWorkspaces = await ZenWorkspaceBookmarksStorage.getBookmarkWorkspaces(aInfo.node.bookmarkGuid);
|
||||
+ }
|
||||
+
|
||||
+
|
||||
+ // Clear existing items
|
||||
+ workspaceList.innerHTML = "";
|
||||
+
|
||||
+ // Create checkbox items for each workspace
|
||||
+ for (let workspace of this._workspaces) {
|
||||
+ const li = document.createElementNS("http://www.w3.org/1999/xhtml", "li");
|
||||
+ const label = document.createElementNS("http://www.w3.org/1999/xhtml", "label");
|
||||
+ const input = document.createElementNS("http://www.w3.org/1999/xhtml", "input");
|
||||
+
|
||||
+ input.setAttribute("type", "checkbox");
|
||||
+ input.setAttribute("name", "workspace");
|
||||
+ input.setAttribute("value", workspace.uuid);
|
||||
+
|
||||
+ // Check if this workspace is selected
|
||||
+ input.checked = this._selectedWorkspaces?.includes(workspace.uuid) ?? false;
|
||||
+
|
||||
+ input.addEventListener("click", this.onWorkspaceSelectionChange.bind(this));
|
||||
+
|
||||
+ label.appendChild(input);
|
||||
+ label.appendChild(document.createTextNode(workspace.name));
|
||||
+ li.appendChild(label);
|
||||
+ workspaceList.appendChild(li);
|
||||
+ }
|
||||
+
|
||||
+ // Update summary text
|
||||
+ const selectedCount = this._selectedWorkspaces?.length;
|
||||
+ this._workspaceSummary.textContent = selectedCount
|
||||
+ ? `${selectedCount} workspace${selectedCount > 1 ? 's' : ''} selected`
|
||||
+ : "Choose Workspaces";
|
||||
+
|
||||
+ // Handle read-only state
|
||||
+ if (this.readOnly) {
|
||||
+ this._workspaceDropdown.setAttribute("disabled", "true");
|
||||
+ } else {
|
||||
+ this._workspaceDropdown.removeAttribute("disabled");
|
||||
+ }
|
||||
+ },
|
||||
+ _selectedWorkspaces : [],
|
||||
};
|
||||
|
||||
ChromeUtils.defineLazyGetter(gEditItemOverlay, "_folderTree", () => {
|
||||
@@ -1267,6 +1404,9 @@ for (let elt of [
|
||||
"locationField",
|
||||
"keywordField",
|
||||
"tagsField",
|
||||
+ "workspaceDropdown",
|
||||
+ "workspaceSummary",
|
||||
+ "workspaceList",
|
||||
]) {
|
||||
let eltScoped = elt;
|
||||
ChromeUtils.defineLazyGetter(gEditItemOverlay, `_${eltScoped}`, () =>
|
@@ -1,5 +1,5 @@
|
||||
diff --git a/browser/components/places/content/editBookmarkPanel.inc.xhtml b/browser/components/places/content/editBookmarkPanel.inc.xhtml
|
||||
index 3ec3f094831c2143a818b43d1761a571f0ffa63d..309dfa8ed628f4cc124fe16d20b7411065c09f23 100644
|
||||
index 3ec3f094831c2143a818b43d1761a571f0ffa63d..677fb9acb39fdfbacef60baa705825278ca2ce91 100644
|
||||
--- a/browser/components/places/content/editBookmarkPanel.inc.xhtml
|
||||
+++ b/browser/components/places/content/editBookmarkPanel.inc.xhtml
|
||||
@@ -5,7 +5,7 @@
|
||||
@@ -29,11 +29,23 @@ index 3ec3f094831c2143a818b43d1761a571f0ffa63d..309dfa8ed628f4cc124fe16d20b74110
|
||||
<label data-l10n-id="bookmark-overlay-location-2"
|
||||
class="editBMPanel_folderRow hideable"
|
||||
control="editBMPanel_folderMenuList"/>
|
||||
@@ -51,6 +51,7 @@
|
||||
@@ -51,6 +51,19 @@
|
||||
data-l10n-id="bookmark-overlay-folders-expander2"
|
||||
oncommand="gEditItemOverlay.toggleFolderTreeVisibility();"/>
|
||||
</hbox>
|
||||
+</hbox>
|
||||
+
|
||||
+<vbox class="zenEditBMPanel_fieldContainer">
|
||||
+ <label
|
||||
+ class="editBMPanel_workspaceRow hideable"
|
||||
+ control="editBMPanel_workspaceDropdown">Workspace</label>
|
||||
+ <html:details id="editBMPanel_workspaceDropdown"
|
||||
+ class="editBMPanel_workspaceRow hideable"
|
||||
+ onclick="gEditItemOverlay.onWorkspaceDropdownToggle(event)">
|
||||
+ <html:summary id="editBMPanel_workspaceSummary">Choose Workspaces</html:summary>
|
||||
+ <html:ul id="editBMPanel_workspaceList">
|
||||
+ </html:ul>
|
||||
+ </html:details>
|
||||
+</vbox>
|
||||
|
||||
<vbox id="editBMPanel_folderTreeRow"
|
||||
class="hideable"
|
||||
|
74
src/browser/themes/shared/places/editBookmark-css.patch
Normal file
74
src/browser/themes/shared/places/editBookmark-css.patch
Normal file
@@ -0,0 +1,74 @@
|
||||
diff --git a/browser/themes/shared/places/editBookmark.css b/browser/themes/shared/places/editBookmark.css
|
||||
index 4c00982e620f4cfd5aa1d97d45a276f5d41d0d74..b75aa721e75cd3a3e707d1a69fca08d6741746a4 100644
|
||||
--- a/browser/themes/shared/places/editBookmark.css
|
||||
+++ b/browser/themes/shared/places/editBookmark.css
|
||||
@@ -158,3 +158,69 @@
|
||||
font-size: 0.9em;
|
||||
margin: 2px 4px;
|
||||
}
|
||||
+
|
||||
+/*Bookmark workspace selector styles*/
|
||||
+#editBMPanel_workspaceRow {
|
||||
+ margin-top: 4px;
|
||||
+}
|
||||
+
|
||||
+details {
|
||||
+ position: relative;
|
||||
+ width: 100%;
|
||||
+}
|
||||
+
|
||||
+summary {
|
||||
+ display: flex;
|
||||
+ align-items: center;
|
||||
+ justify-content: space-between;
|
||||
+ padding: 8px 12px;
|
||||
+ border: 1px solid var(--card-outline-color);
|
||||
+ border-radius: 4px;
|
||||
+ background-color: var(--in-content-box-background);
|
||||
+ cursor: pointer;
|
||||
+}
|
||||
+
|
||||
+
|
||||
+summary:focus {
|
||||
+ border-color: transparent;
|
||||
+ outline: var(--in-content-focus-outline);
|
||||
+ outline-offset: -1px;
|
||||
+}
|
||||
+
|
||||
+details[open] > summary {
|
||||
+ border-bottom-left-radius: 0;
|
||||
+ border-bottom-right-radius: 0;
|
||||
+}
|
||||
+
|
||||
+ul {
|
||||
+ position: absolute;
|
||||
+ top: calc(100% + 4px);
|
||||
+ left: 0;
|
||||
+ right: 0;
|
||||
+ max-height: 200px;
|
||||
+ overflow-y: auto;
|
||||
+ border: 1px solid var(--card-outline-color);
|
||||
+ border-top: none;
|
||||
+ background-color: var(--in-content-box-background);
|
||||
+ box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1);
|
||||
+ z-index: 1000;
|
||||
+ padding: 8px 0;
|
||||
+ border-radius: 0 0 4px 4px;
|
||||
+}
|
||||
+
|
||||
+li {
|
||||
+ list-style: none;
|
||||
+ margin: 0;
|
||||
+ padding: 0;
|
||||
+}
|
||||
+
|
||||
+li > label {
|
||||
+ display: flex;
|
||||
+ align-items: center;
|
||||
+ padding: 8px 12px;
|
||||
+ cursor: pointer;
|
||||
+}
|
||||
+
|
||||
+input[type="checkbox"] {
|
||||
+ margin-right: 8px;
|
||||
+}
|
Reference in New Issue
Block a user