Added workspace selector on bookmarks create/edit modal, added table for storing relationships between workspaces and bookmarks.

This commit is contained in:
Kristijan Ribarić
2024-11-26 22:38:52 +01:00
parent 5497016015
commit 78d10b4c1b
8 changed files with 745 additions and 21 deletions

View File

@@ -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;
}
})();

View File

@@ -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`);
});
},
};