Skip to content

Commit 59aa4c8

Browse files
committed
chore: add is supported in design mode option for registered commands
1 parent 80bd954 commit 59aa4c8

12 files changed

Lines changed: 130 additions & 29 deletions

File tree

src/LiveDevelopment/BrowserScripts/RemoteFunctions.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1573,10 +1573,46 @@ function RemoteFunctions(config = {}) {
15731573
});
15741574
}
15751575

1576+
// Modifier shortcuts forwarded to the Phoenix KeyBindingManager. Clipboard
1577+
// and undo/redo are excluded so form inputs in the previewed page keep
1578+
// working normally.
1579+
const _KEYS_NOT_FORWARDED = { c:1, v:1, x:1, a:1, z:1, y:1, C:1, V:1, X:1, A:1, Z:1, Y:1 };
1580+
1581+
function _isFunctionKey(event) {
1582+
return event.key.length >= 2 && event.key[0] === 'F' && !isNaN(event.key.slice(1));
1583+
}
1584+
1585+
function _forwardKeyEventToPhoenix(event) {
1586+
event.preventDefault();
1587+
event.stopImmediatePropagation();
1588+
MessageBroker.send({
1589+
keyForward: true,
1590+
key: event.key,
1591+
code: event.code,
1592+
ctrlKey: event.ctrlKey,
1593+
metaKey: event.metaKey,
1594+
shiftKey: event.shiftKey,
1595+
altKey: event.altKey
1596+
});
1597+
}
1598+
15761599
document.addEventListener('keydown', function(event) {
15771600
if (config.mode === 'edit' && (event.key === 'Escape' || event.key === 'Esc')) {
15781601
event.preventDefault();
15791602
_handleEscapeKeyPress();
1603+
return;
1604+
}
1605+
// Polite: if the previewed page handled the key, don't double-fire.
1606+
if (event.defaultPrevented) {
1607+
return;
1608+
}
1609+
if (_isFunctionKey(event)) {
1610+
_forwardKeyEventToPhoenix(event);
1611+
return;
1612+
}
1613+
const isMod = event.metaKey || event.ctrlKey;
1614+
if (isMod && event.key && event.key.length === 1 && !_KEYS_NOT_FORWARDED[event.key]) {
1615+
_forwardKeyEventToPhoenix(event);
15801616
}
15811617
});
15821618

src/LiveDevelopment/MultiBrowserImpl/protocol/LiveDevProtocol.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,26 @@ define(function (require, exports, module) {
189189
* set focus to editor
190190
* @private
191191
*/
192+
/**
193+
* Re-dispatches a keyboard shortcut originating from the live preview iframe
194+
* onto document.body so KeyBindingManager picks it up. Needed because key
195+
* events fired inside an iframe don't bubble to the parent's document.
196+
* @private
197+
*/
198+
function _forwardKeyboardShortcutFromIframe(data) {
199+
const event = new KeyboardEvent("keydown", {
200+
key: data.key,
201+
code: data.code,
202+
ctrlKey: !!data.ctrlKey,
203+
metaKey: !!data.metaKey,
204+
shiftKey: !!data.shiftKey,
205+
altKey: !!data.altKey,
206+
bubbles: true,
207+
cancelable: true
208+
});
209+
document.body.dispatchEvent(event);
210+
}
211+
192212
function _focusEditorIfNeeded(editor, tagName, contentEditable) {
193213
if (WorkspaceManager.isInDesignMode()) {
194214
return;
@@ -347,6 +367,8 @@ define(function (require, exports, module) {
347367
pendingHandler.deferred.resolve(msg);
348368
}
349369
}
370+
} else if (msg.keyForward) {
371+
_forwardKeyboardShortcutFromIframe(msg);
350372
} else if (msg.clicked && msg.tagId) {
351373
// While previewing an html file, and if css related file is active in the editor, then clicking on the
352374
// live preview, here we set the cursor position in the css file. but this will also trigger a css

src/command/CommandManager.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,18 @@ define(function (require, exports, module) {
157157
return this._options || {};
158158
};
159159

160+
/**
161+
* Returns true if the command opted in to running while the workspace is in
162+
* design mode (editor collapsed). KeyBindingManager uses this to decide
163+
* whether a keyboard shortcut should fire; commands that don't opt in are
164+
* swallowed in design mode.
165+
*
166+
* @return {boolean}
167+
*/
168+
Command.prototype.isSupportedInDesignMode = function () {
169+
return !!(this._options && this._options.supportsDesignMode);
170+
};
171+
160172
/**
161173
* Sets enabled state of Command and dispatches "enabledStateChange"
162174
* when the enabled state changes.
@@ -250,6 +262,10 @@ define(function (require, exports, module) {
250262
* event.sourceType(Eg. Ctrl-K) parameter.
251263
* @param {string} options.htmlName If set, this will be displayed in ui menus instead of the name given.
252264
* Example: `"Phoenix menu<i class='fa fa-car' style='margin-left: 4px;'></i>"`
265+
* @param {boolean} options.supportsDesignMode If true, this command's keyboard shortcut will still fire when
266+
* the workspace is in design mode. Commands that don't opt in are swallowed in design mode because the
267+
* editor area is collapsed and most shortcuts are nonsensical there. Reserve this flag for commands that
268+
* remain useful with no editor visible (file open/save/close, Quick Open, Find in Files, etc.).
253269
*
254270
* @return {?Command}
255271
*/

src/command/KeyBindingManager.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -947,14 +947,26 @@ define(function (require, exports, module) {
947947
*/
948948
function _handleKey(key) {
949949
if (_enabled && _keyMap[key]) {
950+
let command = CommandManager.get(_keyMap[key].commandID);
951+
// In design mode, when focus is inside the live preview iframe (the
952+
// user is interacting with their previewed page), swallow shortcuts
953+
// that didn't opt in via Command.isSupportedInDesignMode(). When
954+
// focus is elsewhere in the Phoenix UI (e.g. AI chat textarea), let
955+
// shortcuts behave normally — design mode shouldn't break Phoenix's
956+
// own UI interactions.
957+
const focusInIframe = document.activeElement &&
958+
document.activeElement.tagName === 'IFRAME';
959+
if (Phoenix.isInDesignMode() && focusInIframe &&
960+
command && !command.isSupportedInDesignMode()) {
961+
return true;
962+
}
950963
Metrics.countEvent(Metrics.EVENT_TYPE.KEYBOARD, "shortcut", key);
951964
Metrics.countEvent(Metrics.EVENT_TYPE.KEYBOARD, "command", _keyMap[key].commandID);
952965
logger.leaveTrail("Keyboard shortcut: " + key + " command: " + _keyMap[key].commandID);
953966
// If there is a registered and enabled key event except the swallowed key events,
954967
// we always mark the event as processed and return true.
955968
// We don't want multiple behavior tied to the same key event. For Instance, in browser, if `ctrl-k`
956969
// is not handled by quick edit, it will open browser url bar if we return false here(which is bad ux).
957-
let command = CommandManager.get(_keyMap[key].commandID);
958970
let eventDetails = undefined;
959971
if(command._options.eventSource){
960972
eventDetails = {

src/document/DocumentCommandHandlers.js

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2405,29 +2405,33 @@ define(function (require, exports, module) {
24052405
exports.APP_QUIT_CANCELLED = APP_QUIT_CANCELLED;
24062406

24072407

2408+
// Commands allowlisted to fire while in design mode (editor collapsed) so the
2409+
// user can still open / save / close files without leaving design mode.
2410+
const _designModeOpts = { supportsDesignMode: true };
2411+
24082412
// Deprecated commands
24092413
CommandManager.register(Strings.CMD_ADD_TO_WORKING_SET, Commands.FILE_ADD_TO_WORKING_SET, handleFileAddToWorkingSet);
2410-
CommandManager.register(Strings.CMD_FILE_OPEN, Commands.FILE_OPEN, handleDocumentOpen);
2414+
CommandManager.register(Strings.CMD_FILE_OPEN, Commands.FILE_OPEN, handleDocumentOpen, _designModeOpts);
24112415

24122416
// New commands
24132417
CommandManager.register(Strings.CMD_ADD_TO_WORKING_SET, Commands.CMD_ADD_TO_WORKINGSET_AND_OPEN, handleFileAddToWorkingSetAndOpen);
2414-
CommandManager.register(Strings.CMD_FILE_OPEN, Commands.CMD_OPEN, handleFileOpen);
2418+
CommandManager.register(Strings.CMD_FILE_OPEN, Commands.CMD_OPEN, handleFileOpen, _designModeOpts);
24152419

24162420
// File Commands
24172421
CommandManager.register(Strings.CMD_FILE_NEW_UNTITLED, Commands.FILE_NEW_UNTITLED, handleFileNew);
24182422
CommandManager.register(Strings.CMD_FILE_NEW, Commands.FILE_NEW, handleFileNewInProject);
24192423
CommandManager.register(Strings.CMD_FILE_NEW_FOLDER, Commands.FILE_NEW_FOLDER, handleNewFolderInProject);
2420-
CommandManager.register(Strings.CMD_FILE_SAVE, Commands.FILE_SAVE, handleFileSave);
2421-
CommandManager.register(Strings.CMD_FILE_SAVE_ALL, Commands.FILE_SAVE_ALL, handleFileSaveAll);
2424+
CommandManager.register(Strings.CMD_FILE_SAVE, Commands.FILE_SAVE, handleFileSave, _designModeOpts);
2425+
CommandManager.register(Strings.CMD_FILE_SAVE_ALL, Commands.FILE_SAVE_ALL, handleFileSaveAll, _designModeOpts);
24222426
CommandManager.register(Strings.CMD_FILE_SAVE_AS, Commands.FILE_SAVE_AS, handleFileSaveAs);
24232427
CommandManager.register(Strings.CMD_FILE_RENAME, Commands.FILE_RENAME, handleFileRename);
24242428
CommandManager.register(Strings.CMD_FILE_DELETE, Commands.FILE_DELETE, handleFileDelete);
24252429

24262430
// Close Commands
2427-
CommandManager.register(Strings.CMD_FILE_CLOSE, Commands.FILE_CLOSE, handleFileClose);
2428-
CommandManager.register(Strings.CMD_FILE_CLOSE_ALL, Commands.FILE_CLOSE_ALL, handleFileCloseAll);
2431+
CommandManager.register(Strings.CMD_FILE_CLOSE, Commands.FILE_CLOSE, handleFileClose, _designModeOpts);
2432+
CommandManager.register(Strings.CMD_FILE_CLOSE_ALL, Commands.FILE_CLOSE_ALL, handleFileCloseAll, _designModeOpts);
24292433
CommandManager.register(Strings.CMD_FILE_CLOSE_LIST, Commands.FILE_CLOSE_LIST, handleFileCloseList);
2430-
CommandManager.register(Strings.CMD_REOPEN_CLOSED, Commands.FILE_REOPEN_CLOSED, handleReopenClosed);
2434+
CommandManager.register(Strings.CMD_REOPEN_CLOSED, Commands.FILE_REOPEN_CLOSED, handleReopenClosed, _designModeOpts);
24312435

24322436
// Traversal
24332437
CommandManager.register(Strings.CMD_NEXT_DOC, Commands.NAVIGATE_NEXT_DOC, handleGoNextDoc);
@@ -2443,7 +2447,7 @@ define(function (require, exports, module) {
24432447
CommandManager.register(Strings.CMD_OPEN_IN_POWER_SHELL, Commands.NAVIGATE_OPEN_IN_POWERSHELL, openPowerShell);
24442448
}
24452449
CommandManager.register(Strings.CMD_OPEN_IN_DEFAULT_APP, Commands.NAVIGATE_OPEN_IN_DEFAULT_APP, openDefaultApp);
2446-
CommandManager.register(Strings.CMD_NEW_BRACKETS_WINDOW, Commands.FILE_NEW_WINDOW, handleFileNewWindow);
2450+
CommandManager.register(Strings.CMD_NEW_BRACKETS_WINDOW, Commands.FILE_NEW_WINDOW, handleFileNewWindow, _designModeOpts);
24472451
CommandManager.register(quitString, Commands.FILE_QUIT, handleFileCloseWindow);
24482452
CommandManager.register(Strings.CMD_SHOW_IN_TREE, Commands.NAVIGATE_SHOW_IN_FILE_TREE, handleShowInTree);
24492453

src/extensionsIntegrated/Phoenix-live-preview/main.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1476,12 +1476,12 @@ define(function (require, exports, module) {
14761476
}, fileChangeListenerStartDelay);
14771477
CommandManager.register(Strings.CMD_LIVE_FILE_PREVIEW, Commands.FILE_LIVE_FILE_PREVIEW, function () {
14781478
_toggleVisibilityOnClick();
1479-
});
1479+
}, { supportsDesignMode: true });
14801480
CommandManager.register(Strings.CMD_LIVE_FILE_PREVIEW_SETTINGS,
1481-
Commands.FILE_LIVE_FILE_PREVIEW_SETTINGS, _showSettingsDialog);
1481+
Commands.FILE_LIVE_FILE_PREVIEW_SETTINGS, _showSettingsDialog, { supportsDesignMode: true });
14821482
CommandManager.register(Strings.CMD_RELOAD_LIVE_PREVIEW, Commands.CMD_RELOAD_LIVE_PREVIEW, function () {
14831483
_loadPreview(true, true);
1484-
});
1484+
}, { supportsDesignMode: true });
14851485
let fileMenu = Menus.getMenu(Menus.AppMenuBar.FILE_MENU);
14861486
fileMenu.addMenuItem(Commands.FILE_LIVE_FILE_PREVIEW, "", Menus.AFTER, Commands.FILE_EXTENSION_MANAGER);
14871487
fileMenu.addMenuItem(Commands.CMD_RELOAD_LIVE_PREVIEW, "",

src/index.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,10 @@
451451
}
452452
};
453453
window.Phoenix.isNativeApp = window.__IS_NATIVE_SHELL__;
454+
// Default stub — WorkspaceManager replaces this with its real accessor on load.
455+
// Defining it here means leaf modules (e.g. KeyBindingManager) can call
456+
// Phoenix.isInDesignMode() unconditionally without a presence check.
457+
window.Phoenix.isInDesignMode = function () { return false; };
454458
window.Phoenix.TRUSTED_ORIGINS[location.origin] = true;
455459
Phoenix.isSupportedBrowser = Phoenix.isNativeApp ||
456460
(Phoenix.browser.isDeskTop && ("serviceWorker" in navigator));

src/project/ProjectManager.js

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2229,17 +2229,17 @@ define(function (require, exports, module) {
22292229
EventDispatcher.on_duringInit(MainViewManager, "currentFileChange", _currentFileChange);
22302230

22312231
// Commands
2232-
CommandManager.register(Strings.CMD_OPEN_FOLDER, Commands.FILE_OPEN_FOLDER, openProject);
2233-
CommandManager.register(Strings.CMD_PROJECT_SETTINGS, Commands.FILE_PROJECT_SETTINGS, _projectSettings);
2234-
CommandManager.register(Strings.CMD_FILE_REFRESH, Commands.FILE_REFRESH, refreshFileTree);
2235-
CommandManager.register(Strings.CMD_FILE_CUT, Commands.FILE_CUT, _cutFileCMD);
2236-
CommandManager.register(Strings.CMD_FILE_COPY, Commands.FILE_COPY, _copyFileCMD);
2237-
CommandManager.register(Strings.CMD_FILE_COPY_PATH, Commands.FILE_COPY_PATH, _copyProjectRelativePath);
2238-
CommandManager.register(Strings.CMD_FILE_PASTE, Commands.FILE_PASTE, _pasteFileCMD);
2239-
CommandManager.register(Strings.CMD_FILE_DUPLICATE, Commands.FILE_DUPLICATE, _duplicateFileCMD);
2240-
CommandManager.register(Strings.CMD_FILE_DUPLICATE_FILE, Commands.FILE_DUPLICATE_FILE, _duplicateFileCMD);
2241-
CommandManager.register(Strings.CMD_FILE_DOWNLOAD_PROJECT, Commands.FILE_DOWNLOAD_PROJECT, _downloadFolderCommand);
2242-
CommandManager.register(Strings.CMD_FILE_DOWNLOAD, Commands.FILE_DOWNLOAD, _downloadCommand);
2232+
CommandManager.register(Strings.CMD_OPEN_FOLDER, Commands.FILE_OPEN_FOLDER, openProject, { supportsDesignMode: true });
2233+
CommandManager.register(Strings.CMD_PROJECT_SETTINGS, Commands.FILE_PROJECT_SETTINGS, _projectSettings, { supportsDesignMode: true });
2234+
CommandManager.register(Strings.CMD_FILE_REFRESH, Commands.FILE_REFRESH, refreshFileTree, { supportsDesignMode: true });
2235+
CommandManager.register(Strings.CMD_FILE_CUT, Commands.FILE_CUT, _cutFileCMD, { supportsDesignMode: true });
2236+
CommandManager.register(Strings.CMD_FILE_COPY, Commands.FILE_COPY, _copyFileCMD, { supportsDesignMode: true });
2237+
CommandManager.register(Strings.CMD_FILE_COPY_PATH, Commands.FILE_COPY_PATH, _copyProjectRelativePath, { supportsDesignMode: true });
2238+
CommandManager.register(Strings.CMD_FILE_PASTE, Commands.FILE_PASTE, _pasteFileCMD, { supportsDesignMode: true });
2239+
CommandManager.register(Strings.CMD_FILE_DUPLICATE, Commands.FILE_DUPLICATE, _duplicateFileCMD, { supportsDesignMode: true });
2240+
CommandManager.register(Strings.CMD_FILE_DUPLICATE_FILE, Commands.FILE_DUPLICATE_FILE, _duplicateFileCMD, { supportsDesignMode: true });
2241+
CommandManager.register(Strings.CMD_FILE_DOWNLOAD_PROJECT, Commands.FILE_DOWNLOAD_PROJECT, _downloadFolderCommand, { supportsDesignMode: true });
2242+
CommandManager.register(Strings.CMD_FILE_DOWNLOAD, Commands.FILE_DOWNLOAD, _downloadCommand, { supportsDesignMode: true });
22432243

22442244
// Define the preference to decide how to sort the Project Tree files
22452245
PreferencesManager.definePreference(SORT_DIRECTORIES_FIRST, "boolean", true, {
@@ -2250,7 +2250,7 @@ define(function (require, exports, module) {
22502250
actionCreator.setSortDirectoriesFirst(sortPref);
22512251
CommandManager.get(Commands.FILE_SHOW_FOLDERS_FIRST).setChecked(sortPref);
22522252
});
2253-
CommandManager.register(Strings.CMD_FILE_SHOW_FOLDERS_FIRST, Commands.FILE_SHOW_FOLDERS_FIRST, _showFolderFirst);
2253+
CommandManager.register(Strings.CMD_FILE_SHOW_FOLDERS_FIRST, Commands.FILE_SHOW_FOLDERS_FIRST, _showFolderFirst, { supportsDesignMode: true });
22542254
CommandManager.get(Commands.FILE_SHOW_FOLDERS_FIRST).setChecked(PreferencesManager.get(SORT_DIRECTORIES_FIRST));
22552255

22562256
actionCreator.setSortDirectoriesFirst(PreferencesManager.get(SORT_DIRECTORIES_FIRST));

src/search/FindInFilesUI.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -577,11 +577,11 @@ define(function (require, exports, module) {
577577
});
578578

579579
// Initialize: command handlers
580-
CommandManager.register(Strings.CMD_FIND_IN_FILES, Commands.CMD_FIND_IN_FILES, _showFindBar);
581-
CommandManager.register(Strings.CMD_FIND_IN_SUBTREE, Commands.CMD_FIND_IN_SUBTREE, _showFindBarForSubtree);
580+
CommandManager.register(Strings.CMD_FIND_IN_FILES, Commands.CMD_FIND_IN_FILES, _showFindBar, { supportsDesignMode: true });
581+
CommandManager.register(Strings.CMD_FIND_IN_SUBTREE, Commands.CMD_FIND_IN_SUBTREE, _showFindBarForSubtree, { supportsDesignMode: true });
582582

583-
CommandManager.register(Strings.CMD_REPLACE_IN_FILES, Commands.CMD_REPLACE_IN_FILES, _showReplaceBar);
584-
CommandManager.register(Strings.CMD_REPLACE_IN_SUBTREE, Commands.CMD_REPLACE_IN_SUBTREE, _showReplaceBarForSubtree);
583+
CommandManager.register(Strings.CMD_REPLACE_IN_FILES, Commands.CMD_REPLACE_IN_FILES, _showReplaceBar, { supportsDesignMode: true });
584+
CommandManager.register(Strings.CMD_REPLACE_IN_SUBTREE, Commands.CMD_REPLACE_IN_SUBTREE, _showReplaceBarForSubtree, { supportsDesignMode: true });
585585

586586
FindUtils.on(FindUtils.SEARCH_INDEXING_STARTED, _searchIndexingStarted);
587587
FindUtils.on(FindUtils.SEARCH_INDEXING_PROGRESS, _searchIndexingProgressing);

src/search/QuickOpen.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1048,7 +1048,7 @@ define(function (require, exports, module) {
10481048
});
10491049
});
10501050

1051-
CommandManager.register(Strings.CMD_QUICK_OPEN, Commands.NAVIGATE_QUICK_OPEN, doFileSearch);
1051+
CommandManager.register(Strings.CMD_QUICK_OPEN, Commands.NAVIGATE_QUICK_OPEN, doFileSearch, { supportsDesignMode: true });
10521052
CommandManager.register(Strings.CMD_GOTO_DEFINITION, Commands.NAVIGATE_GOTO_DEFINITION, doDefinitionSearch);
10531053
CommandManager.register(Strings.CMD_GOTO_DEFINITION_PROJECT, Commands.NAVIGATE_GOTO_DEFINITION_PROJECT, doDefinitionSearchInProject);
10541054
CommandManager.register(Strings.CMD_GOTO_LINE, Commands.NAVIGATE_GOTO_LINE, doGotoLine);

0 commit comments

Comments
 (0)