From fbbd3be0d38dceffb24944329e8e1fbc2ef2f0b0 Mon Sep 17 00:00:00 2001 From: BibekBhusal0 Date: Thu, 17 Jul 2025 17:57:52 +0545 Subject: [PATCH 01/12] refactor: workspace showing both pinned and not pinned tabs --- sections/workspace.js | 140 +++++++++++++++++------------------------- style.css | 41 +++++++------ 2 files changed, 79 insertions(+), 102 deletions(-) diff --git a/sections/workspace.js b/sections/workspace.js index 03c6b5e..f121858 100644 --- a/sections/workspace.js +++ b/sections/workspace.js @@ -22,7 +22,9 @@ export const workspacesSection = { `, init: function() { - const container = parseElement(`
`); + const container = parseElement(`
`); + const innerContainer = container.querySelector('#haven-workspace-inner-container') + // const outerContainer = container.querySelector('#haven-workspace-outer-container') const addWorkspaceButton = parseElement(`
@@ -41,103 +43,73 @@ export const workspacesSection = { } }); - const workspacesButton = document.getElementById("zen-workspaces-button"); - if (workspacesButton) { - console.log("[ZenHaven] Found workspace button:", workspacesButton); - const workspaceElements = Array.from(workspacesButton.children); - console.log("[ZenHaven] Workspace elements:", workspaceElements); + if (typeof gZenWorkspaces === "undefined") { + console.error("[ZenHaven] gZenWorkspaces is not available."); + innerContainer.appendChild(addWorkspaceButton); + return innerContainer; + } + + gZenWorkspaces + ._workspaces() + .then(({ workspaces: allWorkspaces }) => { + const allTabs = gZenWorkspaces.allStoredTabs || []; - workspaceElements.forEach((workspace) => { - // Create base workspace div - const workspaceDiv = parseElement( - `
`, - ); - const uuid = workspace.getAttribute("zen-workspace-id"); + allWorkspaces.forEach((workspace) => { + const workspaceDiv = parseElement( + `
`, + ); + const { uuid, theme } = workspace; - ZenWorkspacesStorage.getWorkspaces().then((allWorkspaces) => { - const data = allWorkspaces.find((ws) => ws.uuid === uuid); - if ( - data?.theme?.type === "gradient" && - data.theme.gradientColors?.length - ) { - workspaceDiv.style.background = getGradientCSS(data.theme); - workspaceDiv.style.opacity = data.theme.opacity ?? 1; + if (theme?.type === "gradient" && theme.gradientColors?.length) { + workspaceDiv.style.background = getGradientCSS(theme); + workspaceDiv.style.opacity = theme.opacity ?? 1; } else { workspaceDiv.style.background = "var(--zen-colors-border)"; workspaceDiv.style.opacity = 1; } - }); - - // Create content container - const contentDiv = parseElement( - `
`, - ); - - // Find workspace sections using the workspace's own ID - const sections = document.querySelectorAll( - `.zen-workspace-tabs-section[zen-workspace-id="${workspace.getAttribute( - "zen-workspace-id", - )}"]`, - ); - sections.forEach((section) => { - const root = section.shadowRoot || section; - const sectionWrapper = parseElement( - `
`, + const contentDiv = parseElement( + `
`, ); - - // Copy computed styles from original section - const computedStyle = window.getComputedStyle(section); - sectionWrapper.style.cssText = Array.from(computedStyle).reduce( - (str, property) => { - return `${str}${property}:${computedStyle.getPropertyValue( - property, - )};`; - }, - "", + const pinnedTabsContainer = parseElement( + `
`, + ); + const regularTabsContainer = parseElement( + `
`, ); - // Clone tab groups with their styles - const tabGroups = root.querySelectorAll("tab-group"); - tabGroups.forEach((group) => { - const groupClone = group.cloneNode(true); - const groupStyle = window.getComputedStyle(group); - groupClone.style.cssText = Array.from(groupStyle).reduce( - (str, property) => { - return `${str}${property}:${groupStyle.getPropertyValue( - property, - )};`; - }, - "", - ); - sectionWrapper.appendChild(groupClone); - }); + allTabs + .filter( + (tabEl) => + tabEl && + tabEl.getAttribute("zen-workspace-id") === uuid && + !tabEl.hasAttribute("zen-essential"), + ) + .forEach((tabEl) => { + const clonedTab = tabEl.cloneNode(true); + if (clonedTab.hasAttribute("pinned")) { + pinnedTabsContainer.appendChild(clonedTab); + } else { + regularTabsContainer.appendChild(clonedTab); + } + }); + + if (pinnedTabsContainer.hasChildNodes()) { + contentDiv.appendChild(pinnedTabsContainer); + } + if (regularTabsContainer.hasChildNodes()) { + contentDiv.appendChild(regularTabsContainer); + } - // Clone remaining children with their styles - Array.from(root.children).forEach((child) => { - if (!child.classList.contains("zen-tab-group")) { - const clone = child.cloneNode(true); - const childStyle = window.getComputedStyle(child); - clone.style.cssText = Array.from(childStyle).reduce( - (str, property) => { - return `${str}${property}:${childStyle.getPropertyValue( - property, - )};`; - }, - "", - ); - sectionWrapper.appendChild(clone); - } - }); - contentDiv.appendChild(sectionWrapper); + workspaceDiv.appendChild(contentDiv); + innerContainer.insertBefore(workspaceDiv, addWorkspaceButton); }); - - workspaceDiv.appendChild(contentDiv); - container.appendChild(workspaceDiv); + }) + .catch((error) => { + console.error("[ZenHaven] Error building workspaces section:", error); }); - } - container.appendChild(addWorkspaceButton); + innerContainer.appendChild(addWorkspaceButton); return container; }, }; diff --git a/style.css b/style.css index f7ab470..e1ec3c7 100644 --- a/style.css +++ b/style.css @@ -104,7 +104,8 @@ box-shadow: inset 10px 0 20px rgba(0, 0, 0, 0.5), /* left */ inset 0 0 20px rgba(0, 0, 0, 0.5), /* bottom */ inset 0 0 20px rgba(0, 0, 0, 0.5); /* top */ - + max-height:94vh !important; + &[haven-workspaces] { div { display: flex !important; @@ -113,17 +114,25 @@ } } + #haven-workspace-outer-container{ + width: 100% !important; + overflow-x: auto !important; + position: relative !important; + } + #haven-workspace-inner-container{ + gap:30px !important; + } + .haven-workspace { height: 85% !important; - min-width: 20%; + max-height: 85% !important; + min-width: 150px; background-color: var(--zen-primary-color); - margin-left: 30px; - margin-right: 30px; border-radius: 8px; border: 2px solid var(--zen-colors-border); - display: flex; - flex-direction: column; - align-items: center; + display: block !important; + align-items: start !important; + justify-content: start !important; padding: 10px !important; .tab-reset-pin-button { @@ -151,19 +160,15 @@ margin: 0 !important; padding: 10px !important; display: flex !important; - align-items: center !important; - height: fit-content !important; width: 100% !important; - overflow: hidden !important; - align-items: flex-start; - - .haven-workspace-section { + align-items: flex-start !important; + .haven-workspace-pinned-tabs,.haven-workspace-regular-tabs{ display: flex !important; - position: relative !important; - min-height: 70px !important; - margin: 0 !important; - padding-inline: 2px !important; - transform: translateX(0) !important; + height: fit-content !important; + flex-direction: column !important; + } + .haven-workspace-pinned-tabs{ + border-bottom:2px solid rgba(0,0,0,0.3) !important; } } } From 7a3aa30753c89db6cbfe96cc84a1354edd7c1264 Mon Sep 17 00:00:00 2001 From: BibekBhusal0 Date: Thu, 17 Jul 2025 18:03:22 +0545 Subject: [PATCH 02/12] adding header to workspace section --- sections/workspace.js | 14 +++++++++++--- style.css | 4 +++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/sections/workspace.js b/sections/workspace.js index f121858..f33ef31 100644 --- a/sections/workspace.js +++ b/sections/workspace.js @@ -46,7 +46,7 @@ export const workspacesSection = { if (typeof gZenWorkspaces === "undefined") { console.error("[ZenHaven] gZenWorkspaces is not available."); innerContainer.appendChild(addWorkspaceButton); - return innerContainer; + return container; } gZenWorkspaces @@ -58,7 +58,7 @@ export const workspacesSection = { const workspaceDiv = parseElement( `
`, ); - const { uuid, theme } = workspace; + const { uuid, theme, name, icon } = workspace; if (theme?.type === "gradient" && theme.gradientColors?.length) { workspaceDiv.style.background = getGradientCSS(theme); @@ -68,6 +68,14 @@ export const workspacesSection = { workspaceDiv.style.opacity = 1; } + const headerDiv = parseElement( + `
+ ${icon} + ${name} +
`, + ); + workspaceDiv.appendChild(headerDiv); + const contentDiv = parseElement( `
`, ); @@ -102,7 +110,7 @@ export const workspacesSection = { } workspaceDiv.appendChild(contentDiv); - innerContainer.insertBefore(workspaceDiv, addWorkspaceButton); + innerContainer.appendChild(workspaceDiv); }); }) .catch((error) => { diff --git a/style.css b/style.css index e1ec3c7..9d25a0d 100644 --- a/style.css +++ b/style.css @@ -130,7 +130,8 @@ background-color: var(--zen-primary-color); border-radius: 8px; border: 2px solid var(--zen-colors-border); - display: block !important; + display: flex !important; + flex-direction: column !important; align-items: start !important; justify-content: start !important; padding: 10px !important; @@ -144,6 +145,7 @@ } .haven-workspace-header { + height: fit-content !important; margin: 2px !important; .workspace-icon { From 00763ce1ebce68fb483a16a8061076bde9190e67 Mon Sep 17 00:00:00 2001 From: BibekBhusal0 Date: Thu, 17 Jul 2025 18:07:28 +0545 Subject: [PATCH 03/12] fix: opacity of workspace section --- sections/workspace.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sections/workspace.js b/sections/workspace.js index f33ef31..15b3a0e 100644 --- a/sections/workspace.js +++ b/sections/workspace.js @@ -5,10 +5,11 @@ function getGradientCSS(theme) { return "transparent"; const angle = Math.round(theme.rotation || 0); + const opacity = theme.opacity ?? 1; const stops = theme.gradientColors .map(({ c }) => { const [r, g, b] = c; - return `rgb(${r}, ${g}, ${b})`; + return `rgba(${r}, ${g}, ${b}, ${opacity})`; }) .join(", "); @@ -62,10 +63,8 @@ export const workspacesSection = { if (theme?.type === "gradient" && theme.gradientColors?.length) { workspaceDiv.style.background = getGradientCSS(theme); - workspaceDiv.style.opacity = theme.opacity ?? 1; } else { workspaceDiv.style.background = "var(--zen-colors-border)"; - workspaceDiv.style.opacity = 1; } const headerDiv = parseElement( @@ -121,3 +120,4 @@ export const workspacesSection = { return container; }, }; + From 3b37de4ad914900d557a06fa5143e5e119937606 Mon Sep 17 00:00:00 2001 From: BibekBhusal0 Date: Thu, 17 Jul 2025 19:55:27 +0545 Subject: [PATCH 04/12] feat: popup added for selecting options in header --- sections/workspace.js | 39 ++++++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/sections/workspace.js b/sections/workspace.js index 15b3a0e..7d770f4 100644 --- a/sections/workspace.js +++ b/sections/workspace.js @@ -56,10 +56,15 @@ export const workspacesSection = { const allTabs = gZenWorkspaces.allStoredTabs || []; allWorkspaces.forEach((workspace) => { + const { uuid, theme, name, icon } = workspace; const workspaceDiv = parseElement( - `
`, + `
+
+ ${icon} + ${name} +
+
`, ); - const { uuid, theme, name, icon } = workspace; if (theme?.type === "gradient" && theme.gradientColors?.length) { workspaceDiv.style.background = getGradientCSS(theme); @@ -67,13 +72,29 @@ export const workspacesSection = { workspaceDiv.style.background = "var(--zen-colors-border)"; } - const headerDiv = parseElement( - `
- ${icon} - ${name} -
`, - ); - workspaceDiv.appendChild(headerDiv); + const header = workspaceDiv.querySelector(".haven-workspace-header"); + const popupOpenButton = parseElement(` + `, 'xul'); + const menuPopup = parseElement(` + + + + + + + `, 'xul'); + header.appendChild(popupOpenButton); + container.appendChild(menuPopup); + + popupOpenButton.addEventListener("click", (event) => { + event.stopPropagation(); + menuPopup.openPopup(popupOpenButton, "after_start"); + }); const contentDiv = parseElement( `
`, From 2ae7456d4c09aeed0e58a9937a2682a2e9447f92 Mon Sep 17 00:00:00 2001 From: BibekBhusal0 Date: Thu, 17 Jul 2025 20:13:44 +0545 Subject: [PATCH 05/12] feat: made the menu useful --- sections/workspace.js | 96 ++++++++++++++++++++++++++++++++----------- 1 file changed, 73 insertions(+), 23 deletions(-) diff --git a/sections/workspace.js b/sections/workspace.js index 7d770f4..011453c 100644 --- a/sections/workspace.js +++ b/sections/workspace.js @@ -23,8 +23,12 @@ export const workspacesSection = { `, init: function() { - const container = parseElement(`
`); - const innerContainer = container.querySelector('#haven-workspace-inner-container') + const container = parseElement( + `
`, + ); + const innerContainer = container.querySelector( + "#haven-workspace-inner-container", + ); // const outerContainer = container.querySelector('#haven-workspace-outer-container') const addWorkspaceButton = @@ -57,13 +61,15 @@ export const workspacesSection = { allWorkspaces.forEach((workspace) => { const { uuid, theme, name, icon } = workspace; + // TODO: make renamable in douple click + // TODO: Icon picker const workspaceDiv = parseElement( `
-
- ${icon} - ${name} -
-
`, +
+ ${icon} + ${name} +
+
`, ); if (theme?.type === "gradient" && theme.gradientColors?.length) { @@ -73,21 +79,27 @@ export const workspacesSection = { } const header = workspaceDiv.querySelector(".haven-workspace-header"); - const popupOpenButton = parseElement(` - `, 'xul'); - const menuPopup = parseElement(` - - - - - - - `, 'xul'); + const popupOpenButton = parseElement( + ` + `, + "xul", + ); + const menuPopup = parseElement( + ` + + + + + + + `, + "xul", + ); header.appendChild(popupOpenButton); container.appendChild(menuPopup); @@ -96,6 +108,45 @@ export const workspacesSection = { menuPopup.openPopup(popupOpenButton, "after_start"); }); + // TODO: after rename on douple click is implimented, this will not prompt + menuPopup + .querySelector(".rename") + .addEventListener("click", async () => { + const workspace = gZenWorkspaces.getWorkspaceFromId(uuid); + if (workspace) { + const newName = prompt( + "Enter new name for workspace:", + workspace.name, + ); + if (newName?.trim()) { + workspace.name = newName.trim(); + await gZenWorkspaces.saveWorkspace(workspace); + workspaceDiv.querySelector(".workspace-name").textContent = + workspace.name; + } + } + }); + + menuPopup + .querySelector(".switch") + .addEventListener("click", async () => { + await gZenWorkspaces.changeWorkspaceWithID(uuid); + + // close haven + window.haven.destroyUI(); + }); + + menuPopup + .querySelector(".delete-workspace") + .addEventListener("click", async () => { + if ( + confirm(`Are you sure you want to delete workspace "${name}"?`) + ) { + await gZenWorkspaces.removeWorkspace(uuid); + workspaceDiv.remove(); + } + }); + const contentDiv = parseElement( `
`, ); @@ -141,4 +192,3 @@ export const workspacesSection = { return container; }, }; - From 752dd542bd3927f03d286a925a5209b35b0d96a9 Mon Sep 17 00:00:00 2001 From: BibekBhusal0 Date: Thu, 17 Jul 2025 20:20:07 +0545 Subject: [PATCH 06/12] fix: add workspace button --- sections/workspace.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/sections/workspace.js b/sections/workspace.js index 011453c..dff653b 100644 --- a/sections/workspace.js +++ b/sections/workspace.js @@ -37,11 +37,19 @@ export const workspacesSection = { `); addWorkspaceButton.addEventListener("click", () => { try { - if (typeof ZenWorkspaces?.openSaveDialog === "function") { - console.log("[ZenHaven] Attempting to open workspace save dialog..."); - ZenWorkspaces.openSaveDialog(); + if (typeof gZenWorkspaces?.openWorkspaceCreation === "function") { + console.log( + "[ZenHaven] Attempting to open workspace creation dialog...", + ); + + // close haven + window.haven.destroyUI(); + + gZenWorkspaces.openWorkspaceCreation(); } else { - throw new Error("ZenWorkspaces.openSaveDialog is not available"); + throw new Error( + "gZenWorkspaces.openWorkspaceCreation is not available", + ); } } catch (error) { console.error("[ZenHaven] Error opening workspace dialog:", error); From 144e6fcff5191b700e7bf4088ed3b6614c7509de Mon Sep 17 00:00:00 2001 From: BibekBhusal0 Date: Thu, 17 Jul 2025 20:29:55 +0545 Subject: [PATCH 07/12] feat: workspace can be dragged to reorder --- sections/workspace.js | 62 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/sections/workspace.js b/sections/workspace.js index dff653b..43e7801 100644 --- a/sections/workspace.js +++ b/sections/workspace.js @@ -31,6 +31,56 @@ export const workspacesSection = { ); // const outerContainer = container.querySelector('#haven-workspace-outer-container') + function getDragAfterElement(container, x) { + const draggableElements = [ + ...container.querySelectorAll(".haven-workspace:not(.dragging)"), + ]; + + return draggableElements.reduce( + (closest, child) => { + const box = child.getBoundingClientRect(); + const offset = x - box.left - box.width / 2; + if (offset < 0 && offset > closest.offset) { + return { offset: offset, element: child }; + } else { + return closest; + } + }, + { offset: Number.NEGATIVE_INFINITY }, + ).element; + } + + innerContainer.addEventListener("dragover", (e) => { + e.preventDefault(); + const afterElement = getDragAfterElement(innerContainer, e.clientX); + const dragging = innerContainer.querySelector(".dragging"); + if (dragging) { + if (afterElement == null) { + innerContainer.appendChild(dragging); + } else { + innerContainer.insertBefore(dragging, afterElement); + } + } + }); + + innerContainer.addEventListener("drop", async (e) => { + e.preventDefault(); + const draggedElement = innerContainer.querySelector(".dragging"); + if (!draggedElement) return; + + const draggedUuid = draggedElement.dataset.uuid; + const workspaceElements = [ + ...innerContainer.querySelectorAll(".haven-workspace"), + ]; + const newIndex = workspaceElements.findIndex( + (el) => el === draggedElement, + ); + + if (newIndex !== -1) { + await gZenWorkspaces.reorderWorkspace(draggedUuid, newIndex); + } + }); + const addWorkspaceButton = parseElement(`
@@ -80,6 +130,18 @@ export const workspacesSection = {
`, ); + workspaceDiv.draggable = true; + workspaceDiv.dataset.uuid = uuid; + + workspaceDiv.addEventListener("dragstart", (e) => { + e.target.classList.add("dragging"); + e.dataTransfer.effectAllowed = "move"; + }); + + workspaceDiv.addEventListener("dragend", (e) => { + e.target.classList.remove("dragging"); + }); + if (theme?.type === "gradient" && theme.gradientColors?.length) { workspaceDiv.style.background = getGradientCSS(theme); } else { From 81c8e3314d4d86dcbcbca861c7ca17c1c8e5fb3a Mon Sep 17 00:00:00 2001 From: BibekBhusal0 Date: Thu, 17 Jul 2025 23:43:25 +0545 Subject: [PATCH 08/12] feat: workspace drag handle --- sections/workspace.js | 18 ++++++++++++------ style.css | 10 ++++++++++ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/sections/workspace.js b/sections/workspace.js index 43e7801..cc3e795 100644 --- a/sections/workspace.js +++ b/sections/workspace.js @@ -124,22 +124,27 @@ export const workspacesSection = { const workspaceDiv = parseElement( `
+ + ${icon} ${name}
`, ); - - workspaceDiv.draggable = true; + workspaceDiv.dataset.uuid = uuid; - workspaceDiv.addEventListener("dragstart", (e) => { - e.target.classList.add("dragging"); + const dragHandle = workspaceDiv.querySelector('.workspace-drag-handle'); + + dragHandle.addEventListener("dragstart", (e) => { + const workspaceElement = e.target.closest('.haven-workspace'); + workspaceElement.classList.add("dragging"); e.dataTransfer.effectAllowed = "move"; }); - workspaceDiv.addEventListener("dragend", (e) => { - e.target.classList.remove("dragging"); + dragHandle.addEventListener("dragend", (e) => { + const workspaceElement = e.target.closest('.haven-workspace'); + workspaceElement.classList.remove("dragging"); }); if (theme?.type === "gradient" && theme.gradientColors?.length) { @@ -262,3 +267,4 @@ export const workspacesSection = { return container; }, }; + diff --git a/style.css b/style.css index 9d25a0d..a1cf9fb 100644 --- a/style.css +++ b/style.css @@ -135,6 +135,7 @@ align-items: start !important; justify-content: start !important; padding: 10px !important; + position: relative !important; .tab-reset-pin-button { display: none; @@ -173,6 +174,15 @@ border-bottom:2px solid rgba(0,0,0,0.3) !important; } } + + .workspace-drag-handle{ + width: 50% !important; + height: 20px !important; + background: red !important; + position: absolute !important; + bottom:0 !important; + } + } } } From d263bf86ece131be16f59dd4d5cad594825e0729 Mon Sep 17 00:00:00 2001 From: BibekBhusal0 Date: Fri, 18 Jul 2025 00:15:07 +0545 Subject: [PATCH 09/12] feat: rename on double click --- sections/workspace.js | 70 ++++++++++++++++++++++++++++++++----------- 1 file changed, 52 insertions(+), 18 deletions(-) diff --git a/sections/workspace.js b/sections/workspace.js index cc3e795..bbe8493 100644 --- a/sections/workspace.js +++ b/sections/workspace.js @@ -119,7 +119,6 @@ export const workspacesSection = { allWorkspaces.forEach((workspace) => { const { uuid, theme, name, icon } = workspace; - // TODO: make renamable in douple click // TODO: Icon picker const workspaceDiv = parseElement( `
@@ -183,24 +182,60 @@ export const workspacesSection = { menuPopup.openPopup(popupOpenButton, "after_start"); }); - // TODO: after rename on douple click is implimented, this will not prompt + const workspaceNameEl = workspaceDiv.querySelector(".workspace-name"); + + const enableRename = () => { + const originalName = workspace.name; + workspaceNameEl.contentEditable = true; + workspaceNameEl.style.cursor = "text"; + workspaceNameEl.focus(); + + const selection = window.getSelection(); + const range = document.createRange(); + range.selectNodeContents(workspaceNameEl); + selection.removeAllRanges(); + selection.addRange(range); + + const cleanup = () => { + workspaceNameEl.removeEventListener("blur", handleBlur); + workspaceNameEl.removeEventListener("keydown", handleKeyDown); + workspaceNameEl.contentEditable = false; + workspaceNameEl.style.cursor = ""; + }; + + const handleBlur = async () => { + cleanup(); + const newName = workspaceNameEl.textContent.trim(); + const currentWorkspace = gZenWorkspaces.getWorkspaceFromId(uuid); + if (currentWorkspace && newName && newName !== originalName) { + currentWorkspace.name = newName; + await gZenWorkspaces.saveWorkspace(currentWorkspace); + workspace.name = newName; + } else { + workspaceNameEl.textContent = originalName; + } + }; + + const handleKeyDown = (e) => { + if (e.key === "Enter") { + e.preventDefault(); + workspaceNameEl.blur(); + } else if (e.key === "Escape") { + e.preventDefault(); + workspaceNameEl.textContent = originalName; + workspaceNameEl.blur(); + } + }; + + workspaceNameEl.addEventListener("blur", handleBlur); + workspaceNameEl.addEventListener("keydown", handleKeyDown); + }; + + workspaceNameEl.addEventListener("dblclick", enableRename); + menuPopup .querySelector(".rename") - .addEventListener("click", async () => { - const workspace = gZenWorkspaces.getWorkspaceFromId(uuid); - if (workspace) { - const newName = prompt( - "Enter new name for workspace:", - workspace.name, - ); - if (newName?.trim()) { - workspace.name = newName.trim(); - await gZenWorkspaces.saveWorkspace(workspace); - workspaceDiv.querySelector(".workspace-name").textContent = - workspace.name; - } - } - }); + .addEventListener("click", enableRename); menuPopup .querySelector(".switch") @@ -267,4 +302,3 @@ export const workspacesSection = { return container; }, }; - From 8b32a89cf348ef0d1aea663220f0adebe9baaec1 Mon Sep 17 00:00:00 2001 From: BibekBhusal0 Date: Fri, 18 Jul 2025 01:28:37 +0545 Subject: [PATCH 10/12] refactor: formatting spacing --- sections/workspace.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/sections/workspace.js b/sections/workspace.js index bbe8493..17bf68a 100644 --- a/sections/workspace.js +++ b/sections/workspace.js @@ -130,19 +130,21 @@ export const workspacesSection = {
`, ); - + workspaceDiv.dataset.uuid = uuid; - const dragHandle = workspaceDiv.querySelector('.workspace-drag-handle'); + const dragHandle = workspaceDiv.querySelector( + ".workspace-drag-handle", + ); dragHandle.addEventListener("dragstart", (e) => { - const workspaceElement = e.target.closest('.haven-workspace'); + const workspaceElement = e.target.closest(".haven-workspace"); workspaceElement.classList.add("dragging"); e.dataTransfer.effectAllowed = "move"; }); dragHandle.addEventListener("dragend", (e) => { - const workspaceElement = e.target.closest('.haven-workspace'); + const workspaceElement = e.target.closest(".haven-workspace"); workspaceElement.classList.remove("dragging"); }); @@ -230,7 +232,7 @@ export const workspacesSection = { workspaceNameEl.addEventListener("blur", handleBlur); workspaceNameEl.addEventListener("keydown", handleKeyDown); }; - + workspaceNameEl.addEventListener("dblclick", enableRename); menuPopup From b71587bbda6c02cd92df51f7da468a061f0501df Mon Sep 17 00:00:00 2001 From: BibekBhusal0 Date: Fri, 18 Jul 2025 01:35:11 +0545 Subject: [PATCH 11/12] feat: add icon picker --- sections/workspace.js | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/sections/workspace.js b/sections/workspace.js index 17bf68a..65f1100 100644 --- a/sections/workspace.js +++ b/sections/workspace.js @@ -119,7 +119,6 @@ export const workspacesSection = { allWorkspaces.forEach((workspace) => { const { uuid, theme, name, icon } = workspace; - // TODO: Icon picker const workspaceDiv = parseElement( `
@@ -137,6 +136,25 @@ export const workspacesSection = { ".workspace-drag-handle", ); + const iconEl = workspaceDiv.querySelector(".workspace-icon"); + iconEl.addEventListener("click", () => { + gZenEmojiPicker + .open(iconEl) + .then(async (newIcon) => { + console.log("Selected emoji:", newIcon); + iconEl.innerText = newIcon; + const currentWorkspace = + gZenWorkspaces.getWorkspaceFromId(uuid); + if (currentWorkspace && newIcon && newIcon !== icon) { + currentWorkspace.icon = newIcon; + await gZenWorkspaces.saveWorkspace(currentWorkspace); + } else { + workspaceNameEl.textContent = originalName; + } + }) + .catch((e) => console.error(e)); + }); + dragHandle.addEventListener("dragstart", (e) => { const workspaceElement = e.target.closest(".haven-workspace"); workspaceElement.classList.add("dragging"); From 771eb80c238a027f4e26f0e37ee3138c82b2c2f8 Mon Sep 17 00:00:00 2001 From: BibekBhusal0 Date: Fri, 18 Jul 2025 02:00:05 +0545 Subject: [PATCH 12/12] feat: copy url --- sections/workspace.js | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/sections/workspace.js b/sections/workspace.js index 65f1100..b9e81ce 100644 --- a/sections/workspace.js +++ b/sections/workspace.js @@ -296,6 +296,15 @@ export const workspacesSection = { ) .forEach((tabEl) => { const clonedTab = tabEl.cloneNode(true); + let url; + try { + const browser = gBrowser.getBrowserForTab(tabEl); + url = browser.currentURI.spec; + // save url in tab + clonedTab.setAttribute("data-url", url); + } catch (e) { + console.error("Could not get tab URL", e); + } if (clonedTab.hasAttribute("pinned")) { pinnedTabsContainer.appendChild(clonedTab); } else { @@ -311,6 +320,27 @@ export const workspacesSection = { } workspaceDiv.appendChild(contentDiv); + const closeButtons = + workspaceDiv.querySelectorAll(".tab-close-button"); + closeButtons.forEach((btn) => { + btn.addEventListener("click", (e) => { + e.stopPropagation(); + e.preventDefault(); + const tab = e.target.closest("tab.tabbrowser-tab"); + // get saved url + const url = tab.getAttribute("data-url"); + if (url) { + try { + navigator.clipboard.writeText(url); + gZenUIManager.showToast("zen-copy-current-url-confirmation"); + } catch { + (err) => { + console.error("Failed to copy URL:", err); + }; + } + } + }); + }); innerContainer.appendChild(workspaceDiv); }); })