Code Editor: Allow saving with Ctrl/Cmd+S in Theme/Plugin Editors.
* Keyboard shortcuts work when CodeMirror is not enabled (due to syntax highlighting not being enabled), and when the user is not focused inside the CodeMirror editor. * The autocomplete trigger is switched from `keyup` to `inputRead` to improve reliability, support IME composition, and prevent conflicts with modifier keys (e.g., releasing `Ctrl`/`Cmd` before `s` after a save). * A `updateErrorNotice` method is exposed on the code editor instance to ensure validation errors are displayed when a save via shortcut is attempted, preventing "silent" failures. Otherwise, the linting error notice is only shown when focus leaves the editor. * The form submission is modernized by replacing the deprecated jQuery `.submit()` shorthand with `.trigger( 'submit' )`. Developed in https://github.com/WordPress/wordpress-develop/pull/10851 Props westonruter, Junaidkbr, evansolomon, desrosj, mukesh27, jonsurrell, spiraltee, chexee, andrewryno, tusharaddweb, gauri87, huzaifaalmesbah, ocean90, karmatosed, johnbillion, scribu, jcnetsys. Fixes #17133. Built from https://develop.svn.wordpress.org/trunk@61588 git-svn-id: http://core.svn.wordpress.org/trunk@60899 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
@@ -46,7 +46,7 @@ if ( 'undefined' === typeof window.wp.codeEditor ) {
|
||||
* @param {Function} settings.onChangeLintingErrors - Callback for when there are changes to linting errors.
|
||||
* @param {Function} settings.onUpdateErrorNotice - Callback to update error notice.
|
||||
*
|
||||
* @return {void}
|
||||
* @return {Function} Update error notice function.
|
||||
*/
|
||||
function configureLinting( editor, settings ) { // eslint-disable-line complexity
|
||||
var currentErrorAnnotations = [], previouslyShownErrorAnnotations = [];
|
||||
@@ -82,7 +82,7 @@ if ( 'undefined' === typeof window.wp.codeEditor ) {
|
||||
}
|
||||
|
||||
/*
|
||||
* Note that rules must be sent in the "deprecated" lint.options property
|
||||
* Note that rules must be sent in the "deprecated" lint.options property
|
||||
* to prevent linter from complaining about unrecognized options.
|
||||
* See <https://github.com/codemirror/CodeMirror/pull/4944>.
|
||||
*/
|
||||
@@ -209,6 +209,8 @@ if ( 'undefined' === typeof window.wp.codeEditor ) {
|
||||
updateErrorNotice();
|
||||
}
|
||||
});
|
||||
|
||||
return updateErrorNotice;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -261,6 +263,7 @@ if ( 'undefined' === typeof window.wp.codeEditor ) {
|
||||
* @typedef {object} wp.codeEditor~CodeEditorInstance
|
||||
* @property {object} settings - The code editor settings.
|
||||
* @property {CodeMirror} codemirror - The CodeMirror instance.
|
||||
* @property {Function} updateErrorNotice - Force update the error notice.
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -282,7 +285,7 @@ if ( 'undefined' === typeof window.wp.codeEditor ) {
|
||||
* @return {CodeEditorInstance} Instance.
|
||||
*/
|
||||
wp.codeEditor.initialize = function initialize( textarea, settings ) {
|
||||
var $textarea, codemirror, instanceSettings, instance;
|
||||
var $textarea, codemirror, instanceSettings, instance, updateErrorNotice;
|
||||
if ( 'string' === typeof textarea ) {
|
||||
$textarea = $( '#' + textarea );
|
||||
} else {
|
||||
@@ -294,16 +297,33 @@ if ( 'undefined' === typeof window.wp.codeEditor ) {
|
||||
|
||||
codemirror = wp.CodeMirror.fromTextArea( $textarea[0], instanceSettings.codemirror );
|
||||
|
||||
configureLinting( codemirror, instanceSettings );
|
||||
updateErrorNotice = configureLinting( codemirror, instanceSettings );
|
||||
|
||||
instance = {
|
||||
settings: instanceSettings,
|
||||
codemirror: codemirror
|
||||
codemirror,
|
||||
updateErrorNotice,
|
||||
};
|
||||
|
||||
if ( codemirror.showHint ) {
|
||||
codemirror.on( 'keyup', function( editor, event ) { // eslint-disable-line complexity
|
||||
var shouldAutocomplete, isAlphaKey = /^[a-zA-Z]$/.test( event.key ), lineBeforeCursor, innerMode, token;
|
||||
codemirror.on( 'inputRead', function( editor, change ) {
|
||||
var shouldAutocomplete, isAlphaKey, lineBeforeCursor, innerMode, token, char;
|
||||
|
||||
// Only trigger autocompletion for typed input or IME composition.
|
||||
if ( '+input' !== change.origin && ! change.origin.startsWith( '*compose' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only trigger autocompletion for single-character inputs.
|
||||
// The text property is an array of strings, one for each line.
|
||||
// We check that there is only one line and that line has only one character.
|
||||
if ( 1 !== change.text.length || 1 !== change.text[0].length ) {
|
||||
return;
|
||||
}
|
||||
|
||||
char = change.text[0];
|
||||
isAlphaKey = /^[a-zA-Z]$/.test( char );
|
||||
|
||||
if ( codemirror.state.completionActive && isAlphaKey ) {
|
||||
return;
|
||||
}
|
||||
@@ -318,11 +338,11 @@ if ( 'undefined' === typeof window.wp.codeEditor ) {
|
||||
lineBeforeCursor = codemirror.doc.getLine( codemirror.doc.getCursor().line ).substr( 0, codemirror.doc.getCursor().ch );
|
||||
if ( 'html' === innerMode || 'xml' === innerMode ) {
|
||||
shouldAutocomplete = (
|
||||
'<' === event.key ||
|
||||
( '/' === event.key && 'tag' === token.type ) ||
|
||||
'<' === char ||
|
||||
( '/' === char && 'tag' === token.type ) ||
|
||||
( isAlphaKey && 'tag' === token.type ) ||
|
||||
( isAlphaKey && 'attribute' === token.type ) ||
|
||||
( '=' === event.key && (
|
||||
( '=' === char && (
|
||||
token.state.htmlState?.tagName ||
|
||||
token.state.curState?.htmlState?.tagName
|
||||
) )
|
||||
@@ -330,17 +350,17 @@ if ( 'undefined' === typeof window.wp.codeEditor ) {
|
||||
} else if ( 'css' === innerMode ) {
|
||||
shouldAutocomplete =
|
||||
isAlphaKey ||
|
||||
':' === event.key ||
|
||||
( ' ' === event.key && /:\s+$/.test( lineBeforeCursor ) );
|
||||
':' === char ||
|
||||
( ' ' === char && /:\s+$/.test( lineBeforeCursor ) );
|
||||
} else if ( 'javascript' === innerMode ) {
|
||||
shouldAutocomplete = isAlphaKey || '.' === event.key;
|
||||
shouldAutocomplete = isAlphaKey || '.' === char;
|
||||
} else if ( 'clike' === innerMode && 'php' === codemirror.options.mode ) {
|
||||
shouldAutocomplete = isAlphaKey && ( 'keyword' === token.type || 'variable' === token.type );
|
||||
}
|
||||
if ( shouldAutocomplete ) {
|
||||
codemirror.showHint( { completeSingle: false } );
|
||||
}
|
||||
});
|
||||
} );
|
||||
}
|
||||
|
||||
// Facilitate tabbing out of the editor.
|
||||
|
||||
2
wp-admin/js/code-editor.min.js
vendored
2
wp-admin/js/code-editor.min.js
vendored
@@ -1,2 +1,2 @@
|
||||
/*! This file is auto-generated */
|
||||
void 0===window.wp&&(window.wp={}),void 0===window.wp.codeEditor&&(window.wp.codeEditor={}),function(u,d){"use strict";function s(r,s){var a=[],d=[];function c(){s.onUpdateErrorNotice&&!_.isEqual(a,d)&&(s.onUpdateErrorNotice(a,r),d=a)}function i(){var i,t=r.getOption("lint");return!!t&&(!0===t?t={}:_.isObject(t)&&(t=u.extend({},t)),t.options||(t.options={}),"javascript"===s.codemirror.mode&&s.jshint&&u.extend(t.options,s.jshint),"css"===s.codemirror.mode&&s.csslint&&u.extend(t.options,s.csslint),"htmlmixed"===s.codemirror.mode&&s.htmlhint&&(t.options.rules=u.extend({},s.htmlhint),s.jshint&&(t.options.rules.jshint=s.jshint),s.csslint)&&(t.options.rules.csslint=s.csslint),t.onUpdateLinting=(i=t.onUpdateLinting,function(t,e,n){var o=_.filter(t,function(t){return"error"===t.severity});i&&i.apply(t,e,n),!_.isEqual(o,a)&&(a=o,s.onChangeLintingErrors&&s.onChangeLintingErrors(o,t,e,n),!r.state.focused||0===a.length||0<d.length)&&c()}),t)}r.setOption("lint",i()),r.on("optionChange",function(t,e){var n,o="CodeMirror-lint-markers";"lint"===e&&(e=r.getOption("gutters")||[],!0===(n=r.getOption("lint"))?(_.contains(e,o)||r.setOption("gutters",[o].concat(e)),r.setOption("lint",i())):n||r.setOption("gutters",_.without(e,o)),r.getOption("lint")?r.performLint():(a=[],c()))}),r.on("blur",c),r.on("startCompletion",function(){r.off("blur",c)}),r.on("endCompletion",function(){r.on("blur",c),_.delay(function(){r.state.focused||c()},500)}),u(document.body).on("mousedown",function(t){!r.state.focused||u.contains(r.display.wrapper,t.target)||u(t.target).hasClass("CodeMirror-hint")||c()})}d.codeEditor.defaultSettings={codemirror:{},csslint:{},htmlhint:{},jshint:{},onTabNext:function(){},onTabPrevious:function(){},onChangeLintingErrors:function(){},onUpdateErrorNotice:function(){}},d.codeEditor.initialize=function(t,e){var a,n,o,i,t=u("string"==typeof t?"#"+t:t),r=u.extend({},d.codeEditor.defaultSettings,e);return r.codemirror=u.extend({},r.codemirror),s(a=d.CodeMirror.fromTextArea(t[0],r.codemirror),r),t={settings:r,codemirror:a},a.showHint&&a.on("keyup",function(t,e){var n,o,i,r,s=/^[a-zA-Z]$/.test(e.key);a.state.completionActive&&s||"string"!==(r=a.getTokenAt(a.getCursor())).type&&"comment"!==r.type&&(i=d.CodeMirror.innerMode(a.getMode(),r.state).mode.name,o=a.doc.getLine(a.doc.getCursor().line).substr(0,a.doc.getCursor().ch),"html"===i||"xml"===i?n="<"===e.key||"/"===e.key&&"tag"===r.type||s&&"tag"===r.type||s&&"attribute"===r.type||"="===e.key&&(r.state.htmlState?.tagName||r.state.curState?.htmlState?.tagName):"css"===i?n=s||":"===e.key||" "===e.key&&/:\s+$/.test(o):"javascript"===i?n=s||"."===e.key:"clike"===i&&"php"===a.options.mode&&(n=s&&("keyword"===r.type||"variable"===r.type)),n)&&a.showHint({completeSingle:!1})}),o=e,i=u((n=a).getTextArea()),n.on("blur",function(){i.data("next-tab-blurs",!1)}),n.on("keydown",function(t,e){27===e.keyCode?i.data("next-tab-blurs",!0):9===e.keyCode&&i.data("next-tab-blurs")&&(e.shiftKey?o.onTabPrevious(n,e):o.onTabNext(n,e),i.data("next-tab-blurs",!1),e.preventDefault())}),t}}(window.jQuery,window.wp);
|
||||
void 0===window.wp&&(window.wp={}),void 0===window.wp.codeEditor&&(window.wp.codeEditor={}),function(u,d){"use strict";function s(r,s){var a=[],d=[];function c(){s.onUpdateErrorNotice&&!_.isEqual(a,d)&&(s.onUpdateErrorNotice(a,r),d=a)}function i(){var i,t=r.getOption("lint");return!!t&&(!0===t?t={}:_.isObject(t)&&(t=u.extend({},t)),t.options||(t.options={}),"javascript"===s.codemirror.mode&&s.jshint&&u.extend(t.options,s.jshint),"css"===s.codemirror.mode&&s.csslint&&u.extend(t.options,s.csslint),"htmlmixed"===s.codemirror.mode&&s.htmlhint&&(t.options.rules=u.extend({},s.htmlhint),s.jshint&&(t.options.rules.jshint=s.jshint),s.csslint)&&(t.options.rules.csslint=s.csslint),t.onUpdateLinting=(i=t.onUpdateLinting,function(t,n,e){var o=_.filter(t,function(t){return"error"===t.severity});i&&i.apply(t,n,e),!_.isEqual(o,a)&&(a=o,s.onChangeLintingErrors&&s.onChangeLintingErrors(o,t,n,e),!r.state.focused||0===a.length||0<d.length)&&c()}),t)}return r.setOption("lint",i()),r.on("optionChange",function(t,n){var e,o="CodeMirror-lint-markers";"lint"===n&&(n=r.getOption("gutters")||[],!0===(e=r.getOption("lint"))?(_.contains(n,o)||r.setOption("gutters",[o].concat(n)),r.setOption("lint",i())):e||r.setOption("gutters",_.without(n,o)),r.getOption("lint")?r.performLint():(a=[],c()))}),r.on("blur",c),r.on("startCompletion",function(){r.off("blur",c)}),r.on("endCompletion",function(){r.on("blur",c),_.delay(function(){r.state.focused||c()},500)}),u(document.body).on("mousedown",function(t){!r.state.focused||u.contains(r.display.wrapper,t.target)||u(t.target).hasClass("CodeMirror-hint")||c()}),c}d.codeEditor.defaultSettings={codemirror:{},csslint:{},htmlhint:{},jshint:{},onTabNext:function(){},onTabPrevious:function(){},onChangeLintingErrors:function(){},onUpdateErrorNotice:function(){}},d.codeEditor.initialize=function(t,n){var a,e,o,i,t=u("string"==typeof t?"#"+t:t),r=u.extend({},d.codeEditor.defaultSettings,n);return r.codemirror=u.extend({},r.codemirror),t=s(a=d.CodeMirror.fromTextArea(t[0],r.codemirror),r),r={settings:r,codemirror:a,updateErrorNotice:t},a.showHint&&a.on("inputRead",function(t,n){var e,o,i,r,s;"+input"!==n.origin&&!n.origin.startsWith("*compose")||1!==n.text.length||1!==n.text[0].length||(n=n.text[0],o=/^[a-zA-Z]$/.test(n),a.state.completionActive&&o)||"string"!==(s=a.getTokenAt(a.getCursor())).type&&"comment"!==s.type&&(r=d.CodeMirror.innerMode(a.getMode(),s.state).mode.name,i=a.doc.getLine(a.doc.getCursor().line).substr(0,a.doc.getCursor().ch),"html"===r||"xml"===r?e="<"===n||"/"===n&&"tag"===s.type||o&&"tag"===s.type||o&&"attribute"===s.type||"="===n&&(s.state.htmlState?.tagName||s.state.curState?.htmlState?.tagName):"css"===r?e=o||":"===n||" "===n&&/:\s+$/.test(i):"javascript"===r?e=o||"."===n:"clike"===r&&"php"===a.options.mode&&(e=o&&("keyword"===s.type||"variable"===s.type)),e)&&a.showHint({completeSingle:!1})}),o=n,i=u((e=a).getTextArea()),e.on("blur",function(){i.data("next-tab-blurs",!1)}),e.on("keydown",function(t,n){27===n.keyCode?i.data("next-tab-blurs",!0):9===n.keyCode&&i.data("next-tab-blurs")&&(n.shiftKey?o.onTabPrevious(e,n):o.onTabNext(e,n),i.data("next-tab-blurs",!1),n.preventDefault())}),r}}(window.jQuery,window.wp);
|
||||
@@ -2,7 +2,9 @@
|
||||
* @output wp-admin/js/theme-plugin-editor.js
|
||||
*/
|
||||
|
||||
/* eslint no-magic-numbers: ["error", { "ignore": [-1, 0, 1] }] */
|
||||
/* eslint-env es2020 */
|
||||
|
||||
/* eslint no-magic-numbers: ["error", { "ignore": [-1, 0, 1, 9, 1000] }] */
|
||||
|
||||
if ( ! window.wp ) {
|
||||
window.wp = {};
|
||||
@@ -81,6 +83,18 @@ wp.themePluginEditor = (function( $ ) {
|
||||
component.docsLookUpButton.prop( 'disabled', false );
|
||||
}
|
||||
} );
|
||||
|
||||
// Initiate saving the file when not focused in CodeMirror or when the user has syntax highlighting turned off.
|
||||
$( window ).on( 'keydown', function( event ) {
|
||||
if (
|
||||
( event.ctrlKey || event.metaKey ) &&
|
||||
( 's' === event.key.toLowerCase() ) &&
|
||||
( ! component.instance || ! component.instance.codemirror.hasFocus() )
|
||||
) {
|
||||
event.preventDefault();
|
||||
component.form.trigger( 'submit' );
|
||||
}
|
||||
} );
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -191,6 +205,10 @@ wp.themePluginEditor = (function( $ ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( component.instance && component.instance.updateErrorNotice ) {
|
||||
component.instance.updateErrorNotice();
|
||||
}
|
||||
|
||||
// Scroll to the line that has the error.
|
||||
if ( component.lintErrors.length ) {
|
||||
component.instance.codemirror.setCursor( component.lintErrors[0].from.line );
|
||||
@@ -399,6 +417,16 @@ wp.themePluginEditor = (function( $ ) {
|
||||
editor = wp.codeEditor.initialize( $( '#newcontent' ), codeEditorSettings );
|
||||
editor.codemirror.on( 'change', component.onChange );
|
||||
|
||||
function onSaveShortcut() {
|
||||
component.form.trigger( 'submit' );
|
||||
}
|
||||
|
||||
editor.codemirror.setOption( 'extraKeys', {
|
||||
...( editor.codemirror.getOption( 'extraKeys' ) || {} ),
|
||||
'Ctrl-S': onSaveShortcut,
|
||||
'Cmd-S': onSaveShortcut,
|
||||
} );
|
||||
|
||||
// Improve the editor accessibility.
|
||||
$( editor.codemirror.display.lineDiv )
|
||||
.attr({
|
||||
|
||||
2
wp-admin/js/theme-plugin-editor.min.js
vendored
2
wp-admin/js/theme-plugin-editor.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -16,7 +16,7 @@
|
||||
*
|
||||
* @global string $wp_version
|
||||
*/
|
||||
$wp_version = '7.0-alpha-61587';
|
||||
$wp_version = '7.0-alpha-61588';
|
||||
|
||||
/**
|
||||
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.
|
||||
|
||||
Reference in New Issue
Block a user