From 425818ad44a848e031347f3161fa1a35001bdb84 Mon Sep 17 00:00:00 2001 From: Karan Yadav Date: Thu, 10 Apr 2025 18:44:22 +0000 Subject: [PATCH 1/2] [#3444] Add keyboard shortcut update functionality --- .../IDE/components/Editor/contexts.jsx | 38 ++++ .../modules/IDE/components/Editor/index.jsx | 81 ++++--- .../IDE/components/KeyboardShortcutItem.jsx | 79 +++++++ .../IDE/components/KeyboardShortcutModal.jsx | 14 +- client/modules/IDE/pages/IDEView.jsx | 199 +++++++++--------- 5 files changed, 277 insertions(+), 134 deletions(-) create mode 100644 client/modules/IDE/components/Editor/contexts.jsx create mode 100644 client/modules/IDE/components/KeyboardShortcutItem.jsx diff --git a/client/modules/IDE/components/Editor/contexts.jsx b/client/modules/IDE/components/Editor/contexts.jsx new file mode 100644 index 0000000000..7f1702c3b6 --- /dev/null +++ b/client/modules/IDE/components/Editor/contexts.jsx @@ -0,0 +1,38 @@ +import React, { useContext } from 'react'; +import PropTypes from 'prop-types'; +import { createContext, useState } from 'react'; + +export const EditorKeyMapsContext = createContext(); + +export function EditorKeyMapProvider({ children }) { + const [keyMaps, setKeyMaps] = useState({ tidy: 'Shift-Ctrl-F' }); + + const updateKeyMap = (key, value) => { + if (key in keyMaps) { + setKeyMaps((prevKeyMaps) => ({ + ...prevKeyMaps, + [key]: value + })); + } + }; + + return ( + + {children} + + ); +} + +EditorKeyMapProvider.propTypes = { + children: PropTypes.node.isRequired +}; + +export const useEditorKeyMap = () => { + const context = useContext(EditorKeyMapsContext); + if (!context) { + throw new Error( + 'useEditorKeyMap must be used within a EditorKeyMapProvider' + ); + } + return context; +}; diff --git a/client/modules/IDE/components/Editor/index.jsx b/client/modules/IDE/components/Editor/index.jsx index 404741591a..eef77209cf 100644 --- a/client/modules/IDE/components/Editor/index.jsx +++ b/client/modules/IDE/components/Editor/index.jsx @@ -72,6 +72,7 @@ import UnsavedChangesIndicator from '../UnsavedChangesIndicator'; import { EditorContainer, EditorHolder } from './MobileEditor'; import { FolderIcon } from '../../../../common/icons'; import IconButton from '../../../../common/IconButton'; +import { EditorKeyMapsContext } from './contexts'; emmet(CodeMirror); @@ -104,6 +105,7 @@ class Editor extends React.Component { this.showFind = this.showFind.bind(this); this.showReplace = this.showReplace.bind(this); this.getContent = this.getContent.bind(this); + this.updateKeyMaps = this.updateKeyMaps.bind(this); } componentDidMount() { @@ -153,36 +155,9 @@ class Editor extends React.Component { delete this._cm.options.lint.options.errors; - const replaceCommand = - metaKey === 'Ctrl' ? `${metaKey}-H` : `${metaKey}-Option-F`; - this._cm.setOption('extraKeys', { - Tab: (cm) => { - if (!cm.execCommand('emmetExpandAbbreviation')) return; - // might need to specify and indent more? - const selection = cm.doc.getSelection(); - if (selection.length > 0) { - cm.execCommand('indentMore'); - } else { - cm.replaceSelection(' '.repeat(INDENTATION_AMOUNT)); - } - }, - Enter: 'emmetInsertLineBreak', - Esc: 'emmetResetAbbreviation', - [`Shift-Tab`]: false, - [`${metaKey}-Enter`]: () => null, - [`Shift-${metaKey}-Enter`]: () => null, - [`${metaKey}-F`]: 'findPersistent', - [`Shift-${metaKey}-F`]: this.tidyCode, - [`${metaKey}-G`]: 'findPersistentNext', - [`Shift-${metaKey}-G`]: 'findPersistentPrev', - [replaceCommand]: 'replace', - // Cassie Tarakajian: If you don't set a default color, then when you - // choose a color, it deletes characters inline. This is a - // hack to prevent that. - [`${metaKey}-K`]: (cm, event) => - cm.state.colorpicker.popup_color_picker({ length: 0 }), - [`${metaKey}-.`]: 'toggleComment' // Note: most adblockers use the shortcut ctrl+. - }); + const { keyMaps } = this.context; + + this.updateKeyMaps(keyMaps); this.initializeDocuments(this.props.files); this._cm.swapDoc(this._docs[this.props.file.id]); @@ -236,6 +211,16 @@ class Editor extends React.Component { } componentDidUpdate(prevProps) { + const currentKeyMaps = this.context?.keyMaps; + const prevKeyMaps = this.prevKeyMapsRef?.current; + + if ( + prevKeyMaps && + JSON.stringify(currentKeyMaps) !== JSON.stringify(prevKeyMaps) + ) { + this.updateKeyMaps(currentKeyMaps); + } + this.prevKeyMapsRef = { current: currentKeyMaps }; if (this.props.file.id !== prevProps.file.id) { const fileMode = this.getFileMode(this.props.file.name); if (fileMode === 'javascript') { @@ -515,6 +500,42 @@ class Editor extends React.Component { }); } + updateKeyMaps(keyMaps) { + const replaceCommand = + metaKey === 'Ctrl' ? `${metaKey}-H` : `${metaKey}-Option-F`; + + this._cm.setOption('extraKeys', { + Tab: (cm) => { + if (!cm.execCommand('emmetExpandAbbreviation')) return; + // might need to specify and indent more? + const selection = cm.doc.getSelection(); + if (selection.length > 0) { + cm.execCommand('indentMore'); + } else { + cm.replaceSelection(' '.repeat(INDENTATION_AMOUNT)); + } + }, + Enter: 'emmetInsertLineBreak', + Esc: 'emmetResetAbbreviation', + [`Shift-Tab`]: false, + [`${metaKey}-Enter`]: () => null, + [`Shift-${metaKey}-Enter`]: () => null, + [`${metaKey}-F`]: 'findPersistent', + [`${keyMaps.tidy}`]: this.tidyCode, + [`${metaKey}-G`]: 'findPersistentNext', + [`Shift-${metaKey}-G`]: 'findPersistentPrev', + [replaceCommand]: 'replace', + // Cassie Tarakajian: If you don't set a default color, then when you + // choose a color, it deletes characters inline. This is a + // hack to prevent that. + [`${metaKey}-K`]: (cm, event) => + cm.state.colorpicker.popup_color_picker({ length: 0 }), + [`${metaKey}-.`]: 'toggleComment' // Note: most adblockers use the shortcut ctrl+. + }); + } + + static contextType = EditorKeyMapsContext; + render() { const editorSectionClass = classNames({ editor: true, diff --git a/client/modules/IDE/components/KeyboardShortcutItem.jsx b/client/modules/IDE/components/KeyboardShortcutItem.jsx new file mode 100644 index 0000000000..3c2926143c --- /dev/null +++ b/client/modules/IDE/components/KeyboardShortcutItem.jsx @@ -0,0 +1,79 @@ +import React, { useRef, useState } from 'react'; +import PropTypes from 'prop-types'; +import { useEditorKeyMap } from './Editor/contexts'; + +function KeyboardShortcutItem({ shortcut, desc }) { + const [edit, setEdit] = useState(false); + const pressedKeyCombination = useRef({}); + const inputRef = useRef(null); + const { updateKeyMap } = useEditorKeyMap(); + + const handleEdit = (state) => { + setEdit(state); + if (state) { + inputRef.current.focus(); + } else { + inputRef.current.blur(); + updateKeyMap('tidy', inputRef.current.innerText); + } + }; + + return ( +
  • + + { + if (!edit) return; + + event.preventDefault(); + event.stopPropagation(); + let { key } = event; + if (key === 'Control') { + key = 'Ctrl'; + } + if (key === ' ') { + key = 'Space'; + } + + pressedKeyCombination.current[key] = true; + + event.currentTarget.innerText = Object.keys( + pressedKeyCombination.current + ).join('-'); + }} + onKeyUp={(event) => { + if (!edit) return; + event.preventDefault(); + event.stopPropagation(); + let { key } = event; + if (key === 'Control') { + key = 'Ctrl'; + } + if (key === ' ') { + key = 'Space'; + } + + delete pressedKeyCombination.current[key]; + }} + > + {shortcut} + + {desc} +
  • + ); +} + +KeyboardShortcutItem.propTypes = { + shortcut: PropTypes.string.isRequired, + desc: PropTypes.string.isRequired +}; + +export default KeyboardShortcutItem; diff --git a/client/modules/IDE/components/KeyboardShortcutModal.jsx b/client/modules/IDE/components/KeyboardShortcutModal.jsx index fbca28a572..3baa59193e 100644 --- a/client/modules/IDE/components/KeyboardShortcutModal.jsx +++ b/client/modules/IDE/components/KeyboardShortcutModal.jsx @@ -1,9 +1,13 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; import { metaKeyName, metaKey } from '../../../utils/metaKey'; +import KeyboardShortcutItem from './KeyboardShortcutItem'; +import { useEditorKeyMap } from './Editor/contexts'; function KeyboardShortcutModal() { const { t } = useTranslation(); + const { keyMaps } = useEditorKeyMap(); + const replaceCommand = metaKey === 'Ctrl' ? `${metaKeyName} + H` : `${metaKeyName} + ⌥ + F`; const newFileCommand = @@ -25,12 +29,10 @@ function KeyboardShortcutModal() { .