Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
117baeb
feat: add vertical central control bar between sidebar and editor
abose Apr 18, 2026
40a415b
feat: swap collapse-editor icon/title between code and visual edit
abose Apr 18, 2026
7390e5c
fix: stabilize control-bar layout across window resizes in collapsed …
abose Apr 18, 2026
ab20067
feat: move sidebar resizer to right of control bar and unify drag in …
abose Apr 18, 2026
2c60616
fix: cap sidebar at 70% of viewport in design mode and pin width acro…
abose Apr 18, 2026
228be98
feat: exit design mode when live preview is closed via toolbar-go-live
abose Apr 18, 2026
6d4e8d7
fix: replace 70vw sidebar cap with live-preview-minimum cap in design…
abose Apr 18, 2026
e62930b
feat: forward sidebar resize events to main-toolbar in design mode
abose Apr 18, 2026
41e543d
feat: make WorkspaceManager.setPluginPanelWidth work in design mode
abose Apr 18, 2026
7c278a1
feat: expose design-mode toggle as a command, menu item, and shortcut
abose Apr 18, 2026
8e826eb
feat: make no-distractions mode work cleanly in design mode
abose Apr 18, 2026
90a6acf
test: fix .content-left expectation for CCB offset and add coverage plan
abose Apr 18, 2026
504110c
feat: exit design mode when opening tools panel, find-in-files, quick…
abose Apr 18, 2026
db277f4
test: extend control-bar test plan with design-mode auto-exit cases
abose Apr 18, 2026
f2de7fb
feat: spotlight-style floating Quick Open for design mode
abose Apr 18, 2026
d108b58
feat: narrow quick open to previewable/server files in design mode + …
abose Apr 18, 2026
1c1d741
fix: md viewer skips auto-refocus for parent-side picker shortcuts
abose Apr 18, 2026
0c86af0
feat: localize control-bar button titles and rename to Design Mode
abose Apr 18, 2026
99bddfb
test: make CodeInspection scroll-remember test environment-agnostic
abose Apr 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 49 additions & 7 deletions src-mdviewer/src/bridge.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,35 @@ let _baseURL = "";
let _cursorPosBeforeEdit = null; // cursor position before current edit batch
let _cursorPosDirty = false; // true after content changes, reset when emitted
let _pendingReloadScroll = null; // { filePath, scrollSourceLine } for scroll restore after reload
// Key strings ("Ctrl-Shift-O", "Ctrl-Shift-F", …) for shortcuts Phoenix
// handles by opening a UI that needs focus — we must not auto-refocus our
// own editor after forwarding those. Populated from the parent via the
// MDVIEWR_SKIP_REFOCUS_KEYS message; starts empty so bugs in the parent
// can't break the default refocus behavior.
let _skipRefocusKeys = new Set();
const _isMacForKey = /Mac|iPhone|iPad/.test(navigator.platform);

/**
* Mirror Phoenix's KeyBindingManager _buildKeyDescriptor to produce the
* same canonical key string from a KeyboardEvent so we can look it up in
* _skipRefocusKeys. Non-mac: "Ctrl-Shift-O"; mac: "Shift-Cmd-O".
*/
function _eventToKeyString(e) {
const parts = [];
if (_isMacForKey && e.ctrlKey) { parts.push("Ctrl"); }
if (e.altKey) { parts.push("Alt"); }
if (e.shiftKey) { parts.push("Shift"); }
if (!_isMacForKey && e.ctrlKey) { parts.unshift("Ctrl"); }
if (_isMacForKey && e.metaKey) { parts.push("Cmd"); }
let key = e.key || "";
if (key.length === 1) { key = key.toUpperCase(); }
parts.push(key);
return parts.join("-");
}

function _isSkipRefocusShortcut(e) {
return _skipRefocusKeys.has(_eventToKeyString(e));
}

/**
* Check if a URL is absolute (not relative to the document).
Expand Down Expand Up @@ -317,6 +346,14 @@ export function initBridge() {
window.getSelection().removeAllRanges();
document.body.click();
break;
case "MDVIEWR_SKIP_REFOCUS_KEYS":
// Phoenix tells us which forwarded shortcuts should NOT
// trigger our auto-refocus (e.g. Quick Open, Find in Files
// — shortcuts that open parent-side UI which needs focus).
if (Array.isArray(data.keys)) {
_skipRefocusKeys = new Set(data.keys);
}
break;
}
});

Expand Down Expand Up @@ -416,13 +453,18 @@ export function initBridge() {
altKey: e.altKey
});
// Refocus md editor after Phoenix handles the shortcut
// (some commands like Save focus the CM editor)
setTimeout(() => {
const content = document.getElementById("viewer-content");
if (content && getState().editMode) {
content.focus({ preventScroll: true });
}
}, 100);
// (e.g. Save focuses the CM editor). Phoenix sends us the
// list of shortcuts for which focus should stay with the
// parent (Quick Open, Find in Files …) via
// MDVIEWR_SKIP_REFOCUS_KEYS. Don't refocus for those.
if (!_isSkipRefocusShortcut(e)) {
setTimeout(() => {
const content = document.getElementById("viewer-content");
if (content && getState().editMode) {
content.focus({ preventScroll: true });
}
}, 100);
}
}
}
}, true);
Expand Down
3 changes: 3 additions & 0 deletions src/base-config/keyboard.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
"file.liveFilePreview": [
"Ctrl-Alt-L"
],
"view.toggleDesignMode": [
"Ctrl-F11"
],
"file.reloadLivePreview": [
{
"key": "Ctrl-Shift-R"
Expand Down
1 change: 1 addition & 0 deletions src/brackets.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ define(function (require, exports, module) {
require("file/FileUtils");
require("project/SidebarView");
require("view/SidebarTabs");
require("view/CentralControlBar");
require("utils/Resizer");
require("LiveDevelopment/main");
require("utils/NodeConnection");
Expand Down
3 changes: 3 additions & 0 deletions src/command/Commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,9 @@ define(function (require, exports, module) {
/** Toggles sidebar visibility */
exports.VIEW_HIDE_SIDEBAR = "view.toggleSidebar"; // SidebarView.js toggle()

/** Toggles the design (full live-preview) mode — collapses/expands the editor */
exports.VIEW_TOGGLE_DESIGN_MODE = "view.toggleDesignMode"; // view/CentralControlBar.js _setEditorCollapsed()

/** Toggles tabbar visibility */
exports.TOGGLE_TABBAR = "view.toggleTabbar";
// extensionsIntegrated/TabBar/main.js
Expand Down
29 changes: 0 additions & 29 deletions src/command/Menus.js
Original file line number Diff line number Diff line change
Expand Up @@ -1778,35 +1778,6 @@ define(function (require, exports, module) {
const $hamburgerToggle = $hamburger.find(".hamburger-toggle");
let _activeSubmenuId = null;

// Sidebar collapse/expand toggle button before the File menu
const $sidebarToggle = $(`<li class="sidebar-toggle-btn" id="sidebar-toggle-btn">
<a href="#">
<i class="fa-solid fa-angles-left"></i>
</a>
</li>`);
$menubar.prepend($sidebarToggle);
const $sidebarIcon = $sidebarToggle.find("a");

function _updateSidebarToggleIcon() {
const isVisible = $("#sidebar").is(":visible");
if (isVisible) {
$sidebarIcon.html('<i class="fa-solid fa-angles-left"></i>');
$sidebarIcon.attr("title", Strings.CMD_HIDE_SIDEBAR);
} else {
$sidebarIcon.html('<i class="fa-solid fa-angles-right"></i>');
$sidebarIcon.attr("title", Strings.CMD_SHOW_SIDEBAR);
}
}

$sidebarIcon.on("click", function (e) {
e.preventDefault();
e.stopPropagation();
CommandManager.execute(Commands.VIEW_HIDE_SIDEBAR);
});

$("#sidebar").on("panelCollapsed panelExpanded", _updateSidebarToggleIcon);
_updateSidebarToggleIcon();

function _resetMenuItemStyles($menuItem) {
const menu = menuMap[$menuItem.attr("id")];
if (menu) {
Expand Down
3 changes: 3 additions & 0 deletions src/document/DocumentCommandHandlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -1953,6 +1953,9 @@ define(function (require, exports, module) {
function handleShowInTree() {
let activeFile = MainViewManager.getCurrentlyViewedFile(MainViewManager.ACTIVE_PANE);
if(activeFile){
if (!$("#sidebar").is(":visible")) {
CommandManager.execute(Commands.VIEW_HIDE_SIDEBAR);
}
SidebarTabs.setActiveTab(SidebarTabs.SIDEBAR_TAB_FILES);
ProjectManager.showInTree(activeFile);
}
Expand Down
15 changes: 14 additions & 1 deletion src/extensions/default/Git/src/Main.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,20 @@
Branch.init();
CloseNotModified.init();
// Attach events
$icon.on("click", Panel.toggle);
$icon.on("click", function () {
// Design mode collapses the editor and stretches live preview,
// which leaves no room for the git bottom panel. Exit design mode
// first so the panel has somewhere to render.
// TODO: make git panel float/overlay live preview so users can

Check warning on line 56 in src/extensions/default/Git/src/Main.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Complete the task associated to this "TODO" comment.

See more on https://sonarcloud.io/project/issues?id=phcode-dev_phoenix&issues=AZ2g9gZNVVB4PQRwSccp&open=AZ2g9gZNVVB4PQRwSccp&pullRequest=2820
// peek at git status without leaving design mode.
const WorkspaceManager = brackets.getModule("view/WorkspaceManager");
const CommandManager = brackets.getModule("command/CommandManager");
const Commands = brackets.getModule("command/Commands");
if (WorkspaceManager.isInDesignMode()) {
CommandManager.execute(Commands.VIEW_TOGGLE_DESIGN_MODE);
}
Panel.toggle();
});
}

function _addRemoveItemInGitignore(selectedEntry, method) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -745,12 +745,11 @@ define(function (require, exports, module) {
}

function _setupNavigationButtons() {
let $mainNavBarRight = $("#mainNavBarRight");
let $mainNavBarLeft = $("#mainNavBarLeft");
$showInTree = $mainNavBarRight.find("#showInfileTree");
$navback = $mainNavBarRight.find("#navBackButton");
$navForward = $mainNavBarRight.find("#navForwardButton");
$searchNav = $mainNavBarRight.find("#searchNav");
$showInTree = $("#showInfileTree");
$navback = $("#navBackButton");
$navForward = $("#navForwardButton");
$searchNav = $("#searchNav");

$newProject = $mainNavBarLeft.find("#newProject");
updateTooltips();
Expand Down
25 changes: 19 additions & 6 deletions src/extensionsIntegrated/NoDistractions/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,14 +168,27 @@
KeyBindingManager.addBinding(CMD_TOGGLE_PANELS, [ {key: togglePanelsKey_EN}, {key: togglePanelsKeyMac_EN, platform: "mac"} ]);

PreferencesManager.on("change", PREFS_PURE_CODE, function () {
const inDesignMode = WorkspaceManager.isInDesignMode &&
WorkspaceManager.isInDesignMode();

Check warning on line 172 in src/extensionsIntegrated/NoDistractions/main.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer using an optional chain expression instead, as it's more concise and easier to read.

See more on https://sonarcloud.io/project/issues?id=phcode-dev_phoenix&issues=AZ2g9gYhVVB4PQRwSccl&open=AZ2g9gYhVVB4PQRwSccl&pullRequest=2820
if (PreferencesManager.get(PREFS_PURE_CODE)) {
ViewUtils.hideMainToolBar();
CommandManager.execute(Commands.HIDE_SIDEBAR);
_hidePanelsIfRequired();
if (inDesignMode) {
// In design mode the live preview already fills the editor
// area and the main toolbar is the visible surface; just
// collapse the sidebar so LP can stretch to the full width.
CommandManager.execute(Commands.HIDE_SIDEBAR);
} else {
ViewUtils.hideMainToolBar();
CommandManager.execute(Commands.HIDE_SIDEBAR);
_hidePanelsIfRequired();
}
} else {
ViewUtils.showMainToolBar();
CommandManager.execute(Commands.SHOW_SIDEBAR);
_showPanelsIfRequired();
if (inDesignMode) {

Check warning on line 185 in src/extensionsIntegrated/NoDistractions/main.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

'If' statement should not be the only statement in 'else' block

See more on https://sonarcloud.io/project/issues?id=phcode-dev_phoenix&issues=AZ2g9gYhVVB4PQRwSccm&open=AZ2g9gYhVVB4PQRwSccm&pullRequest=2820
CommandManager.execute(Commands.SHOW_SIDEBAR);
} else {
ViewUtils.showMainToolBar();
CommandManager.execute(Commands.SHOW_SIDEBAR);
_showPanelsIfRequired();
}
}
_updateCheckedState();
});
Expand Down
51 changes: 51 additions & 0 deletions src/extensionsIntegrated/Phoenix-live-preview/MarkdownSync.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,23 @@
const ThemeManager = require("view/ThemeManager"),
NativeApp = require("utils/NativeApp"),
EditorManager = require("editor/EditorManager"),
AppInit = require("utils/AppInit"),
CommandManager = require("command/CommandManager"),
Commands = require("command/Commands"),
KeyBindingManager = require("command/KeyBindingManager"),
utils = require("./utils");

// Commands whose shortcuts, when forwarded from the md viewer iframe,
// open a parent-side UI that needs to keep keyboard focus. The iframe's
// 100ms auto-refocus must skip these shortcuts — otherwise it yanks
// focus out of the picker immediately after it opens. We send the raw
// key strings (resolved at runtime from KeyBindingManager) so the
// iframe stays theme/rebind-agnostic.
const SKIP_REFOCUS_COMMANDS = [
Commands.NAVIGATE_QUICK_OPEN,
Commands.CMD_FIND_IN_FILES
];

let _active = false;
let _doc = null;
let _$iframe = null;
Expand Down Expand Up @@ -348,16 +363,52 @@

// --- iframe ready ---

/**
* Collect the current `key` strings bound to each SKIP_REFOCUS_COMMANDS
* command and send them to the iframe so its keyboard-shortcut forward
* path can skip its own 100ms auto-refocus for these shortcuts.
*/
function _sendSkipRefocusShortcuts() {
const iframeWindow = _getIframeWindow();
if (!iframeWindow) { return; }
const keys = [];
SKIP_REFOCUS_COMMANDS.forEach(function (cmdID) {
const bindings = KeyBindingManager.getKeyBindings(cmdID) || [];
bindings.forEach(function (b) {
if (b && b.key) { keys.push(b.key); }

Check warning on line 378 in src/extensionsIntegrated/Phoenix-live-preview/MarkdownSync.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer using an optional chain expression instead, as it's more concise and easier to read.

See more on https://sonarcloud.io/project/issues?id=phcode-dev_phoenix&issues=AZ2g9gUfVVB4PQRwScci&open=AZ2g9gUfVVB4PQRwScci&pullRequest=2820
});
});
iframeWindow.postMessage({

Check failure on line 381 in src/extensionsIntegrated/Phoenix-live-preview/MarkdownSync.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Specify a target origin for this message.

See more on https://sonarcloud.io/project/issues?id=phcode-dev_phoenix&issues=AZ2g9gUfVVB4PQRwSccj&open=AZ2g9gUfVVB4PQRwSccj&pullRequest=2820
type: "MDVIEWR_SKIP_REFOCUS_KEYS",
keys: keys
}, "*");
}

function _onIframeReady() {
_iframeReady = true;
_sendContent();
_sendTheme();
_sendLocale();
_sendSkipRefocusShortcuts();
if (_onIframeReadyCallback) {
_onIframeReadyCallback();
}
}

// Re-send shortcuts if the user rebinds Quick Open / Find in Files.
// Deferred to appReady so the commands have been registered before we
// try to hook their key-binding-change events.
AppInit.appReady(function () {
SKIP_REFOCUS_COMMANDS.forEach(function (cmdID) {
const cmd = CommandManager.get(cmdID);
if (cmd && cmd.on) {

Check warning on line 404 in src/extensionsIntegrated/Phoenix-live-preview/MarkdownSync.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer using an optional chain expression instead, as it's more concise and easier to read.

See more on https://sonarcloud.io/project/issues?id=phcode-dev_phoenix&issues=AZ2g9gUfVVB4PQRwScck&open=AZ2g9gUfVVB4PQRwScck&pullRequest=2820
cmd.on(KeyBindingManager.EVENT_KEY_BINDING_ADDED, function () {
if (_iframeReady) { _sendSkipRefocusShortcuts(); }
});
}
});
});

// --- Phoenix → iframe ---

/**
Expand Down
8 changes: 8 additions & 0 deletions src/extensionsIntegrated/Phoenix-live-preview/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -1451,6 +1451,14 @@ define(function (require, exports, module) {
Menus.AFTER, Commands.FILE_LIVE_FILE_PREVIEW);
fileMenu.addMenuItem(Commands.FILE_LIVE_FILE_PREVIEW_SETTINGS, "",
Menus.AFTER, Commands.CMD_RELOAD_LIVE_PREVIEW);
// Design-mode toggle is registered by view/CentralControlBar at module load
// so the command exists by now. Insert it last with `AFTER Live Preview` so
// it lands directly below the Live Preview item (above Reload / Settings
// which were inserted earlier into the same AFTER slot).
if (CommandManager.get(Commands.VIEW_TOGGLE_DESIGN_MODE)) {
fileMenu.addMenuItem(Commands.VIEW_TOGGLE_DESIGN_MODE, "",
Menus.AFTER, Commands.FILE_LIVE_FILE_PREVIEW);
}
fileMenu.addMenuDivider(Menus.BEFORE, Commands.FILE_LIVE_FILE_PREVIEW);

_registerHandlers();
Expand Down
Loading
Loading