Editor: Add CodeMirror-powered code editor with syntax highlighting, linting, and auto-completion.
* Code editor is integrated into the Theme/Plugin Editor, Additional CSS in Customizer, and Custom HTML widget. Code editor is not yet integrated into the post editor, and it may not be until accessibility concerns are addressed. * The CodeMirror component in the Custom HTML widget is integrated in a similar way to TinyMCE being integrated into the Text widget, adopting the same approach for integrating dynamic JavaScript-initialized fields. * Linting is performed for JS, CSS, HTML, and JSON via JSHint, CSSLint, HTMLHint, and JSONLint respectively. Linting is not yet supported for PHP. * When user lacks `unfiltered_html` the capability, the Custom HTML widget will report any Kses-invalid elements and attributes as errors via a custom Kses rule for HTMLHint. * When linting errors are detected, the user will be prevented from saving the code until the errors are fixed, reducing instances of broken websites. * The placeholder value is removed from Custom CSS in favor of a fleshed-out section description which now auto-expands when the CSS field is empty. See #39892. * The CodeMirror library is included as `wp.CodeMirror` to prevent conflicts with any existing `CodeMirror` global. * An `wp.codeEditor.initialize()` API in JS is provided to convert a `textarea` into CodeMirror, with a `wp_enqueue_code_editor()` function in PHP to manage enqueueing the assets and settings needed to edit a given type of code. * A user preference is added to manage whether or not "syntax highlighting" is enabled. The feature is opt-out, being enabled by default. * Allowed file extensions in the theme and plugin editors have been updated to include formats which CodeMirror has modes for: `conf`, `css`, `diff`, `patch`, `html`, `htm`, `http`, `js`, `json`, `jsx`, `less`, `md`, `php`, `phtml`, `php3`, `php4`, `php5`, `php7`, `phps`, `scss`, `sass`, `sh`, `bash`, `sql`, `svg`, `xml`, `yml`, `yaml`, `txt`. Props westonruter, georgestephanis, obenland, melchoyce, pixolin, mizejewski, michelleweber, afercia, grahamarmfield, samikeijonen, rianrietveld, iseulde. See #38707. Fixes #12423, #39892. Built from https://develop.svn.wordpress.org/trunk@41376 git-svn-id: http://core.svn.wordpress.org/trunk@41209 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
62
wp-admin/css/code-editor-rtl.css
Normal file
62
wp-admin/css/code-editor-rtl.css
Normal file
@@ -0,0 +1,62 @@
|
||||
.wrap [class*="CodeMirror-lint-marker"],
|
||||
.wp-core-ui [class*="CodeMirror-lint-message"],
|
||||
.wrap .CodeMirror-lint-marker-multiple {
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
.wrap [class*="CodeMirror-lint-marker"]:before {
|
||||
font: normal 18px/1 dashicons;
|
||||
position: relative;
|
||||
top: -2px;
|
||||
}
|
||||
|
||||
.wp-core-ui [class*="CodeMirror-lint-message"]:before {
|
||||
font: normal 16px/1 dashicons;
|
||||
right: 16px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.wp-core-ui .CodeMirror-lint-message-error,
|
||||
.wp-core-ui .CodeMirror-lint-message-warning {
|
||||
box-shadow: 0 1px 1px 0 rgba( 0, 0, 0, 0.1 );
|
||||
margin: 5px 0 2px;
|
||||
padding: 3px 28px 3px 12px;
|
||||
}
|
||||
|
||||
.wp-core-ui .CodeMirror-lint-message-warning {
|
||||
background-color: #fff8e5;
|
||||
border-right: 4px solid #ffb900;
|
||||
}
|
||||
|
||||
.wrap .CodeMirror-lint-marker-warning:before,
|
||||
.wp-core-ui .CodeMirror-lint-message-warning:before {
|
||||
content: "\f534";
|
||||
color: #f6a306;
|
||||
}
|
||||
|
||||
.wp-core-ui .CodeMirror-lint-message-error {
|
||||
background-color: #fbeaea;
|
||||
border-right: 4px solid #dc3232;
|
||||
}
|
||||
|
||||
.wrap .CodeMirror-lint-marker-error:before,
|
||||
.wp-core-ui .CodeMirror-lint-message-error:before {
|
||||
content: "\f153";
|
||||
color: #dc3232;
|
||||
}
|
||||
|
||||
.wp-core-ui .CodeMirror-lint-tooltip {
|
||||
background: none;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
.wrap .CodeMirror .CodeMirror-matchingbracket {
|
||||
background: rgba(255, 150, 0, .3);
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.CodeMirror {
|
||||
text-align: right;
|
||||
}
|
||||
2
wp-admin/css/code-editor-rtl.min.css
vendored
Normal file
2
wp-admin/css/code-editor-rtl.min.css
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/*! This file is auto-generated */
|
||||
.wp-core-ui [class*=CodeMirror-lint-message],.wrap .CodeMirror-lint-marker-multiple,.wrap [class*=CodeMirror-lint-marker]{background-image:none}.wrap [class*=CodeMirror-lint-marker]:before{font:400 18px/1 dashicons;position:relative;top:-2px}.wp-core-ui [class*=CodeMirror-lint-message]:before{font:400 16px/1 dashicons;right:16px;position:absolute}.wp-core-ui .CodeMirror-lint-message-error,.wp-core-ui .CodeMirror-lint-message-warning{box-shadow:0 1px 1px 0 rgba(0,0,0,.1);margin:5px 0 2px;padding:3px 28px 3px 12px}.wp-core-ui .CodeMirror-lint-message-warning{background-color:#fff8e5;border-right:4px solid #ffb900}.wp-core-ui .CodeMirror-lint-message-warning:before,.wrap .CodeMirror-lint-marker-warning:before{content:"\f534";color:#f6a306}.wp-core-ui .CodeMirror-lint-message-error{background-color:#fbeaea;border-right:4px solid #dc3232}.wp-core-ui .CodeMirror-lint-message-error:before,.wrap .CodeMirror-lint-marker-error:before{content:"\f153";color:#dc3232}.wp-core-ui .CodeMirror-lint-tooltip{background:0 0;border:none;border-radius:0;direction:rtl}.wrap .CodeMirror .CodeMirror-matchingbracket{background:rgba(255,150,0,.3);color:inherit}.CodeMirror{text-align:right}
|
||||
62
wp-admin/css/code-editor.css
Normal file
62
wp-admin/css/code-editor.css
Normal file
@@ -0,0 +1,62 @@
|
||||
.wrap [class*="CodeMirror-lint-marker"],
|
||||
.wp-core-ui [class*="CodeMirror-lint-message"],
|
||||
.wrap .CodeMirror-lint-marker-multiple {
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
.wrap [class*="CodeMirror-lint-marker"]:before {
|
||||
font: normal 18px/1 dashicons;
|
||||
position: relative;
|
||||
top: -2px;
|
||||
}
|
||||
|
||||
.wp-core-ui [class*="CodeMirror-lint-message"]:before {
|
||||
font: normal 16px/1 dashicons;
|
||||
left: 16px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.wp-core-ui .CodeMirror-lint-message-error,
|
||||
.wp-core-ui .CodeMirror-lint-message-warning {
|
||||
box-shadow: 0 1px 1px 0 rgba( 0, 0, 0, 0.1 );
|
||||
margin: 5px 0 2px;
|
||||
padding: 3px 12px 3px 28px;
|
||||
}
|
||||
|
||||
.wp-core-ui .CodeMirror-lint-message-warning {
|
||||
background-color: #fff8e5;
|
||||
border-left: 4px solid #ffb900;
|
||||
}
|
||||
|
||||
.wrap .CodeMirror-lint-marker-warning:before,
|
||||
.wp-core-ui .CodeMirror-lint-message-warning:before {
|
||||
content: "\f534";
|
||||
color: #f6a306;
|
||||
}
|
||||
|
||||
.wp-core-ui .CodeMirror-lint-message-error {
|
||||
background-color: #fbeaea;
|
||||
border-left: 4px solid #dc3232;
|
||||
}
|
||||
|
||||
.wrap .CodeMirror-lint-marker-error:before,
|
||||
.wp-core-ui .CodeMirror-lint-message-error:before {
|
||||
content: "\f153";
|
||||
color: #dc3232;
|
||||
}
|
||||
|
||||
.wp-core-ui .CodeMirror-lint-tooltip {
|
||||
background: none;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
.wrap .CodeMirror .CodeMirror-matchingbracket {
|
||||
background: rgba(255, 150, 0, .3);
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.CodeMirror {
|
||||
text-align: left;
|
||||
}
|
||||
2
wp-admin/css/code-editor.min.css
vendored
Normal file
2
wp-admin/css/code-editor.min.css
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/*! This file is auto-generated */
|
||||
.wp-core-ui [class*=CodeMirror-lint-message],.wrap .CodeMirror-lint-marker-multiple,.wrap [class*=CodeMirror-lint-marker]{background-image:none}.wrap [class*=CodeMirror-lint-marker]:before{font:400 18px/1 dashicons;position:relative;top:-2px}.wp-core-ui [class*=CodeMirror-lint-message]:before{font:400 16px/1 dashicons;left:16px;position:absolute}.wp-core-ui .CodeMirror-lint-message-error,.wp-core-ui .CodeMirror-lint-message-warning{box-shadow:0 1px 1px 0 rgba(0,0,0,.1);margin:5px 0 2px;padding:3px 12px 3px 28px}.wp-core-ui .CodeMirror-lint-message-warning{background-color:#fff8e5;border-left:4px solid #ffb900}.wp-core-ui .CodeMirror-lint-message-warning:before,.wrap .CodeMirror-lint-marker-warning:before{content:"\f534";color:#f6a306}.wp-core-ui .CodeMirror-lint-message-error{background-color:#fbeaea;border-left:4px solid #dc3232}.wp-core-ui .CodeMirror-lint-message-error:before,.wrap .CodeMirror-lint-marker-error:before{content:"\f153";color:#dc3232}.wp-core-ui .CodeMirror-lint-tooltip{background:0 0;border:none;border-radius:0;direction:ltr}.wrap .CodeMirror .CodeMirror-matchingbracket{background:rgba(255,150,0,.3);color:inherit}.CodeMirror{text-align:left}
|
||||
@@ -2200,7 +2200,7 @@ h1.nav-tab-wrapper, /* Back-compat for pre-4.4 */
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
#template div {
|
||||
#template > div {
|
||||
margin-left: 190px;
|
||||
}
|
||||
|
||||
@@ -3015,13 +3015,18 @@ img {
|
||||
#template textarea {
|
||||
font-family: Consolas, Monaco, monospace;
|
||||
font-size: 13px;
|
||||
width: 97%;
|
||||
background: #f9f9f9;
|
||||
-moz-tab-size: 4;
|
||||
-o-tab-size: 4;
|
||||
tab-size: 4;
|
||||
}
|
||||
|
||||
#template textarea,
|
||||
#template .CodeMirror {
|
||||
width: 97%;
|
||||
height: calc( 100vh - 220px );
|
||||
}
|
||||
|
||||
/* rtl:ignore */
|
||||
#template textarea,
|
||||
#docs-list {
|
||||
@@ -3032,6 +3037,25 @@ img {
|
||||
width: 97%;
|
||||
}
|
||||
|
||||
#file-editor-linting-error {
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
#file-editor-linting-error > .notice {
|
||||
margin: 0;
|
||||
display: inline-block;
|
||||
}
|
||||
#file-editor-linting-error > .notice > p {
|
||||
width: auto;
|
||||
}
|
||||
#template .submit {
|
||||
margin-top: 1em;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#template .submit input[type=submit][disabled] {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
#templateside {
|
||||
float: left;
|
||||
width: 190px;
|
||||
@@ -3585,12 +3609,13 @@ img {
|
||||
margin-top: -5px;
|
||||
}
|
||||
|
||||
#template div {
|
||||
#template > div {
|
||||
float: none;
|
||||
margin: 0;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
#template .CodeMirror,
|
||||
#template textarea {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
4
wp-admin/css/common-rtl.min.css
vendored
4
wp-admin/css/common-rtl.min.css
vendored
File diff suppressed because one or more lines are too long
@@ -2200,7 +2200,7 @@ h1.nav-tab-wrapper, /* Back-compat for pre-4.4 */
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
#template div {
|
||||
#template > div {
|
||||
margin-right: 190px;
|
||||
}
|
||||
|
||||
@@ -3015,13 +3015,18 @@ img {
|
||||
#template textarea {
|
||||
font-family: Consolas, Monaco, monospace;
|
||||
font-size: 13px;
|
||||
width: 97%;
|
||||
background: #f9f9f9;
|
||||
-moz-tab-size: 4;
|
||||
-o-tab-size: 4;
|
||||
tab-size: 4;
|
||||
}
|
||||
|
||||
#template textarea,
|
||||
#template .CodeMirror {
|
||||
width: 97%;
|
||||
height: calc( 100vh - 220px );
|
||||
}
|
||||
|
||||
/* rtl:ignore */
|
||||
#template textarea,
|
||||
#docs-list {
|
||||
@@ -3032,6 +3037,25 @@ img {
|
||||
width: 97%;
|
||||
}
|
||||
|
||||
#file-editor-linting-error {
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
#file-editor-linting-error > .notice {
|
||||
margin: 0;
|
||||
display: inline-block;
|
||||
}
|
||||
#file-editor-linting-error > .notice > p {
|
||||
width: auto;
|
||||
}
|
||||
#template .submit {
|
||||
margin-top: 1em;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#template .submit input[type=submit][disabled] {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
#templateside {
|
||||
float: right;
|
||||
width: 190px;
|
||||
@@ -3585,12 +3609,13 @@ img {
|
||||
margin-top: -5px;
|
||||
}
|
||||
|
||||
#template div {
|
||||
#template > div {
|
||||
float: none;
|
||||
margin: 0;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
#template .CodeMirror,
|
||||
#template textarea {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
4
wp-admin/css/common.min.css
vendored
4
wp-admin/css/common.min.css
vendored
File diff suppressed because one or more lines are too long
@@ -550,6 +550,20 @@ p.customize-section-description {
|
||||
margin-top: 22px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.customize-section-description ul {
|
||||
margin-right: 1em;
|
||||
}
|
||||
.customize-section-description ul > li {
|
||||
list-style: disc;
|
||||
}
|
||||
.section-description-buttons {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.section-description-buttons button.button-link {
|
||||
color: #0073aa;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.customize-control {
|
||||
width: 100%;
|
||||
@@ -1154,6 +1168,19 @@ p.customize-section-description {
|
||||
margin-bottom: -12px;
|
||||
}
|
||||
|
||||
.customize-section-description-container + #customize-control-custom_css:last-child .CodeMirror {
|
||||
height: calc( 100vh - 185px );
|
||||
}
|
||||
.CodeMirror-lint-tooltip,
|
||||
.CodeMirror-hints {
|
||||
z-index: 500000 !important;
|
||||
}
|
||||
|
||||
.customize-section-description-container + #customize-control-custom_css:last-child .customize-control-notifications-container {
|
||||
margin-right: 12px;
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
@media screen and ( max-width: 640px ) {
|
||||
.customize-section-description-container + #customize-control-custom_css:last-child {
|
||||
margin-left: 0;
|
||||
|
||||
2
wp-admin/css/customize-controls-rtl.min.css
vendored
2
wp-admin/css/customize-controls-rtl.min.css
vendored
File diff suppressed because one or more lines are too long
@@ -550,6 +550,20 @@ p.customize-section-description {
|
||||
margin-top: 22px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.customize-section-description ul {
|
||||
margin-left: 1em;
|
||||
}
|
||||
.customize-section-description ul > li {
|
||||
list-style: disc;
|
||||
}
|
||||
.section-description-buttons {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.section-description-buttons button.button-link {
|
||||
color: #0073aa;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.customize-control {
|
||||
width: 100%;
|
||||
@@ -1154,6 +1168,19 @@ p.customize-section-description {
|
||||
margin-bottom: -12px;
|
||||
}
|
||||
|
||||
.customize-section-description-container + #customize-control-custom_css:last-child .CodeMirror {
|
||||
height: calc( 100vh - 185px );
|
||||
}
|
||||
.CodeMirror-lint-tooltip,
|
||||
.CodeMirror-hints {
|
||||
z-index: 500000 !important;
|
||||
}
|
||||
|
||||
.customize-section-description-container + #customize-control-custom_css:last-child .customize-control-notifications-container {
|
||||
margin-left: 12px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
@media screen and ( max-width: 640px ) {
|
||||
.customize-section-description-container + #customize-control-custom_css:last-child {
|
||||
margin-right: 0;
|
||||
|
||||
2
wp-admin/css/customize-controls.min.css
vendored
2
wp-admin/css/customize-controls.min.css
vendored
File diff suppressed because one or more lines are too long
@@ -645,6 +645,20 @@ div#widgets-right .widget-top:hover,
|
||||
line-height: 1.4em;
|
||||
}
|
||||
|
||||
.custom-html-widget-fields > p > .CodeMirror {
|
||||
border: 1px solid #e5e5e5;
|
||||
}
|
||||
.custom-html-widget-fields code {
|
||||
padding-top: 1px;
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
ul.CodeMirror-hints {
|
||||
z-index: 101; /* Due to z-index 100 set on .widget.open */
|
||||
}
|
||||
.widget-control-actions .custom-html-widget-save-button.button.validation-blocked {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* =Media Queries
|
||||
-------------------------------------------------------------- */
|
||||
|
||||
|
||||
2
wp-admin/css/widgets-rtl.min.css
vendored
2
wp-admin/css/widgets-rtl.min.css
vendored
File diff suppressed because one or more lines are too long
@@ -645,6 +645,20 @@ div#widgets-right .widget-top:hover,
|
||||
line-height: 1.4em;
|
||||
}
|
||||
|
||||
.custom-html-widget-fields > p > .CodeMirror {
|
||||
border: 1px solid #e5e5e5;
|
||||
}
|
||||
.custom-html-widget-fields code {
|
||||
padding-top: 1px;
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
ul.CodeMirror-hints {
|
||||
z-index: 101; /* Due to z-index 100 set on .widget.open */
|
||||
}
|
||||
.widget-control-actions .custom-html-widget-save-button.button.validation-blocked {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* =Media Queries
|
||||
-------------------------------------------------------------- */
|
||||
|
||||
|
||||
2
wp-admin/css/widgets.min.css
vendored
2
wp-admin/css/widgets.min.css
vendored
File diff suppressed because one or more lines are too long
@@ -91,7 +91,8 @@ function edit_user( $user_id = 0 ) {
|
||||
}
|
||||
|
||||
if ( $update ) {
|
||||
$user->rich_editing = isset( $_POST['rich_editing'] ) && 'false' == $_POST['rich_editing'] ? 'false' : 'true';
|
||||
$user->rich_editing = isset( $_POST['rich_editing'] ) && 'false' === $_POST['rich_editing'] ? 'false' : 'true';
|
||||
$user->syntax_highlighting = isset( $_POST['syntax_highlighting'] ) && 'false' === $_POST['syntax_highlighting'] ? 'false' : 'true';
|
||||
$user->admin_color = isset( $_POST['admin_color'] ) ? sanitize_text_field( $_POST['admin_color'] ) : 'fresh';
|
||||
$user->show_admin_bar_front = isset( $_POST['admin_bar_front'] ) ? 'true' : 'false';
|
||||
$user->locale = '';
|
||||
|
||||
309
wp-admin/js/code-editor.js
Normal file
309
wp-admin/js/code-editor.js
Normal file
@@ -0,0 +1,309 @@
|
||||
if ( 'undefined' === typeof window.wp ) {
|
||||
window.wp = {};
|
||||
}
|
||||
if ( 'undefined' === typeof window.wp.codeEditor ) {
|
||||
window.wp.codeEditor = {};
|
||||
}
|
||||
|
||||
( function( $, wp ) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Default settings for code editor.
|
||||
*
|
||||
* @since 4.9.0
|
||||
* @type {object}
|
||||
*/
|
||||
wp.codeEditor.defaultSettings = {
|
||||
codemirror: {},
|
||||
csslint: {},
|
||||
htmlhint: {},
|
||||
jshint: {},
|
||||
onTabNext: function() {},
|
||||
onTabPrevious: function() {},
|
||||
onChangeLintingErrors: function() {},
|
||||
onUpdateErrorNotice: function() {}
|
||||
};
|
||||
|
||||
/**
|
||||
* All instances of code editors.
|
||||
*
|
||||
* @since 4.9.0
|
||||
* @type {Array.<CodeEditorInstance>}
|
||||
*/
|
||||
wp.codeEditor.instances = [];
|
||||
|
||||
/**
|
||||
* Configure linting.
|
||||
*
|
||||
* @param {CodeMirror} editor - Editor.
|
||||
* @param {object} settings - Code editor settings.
|
||||
* @param {object} settings.codeMirror - Settings for CodeMirror.
|
||||
* @param {Function} settings.onChangeLintingErrors - Callback for when there are changes to linting errors.
|
||||
* @param {Function} settings.onUpdateErrorNotice - Callback to update error notice.
|
||||
* @returns {void}
|
||||
*/
|
||||
function configureLinting( editor, settings ) { // eslint-disable-line complexity
|
||||
var lintOptions = editor.getOption( 'lint' ), currentErrorAnnotations = [], updateErrorNotice, previouslyShownErrorAnnotations = [];
|
||||
if ( ! lintOptions ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( true === lintOptions ) {
|
||||
lintOptions = {};
|
||||
} else {
|
||||
lintOptions = $.extend( {}, lintOptions );
|
||||
}
|
||||
|
||||
// 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>.
|
||||
if ( ! lintOptions.options ) {
|
||||
lintOptions.options = {};
|
||||
}
|
||||
|
||||
// Configure JSHint.
|
||||
if ( 'javascript' === settings.codemirror.mode && settings.jshint ) {
|
||||
$.extend( lintOptions.options, settings.jshint );
|
||||
}
|
||||
|
||||
// Configure CSSLint.
|
||||
if ( 'css' === settings.codemirror.mode && settings.csslint ) {
|
||||
$.extend( lintOptions.options, settings.csslint );
|
||||
}
|
||||
|
||||
// Configure HTMLHint.
|
||||
if ( 'htmlmixed' === settings.codemirror.mode && settings.htmlhint ) {
|
||||
lintOptions.options.rules = $.extend( {}, settings.htmlhint );
|
||||
|
||||
if ( settings.jshint ) {
|
||||
lintOptions.options.rules.jshint = settings.jshint;
|
||||
}
|
||||
if ( settings.csslint ) {
|
||||
lintOptions.options.rules.csslint = settings.csslint;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call the onUpdateErrorNotice if there are new errors to show.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
updateErrorNotice = function() {
|
||||
if ( settings.onUpdateErrorNotice && ! _.isEqual( currentErrorAnnotations, previouslyShownErrorAnnotations ) ) {
|
||||
settings.onUpdateErrorNotice( currentErrorAnnotations, editor );
|
||||
previouslyShownErrorAnnotations = currentErrorAnnotations;
|
||||
}
|
||||
};
|
||||
|
||||
// Wrap the onUpdateLinting CodeMirror event to route to onChangeLintingErrors and onUpdateErrorNotice.
|
||||
lintOptions.onUpdateLinting = (function( onUpdateLintingOverridden ) {
|
||||
return function( annotations, annotationsSorted, cm ) {
|
||||
var errorAnnotations = _.filter( annotations, function( annotation ) {
|
||||
return 'error' === annotation.severity;
|
||||
} );
|
||||
|
||||
if ( onUpdateLintingOverridden ) {
|
||||
onUpdateLintingOverridden.apply( annotations, annotationsSorted, cm );
|
||||
}
|
||||
|
||||
// Skip if there are no changes to the errors.
|
||||
if ( _.isEqual( errorAnnotations, currentErrorAnnotations ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
currentErrorAnnotations = errorAnnotations;
|
||||
|
||||
if ( settings.onChangeLintingErrors ) {
|
||||
settings.onChangeLintingErrors( errorAnnotations, annotations, annotationsSorted, cm );
|
||||
}
|
||||
|
||||
/*
|
||||
* Update notifications when the editor is not focused to prevent error message
|
||||
* from overwhelming the user during input, unless there are now no errors or there
|
||||
* were previously errors shown. In these cases, update immediately so they can know
|
||||
* that they fixed the errors.
|
||||
*/
|
||||
if ( ! cm.state.focused || 0 === currentErrorAnnotations.length || previouslyShownErrorAnnotations.length > 0 ) {
|
||||
updateErrorNotice();
|
||||
}
|
||||
};
|
||||
})( lintOptions.onUpdateLinting );
|
||||
|
||||
editor.setOption( 'lint', lintOptions );
|
||||
|
||||
// Update error notice when leaving the editor.
|
||||
editor.on( 'blur', updateErrorNotice );
|
||||
|
||||
// Work around hint selection with mouse causing focus to leave editor.
|
||||
editor.on( 'startCompletion', function() {
|
||||
editor.off( 'blur', updateErrorNotice );
|
||||
} );
|
||||
editor.on( 'endCompletion', function() {
|
||||
var editorRefocusWait = 500;
|
||||
editor.on( 'blur', updateErrorNotice );
|
||||
|
||||
// Wait for editor to possibly get re-focused after selection.
|
||||
_.delay( function() {
|
||||
if ( ! editor.state.focused ) {
|
||||
updateErrorNotice();
|
||||
}
|
||||
}, editorRefocusWait );
|
||||
});
|
||||
|
||||
/*
|
||||
* Make sure setting validities are set if the user tries to click Publish
|
||||
* while an autocomplete dropdown is still open. The Customizer will block
|
||||
* saving when a setting has an error notifications on it. This is only
|
||||
* necessary for mouse interactions because keyboards will have already
|
||||
* blurred the field and cause onUpdateErrorNotice to have already been
|
||||
* called.
|
||||
*/
|
||||
$( document.body ).on( 'mousedown', function( event ) {
|
||||
if ( editor.state.focused && ! $.contains( editor.display.wrapper, event.target ) && ! $( event.target ).hasClass( 'CodeMirror-hint' ) ) {
|
||||
updateErrorNotice();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure tabbing.
|
||||
*
|
||||
* @param {CodeMirror} codemirror - Editor.
|
||||
* @param {object} settings - Code editor settings.
|
||||
* @param {object} settings.codeMirror - Settings for CodeMirror.
|
||||
* @param {Function} settings.onTabNext - Callback to handle tabbing to the next tabbable element.
|
||||
* @param {Function} settings.onTabPrevious - Callback to handle tabbing to the previous tabbable element.
|
||||
* @returns {void}
|
||||
*/
|
||||
function configureTabbing( codemirror, settings ) {
|
||||
var $textarea = $( codemirror.getTextArea() );
|
||||
|
||||
codemirror.on( 'blur', function() {
|
||||
$textarea.data( 'next-tab-blurs', false );
|
||||
});
|
||||
codemirror.on( 'focus', function() {
|
||||
if ( codemirror.display.wrapper.scrollIntoViewIfNeeded ) {
|
||||
codemirror.display.wrapper.scrollIntoViewIfNeeded();
|
||||
} else {
|
||||
codemirror.display.wrapper.scrollIntoView();
|
||||
}
|
||||
});
|
||||
codemirror.on( 'keydown', function onKeydown( editor, event ) {
|
||||
var tabKeyCode = 9, escKeyCode = 27;
|
||||
|
||||
// Take note of the ESC keypress so that the next TAB can focus outside the editor.
|
||||
if ( escKeyCode === event.keyCode ) {
|
||||
$textarea.data( 'next-tab-blurs', true );
|
||||
return;
|
||||
}
|
||||
|
||||
// Short-circuit if tab key is not being pressed or the tab key press should move focus.
|
||||
if ( tabKeyCode !== event.keyCode || ! $textarea.data( 'next-tab-blurs' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Focus on previous or next focusable item.
|
||||
if ( event.shiftKey ) {
|
||||
settings.onTabPrevious( codemirror, event );
|
||||
} else {
|
||||
settings.onTabNext( codemirror, event );
|
||||
}
|
||||
|
||||
// Reset tab state.
|
||||
$textarea.data( 'next-tab-blurs', false );
|
||||
|
||||
// Prevent tab character from being added.
|
||||
event.preventDefault();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {object} CodeEditorInstance
|
||||
* @property {object} settings - The code editor settings.
|
||||
* @property {CodeMirror} codemirror - The CodeMirror instance.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Initialize Code Editor (CodeMirror) for an existing textarea.
|
||||
*
|
||||
* @since 4.9.0
|
||||
*
|
||||
* @param {string|jQuery|Element} textarea - The HTML id, jQuery object, or DOM Element for the textarea that is used for the editor.
|
||||
* @param {object} [settings] - Settings to override defaults.
|
||||
* @param {Function} [settings.onChangeLintingErrors] - Callback for when the linting errors have changed.
|
||||
* @param {Function} [settings.onUpdateErrorNotice] - Callback for when error notice should be displayed.
|
||||
* @param {Function} [settings.onTabPrevious] - Callback to handle tabbing to the previous tabbable element.
|
||||
* @param {Function} [settings.onTabNext] - Callback to handle tabbing to the next tabbable element.
|
||||
* @param {object} [settings.codemirror] - Options for CodeMirror.
|
||||
* @param {object} [settings.csslint] - Rules for CSSLint.
|
||||
* @param {object} [settings.htmlhint] - Rules for HTMLHint.
|
||||
* @param {object} [settings.jshint] - Rules for JSHint.
|
||||
* @returns {CodeEditorInstance} Instance.
|
||||
*/
|
||||
wp.codeEditor.initialize = function initialize( textarea, settings ) {
|
||||
var $textarea, codemirror, instanceSettings, instance;
|
||||
if ( 'string' === typeof textarea ) {
|
||||
$textarea = $( '#' + textarea );
|
||||
} else {
|
||||
$textarea = $( textarea );
|
||||
}
|
||||
|
||||
instanceSettings = $.extend( {}, wp.codeEditor.defaultSettings, settings );
|
||||
instanceSettings.codemirror = $.extend( {}, instanceSettings.codemirror );
|
||||
|
||||
codemirror = wp.CodeMirror.fromTextArea( $textarea[0], instanceSettings.codemirror );
|
||||
|
||||
configureLinting( codemirror, instanceSettings );
|
||||
|
||||
instance = {
|
||||
settings: instanceSettings,
|
||||
codemirror: codemirror
|
||||
};
|
||||
|
||||
// Keep track of the instances that have been created.
|
||||
wp.codeEditor.instances.push( instance );
|
||||
|
||||
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;
|
||||
if ( codemirror.state.completionActive && isAlphaKey ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Prevent autocompletion in string literals or comments.
|
||||
token = codemirror.getTokenAt( codemirror.getCursor() );
|
||||
if ( 'string' === token.type || 'comment' === token.type ) {
|
||||
return;
|
||||
}
|
||||
|
||||
innerMode = wp.CodeMirror.innerMode( codemirror.getMode(), token.state ).mode.name;
|
||||
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 ||
|
||||
isAlphaKey && 'tag' === token.type ||
|
||||
isAlphaKey && 'attribute' === token.type ||
|
||||
'=' === token.string && token.state.htmlState && token.state.htmlState.tagName;
|
||||
} else if ( 'css' === innerMode ) {
|
||||
shouldAutocomplete =
|
||||
isAlphaKey ||
|
||||
':' === event.key ||
|
||||
' ' === event.key && /:\s+$/.test( lineBeforeCursor );
|
||||
} else if ( 'javascript' === innerMode ) {
|
||||
shouldAutocomplete = isAlphaKey || '.' === event.key;
|
||||
} else if ( 'clike' === innerMode && 'application/x-httpd-php' === codemirror.options.mode ) {
|
||||
shouldAutocomplete = 'keyword' === token.type || 'variable' === token.type;
|
||||
}
|
||||
if ( shouldAutocomplete ) {
|
||||
codemirror.showHint( { completeSingle: false } );
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Facilitate tabbing out of the editor.
|
||||
configureTabbing( codemirror, settings );
|
||||
|
||||
return instance;
|
||||
};
|
||||
|
||||
})( window.jQuery, window.wp );
|
||||
1
wp-admin/js/code-editor.min.js
vendored
Normal file
1
wp-admin/js/code-editor.min.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
"undefined"==typeof window.wp&&(window.wp={}),"undefined"==typeof window.wp.codeEditor&&(window.wp.codeEditor={}),function(a,b){"use strict";function c(b,c){var d,e=b.getOption("lint"),f=[],g=[];e&&(e=!0===e?{}:a.extend({},e),e.options||(e.options={}),"javascript"===c.codemirror.mode&&c.jshint&&a.extend(e.options,c.jshint),"css"===c.codemirror.mode&&c.csslint&&a.extend(e.options,c.csslint),"htmlmixed"===c.codemirror.mode&&c.htmlhint&&(e.options.rules=a.extend({},c.htmlhint),c.jshint&&(e.options.rules.jshint=c.jshint),c.csslint&&(e.options.rules.csslint=c.csslint)),d=function(){c.onUpdateErrorNotice&&!_.isEqual(f,g)&&(c.onUpdateErrorNotice(f,b),g=f)},e.onUpdateLinting=function(a){return function(b,e,h){var i=_.filter(b,function(a){return"error"===a.severity});a&&a.apply(b,e,h),_.isEqual(i,f)||(f=i,c.onChangeLintingErrors&&c.onChangeLintingErrors(i,b,e,h),(!h.state.focused||0===f.length||g.length>0)&&d())}}(e.onUpdateLinting),b.setOption("lint",e),b.on("blur",d),b.on("startCompletion",function(){b.off("blur",d)}),b.on("endCompletion",function(){var a=500;b.on("blur",d),_.delay(function(){b.state.focused||d()},a)}),a(document.body).on("mousedown",function(c){!b.state.focused||a.contains(b.display.wrapper,c.target)||a(c.target).hasClass("CodeMirror-hint")||d()}))}function d(b,c){var d=a(b.getTextArea());b.on("blur",function(){d.data("next-tab-blurs",!1)}),b.on("focus",function(){b.display.wrapper.scrollIntoViewIfNeeded?b.display.wrapper.scrollIntoViewIfNeeded():b.display.wrapper.scrollIntoView()}),b.on("keydown",function(a,e){var f=9,g=27;return g===e.keyCode?void d.data("next-tab-blurs",!0):void(f===e.keyCode&&d.data("next-tab-blurs")&&(e.shiftKey?c.onTabPrevious(b,e):c.onTabNext(b,e),d.data("next-tab-blurs",!1),e.preventDefault()))})}b.codeEditor.defaultSettings={codemirror:{},csslint:{},htmlhint:{},jshint:{},onTabNext:function(){},onTabPrevious:function(){},onChangeLintingErrors:function(){},onUpdateErrorNotice:function(){}},b.codeEditor.instances=[],b.codeEditor.initialize=function(e,f){var g,h,i,j;return g=a("string"==typeof e?"#"+e:e),i=a.extend({},b.codeEditor.defaultSettings,f),i.codemirror=a.extend({},i.codemirror),h=b.CodeMirror.fromTextArea(g[0],i.codemirror),c(h,i),j={settings:i,codemirror:h},b.codeEditor.instances.push(j),h.showHint&&h.on("keyup",function(a,c){var d,e,f,g,i=/^[a-zA-Z]$/.test(c.key);h.state.completionActive&&i||(g=h.getTokenAt(h.getCursor()),"string"!==g.type&&"comment"!==g.type&&(f=b.CodeMirror.innerMode(h.getMode(),g.state).mode.name,e=h.doc.getLine(h.doc.getCursor().line).substr(0,h.doc.getCursor().ch),"html"===f||"xml"===f?d="<"===c.key||"/"===c.key&&"tag"===g.type||i&&"tag"===g.type||i&&"attribute"===g.type||"="===g.string&&g.state.htmlState&&g.state.htmlState.tagName:"css"===f?d=i||":"===c.key||" "===c.key&&/:\s+$/.test(e):"javascript"===f?d=i||"."===c.key:"clike"===f&&"application/x-httpd-php"===h.options.mode&&(d="keyword"===g.type||"variable"===g.type),d&&h.showHint({completeSingle:!1})))}),d(h,f),j}}(window.jQuery,window.wp);
|
||||
@@ -5631,50 +5631,212 @@
|
||||
});
|
||||
});
|
||||
|
||||
// Allow tabs to be entered in Custom CSS textarea.
|
||||
api.control( 'custom_css', function setupCustomCssControl( control ) {
|
||||
control.deferred.embedded.done( function allowTabs() {
|
||||
var $textarea = control.container.find( 'textarea' ), textarea = $textarea[0];
|
||||
// Add code editor for Custom CSS.
|
||||
(function() {
|
||||
var ready, sectionReady = $.Deferred(), controlReady = $.Deferred();
|
||||
|
||||
$textarea.on( 'blur', function onBlur() {
|
||||
$textarea.data( 'next-tab-blurs', false );
|
||||
} );
|
||||
api.section( 'custom_css', function( section ) {
|
||||
section.deferred.embedded.done( function() {
|
||||
if ( section.expanded() ) {
|
||||
sectionReady.resolve( section );
|
||||
} else {
|
||||
section.expanded.bind( function( isExpanded ) {
|
||||
if ( isExpanded ) {
|
||||
sectionReady.resolve( section );
|
||||
}
|
||||
} );
|
||||
}
|
||||
});
|
||||
});
|
||||
api.control( 'custom_css', function( control ) {
|
||||
control.deferred.embedded.done( function() {
|
||||
controlReady.resolve( control );
|
||||
});
|
||||
});
|
||||
|
||||
$textarea.on( 'keydown', function onKeydown( event ) {
|
||||
var selectionStart, selectionEnd, value, tabKeyCode = 9, escKeyCode = 27;
|
||||
ready = $.when( sectionReady, controlReady );
|
||||
|
||||
if ( escKeyCode === event.keyCode ) {
|
||||
if ( ! $textarea.data( 'next-tab-blurs' ) ) {
|
||||
$textarea.data( 'next-tab-blurs', true );
|
||||
event.stopPropagation(); // Prevent collapsing the section.
|
||||
// Set up the section desription behaviors.
|
||||
ready.done( function setupSectionDescription( section, control ) {
|
||||
|
||||
// Close the section description when clicking the close button.
|
||||
section.container.find( '.section-description-buttons .section-description-close' ).on( 'click', function() {
|
||||
section.container.find( '.section-meta .customize-section-description:first' )
|
||||
.removeClass( 'open' )
|
||||
.slideUp()
|
||||
.attr( 'aria-expanded', 'false' );
|
||||
});
|
||||
|
||||
// Reveal help text if setting is empty.
|
||||
if ( ! control.setting.get() ) {
|
||||
section.container.find( '.section-meta .customize-section-description:first' )
|
||||
.addClass( 'open' )
|
||||
.show()
|
||||
.attr( 'aria-expanded', 'true' );
|
||||
}
|
||||
});
|
||||
|
||||
// Set up the code editor itself.
|
||||
if ( api.settings.customCss && api.settings.customCss.codeEditor ) {
|
||||
|
||||
// Set up the syntax highlighting editor.
|
||||
ready.done( function setupSyntaxHighlightingEditor( section, control ) {
|
||||
var $textarea = control.container.find( 'textarea' ), settings, suspendEditorUpdate = false;
|
||||
|
||||
// Make sure editor gets focused when control is focused.
|
||||
control.focus = (function( originalFocus ) { // eslint-disable-line max-nested-callbacks
|
||||
return function( params ) { // eslint-disable-line max-nested-callbacks
|
||||
var extendedParams = _.extend( {}, params ), originalCompleteCallback;
|
||||
originalCompleteCallback = extendedParams.completeCallback;
|
||||
extendedParams.completeCallback = function() {
|
||||
if ( originalCompleteCallback ) {
|
||||
originalCompleteCallback();
|
||||
}
|
||||
if ( control.editor ) {
|
||||
control.editor.codemirror.focus();
|
||||
}
|
||||
};
|
||||
originalFocus.call( this, extendedParams );
|
||||
};
|
||||
})( control.focus );
|
||||
|
||||
settings = _.extend( {}, api.settings.customCss.codeEditor, {
|
||||
|
||||
/**
|
||||
* Handle tabbing to the field after the editor.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
onTabNext: function onTabNext() {
|
||||
var controls, controlIndex;
|
||||
controls = section.controls();
|
||||
controlIndex = controls.indexOf( control );
|
||||
if ( controls.length === controlIndex + 1 ) {
|
||||
$( '#customize-footer-actions .collapse-sidebar' ).focus();
|
||||
} else {
|
||||
controls[ controlIndex + 1 ].container.find( ':focusable:first' ).focus();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle tabbing to the field before the editor.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
onTabPrevious: function onTabPrevious() {
|
||||
var controls, controlIndex;
|
||||
controls = section.controls();
|
||||
controlIndex = controls.indexOf( control );
|
||||
if ( 0 === controlIndex ) {
|
||||
section.contentContainer.find( '.customize-section-title .customize-help-toggle, .customize-section-title .customize-section-description.open .section-description-close' ).last().focus();
|
||||
} else {
|
||||
controls[ controlIndex - 1 ].contentContainer.find( ':focusable:first' ).focus();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Update error notice.
|
||||
*
|
||||
* @param {Array} errorAnnotations - Error annotations.
|
||||
* @returns {void}
|
||||
*/
|
||||
onUpdateErrorNotice: function onUpdateErrorNotice( errorAnnotations ) {
|
||||
var message;
|
||||
control.setting.notifications.remove( 'csslint_error' );
|
||||
|
||||
if ( 0 !== errorAnnotations.length ) {
|
||||
if ( 1 === errorAnnotations.length ) {
|
||||
message = api.l10n.customCssError.singular.replace( '%d', '1' );
|
||||
} else {
|
||||
message = api.l10n.customCssError.plural.replace( '%d', String( errorAnnotations.length ) );
|
||||
}
|
||||
control.setting.notifications.add( 'csslint_error', new api.Notification( 'csslint_error', {
|
||||
message: message,
|
||||
type: 'error'
|
||||
} ) );
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
// Short-circuit if tab key is not being pressed or if a modifier key *is* being pressed.
|
||||
if ( tabKeyCode !== event.keyCode || event.ctrlKey || event.altKey || event.shiftKey ) {
|
||||
return;
|
||||
}
|
||||
control.editor = wp.codeEditor.initialize( $textarea, settings );
|
||||
|
||||
// Prevent capturing Tab characters if Esc was pressed.
|
||||
if ( $textarea.data( 'next-tab-blurs' ) ) {
|
||||
return;
|
||||
}
|
||||
// Refresh when receiving focus.
|
||||
control.editor.codemirror.on( 'focus', function( codemirror ) {
|
||||
codemirror.refresh();
|
||||
});
|
||||
|
||||
selectionStart = textarea.selectionStart;
|
||||
selectionEnd = textarea.selectionEnd;
|
||||
value = textarea.value;
|
||||
/*
|
||||
* When the CodeMirror instance changes, mirror to the textarea,
|
||||
* where we have our "true" change event handler bound.
|
||||
*/
|
||||
control.editor.codemirror.on( 'change', function( codemirror ) {
|
||||
suspendEditorUpdate = true;
|
||||
$textarea.val( codemirror.getValue() ).trigger( 'change' );
|
||||
suspendEditorUpdate = false;
|
||||
});
|
||||
|
||||
if ( selectionStart >= 0 ) {
|
||||
textarea.value = value.substring( 0, selectionStart ).concat( '\t', value.substring( selectionEnd ) );
|
||||
$textarea.selectionStart = textarea.selectionEnd = selectionStart + 1;
|
||||
}
|
||||
// Update CodeMirror when the setting is changed by another plugin.
|
||||
control.setting.bind( function( value ) {
|
||||
if ( ! suspendEditorUpdate ) {
|
||||
control.editor.codemirror.setValue( value );
|
||||
}
|
||||
});
|
||||
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
// Prevent collapsing section when hitting Esc to tab out of editor.
|
||||
control.editor.codemirror.on( 'keydown', function onKeydown( codemirror, event ) {
|
||||
var escKeyCode = 27;
|
||||
if ( escKeyCode === event.keyCode ) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
|
||||
// Allow tabs to be entered in Custom CSS textarea.
|
||||
ready.done( function allowTabs( section, control ) {
|
||||
|
||||
var $textarea = control.container.find( 'textarea' ), textarea = $textarea[0];
|
||||
|
||||
$textarea.on( 'blur', function onBlur() {
|
||||
$textarea.data( 'next-tab-blurs', false );
|
||||
} );
|
||||
|
||||
$textarea.on( 'keydown', function onKeydown( event ) {
|
||||
var selectionStart, selectionEnd, value, tabKeyCode = 9, escKeyCode = 27;
|
||||
|
||||
if ( escKeyCode === event.keyCode ) {
|
||||
if ( ! $textarea.data( 'next-tab-blurs' ) ) {
|
||||
$textarea.data( 'next-tab-blurs', true );
|
||||
event.stopPropagation(); // Prevent collapsing the section.
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Short-circuit if tab key is not being pressed or if a modifier key *is* being pressed.
|
||||
if ( tabKeyCode !== event.keyCode || event.ctrlKey || event.altKey || event.shiftKey ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Prevent capturing Tab characters if Esc was pressed.
|
||||
if ( $textarea.data( 'next-tab-blurs' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
selectionStart = textarea.selectionStart;
|
||||
selectionEnd = textarea.selectionEnd;
|
||||
value = textarea.value;
|
||||
|
||||
if ( selectionStart >= 0 ) {
|
||||
textarea.value = value.substring( 0, selectionStart ).concat( '\t', value.substring( selectionEnd ) );
|
||||
$textarea.selectionStart = textarea.selectionEnd = selectionStart + 1;
|
||||
}
|
||||
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
});
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
||||
// Toggle visibility of Header Video notice when active state change.
|
||||
api.control( 'header_video', function( headerVideoControl ) {
|
||||
|
||||
2
wp-admin/js/customize-controls.min.js
vendored
2
wp-admin/js/customize-controls.min.js
vendored
File diff suppressed because one or more lines are too long
90
wp-admin/js/theme-plugin-editor.js
Normal file
90
wp-admin/js/theme-plugin-editor.js
Normal file
@@ -0,0 +1,90 @@
|
||||
/* eslint no-magic-numbers: ["error", { "ignore": [-1, 0, 1] }] */
|
||||
|
||||
if ( ! window.wp ) {
|
||||
window.wp = {};
|
||||
}
|
||||
|
||||
wp.themePluginEditor = (function( $ ) {
|
||||
'use strict';
|
||||
|
||||
var component = {
|
||||
l10n: {
|
||||
lintError: {
|
||||
singular: '',
|
||||
plural: ''
|
||||
}
|
||||
},
|
||||
instance: null
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize component.
|
||||
*
|
||||
* @param {object} settings Settings.
|
||||
* @returns {void}
|
||||
*/
|
||||
component.init = function( settings ) {
|
||||
var codeEditorSettings, noticeContainer, errorNotice = [], editor;
|
||||
|
||||
codeEditorSettings = $.extend( {}, settings );
|
||||
|
||||
/**
|
||||
* Handle tabbing to the field before the editor.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
codeEditorSettings.onTabPrevious = function() {
|
||||
$( '#templateside' ).find( ':tabbable' ).last().focus();
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle tabbing to the field after the editor.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
codeEditorSettings.onTabNext = function() {
|
||||
$( '#template' ).find( ':tabbable:not(.CodeMirror-code)' ).first().focus();
|
||||
};
|
||||
|
||||
// Create the error notice container.
|
||||
noticeContainer = $( '<div id="file-editor-linting-error"></div>' );
|
||||
errorNotice = $( '<div class="inline notice notice-error"></div>' );
|
||||
noticeContainer.append( errorNotice );
|
||||
noticeContainer.hide();
|
||||
$( 'p.submit' ).before( noticeContainer );
|
||||
|
||||
/**
|
||||
* Update error notice.
|
||||
*
|
||||
* @param {Array} errorAnnotations - Error annotations.
|
||||
* @returns {void}
|
||||
*/
|
||||
codeEditorSettings.onUpdateErrorNotice = function onUpdateErrorNotice( errorAnnotations ) {
|
||||
var message;
|
||||
|
||||
$( '#submit' ).prop( 'disabled', 0 !== errorAnnotations.length );
|
||||
|
||||
if ( 0 !== errorAnnotations.length ) {
|
||||
errorNotice.empty();
|
||||
if ( 1 === errorAnnotations.length ) {
|
||||
message = component.l10n.singular.replace( '%d', '1' );
|
||||
} else {
|
||||
message = component.l10n.plural.replace( '%d', String( errorAnnotations.length ) );
|
||||
}
|
||||
errorNotice.append( $( '<p></p>', {
|
||||
text: message
|
||||
} ) );
|
||||
noticeContainer.slideDown( 'fast' );
|
||||
wp.a11y.speak( message );
|
||||
} else {
|
||||
noticeContainer.slideUp( 'fast' );
|
||||
}
|
||||
};
|
||||
|
||||
editor = wp.codeEditor.initialize( $( '#newcontent' ), codeEditorSettings );
|
||||
|
||||
component.instance = editor;
|
||||
};
|
||||
|
||||
return component;
|
||||
})( jQuery );
|
||||
1
wp-admin/js/theme-plugin-editor.min.js
vendored
Normal file
1
wp-admin/js/theme-plugin-editor.min.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
window.wp||(window.wp={}),wp.themePluginEditor=function(a){"use strict";var b={l10n:{lintError:{singular:"",plural:""}},instance:null};return b.init=function(c){var d,e,f,g=[];d=a.extend({},c),d.onTabPrevious=function(){a("#templateside").find(":tabbable").last().focus()},d.onTabNext=function(){a("#template").find(":tabbable:not(.CodeMirror-code)").first().focus()},e=a('<div id="file-editor-linting-error"></div>'),g=a('<div class="inline notice notice-error"></div>'),e.append(g),e.hide(),a("p.submit").before(e),d.onUpdateErrorNotice=function(c){var d;a("#submit").prop("disabled",0!==c.length),0!==c.length?(g.empty(),d=1===c.length?b.l10n.singular.replace("%d","1"):b.l10n.plural.replace("%d",String(c.length)),g.append(a("<p></p>",{text:d})),e.slideDown("fast"),wp.a11y.speak(d)):e.slideUp("fast")},f=wp.codeEditor.initialize(a("#newcontent"),d),b.instance=f},b}(jQuery);
|
||||
414
wp-admin/js/widgets/custom-html-widgets.js
Normal file
414
wp-admin/js/widgets/custom-html-widgets.js
Normal file
@@ -0,0 +1,414 @@
|
||||
/* global wp */
|
||||
/* eslint consistent-this: [ "error", "control" ] */
|
||||
/* eslint no-magic-numbers: ["error", { "ignore": [0,1,-1] }] */
|
||||
wp.customHtmlWidgets = ( function( $ ) {
|
||||
'use strict';
|
||||
|
||||
var component = {
|
||||
idBases: [ 'custom_html' ],
|
||||
codeEditorSettings: {},
|
||||
l10n: {
|
||||
errorNotice: {
|
||||
singular: '',
|
||||
plural: ''
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Text widget control.
|
||||
*
|
||||
* @class CustomHtmlWidgetControl
|
||||
* @constructor
|
||||
* @abstract
|
||||
*/
|
||||
component.CustomHtmlWidgetControl = Backbone.View.extend({
|
||||
|
||||
/**
|
||||
* View events.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
events: {},
|
||||
|
||||
/**
|
||||
* Initialize.
|
||||
*
|
||||
* @param {Object} options - Options.
|
||||
* @param {jQuery} options.el - Control field container element.
|
||||
* @param {jQuery} options.syncContainer - Container element where fields are synced for the server.
|
||||
* @returns {void}
|
||||
*/
|
||||
initialize: function initialize( options ) {
|
||||
var control = this;
|
||||
|
||||
if ( ! options.el ) {
|
||||
throw new Error( 'Missing options.el' );
|
||||
}
|
||||
if ( ! options.syncContainer ) {
|
||||
throw new Error( 'Missing options.syncContainer' );
|
||||
}
|
||||
|
||||
Backbone.View.prototype.initialize.call( control, options );
|
||||
control.syncContainer = options.syncContainer;
|
||||
control.widgetIdBase = control.syncContainer.parent().find( '.id_base' ).val();
|
||||
control.widgetNumber = control.syncContainer.parent().find( '.widget_number' ).val();
|
||||
control.customizeSettingId = 'widget_' + control.widgetIdBase + '[' + String( control.widgetNumber ) + ']';
|
||||
|
||||
control.$el.addClass( 'custom-html-widget-fields' );
|
||||
control.$el.html( wp.template( 'widget-custom-html-control-fields' )( { codeEditorDisabled: component.codeEditorSettings.disabled } ) );
|
||||
|
||||
control.errorNoticeContainer = control.$el.find( '.code-editor-error-container' );
|
||||
control.currentErrorAnnotations = [];
|
||||
control.saveButton = control.syncContainer.add( control.syncContainer.parent().find( '.widget-control-actions' ) ).find( '.widget-control-save, #savewidget' );
|
||||
control.saveButton.addClass( 'custom-html-widget-save-button' ); // To facilitate style targeting.
|
||||
|
||||
control.fields = {
|
||||
title: control.$el.find( '.title' ),
|
||||
content: control.$el.find( '.content' )
|
||||
};
|
||||
|
||||
// Sync input fields to hidden sync fields which actually get sent to the server.
|
||||
_.each( control.fields, function( fieldInput, fieldName ) {
|
||||
fieldInput.on( 'input change', function updateSyncField() {
|
||||
var syncInput = control.syncContainer.find( '.sync-input.' + fieldName );
|
||||
if ( syncInput.val() !== fieldInput.val() ) {
|
||||
syncInput.val( fieldInput.val() );
|
||||
syncInput.trigger( 'change' );
|
||||
}
|
||||
});
|
||||
|
||||
// Note that syncInput cannot be re-used because it will be destroyed with each widget-updated event.
|
||||
fieldInput.val( control.syncContainer.find( '.sync-input.' + fieldName ).val() );
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Update input fields from the sync fields.
|
||||
*
|
||||
* This function is called at the widget-updated and widget-synced events.
|
||||
* A field will only be updated if it is not currently focused, to avoid
|
||||
* overwriting content that the user is entering.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
updateFields: function updateFields() {
|
||||
var control = this, syncInput;
|
||||
|
||||
if ( ! control.fields.title.is( document.activeElement ) ) {
|
||||
syncInput = control.syncContainer.find( '.sync-input.title' );
|
||||
control.fields.title.val( syncInput.val() );
|
||||
}
|
||||
|
||||
/*
|
||||
* Prevent updating content when the editor is focused or if there are current error annotations,
|
||||
* to prevent the editor's contents from getting sanitized as soon as a user removes focus from
|
||||
* the editor. This is particularly important for users who cannot unfiltered_html.
|
||||
*/
|
||||
control.contentUpdateBypassed = control.fields.content.is( document.activeElement ) || control.editor && control.editor.codemirror.state.focused || 0 !== control.currentErrorAnnotations;
|
||||
if ( ! control.contentUpdateBypassed ) {
|
||||
syncInput = control.syncContainer.find( '.sync-input.content' );
|
||||
control.fields.content.val( syncInput.val() ).trigger( 'change' );
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Show linting error notice.
|
||||
*
|
||||
* @param {Array} errorAnnotations - Error annotations.
|
||||
* @returns {void}
|
||||
*/
|
||||
updateErrorNotice: function( errorAnnotations ) {
|
||||
var control = this, errorNotice, message = '', customizeSetting;
|
||||
|
||||
if ( 1 === errorAnnotations.length ) {
|
||||
message = component.l10n.errorNotice.singular.replace( '%d', '1' );
|
||||
} else if ( errorAnnotations.length > 1 ) {
|
||||
message = component.l10n.errorNotice.plural.replace( '%d', String( errorAnnotations.length ) );
|
||||
}
|
||||
|
||||
if ( control.fields.content[0].setCustomValidity ) {
|
||||
control.fields.content[0].setCustomValidity( message );
|
||||
}
|
||||
|
||||
if ( wp.customize && wp.customize.has( control.customizeSettingId ) ) {
|
||||
customizeSetting = wp.customize( control.customizeSettingId );
|
||||
customizeSetting.notifications.remove( 'htmlhint_error' );
|
||||
if ( 0 !== errorAnnotations.length ) {
|
||||
customizeSetting.notifications.add( 'htmlhint_error', new wp.customize.Notification( 'htmlhint_error', {
|
||||
message: message,
|
||||
type: 'error'
|
||||
} ) );
|
||||
}
|
||||
} else if ( 0 !== errorAnnotations.length ) {
|
||||
errorNotice = $( '<div class="inline notice notice-error notice-alt"></div>' );
|
||||
errorNotice.append( $( '<p></p>', {
|
||||
text: message
|
||||
} ) );
|
||||
control.errorNoticeContainer.empty();
|
||||
control.errorNoticeContainer.append( errorNotice );
|
||||
control.errorNoticeContainer.slideDown( 'fast' );
|
||||
wp.a11y.speak( message );
|
||||
} else {
|
||||
control.errorNoticeContainer.slideUp( 'fast' );
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialize editor.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
initializeEditor: function initializeEditor() {
|
||||
var control = this, settings;
|
||||
|
||||
if ( component.codeEditorSettings.disabled ) {
|
||||
return;
|
||||
}
|
||||
|
||||
settings = _.extend( {}, component.codeEditorSettings, {
|
||||
|
||||
/**
|
||||
* Handle tabbing to the field before the editor.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
onTabPrevious: function onTabPrevious() {
|
||||
control.fields.title.focus();
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle tabbing to the field after the editor.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
onTabNext: function onTabNext() {
|
||||
var tabbables = control.syncContainer.add( control.syncContainer.parent().find( '.widget-position, .widget-control-actions' ) ).find( ':tabbable' );
|
||||
tabbables.first().focus();
|
||||
},
|
||||
|
||||
/**
|
||||
* Disable save button and store linting errors for use in updateFields.
|
||||
*
|
||||
* @param {Array} errorAnnotations - Error notifications.
|
||||
* @returns {void}
|
||||
*/
|
||||
onChangeLintingErrors: function onChangeLintingErrors( errorAnnotations ) {
|
||||
control.currentErrorAnnotations = errorAnnotations;
|
||||
},
|
||||
|
||||
/**
|
||||
* Update error notice.
|
||||
*
|
||||
* @param {Array} errorAnnotations - Error annotations.
|
||||
* @returns {void}
|
||||
*/
|
||||
onUpdateErrorNotice: function onUpdateErrorNotice( errorAnnotations ) {
|
||||
control.saveButton.toggleClass( 'validation-blocked', errorAnnotations.length );
|
||||
control.updateErrorNotice( errorAnnotations );
|
||||
}
|
||||
});
|
||||
|
||||
control.editor = wp.codeEditor.initialize( control.fields.content, settings );
|
||||
control.fields.content.on( 'change', function() {
|
||||
if ( this.value !== control.editor.codemirror.getValue() ) {
|
||||
control.editor.codemirror.setValue( this.value );
|
||||
}
|
||||
});
|
||||
control.editor.codemirror.on( 'change', function() {
|
||||
var value = control.editor.codemirror.getValue();
|
||||
if ( value !== control.fields.content.val() ) {
|
||||
control.fields.content.val( value ).trigger( 'change' );
|
||||
}
|
||||
});
|
||||
|
||||
// Make sure the editor gets updated if the content was updated on the server (sanitization) but not updated in the editor since it was focused.
|
||||
control.editor.codemirror.on( 'blur', function() {
|
||||
if ( control.contentUpdateBypassed ) {
|
||||
control.syncContainer.find( '.sync-input.content' ).trigger( 'change' );
|
||||
}
|
||||
});
|
||||
|
||||
// Prevent hitting Esc from collapsing the widget control.
|
||||
if ( wp.customize ) {
|
||||
control.editor.codemirror.on( 'keydown', function onKeydown( codemirror, event ) {
|
||||
var escKeyCode = 27;
|
||||
if ( escKeyCode === event.keyCode ) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Mapping of widget ID to instances of CustomHtmlWidgetControl subclasses.
|
||||
*
|
||||
* @type {Object.<string, wp.textWidgets.CustomHtmlWidgetControl>}
|
||||
*/
|
||||
component.widgetControls = {};
|
||||
|
||||
/**
|
||||
* Handle widget being added or initialized for the first time at the widget-added event.
|
||||
*
|
||||
* @param {jQuery.Event} event - Event.
|
||||
* @param {jQuery} widgetContainer - Widget container element.
|
||||
* @returns {void}
|
||||
*/
|
||||
component.handleWidgetAdded = function handleWidgetAdded( event, widgetContainer ) {
|
||||
var widgetForm, idBase, widgetControl, widgetId, animatedCheckDelay = 50, renderWhenAnimationDone, fieldContainer, syncContainer;
|
||||
widgetForm = widgetContainer.find( '> .widget-inside > .form, > .widget-inside > form' ); // Note: '.form' appears in the customizer, whereas 'form' on the widgets admin screen.
|
||||
|
||||
idBase = widgetForm.find( '> .id_base' ).val();
|
||||
if ( -1 === component.idBases.indexOf( idBase ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Prevent initializing already-added widgets.
|
||||
widgetId = widgetForm.find( '.widget-id' ).val();
|
||||
if ( component.widgetControls[ widgetId ] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a container element for the widget control fields.
|
||||
* This is inserted into the DOM immediately before the the .widget-content
|
||||
* element because the contents of this element are essentially "managed"
|
||||
* by PHP, where each widget update cause the entire element to be emptied
|
||||
* and replaced with the rendered output of WP_Widget::form() which is
|
||||
* sent back in Ajax request made to save/update the widget instance.
|
||||
* To prevent a "flash of replaced DOM elements and re-initialized JS
|
||||
* components", the JS template is rendered outside of the normal form
|
||||
* container.
|
||||
*/
|
||||
fieldContainer = $( '<div></div>' );
|
||||
syncContainer = widgetContainer.find( '.widget-content:first' );
|
||||
syncContainer.before( fieldContainer );
|
||||
|
||||
widgetControl = new component.CustomHtmlWidgetControl({
|
||||
el: fieldContainer,
|
||||
syncContainer: syncContainer
|
||||
});
|
||||
|
||||
component.widgetControls[ widgetId ] = widgetControl;
|
||||
|
||||
/*
|
||||
* Render the widget once the widget parent's container finishes animating,
|
||||
* as the widget-added event fires with a slideDown of the container.
|
||||
* This ensures that the textarea is visible and the editor can be initialized.
|
||||
*/
|
||||
renderWhenAnimationDone = function() {
|
||||
if ( ! ( wp.customize ? widgetContainer.parent().hasClass( 'expanded' ) : widgetContainer.hasClass( 'open' ) ) ) { // Core merge: The wp.customize condition can be eliminated with this change being in core: https://github.com/xwp/wordpress-develop/pull/247/commits/5322387d
|
||||
setTimeout( renderWhenAnimationDone, animatedCheckDelay );
|
||||
} else {
|
||||
widgetControl.initializeEditor();
|
||||
}
|
||||
};
|
||||
renderWhenAnimationDone();
|
||||
};
|
||||
|
||||
/**
|
||||
* Setup widget in accessibility mode.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
component.setupAccessibleMode = function setupAccessibleMode() {
|
||||
var widgetForm, idBase, widgetControl, fieldContainer, syncContainer;
|
||||
widgetForm = $( '.editwidget > form' );
|
||||
if ( 0 === widgetForm.length ) {
|
||||
return;
|
||||
}
|
||||
|
||||
idBase = widgetForm.find( '> .widget-control-actions > .id_base' ).val();
|
||||
if ( -1 === component.idBases.indexOf( idBase ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
fieldContainer = $( '<div></div>' );
|
||||
syncContainer = widgetForm.find( '> .widget-inside' );
|
||||
syncContainer.before( fieldContainer );
|
||||
|
||||
widgetControl = new component.CustomHtmlWidgetControl({
|
||||
el: fieldContainer,
|
||||
syncContainer: syncContainer
|
||||
});
|
||||
|
||||
widgetControl.initializeEditor();
|
||||
};
|
||||
|
||||
/**
|
||||
* Sync widget instance data sanitized from server back onto widget model.
|
||||
*
|
||||
* This gets called via the 'widget-updated' event when saving a widget from
|
||||
* the widgets admin screen and also via the 'widget-synced' event when making
|
||||
* a change to a widget in the customizer.
|
||||
*
|
||||
* @param {jQuery.Event} event - Event.
|
||||
* @param {jQuery} widgetContainer - Widget container element.
|
||||
* @returns {void}
|
||||
*/
|
||||
component.handleWidgetUpdated = function handleWidgetUpdated( event, widgetContainer ) {
|
||||
var widgetForm, widgetId, widgetControl, idBase;
|
||||
widgetForm = widgetContainer.find( '> .widget-inside > .form, > .widget-inside > form' );
|
||||
|
||||
idBase = widgetForm.find( '> .id_base' ).val();
|
||||
if ( -1 === component.idBases.indexOf( idBase ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
widgetId = widgetForm.find( '> .widget-id' ).val();
|
||||
widgetControl = component.widgetControls[ widgetId ];
|
||||
if ( ! widgetControl ) {
|
||||
return;
|
||||
}
|
||||
|
||||
widgetControl.updateFields();
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize functionality.
|
||||
*
|
||||
* This function exists to prevent the JS file from having to boot itself.
|
||||
* When WordPress enqueues this script, it should have an inline script
|
||||
* attached which calls wp.textWidgets.init().
|
||||
*
|
||||
* @param {object} settings - Options for code editor, exported from PHP.
|
||||
* @returns {void}
|
||||
*/
|
||||
component.init = function init( settings ) {
|
||||
var $document = $( document );
|
||||
_.extend( component.codeEditorSettings, settings );
|
||||
|
||||
$document.on( 'widget-added', component.handleWidgetAdded );
|
||||
$document.on( 'widget-synced widget-updated', component.handleWidgetUpdated );
|
||||
|
||||
/*
|
||||
* Manually trigger widget-added events for media widgets on the admin
|
||||
* screen once they are expanded. The widget-added event is not triggered
|
||||
* for each pre-existing widget on the widgets admin screen like it is
|
||||
* on the customizer. Likewise, the customizer only triggers widget-added
|
||||
* when the widget is expanded to just-in-time construct the widget form
|
||||
* when it is actually going to be displayed. So the following implements
|
||||
* the same for the widgets admin screen, to invoke the widget-added
|
||||
* handler when a pre-existing media widget is expanded.
|
||||
*/
|
||||
$( function initializeExistingWidgetContainers() {
|
||||
var widgetContainers;
|
||||
if ( 'widgets' !== window.pagenow ) {
|
||||
return;
|
||||
}
|
||||
widgetContainers = $( '.widgets-holder-wrap:not(#available-widgets)' ).find( 'div.widget' );
|
||||
widgetContainers.one( 'click.toggle-widget-expanded', function toggleWidgetExpanded() {
|
||||
var widgetContainer = $( this );
|
||||
component.handleWidgetAdded( new jQuery.Event( 'widget-added' ), widgetContainer );
|
||||
});
|
||||
|
||||
// Accessibility mode.
|
||||
$( window ).on( 'load', function() {
|
||||
component.setupAccessibleMode();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return component;
|
||||
})( jQuery );
|
||||
1
wp-admin/js/widgets/custom-html-widgets.min.js
vendored
Normal file
1
wp-admin/js/widgets/custom-html-widgets.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -115,7 +115,40 @@ if ( isset( $_REQUEST['action'] ) && 'update' === $_REQUEST['action'] ) {
|
||||
}
|
||||
|
||||
// List of allowable extensions
|
||||
$editable_extensions = array('php', 'txt', 'text', 'js', 'css', 'html', 'htm', 'xml', 'inc', 'include');
|
||||
$editable_extensions = array(
|
||||
'bash',
|
||||
'conf',
|
||||
'css',
|
||||
'diff',
|
||||
'htm',
|
||||
'html',
|
||||
'http',
|
||||
'inc',
|
||||
'include',
|
||||
'js',
|
||||
'json',
|
||||
'jsx',
|
||||
'less',
|
||||
'md',
|
||||
'patch',
|
||||
'php',
|
||||
'php3',
|
||||
'php4',
|
||||
'php5',
|
||||
'php7',
|
||||
'phps',
|
||||
'phtml',
|
||||
'sass',
|
||||
'scss',
|
||||
'sh',
|
||||
'sql',
|
||||
'svg',
|
||||
'text',
|
||||
'txt',
|
||||
'xml',
|
||||
'yaml',
|
||||
'yml',
|
||||
);
|
||||
|
||||
/**
|
||||
* Filters file type extensions editable in the plugin editor.
|
||||
@@ -157,6 +190,12 @@ if ( isset( $_REQUEST['action'] ) && 'update' === $_REQUEST['action'] ) {
|
||||
'<p>' . __('<a href="https://wordpress.org/support/">Support Forums</a>') . '</p>'
|
||||
);
|
||||
|
||||
$settings = wp_enqueue_code_editor( array( 'file' => $real_file ) );
|
||||
if ( ! empty( $settings ) ) {
|
||||
wp_enqueue_script( 'wp-theme-plugin-editor' );
|
||||
wp_add_inline_script( 'wp-theme-plugin-editor', sprintf( 'jQuery( function() { wp.themePluginEditor.init( %s ); } )', wp_json_encode( $settings ) ) );
|
||||
}
|
||||
|
||||
require_once(ABSPATH . 'wp-admin/admin-header.php');
|
||||
|
||||
update_recently_edited(WP_PLUGIN_DIR . '/' . $file);
|
||||
|
||||
@@ -63,7 +63,40 @@ if ( $theme->errors() && 'theme_no_stylesheet' == $theme->errors()->get_error_co
|
||||
|
||||
$allowed_files = $style_files = array();
|
||||
$has_templates = false;
|
||||
$default_types = array( 'php', 'css' );
|
||||
$default_types = array(
|
||||
'bash',
|
||||
'conf',
|
||||
'css',
|
||||
'diff',
|
||||
'htm',
|
||||
'html',
|
||||
'http',
|
||||
'inc',
|
||||
'include',
|
||||
'js',
|
||||
'json',
|
||||
'jsx',
|
||||
'less',
|
||||
'md',
|
||||
'patch',
|
||||
'php',
|
||||
'php3',
|
||||
'php4',
|
||||
'php5',
|
||||
'php7',
|
||||
'phps',
|
||||
'phtml',
|
||||
'sass',
|
||||
'scss',
|
||||
'sh',
|
||||
'sql',
|
||||
'svg',
|
||||
'text',
|
||||
'txt',
|
||||
'xml',
|
||||
'yaml',
|
||||
'yml',
|
||||
);
|
||||
|
||||
/**
|
||||
* Filters the list of file types allowed for editing in the Theme editor.
|
||||
@@ -126,6 +159,12 @@ case 'update':
|
||||
|
||||
default:
|
||||
|
||||
$settings = wp_enqueue_code_editor( compact( 'file' ) );
|
||||
if ( ! empty( $settings ) ) {
|
||||
wp_enqueue_script( 'wp-theme-plugin-editor' );
|
||||
wp_add_inline_script( 'wp-theme-plugin-editor', sprintf( 'jQuery( function() { wp.themePluginEditor.init( %s ); } )', wp_json_encode( $settings ) ) );
|
||||
}
|
||||
|
||||
require_once( ABSPATH . 'wp-admin/admin-header.php' );
|
||||
|
||||
update_recently_edited( $file );
|
||||
|
||||
@@ -247,6 +247,26 @@ if ( ! IS_PROFILE_PAGE ) {
|
||||
<td><label for="rich_editing"><input name="rich_editing" type="checkbox" id="rich_editing" value="false" <?php if ( ! empty( $profileuser->rich_editing ) ) checked( 'false', $profileuser->rich_editing ); ?> /> <?php _e( 'Disable the visual editor when writing' ); ?></label></td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
<?php
|
||||
$show_syntax_highlighting_preference = (
|
||||
// For Custom HTML widget and Additional CSS in Customizer.
|
||||
user_can( $profileuser, 'edit_theme_options' )
|
||||
||
|
||||
// Edit plugins.
|
||||
user_can( $profileuser, 'edit_plugins' )
|
||||
||
|
||||
// Edit themes.
|
||||
user_can( $profileuser, 'edit_themes' )
|
||||
);
|
||||
?>
|
||||
<?php if ( $show_syntax_highlighting_preference ) : ?>
|
||||
<tr class="user-syntax-highlighting-wrap">
|
||||
<th scope="row"><?php _e( 'Syntax Highlighting' ); ?></th>
|
||||
<td>
|
||||
<label for="syntax_highlighting"><input name="syntax_highlighting" type="checkbox" id="syntax_highlighting" value="false" <?php if ( ! empty( $profileuser->syntax_highlighting ) ) checked( 'false', $profileuser->syntax_highlighting ); ?> /> <?php _e( 'Disable syntax highlighting when editing code' ); ?></label>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
<?php if ( count($_wp_admin_css_colors) > 1 && has_action('admin_color_scheme_picker') ) : ?>
|
||||
<tr class="user-admin-color-wrap">
|
||||
<th scope="row"><?php _e('Admin Color Scheme')?></th>
|
||||
|
||||
Reference in New Issue
Block a user