From 37e0f30bb7744d8105b6ef8b4babca1acace9441 Mon Sep 17 00:00:00 2001 From: abose Date: Mon, 20 Apr 2026 16:48:23 +0530 Subject: [PATCH 1/5] fix: right toolbar disappearing and sidebar overflow in editor mode - Guard Resizer drag-hide behind the collapsible flag so non-collapsible panels (main-toolbar, pane splitters, bottom-panel-container) cannot be squeezed to zero and hidden when maxsize is driven below 10px by a narrow window. - Floor main-toolbar's data("maxsize") at its minimum width in updateResizeLimits, so narrow-window/wide-sidebar cases cannot cap the toolbar below usable size. - Add _getSidebarWidth() using offsetWidth; jQuery outerWidth() returns the stale style.width on a display:none sidebar, which corrupted the layout math. - Re-clamp the plugin panel when the sidebar is shown/hidden/resized. - Add _ensureEditorLayoutFits(): in editor mode, coordinate sidebar, editor, and plugin-panel so all three share the window. Shrinks the sidebar after toolbar clamping if sidebar + CCB + toolbar + min editor exceeds the window width. No-op in design mode, which pins its own geometry with !important. --- src/utils/Resizer.js | 16 ++++--- src/view/WorkspaceManager.js | 82 ++++++++++++++++++++++++++++++++++-- 2 files changed, 88 insertions(+), 10 deletions(-) diff --git a/src/utils/Resizer.js b/src/utils/Resizer.js index 9ae4e29886..3809aea9ff 100644 --- a/src/utils/Resizer.js +++ b/src/utils/Resizer.js @@ -503,22 +503,26 @@ define(function (require, exports, module) { previousSize = newSize; if ($element.is(":visible")) { - if (newSize < 10) { + if (collapsible && newSize < 10) { toggle($element); elementSizeFunction.apply($element, [0]); } else { - // Trigger resizeStarted just before the first successful resize update + // Non-collapsible panels must never be auto-hidden by a drag that + // squeezes them to near-zero — a narrow window with a wide sidebar + // can push maxsize below 10, and hiding the toolbar that way + // strands the user with no way to get it back. + var effectiveSize = collapsible ? newSize : Math.max(newSize, minSize); if (!resizeStarted) { resizeStarted = true; - $element.trigger(EVENT_PANEL_RESIZE_START, newSize); + $element.trigger(EVENT_PANEL_RESIZE_START, effectiveSize); } // Resize the main element to the new size. If there is a content element, // its size is the new size minus the size of the non-resizable elements - resizeElement(newSize, (newSize - baseSize)); - adjustSibling(newSize); + resizeElement(effectiveSize, (effectiveSize - baseSize)); + adjustSibling(effectiveSize); - $element.trigger(EVENT_PANEL_RESIZE_UPDATE, [newSize]); + $element.trigger(EVENT_PANEL_RESIZE_UPDATE, [effectiveSize]); } } else if (newSize > 10) { elementSizeFunction.apply($element, [newSize]); diff --git a/src/view/WorkspaceManager.js b/src/view/WorkspaceManager.js index 6cabd82c2b..77b2239975 100644 --- a/src/view/WorkspaceManager.js +++ b/src/view/WorkspaceManager.js @@ -88,6 +88,20 @@ define(function (require, exports, module) { */ const MAIN_TOOLBAR_WIDTH = 30; + /** + * Returns the sidebar's rendered width, or 0 if hidden. jQuery's outerWidth() + * on a display:none element that still carries an explicit style.width returns + * that stale width rather than 0, which corrupts main-view layout math. + * @private + */ + function _getSidebarWidth() { + var sb = document.getElementById("sidebar"); + if (!sb || sb.offsetWidth === 0) { + return 0; + } + return sb.offsetWidth; + } + /** * The ".content" vertical stack (editor + all header/footer panels) * @type {jQueryObject} @@ -184,8 +198,14 @@ define(function (require, exports, module) { } }); - var sidebarWidth = $("#sidebar").outerWidth() || 0; - $mainToolbar.data("maxsize", Math.min(window.innerWidth * 0.75, window.innerWidth - sidebarWidth - 100)); + var sidebarWidth = _getSidebarWidth(); + var pluginIconsBarWidth = $pluginIconsBar.outerWidth() || MAIN_TOOLBAR_WIDTH; + var minToolbarWidth = ((currentlyShownPanel && currentlyShownPanel.minWidth) || 0) + pluginIconsBarWidth; + // Floor the toolbar's maxsize at its minimum width. Without the floor, a narrow + // window with a wide sidebar can drive the cap below 10px, and Resizer's drag + // logic would then squeeze the toolbar to zero and hide it. + var rawMax = Math.min(window.innerWidth * 0.75, window.innerWidth - sidebarWidth - 100); + $mainToolbar.data("maxsize", Math.max(minToolbarWidth, rawMax)); } @@ -224,6 +244,8 @@ define(function (require, exports, module) { if (currentlyShownPanel && $mainToolbar.is(":visible")) { _clampPluginPanelWidth(currentlyShownPanel); } + // Then coordinate sidebar width in editor mode so the three regions fit. + _ensureEditorLayoutFits(); // FIXME (issue #4564) Workaround https://github.com/codemirror/CodeMirror/issues/1787 triggerUpdateLayout(); @@ -375,6 +397,19 @@ define(function (require, exports, module) { $mainPluginPanel = $("#main-plugin-panel"); $pluginIconsBar = $("#plugin-icons-bar"); + // Sidebar show/resize steals width from the plugin-panel area. Without + // re-clamping, the main-toolbar keeps its previous (now-oversized) width + // and overlaps the sidebar/editor. Mirror the window-resize handling. + $("#sidebar").on("panelExpanded.workspace panelCollapsed.workspace panelResizeEnd.workspace", + function () { + if (currentlyShownPanel && $mainToolbar.is(":visible")) { + _clampPluginPanelWidth(currentlyShownPanel); + } + _ensureEditorLayoutFits(); + triggerUpdateLayout(); + updateResizeLimits(); + }); + // --- Create the bottom panel tabbed container --- $bottomPanelContainer = $('
'); let $bottomPanelTabBar = $('
'); @@ -530,7 +565,7 @@ define(function (require, exports, module) { } function _clampPluginPanelWidth(panelBeingShown) { - let sidebarWidth = $("#sidebar").outerWidth() || 0; + let sidebarWidth = _getSidebarWidth(); let pluginIconsBarWidth = $pluginIconsBar.outerWidth(); let minToolbarWidth = (panelBeingShown.minWidth || 0) + pluginIconsBarWidth; let maxToolbarWidth = Math.max( @@ -546,6 +581,45 @@ define(function (require, exports, module) { } } + const CCB_WIDTH = 30; + const MIN_EDITOR_WIDTH = 100; + const MIN_SIDEBAR_WIDTH = 30; + + /** + * In editor mode the sidebar, editor, and plugin-panel (main-toolbar) must + * share the horizontal space. Resizer's own sidebar clamp runs lazily on + * the next mousemove, and only considers percent-based max sizes — so a + * window shrink, or showing the sidebar at its remembered width, can leave + * the sidebar overlapping the live-preview panel. Here we do an eager + * coordinated shrink: clamp sidebar so sidebar + CCB + current toolbar + + * MIN_EDITOR_WIDTH fits in the window. Design mode handles its own + * geometry with !important, so this is a no-op there. + * @private + */ + function _ensureEditorLayoutFits() { + if (_isInDesignMode) { + return; + } + var $sb = $("#sidebar"); + if (!$sb.length || $sb[0].offsetWidth === 0) { + return; + } + var toolbarW = $mainToolbar.is(":visible") ? $mainToolbar.width() : 0; + var maxSidebar = window.innerWidth - CCB_WIDTH - toolbarW - MIN_EDITOR_WIDTH; + var currentSb = $sb[0].offsetWidth; + if (currentSb > maxSidebar) { + var newSb = Math.max(MIN_SIDEBAR_WIDTH, maxSidebar); + $sb.width(newSb); + var resync = $sb.data("resyncSizer"); + if (typeof resync === "function") { + resync(); + } + // Notify listeners (CCB syncs its left offset; content tracks via forceleft). + $sb.trigger("panelResizeUpdate", [newSb]); + $sb.trigger("panelResizeEnd", [newSb]); + } + } + function _showPluginSidePanel(panelID) { let panelBeingShown = getPanelForID(panelID); let pluginIconsBarWidth = $pluginIconsBar.outerWidth(); @@ -615,7 +689,7 @@ define(function (require, exports, module) { // Respect min/max constraints var minSize = currentlyShownPanel.minWidth || 0; var minToolbarWidth = minSize + pluginIconsBarWidth; - var sidebarWidth = $("#sidebar").outerWidth() || 0; + var sidebarWidth = _getSidebarWidth(); var maxToolbarWidth = Math.min(window.innerWidth * 0.75, window.innerWidth - sidebarWidth - 100); newToolbarWidth = Math.max(newToolbarWidth, minToolbarWidth); newToolbarWidth = Math.min(newToolbarWidth, maxToolbarWidth); From b584cc68a884ba5899ba4ef73effd7cfbfa4a9b0 Mon Sep 17 00:00:00 2001 From: abose Date: Mon, 20 Apr 2026 16:56:22 +0530 Subject: [PATCH 2/5] chore: update design icon to match with other design tools --- src/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.html b/src/index.html index 4d89b9020a..7cdf96e0a4 100644 --- a/src/index.html +++ b/src/index.html @@ -937,7 +937,7 @@
- + From 6c4736dde1f5867b066471fe70f17081cbb13289 Mon Sep 17 00:00:00 2001 From: abose Date: Mon, 20 Apr 2026 17:18:36 +0530 Subject: [PATCH 3/5] fix: preserve pen-nib icon on design-mode toggle The toggle handler hardcoded fa-feather when exiting design mode, overwriting the pen-nib icon set in HTML. --- src/view/CentralControlBar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/view/CentralControlBar.js b/src/view/CentralControlBar.js index 098a286991..ccefdb1f3b 100644 --- a/src/view/CentralControlBar.js +++ b/src/view/CentralControlBar.js @@ -238,7 +238,7 @@ define(function (require, exports, module) { const $collapseBtn = $("#ccbCollapseEditorBtn"); $collapseBtn.toggleClass("is-active", editorCollapsed) .attr("title", editorCollapsed ? Strings.CCB_SWITCH_TO_CODE_EDITOR : Strings.CCB_SWITCH_TO_DESIGN_MODE); - $collapseBtn.find("i").attr("class", editorCollapsed ? "fa-solid fa-code" : "fa-solid fa-feather"); + $collapseBtn.find("i").attr("class", editorCollapsed ? "fa-solid fa-code" : "fa-solid fa-pen-nib fa-rotate-90"); if (_toggleDesignModeCommand) { _toggleDesignModeCommand.setChecked(editorCollapsed); } From 37a0952e2681feb247c7da2e9603c3683e8ad34f Mon Sep 17 00:00:00 2001 From: abose Date: Mon, 20 Apr 2026 17:33:31 +0530 Subject: [PATCH 4/5] feat: use designer pen-nib SVG for design-mode toggle button - Inline the SVG in index.html so it renders on first paint without a blank-button flash. - Switch fills/strokes to currentColor so the icon picks up the CCB button color and hover/active states. - Cache the rendered SVG HTML at init; the toggle swaps it with an fa-code icon for design-mode state and restores the cached SVG on return to editor mode. - Size the inline SVG to 15px via CSS to match neighboring FA icons. --- src/index.html | 5 ++++- src/styles/CentralControlBar.less | 6 ++++++ src/styles/images/pen_nib.svg | 4 ++++ src/view/CentralControlBar.js | 7 ++++++- 4 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 src/styles/images/pen_nib.svg diff --git a/src/index.html b/src/index.html index 7cdf96e0a4..88d90271b4 100644 --- a/src/index.html +++ b/src/index.html @@ -937,7 +937,10 @@
- + diff --git a/src/styles/CentralControlBar.less b/src/styles/CentralControlBar.less index ce01989769..d6c5115638 100644 --- a/src/styles/CentralControlBar.less +++ b/src/styles/CentralControlBar.less @@ -101,6 +101,12 @@ /* Nudge the collapse-editor icon up so it visually aligns with titlebar icons */ #ccbCollapseEditorBtn { margin-top: -2px; + + svg { + width: 15px; + height: 15px; + pointer-events: none; + } } /* Nav buttons: suppress legacy sprite styles (NavigationProvider toggles enabled/disabled class) */ diff --git a/src/styles/images/pen_nib.svg b/src/styles/images/pen_nib.svg new file mode 100644 index 0000000000..97b5b2c982 --- /dev/null +++ b/src/styles/images/pen_nib.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/view/CentralControlBar.js b/src/view/CentralControlBar.js index ccefdb1f3b..904e21a066 100644 --- a/src/view/CentralControlBar.js +++ b/src/view/CentralControlBar.js @@ -40,6 +40,7 @@ define(function (require, exports, module) { let livePreviewWasOpen = false; let savedSidebarMaxSize = null; let applyingCollapsedLayout = false; + let penNibIconHTML = null; function _getRenderedSidebarWidth() { // Use offsetWidth (not jQuery's outerWidth) to force a synchronous reflow @@ -238,7 +239,7 @@ define(function (require, exports, module) { const $collapseBtn = $("#ccbCollapseEditorBtn"); $collapseBtn.toggleClass("is-active", editorCollapsed) .attr("title", editorCollapsed ? Strings.CCB_SWITCH_TO_CODE_EDITOR : Strings.CCB_SWITCH_TO_DESIGN_MODE); - $collapseBtn.find("i").attr("class", editorCollapsed ? "fa-solid fa-code" : "fa-solid fa-pen-nib fa-rotate-90"); + $collapseBtn.html(editorCollapsed ? '' : penNibIconHTML); if (_toggleDesignModeCommand) { _toggleDesignModeCommand.setChecked(editorCollapsed); } @@ -303,6 +304,10 @@ define(function (require, exports, module) { $fileLabel = $("#ccbFileLabel"); $fileName = $fileLabel.find(".ccb-file-name"); + // Cache the authored pen-nib SVG from the DOM so the toggle handler + // can restore it after swapping in the fa-code icon for design mode. + penNibIconHTML = $("#ccbCollapseEditorBtn").html(); + _wireButtons(); // The HTML titles on the control-bar buttons are fallback English // strings; set the localized versions up front so the initial render From 6d0b3599ecdf990eeb89f1efca241a40badb206e Mon Sep 17 00:00:00 2001 From: abose Date: Mon, 20 Apr 2026 17:44:18 +0530 Subject: [PATCH 5/5] chore: update strings --- src/nls/root/strings.js | 1 + tracking-repos.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 7350fda57a..482fec812a 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -2074,6 +2074,7 @@ define({ "PROMO_LEARN_MORE": "Learn More\u2026", "PROMO_OPT_OUT_LINK": "Opt out?", "PROMO_OPT_OUT_NOTE": "You can cancel your trial anytime by selecting `Help > Cancel Phoenix Pro Trial`.", + "PROMO_OPT_OUT_DIALOG_TITLE": "Cancel Phoenix Pro Trial", "PROMO_UPGRADE_APP_UPSELL_BUTTON": "Upgrade to {0}", "PROMO_PRO_ENDED_TITLE": "{0} trial ended", "PROMO_PHOENIX_PRO_ENDED_MESSAGE": "Thanks for trying Pro! You're now on the Free Community Edition.
Upgrade anytime to unlock these features:", diff --git a/tracking-repos.json b/tracking-repos.json index 7eab94bfac..e555dba242 100644 --- a/tracking-repos.json +++ b/tracking-repos.json @@ -1,5 +1,5 @@ { "phoenixPro": { - "commitID": "227551b263c5d4ea5233e3920639bc5aa493b321" + "commitID": "59e1d73841f94f546ad9b62a76217eb0a1e450e8" } }