diff --git a/locales/en-US/browser/browser/zen-general.ftl b/locales/en-US/browser/browser/zen-general.ftl index d1229b04f..c1a4efa7c 100644 --- a/locales/en-US/browser/browser/zen-general.ftl +++ b/locales/en-US/browser/browser/zen-general.ftl @@ -19,13 +19,19 @@ tab-context-zen-add-essential-badge = { $num } / { $max } tab-context-zen-remove-essential = .label = Remove from Essentials .accesskey = R -tab-context-zen-replace-pinned-url-with-current = +tab-context-zen-edit-pinned-page = .label = { $isEssential -> - [true] Replace Essential URL with Current - *[false] Replace Pinned URL with Current + [true] Edit Essential URL + *[false] Edit Pinned URL } + .accesskey = P +tab-context-zen-replace-pinned-url-with-current = + .label = Replace with Current URL .accesskey = C +tab-context-zen-edit-pinned-url = + .label = Edit... + .accesskey = E tab-context-zen-edit-title = .label = Change Label... tab-context-zen-edit-icon = @@ -55,6 +61,10 @@ zen-general-confirm = .label = Confirm zen-pinned-tab-replaced = Pinned tab URL has been replaced with the current URL! +zen-pinned-tab-url-edited = Pinned tab URL has been updated! +zen-pinned-tab-url-invalid = That doesn't look like a valid URL. +zen-pinned-tab-edit-url-title = Edit Pinned URL +zen-pinned-tab-edit-url-label = Enter the URL this pinned tab should point to: zen-tabs-renamed = Tab has been successfully renamed! zen-background-tab-opened-toast = New background tab opened! zen-workspace-renamed-toast = Workspace has been successfully renamed! diff --git a/src/browser/base/content/zen-commands.inc.xhtml b/src/browser/base/content/zen-commands.inc.xhtml index 2122c56f0..3019feaca 100644 --- a/src/browser/base/content/zen-commands.inc.xhtml +++ b/src/browser/base/content/zen-commands.inc.xhtml @@ -35,6 +35,7 @@ + diff --git a/src/zen/common/zen-sets.js b/src/zen/common/zen-sets.js index 289dbe9eb..e9fecea05 100644 --- a/src/zen/common/zen-sets.js +++ b/src/zen/common/zen-sets.js @@ -78,6 +78,9 @@ document.addEventListener( case "cmd_zenReplacePinnedUrlWithCurrent": gZenPinnedTabManager.replacePinnedUrlWithCurrent(); break; + case "cmd_zenEditPinnedUrl": + gZenPinnedTabManager.editPinnedUrl(); + break; case "cmd_contextZenAddToEssentials": gZenPinnedTabManager.addToEssentials(); break; diff --git a/src/zen/sessionstore/ZenWindowSync.sys.mjs b/src/zen/sessionstore/ZenWindowSync.sys.mjs index 0d4ad78ec..f4f993923 100644 --- a/src/zen/sessionstore/ZenWindowSync.sys.mjs +++ b/src/zen/sessionstore/ZenWindowSync.sys.mjs @@ -1232,19 +1232,38 @@ class nsZenWindowSync { activeIndex = Math.min(activeIndex, entries.length - 1); activeIndex = Math.max(activeIndex, 0); let entryToUse = (entries[activeIndex] || entries[0]) ?? null; - const initialState = { - entry: { - url: entryToUse?.url, - title: entryToUse?.title, - }, - image, - }; - this.#runOnAllWindows(null, win => { - const targetTab = this.getItemFromWindow(win, aTab.id); - if (targetTab) { - targetTab._zenPinnedInitialState = initialState; - } - }); + this.#setPinnedInitialState( + aTab, + { url: entryToUse?.url, title: entryToUse?.title }, + image + ); + }); + } + + /** + * Sets the canonical pinned URL for a tab across all windows. Used to let the + * user edit a pinned tab's URL directly. + * + * @param {object} aTab - The tab to set the pinned URL for. + * @param {string} aUrl - The URL to store as the canonical pinned URL. + * @param {string} [aImage] - Optional Icon to store. + */ + setPinnedUrl(aTab, aUrl, aImage) { + this.log(`Setting pinned url for tab ${aTab.id}`); + this.#setPinnedInitialState( + aTab, + { url: aUrl, title: aTab.zenStaticLabel }, + aImage + ); + } + + #setPinnedInitialState(aTab, aEntry, aImage) { + const initialState = { entry: aEntry, image: aImage }; + this.#runOnAllWindows(null, win => { + const targetTab = this.getItemFromWindow(win, aTab.id); + if (targetTab) { + targetTab._zenPinnedInitialState = initialState; + } }); } diff --git a/src/zen/tabs/ZenPinnedTabManager.mjs b/src/zen/tabs/ZenPinnedTabManager.mjs index b2adb121f..4a441f8b2 100644 --- a/src/zen/tabs/ZenPinnedTabManager.mjs +++ b/src/zen/tabs/ZenPinnedTabManager.mjs @@ -246,6 +246,66 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature { gZenUIManager.showToast("zen-pinned-tab-replaced"); } + async editPinnedUrl(tab = undefined) { + tab ??= TabContextMenu.contextTab; + if (!tab || !tab.pinned) { + return; + } + + const initialUrl = + tab._zenPinnedInitialState?.entry?.url || + tab.linkedBrowser?.currentURI?.spec; + const [title, label] = await document.l10n.formatValues([ + { id: "zen-pinned-tab-edit-url-title" }, + { id: "zen-pinned-tab-edit-url-label" }, + ]); + const result = { value: initialUrl ?? "" }; + const confirmed = Services.prompt.prompt( + window, + title, + label, + result, + null, + { value: false } + ); + if (!confirmed) { + return; + } + + let uri; + try { + uri = Services.uriFixup.getFixupURIInfo( + result.value.trim(), + Ci.nsIURIFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS + ).preferredURI; + } catch (_) {} + if (!uri) { + gZenUIManager.showToast("zen-pinned-tab-url-invalid"); + return; + } + const url = uri.spec; + + // Skip when the value wasn't actually changed from what was prefilled. + if (!url || url === initialUrl) { + return; + } + + const image = tab.zenStaticIcon || (await this.#getCachedFavicon(uri)); + window.gZenWindowSync.setPinnedUrl(tab, url, image); + this.#resetTabToStoredState(tab); + gZenUIManager.showToast("zen-pinned-tab-url-edited"); + } + + async #getCachedFavicon(uri) { + try { + const favicon = await PlacesUtils.favicons.getFaviconForPage(uri); + return favicon?.dataURI?.spec; + } catch (ex) { + console.error("Failed to get favicon for edited pinned url:", ex); + return null; + } + } + _initClosePinnedTabShortcut() { let cmdClose = document.getElementById("cmd_close"); @@ -545,11 +605,20 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature { } const elements = window.MozXULElement.parseXULToFragment(`