diff --git a/src-mdviewer/src/bridge.js b/src-mdviewer/src/bridge.js
index a998a76ea0..f1c80d56ee 100644
--- a/src-mdviewer/src/bridge.js
+++ b/src-mdviewer/src/bridge.js
@@ -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).
@@ -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;
}
});
@@ -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);
diff --git a/src/base-config/keyboard.json b/src/base-config/keyboard.json
index 488573c9a4..5f9f685721 100644
--- a/src/base-config/keyboard.json
+++ b/src/base-config/keyboard.json
@@ -23,6 +23,9 @@
"file.liveFilePreview": [
"Ctrl-Alt-L"
],
+ "view.toggleDesignMode": [
+ "Ctrl-F11"
+ ],
"file.reloadLivePreview": [
{
"key": "Ctrl-Shift-R"
diff --git a/src/brackets.js b/src/brackets.js
index 3a3e08c739..4c53bb9c83 100644
--- a/src/brackets.js
+++ b/src/brackets.js
@@ -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");
diff --git a/src/command/Commands.js b/src/command/Commands.js
index 30403d1cbe..caa7c4bd14 100644
--- a/src/command/Commands.js
+++ b/src/command/Commands.js
@@ -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
diff --git a/src/command/Menus.js b/src/command/Menus.js
index 18fb38ea21..c9a21f8a7c 100644
--- a/src/command/Menus.js
+++ b/src/command/Menus.js
@@ -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 = $(`
`);
- $menubar.prepend($sidebarToggle);
- const $sidebarIcon = $sidebarToggle.find("a");
-
- function _updateSidebarToggleIcon() {
- const isVisible = $("#sidebar").is(":visible");
- if (isVisible) {
- $sidebarIcon.html('');
- $sidebarIcon.attr("title", Strings.CMD_HIDE_SIDEBAR);
- } else {
- $sidebarIcon.html('');
- $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) {
diff --git a/src/document/DocumentCommandHandlers.js b/src/document/DocumentCommandHandlers.js
index 2c35042527..7292015592 100644
--- a/src/document/DocumentCommandHandlers.js
+++ b/src/document/DocumentCommandHandlers.js
@@ -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);
}
diff --git a/src/extensions/default/Git/src/Main.js b/src/extensions/default/Git/src/Main.js
index 89f94df33a..49a9723912 100644
--- a/src/extensions/default/Git/src/Main.js
+++ b/src/extensions/default/Git/src/Main.js
@@ -49,7 +49,20 @@ define(function (require, exports) {
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
+ // 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) {
diff --git a/src/extensionsIntegrated/NavigationAndHistory/NavigationProvider.js b/src/extensionsIntegrated/NavigationAndHistory/NavigationProvider.js
index 304dfe6795..ba12210795 100644
--- a/src/extensionsIntegrated/NavigationAndHistory/NavigationProvider.js
+++ b/src/extensionsIntegrated/NavigationAndHistory/NavigationProvider.js
@@ -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();
diff --git a/src/extensionsIntegrated/NoDistractions/main.js b/src/extensionsIntegrated/NoDistractions/main.js
index 55f8d27a94..4337b689dc 100644
--- a/src/extensionsIntegrated/NoDistractions/main.js
+++ b/src/extensionsIntegrated/NoDistractions/main.js
@@ -168,14 +168,27 @@ define(function (require, exports, module) {
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();
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) {
+ CommandManager.execute(Commands.SHOW_SIDEBAR);
+ } else {
+ ViewUtils.showMainToolBar();
+ CommandManager.execute(Commands.SHOW_SIDEBAR);
+ _showPanelsIfRequired();
+ }
}
_updateCheckedState();
});
diff --git a/src/extensionsIntegrated/Phoenix-live-preview/MarkdownSync.js b/src/extensionsIntegrated/Phoenix-live-preview/MarkdownSync.js
index 4bfdeee5f7..e899bc281d 100644
--- a/src/extensionsIntegrated/Phoenix-live-preview/MarkdownSync.js
+++ b/src/extensionsIntegrated/Phoenix-live-preview/MarkdownSync.js
@@ -25,8 +25,23 @@ define(function (require, exports, module) {
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;
@@ -348,16 +363,52 @@ define(function (require, exports, module) {
// --- 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); }
+ });
+ });
+ iframeWindow.postMessage({
+ 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) {
+ cmd.on(KeyBindingManager.EVENT_KEY_BINDING_ADDED, function () {
+ if (_iframeReady) { _sendSkipRefocusShortcuts(); }
+ });
+ }
+ });
+ });
+
// --- Phoenix → iframe ---
/**
diff --git a/src/extensionsIntegrated/Phoenix-live-preview/main.js b/src/extensionsIntegrated/Phoenix-live-preview/main.js
index 3129acb45e..b08e322798 100644
--- a/src/extensionsIntegrated/Phoenix-live-preview/main.js
+++ b/src/extensionsIntegrated/Phoenix-live-preview/main.js
@@ -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();
diff --git a/src/extensionsIntegrated/Phoenix-live-preview/utils.js b/src/extensionsIntegrated/Phoenix-live-preview/utils.js
index 0697bac805..db7c4c45a1 100644
--- a/src/extensionsIntegrated/Phoenix-live-preview/utils.js
+++ b/src/extensionsIntegrated/Phoenix-live-preview/utils.js
@@ -39,60 +39,10 @@ define(function (require, exports, module) {
const LIVE_PREVIEW_IFRAME_ID = "panel-live-preview-frame";
const MDVIEWR_IFRAME_ID = "panel-md-preview-frame";
const EditorManager = require("editor/EditorManager");
- function getExtension(filePath) {
- filePath = filePath || '';
- let pathSplit = filePath.split('.');
- return pathSplit && pathSplit.length>1 ? pathSplit[pathSplit.length-1] : '';
- }
-
- function isPreviewableFile(filePath) {
- // only svg images should appear in the live preview as it needs text editor.
- // All other image types should appear in the image previewer
- return isSVG(filePath) || isMarkdownFile(filePath) || isHTMLFile(filePath) || isPDF(filePath);
- }
-
- function isPDF(filePath) {
- let extension = getExtension(filePath);
- return extension === "pdf";
- }
-
- function isSVG(filePath) {
- let extension = getExtension(filePath);
- return extension === "svg";
- }
-
- function isImage(filePath) {
- let extension = getExtension(filePath);
- return ["jpg", "jpeg", "png", "gif", "svg", "webp", "bmp", "ico", "avif"]
- .includes(extension.toLowerCase());
- }
-
- function isMarkdownFile(filePath) {
- let extension = getExtension(filePath);
- return ['md', 'markdown', 'mdx'].includes(extension.toLowerCase());
- }
-
- function isHTMLFile(filePath) {
- let extension = getExtension(filePath);
- return ['html', 'htm', 'xhtml'].includes(extension.toLowerCase());
- }
-
- function isServerRenderedFile(filePath) {
- let extension = getExtension(filePath);
- return [
- "shtml",
- "asp",
- "aspx",
- "php",
- "jsp",
- "jspx",
- "cfm",
- "cfc", // ColdFusion Component
- "rb", // Ruby file, used in Ruby on Rails for views with ERB
- "erb", // Embedded Ruby, used in Ruby on Rails views
- "py" // Python file, used in web frameworks like Django or Flask for views
- ].includes(extension.toLowerCase());
- }
+ // File-type classification helpers live in a shared utility module so
+ // non-live-preview code (e.g. Quick Open in design mode) can use them
+ // without depending on this extension.
+ const FileTypeUtils = require("utils/FileTypeUtils");
function focusActiveEditorIfFocusInLivePreview() {
const editor = EditorManager.getActiveEditor();
@@ -105,15 +55,14 @@ define(function (require, exports, module) {
}
}
- exports.getExtension = getExtension;
- exports.isPreviewableFile = isPreviewableFile;
- exports.isImage = isImage;
- exports.isPDF = isPDF;
- exports.isSVG = isSVG;
- exports.isHTMLFile = isHTMLFile;
- exports.isServerRenderedFile = isServerRenderedFile;
- exports.isMarkdownFile = isMarkdownFile;
+ // Re-export the shared helpers so existing callers keep working.
+ exports.getExtension = FileTypeUtils.getExtension;
+ exports.isPreviewableFile = FileTypeUtils.isPreviewableFile;
+ exports.isImage = FileTypeUtils.isImage;
+ exports.isPDF = FileTypeUtils.isPDF;
+ exports.isSVG = FileTypeUtils.isSVG;
+ exports.isHTMLFile = FileTypeUtils.isHTMLFile;
+ exports.isServerRenderedFile = FileTypeUtils.isServerRenderedFile;
+ exports.isMarkdownFile = FileTypeUtils.isMarkdownFile;
exports.focusActiveEditorIfFocusInLivePreview = focusActiveEditorIfFocusInLivePreview;
});
-
-
diff --git a/src/index.html b/src/index.html
index ac7e396d5b..547b96dfdc 100644
--- a/src/index.html
+++ b/src/index.html
@@ -905,7 +905,7 @@