mirror of
https://github.com/zen-browser/desktop.git
synced 2025-10-05 17:36:34 +00:00
Merge pull request #3215 from kristijanribaric/feature/workspace-specific-bookmarks
Feature: Workspace-specific bookmarks
This commit is contained in:
@@ -13,6 +13,13 @@ var ZenWorkspaces = new (class extends ZenMultiWindowFeature {
|
|||||||
};
|
};
|
||||||
_hoveringSidebar = false;
|
_hoveringSidebar = false;
|
||||||
_lastScrollTime = 0;
|
_lastScrollTime = 0;
|
||||||
|
bookmarkMenus = [
|
||||||
|
"PlacesToolbar",
|
||||||
|
"bookmarks-menu-button",
|
||||||
|
"BMB_bookmarksToolbar",
|
||||||
|
"BMB_unsortedBookmarks",
|
||||||
|
"BMB_mobileBookmarks"
|
||||||
|
];
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
if (!this.shouldHaveWorkspaces) {
|
if (!this.shouldHaveWorkspaces) {
|
||||||
@@ -61,6 +68,11 @@ var ZenWorkspaces = new (class extends ZenMultiWindowFeature {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Services.obs.addObserver(this, 'weave:engine:sync:finish');
|
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() {
|
initializeWorkspaceNavigation() {
|
||||||
@@ -128,7 +140,7 @@ var ZenWorkspaces = new (class extends ZenMultiWindowFeature {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Change workspace based on scroll direction
|
// Change workspace based on scroll direction
|
||||||
const direction = event.deltaX > 0 ? -1 : 1;
|
const direction = event.deltaX > 0 ? 1 : -1;
|
||||||
await this.changeWorkspaceShortcut(direction);
|
await this.changeWorkspaceShortcut(direction);
|
||||||
this._lastScrollTime = currentTime;
|
this._lastScrollTime = currentTime;
|
||||||
}, { passive: true });
|
}, { passive: true });
|
||||||
@@ -320,6 +332,21 @@ var ZenWorkspaces = new (class extends ZenMultiWindowFeature {
|
|||||||
return this._workspaceCache;
|
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() {
|
async onWorkspacesEnabledChanged() {
|
||||||
if (this.workspaceEnabled) {
|
if (this.workspaceEnabled) {
|
||||||
throw Error("Shoud've had reloaded the window");
|
throw Error("Shoud've had reloaded the window");
|
||||||
@@ -339,6 +366,7 @@ var ZenWorkspaces = new (class extends ZenMultiWindowFeature {
|
|||||||
if (this.workspaceEnabled) {
|
if (this.workspaceEnabled) {
|
||||||
this._initializeWorkspaceCreationIcons();
|
this._initializeWorkspaceCreationIcons();
|
||||||
this._initializeWorkspaceTabContextMenus();
|
this._initializeWorkspaceTabContextMenus();
|
||||||
|
await this.workspaceBookmarks();
|
||||||
window.addEventListener('TabBrowserInserted', this.onTabBrowserInserted.bind(this));
|
window.addEventListener('TabBrowserInserted', this.onTabBrowserInserted.bind(this));
|
||||||
await SessionStore.promiseInitialized;
|
await SessionStore.promiseInitialized;
|
||||||
let workspaces = await this._workspaces();
|
let workspaces = await this._workspaces();
|
||||||
@@ -754,8 +782,10 @@ var ZenWorkspaces = new (class extends ZenMultiWindowFeature {
|
|||||||
|
|
||||||
if(clearCache) {
|
if(clearCache) {
|
||||||
browser.ZenWorkspaces._workspaceCache = null;
|
browser.ZenWorkspaces._workspaceCache = null;
|
||||||
|
browser.ZenWorkspaces._workspaceBookmarksCache = null;
|
||||||
}
|
}
|
||||||
let workspaces = await browser.ZenWorkspaces._workspaces();
|
let workspaces = await browser.ZenWorkspaces._workspaces();
|
||||||
|
await browser.ZenWorkspaces.workspaceBookmarks();
|
||||||
workspaceList.innerHTML = '';
|
workspaceList.innerHTML = '';
|
||||||
workspaceList.parentNode.style.display = 'flex';
|
workspaceList.parentNode.style.display = 'flex';
|
||||||
if (workspaces.workspaces.length <= 0) {
|
if (workspaces.workspaces.length <= 0) {
|
||||||
@@ -1268,16 +1298,23 @@ var ZenWorkspaces = new (class extends ZenMultiWindowFeature {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset bookmarks toolbar
|
// Reset bookmarks
|
||||||
const placesToolbar = document.getElementById("PlacesToolbar");
|
this._invalidateBookmarkContainers();
|
||||||
if (placesToolbar?._placesView) {
|
|
||||||
placesToolbar._placesView.invalidateContainer(placesToolbar._placesView._resultNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update workspace indicator
|
// Update workspace indicator
|
||||||
await this.updateWorkspaceIndicator();
|
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() {
|
async updateWorkspaceIndicator() {
|
||||||
// Update current workspace indicator
|
// Update current workspace indicator
|
||||||
const currentWorkspace = await this.getActiveWorkspace();
|
const currentWorkspace = await this.getActiveWorkspace();
|
||||||
@@ -1550,12 +1587,24 @@ var ZenWorkspaces = new (class extends ZenMultiWindowFeature {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isBookmarkInAnotherWorkspace(bookmark) {
|
isBookmarkInAnotherWorkspace(bookmark) {
|
||||||
let tags = bookmark.tags;
|
if (!this._workspaceBookmarksCache?.bookmarks) return false;
|
||||||
// if any tag starts with "_workspace_id" and the workspace id doesnt match the active workspace id, return null
|
const bookmarkGuid = bookmark.bookmarkGuid;
|
||||||
if (tags) {
|
const activeWorkspaceUuid = this.activeWorkspace;
|
||||||
for (let tag of tags.split(",")) {
|
let isInActiveWorkspace = false;
|
||||||
return !!(tag.startsWith("zen_workspace_") && this.getActiveWorkspaceFromCache()?.uuid !== tag.split("_")[2]);
|
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() {
|
async init() {
|
||||||
console.log('ZenWorkspacesStorage: Initializing...');
|
console.log('ZenWorkspacesStorage: Initializing...');
|
||||||
await this._ensureTable();
|
await this._ensureTable();
|
||||||
|
await ZenWorkspaceBookmarksStorage.init();
|
||||||
|
ZenWorkspaces._delayedStartup();
|
||||||
},
|
},
|
||||||
|
|
||||||
async _ensureTable() {
|
async _ensureTable() {
|
||||||
@@ -64,7 +66,6 @@ var ZenWorkspacesStorage = {
|
|||||||
await ZenWorkspacesStorage.migrateWorkspacesFromJSON();
|
await ZenWorkspacesStorage.migrateWorkspacesFromJSON();
|
||||||
}
|
}
|
||||||
|
|
||||||
ZenWorkspaces._delayedStartup();
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -405,3 +406,152 @@ var ZenWorkspacesStorage = {
|
|||||||
this._notifyWorkspacesChanged("zen-workspace-updated", Array.from(changedUUIDs));
|
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
|
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
|
--- a/browser/components/places/content/bookmarkProperties.xhtml
|
||||||
+++ b/browser/components/places/content/bookmarkProperties.xhtml
|
+++ b/browser/components/places/content/bookmarkProperties.xhtml
|
||||||
@@ -37,6 +37,7 @@
|
@@ -37,6 +37,7 @@
|
||||||
@@ -10,3 +10,11 @@ index 047652a52e705d49f870399992873fce536c07b9..2932eb94e8c16eb05f172322a6ce3ea2
|
|||||||
</linkset>
|
</linkset>
|
||||||
|
|
||||||
<stringbundleset id="stringbundleset">
|
<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
|
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
|
--- a/browser/components/places/content/browserPlacesViews.js
|
||||||
+++ b/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",
|
"scheme",
|
||||||
PlacesUIUtils.guessUrlSchemeForUI(aPlacesNode.uri)
|
PlacesUIUtils.guessUrlSchemeForUI(aPlacesNode.uri)
|
||||||
);
|
);
|
||||||
@@ -10,16 +37,89 @@ index 1bfa0af16178c9b42172bc1b1e0249d28ff8e9e6..e7e76a6d548b32887c1d39053e42c5e3
|
|||||||
} else if (PlacesUtils.containerTypes.includes(type)) {
|
} else if (PlacesUtils.containerTypes.includes(type)) {
|
||||||
element = document.createXULElement("menu");
|
element = document.createXULElement("menu");
|
||||||
element.setAttribute("container", "true");
|
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",
|
"scheme",
|
||||||
PlacesUIUtils.guessUrlSchemeForUI(aChild.uri)
|
PlacesUIUtils.guessUrlSchemeForUI(aChild.uri)
|
||||||
);
|
);
|
||||||
+ button.hidden = ZenWorkspaces.isBookmarkInAnotherWorkspace(aChild);
|
+
|
||||||
+ button.addEventListener("command", gZenGlanceManager.openGlanceForBookmark.bind(gZenGlanceManager));
|
+ 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)
|
PlacesUIUtils.guessUrlSchemeForUI(placesNode.uri)
|
||||||
);
|
);
|
||||||
element.setAttribute("label", PlacesUIUtils.getBestTitle(placesNode));
|
element.setAttribute("label", PlacesUIUtils.getBestTitle(placesNode));
|
||||||
|
182
src/browser/components/places/content/editBookmark-js.patch
Normal file
182
src/browser/components/places/content/editBookmark-js.patch
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
diff --git a/browser/components/places/content/editBookmark.js b/browser/components/places/content/editBookmark.js
|
||||||
|
index 9f17174fdd9cc1eaefb4330da1e10f40eeda2f31..e2c38872ee3a5c45d2e288e67d33f9ce24cab2b9 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,148 @@ 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 = [];
|
||||||
|
+ const selectedNames = [];
|
||||||
|
+
|
||||||
|
+ checkboxes.forEach(checkbox => {
|
||||||
|
+ if (checkbox.checked) {
|
||||||
|
+ newWorkspaces.push(checkbox.value);
|
||||||
|
+
|
||||||
|
+ const label = checkbox.parentNode.textContent.trim();
|
||||||
|
+ selectedNames.push(label);
|
||||||
|
+ }
|
||||||
|
+ });
|
||||||
|
+
|
||||||
|
+ this._selectedWorkspaces = [ ...newWorkspaces];
|
||||||
|
+
|
||||||
|
+ // Update the bookmark state
|
||||||
|
+ if (this._bookmarkState) {
|
||||||
|
+ await this._bookmarkState._workspacesChanged(this._selectedWorkspaces);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ // Update summary text
|
||||||
|
+ this._workspaceSummary.textContent = selectedNames.length
|
||||||
|
+ ? selectedNames.join(", ")
|
||||||
|
+ : "-";
|
||||||
|
+ },
|
||||||
|
+
|
||||||
|
+ onWorkspaceDropdownToggle() {
|
||||||
|
+ if(document.documentElement.getAttribute("windowtype") === "Places:Organizer") {
|
||||||
|
+ return;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ // Toggle active class on the container
|
||||||
|
+ const dropdown = this._workspaceList;
|
||||||
|
+ const button = this._workspaceSummary;
|
||||||
|
+
|
||||||
|
+ dropdown.hidden = !dropdown.hidden;
|
||||||
|
+
|
||||||
|
+ var expander = this._element("workspacesSelectorExpander");
|
||||||
|
+ expander.classList.toggle("expander-up", !dropdown.hidden);
|
||||||
|
+ expander.classList.toggle("expander-down", dropdown.hidden);
|
||||||
|
+
|
||||||
|
+ // Only update summary text when closing the dropdown
|
||||||
|
+ if (dropdown.hidden) {
|
||||||
|
+ const checkboxes = this._workspaceList.querySelectorAll("input[type='checkbox']");
|
||||||
|
+ const selectedLabels = [];
|
||||||
|
+
|
||||||
|
+ checkboxes.forEach(checkbox => {
|
||||||
|
+ if (checkbox.checked) {
|
||||||
|
+ const label = checkbox.parentNode.textContent.trim();
|
||||||
|
+ selectedLabels.push(label);
|
||||||
|
+ }
|
||||||
|
+ });
|
||||||
|
+
|
||||||
|
+ button.textContent = selectedLabels.length
|
||||||
|
+ ? selectedLabels.join(", ")
|
||||||
|
+ : "-";
|
||||||
|
+ }
|
||||||
|
+ },
|
||||||
|
+
|
||||||
|
+ 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);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ // Get the names of selected workspaces for initial summary
|
||||||
|
+ const selectedNames = this._workspaces
|
||||||
|
+ .filter(ws => this._selectedWorkspaces?.includes(ws.uuid))
|
||||||
|
+ .map(ws => ws.name);
|
||||||
|
+
|
||||||
|
+ // Update summary text with comma-separated list
|
||||||
|
+ this._workspaceSummary.textContent = selectedNames.length
|
||||||
|
+ ? selectedNames.join(", ")
|
||||||
|
+ : "-";
|
||||||
|
+
|
||||||
|
+ // Handle read-only state
|
||||||
|
+ if (this.readOnly) {
|
||||||
|
+ this._workspaceDropdown.setAttribute("disabled", "true");
|
||||||
|
+ } else {
|
||||||
|
+ this._workspaceDropdown.removeAttribute("disabled");
|
||||||
|
+ }
|
||||||
|
+ },
|
||||||
|
+ _selectedWorkspaces : [],
|
||||||
|
};
|
||||||
|
|
||||||
|
ChromeUtils.defineLazyGetter(gEditItemOverlay, "_folderTree", () => {
|
||||||
|
@@ -1267,6 +1414,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
|
diff --git a/browser/components/places/content/editBookmarkPanel.inc.xhtml b/browser/components/places/content/editBookmarkPanel.inc.xhtml
|
||||||
index 3ec3f094831c2143a818b43d1761a571f0ffa63d..309dfa8ed628f4cc124fe16d20b7411065c09f23 100644
|
index 3ec3f094831c2143a818b43d1761a571f0ffa63d..c4dd904604ee10a909bbcc7c03dd0dd3536020b1 100644
|
||||||
--- a/browser/components/places/content/editBookmarkPanel.inc.xhtml
|
--- a/browser/components/places/content/editBookmarkPanel.inc.xhtml
|
||||||
+++ b/browser/components/places/content/editBookmarkPanel.inc.xhtml
|
+++ b/browser/components/places/content/editBookmarkPanel.inc.xhtml
|
||||||
@@ -5,7 +5,7 @@
|
@@ -5,7 +5,7 @@
|
||||||
@@ -29,11 +29,30 @@ index 3ec3f094831c2143a818b43d1761a571f0ffa63d..309dfa8ed628f4cc124fe16d20b74110
|
|||||||
<label data-l10n-id="bookmark-overlay-location-2"
|
<label data-l10n-id="bookmark-overlay-location-2"
|
||||||
class="editBMPanel_folderRow hideable"
|
class="editBMPanel_folderRow hideable"
|
||||||
control="editBMPanel_folderMenuList"/>
|
control="editBMPanel_folderMenuList"/>
|
||||||
@@ -51,6 +51,7 @@
|
@@ -51,6 +51,26 @@
|
||||||
data-l10n-id="bookmark-overlay-folders-expander2"
|
data-l10n-id="bookmark-overlay-folders-expander2"
|
||||||
oncommand="gEditItemOverlay.toggleFolderTreeVisibility();"/>
|
oncommand="gEditItemOverlay.toggleFolderTreeVisibility();"/>
|
||||||
</hbox>
|
</hbox>
|
||||||
+</hbox>
|
+</hbox>
|
||||||
|
+ <vbox>
|
||||||
|
+ <label data-l10n-id="zen-bookmark-edit-panel-workspace-selector"
|
||||||
|
+ class="hideable"
|
||||||
|
+ control="editBMPanel_workspacesSelectorExpander"/>
|
||||||
|
+ <div id="editBMPanel_workspaceDropdown"
|
||||||
|
+ class="editBMPanel_workspaceRow hideable workspace-dropdown">
|
||||||
|
+ <div
|
||||||
|
+ id="editBMPanel_workspaceSummary"
|
||||||
|
+ class="workspace-trigger">-</div>
|
||||||
|
+ <button id="editBMPanel_workspacesSelectorExpander"
|
||||||
|
+ class="expander-down panel-button"
|
||||||
|
+ data-l10n-id="bookmark-overlay-tags-expander2"
|
||||||
|
+ oncommand="gEditItemOverlay.onWorkspaceDropdownToggle();"/>
|
||||||
|
+
|
||||||
|
+ </div>
|
||||||
|
+ </vbox>
|
||||||
|
+
|
||||||
|
+ <ul id="editBMPanel_workspaceList" class="workspace-list hideable" hidden="true">
|
||||||
|
+ </ul>
|
||||||
|
|
||||||
<vbox id="editBMPanel_folderTreeRow"
|
<vbox id="editBMPanel_folderTreeRow"
|
||||||
class="hideable"
|
class="hideable"
|
||||||
|
@@ -0,0 +1,15 @@
|
|||||||
|
diff --git a/browser/locales/en-US/browser/editBookmarkOverlay.ftl b/browser/locales/en-US/browser/editBookmarkOverlay.ftl
|
||||||
|
index da74660e48620fe9097d05a51ba4be34f21246e6..d70b98c71ca3a70732c633d939079f9fb589726f 100644
|
||||||
|
--- a/browser/locales/en-US/browser/editBookmarkOverlay.ftl
|
||||||
|
+++ b/browser/locales/en-US/browser/editBookmarkOverlay.ftl
|
||||||
|
@@ -37,6 +37,10 @@ bookmark-overlay-tags-2 =
|
||||||
|
.value = Tags
|
||||||
|
.accesskey = T
|
||||||
|
|
||||||
|
+zen-bookmark-edit-panel-workspace-selector =
|
||||||
|
+ .value = Workspaces
|
||||||
|
+ .accesskey = W
|
||||||
|
+
|
||||||
|
bookmark-overlay-tags-empty-description =
|
||||||
|
.placeholder = Separate tags with commas
|
||||||
|
|
59
src/browser/themes/shared/places/editBookmark-css.patch
Normal file
59
src/browser/themes/shared/places/editBookmark-css.patch
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
diff --git a/browser/themes/shared/places/editBookmark.css b/browser/themes/shared/places/editBookmark.css
|
||||||
|
index 4c00982e620f4cfd5aa1d97d45a276f5d41d0d74..58018015d6046895c996f808785ab7282e5fed81 100644
|
||||||
|
--- a/browser/themes/shared/places/editBookmark.css
|
||||||
|
+++ b/browser/themes/shared/places/editBookmark.css
|
||||||
|
@@ -158,3 +158,53 @@
|
||||||
|
font-size: 0.9em;
|
||||||
|
margin: 2px 4px;
|
||||||
|
}
|
||||||
|
+
|
||||||
|
+/*Bookmark workspace selector styles*/
|
||||||
|
+.workspace-dropdown {
|
||||||
|
+ position: relative;
|
||||||
|
+ width: 100%;
|
||||||
|
+ display: flex;
|
||||||
|
+ gap: 16px;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+.workspace-trigger {
|
||||||
|
+ width: 100%;
|
||||||
|
+ text-align: left;
|
||||||
|
+ padding: 8px 12px;
|
||||||
|
+ border: 1px solid var(--card-outline-color);
|
||||||
|
+ border-radius: 4px;
|
||||||
|
+ background-color: var(--zen-colors-tertiary);
|
||||||
|
+ display: flex;
|
||||||
|
+ align-items: center;
|
||||||
|
+ justify-content: space-between;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+.workspace-list {
|
||||||
|
+ flex-direction: column;
|
||||||
|
+ width: 100%;
|
||||||
|
+ max-height: 200px;
|
||||||
|
+ overflow-y: auto;
|
||||||
|
+ margin-top: 4px;
|
||||||
|
+ border: 1px solid var(--card-outline-color);
|
||||||
|
+ border-radius: 4px;
|
||||||
|
+ background-color: var(--zen-colors-tertiary);
|
||||||
|
+ box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1);
|
||||||
|
+ padding: 4px 0;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+.workspace-list li {
|
||||||
|
+ list-style: none;
|
||||||
|
+ margin: 0;
|
||||||
|
+ padding: 0;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+.workspace-list li > label {
|
||||||
|
+ display: flex;
|
||||||
|
+ align-items: center;
|
||||||
|
+ padding: 4px 12px;
|
||||||
|
+ cursor: pointer;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+.workspace-list input[type="checkbox"] {
|
||||||
|
+ margin-right: 8px;
|
||||||
|
+}
|
||||||
|
\ No newline at end of file
|
Reference in New Issue
Block a user