Skip to content

Commit c29ee85

Browse files
committed
Merge branch 'trunk' into fix/issue-64568
2 parents 0677f37 + b2486b8 commit c29ee85

37 files changed

Lines changed: 1054 additions & 287 deletions

.github/pull_request_template.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,18 @@ If this is your first time contributing, you may also find reviewing these guide
1212
- Inline Documentation Standards: https://make.wordpress.org/core/handbook/best-practices/inline-documentation-standards/
1313
- Browser Support Policies: https://make.wordpress.org/core/handbook/best-practices/browser-support/
1414
- Proper spelling and grammar related best practices: https://make.wordpress.org/core/handbook/best-practices/spelling/
15+
- ✨ If you are using AI tools, you must adhere to the AI Guidelines: https://make.wordpress.org/ai/handbook/ai-guidelines/
1516
-->
1617

1718
<!-- Insert a description of your changes here -->
1819

1920
Trac ticket: <!-- insert a link to the WordPress Trac ticket here -->
2021

22+
## Use of AI Tools
23+
24+
<!--
25+
You are free to use artificial intelligence (AI) tooling to contribute, but you must disclose what tooling you are using and to what extent a pull request has been authored by AI. It is your responsibility to review and take responsibility for what AI generates. See the WordPress AI Guidelines: <https://make.wordpress.org/ai/handbook/ai-guidelines/>.
26+
-->
27+
2128
---
2229
**This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See [GitHub Pull Requests for Code Review](https://make.wordpress.org/core/handbook/contribute/git/github-pull-requests-for-code-review/) in the Core Handbook for more details.**
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
##
2+
# A reusable workflow that tests the Gutenberg plugin build process when run within a wordpress-develop checkout.
3+
##
4+
name: Test the Gutenberg plugin Build Process
5+
6+
on:
7+
workflow_call:
8+
inputs:
9+
os:
10+
description: 'Operating system to run tests on'
11+
required: false
12+
type: 'string'
13+
default: 'ubuntu-24.04'
14+
directory:
15+
description: 'Directory to run WordPress from. Valid values are `src` or `build`'
16+
required: false
17+
type: 'string'
18+
default: 'src'
19+
20+
env:
21+
GUTENBERG_DIRECTORY: ${{ inputs.directory == 'build' && 'build' || 'src' }}/wp-content/plugins/gutenberg
22+
PUPPETEER_SKIP_DOWNLOAD: ${{ true }}
23+
NODE_OPTIONS: '--max-old-space-size=8192'
24+
25+
# Disable permissions for all available scopes by default.
26+
# Any needed permissions should be configured at the job level.
27+
permissions: {}
28+
29+
jobs:
30+
# Verifies that installing npm dependencies and building the Gutenberg plugin works as expected.
31+
#
32+
# Performs the following steps:
33+
# - Checks out the repository.
34+
# - Checks out the Gutenberg plugin into the plugins directory.
35+
# - Sets up Node.js.
36+
# - Logs debug information about the GitHub Action runner.
37+
# - Installs Gutenberg npm dependencies.
38+
# - Runs the Gutenberg build process.
39+
# - Installs Core npm dependencies.
40+
# - Builds WordPress to run from the relevant location (src or build).
41+
# - Builds Gutenberg.
42+
# - Ensures version-controlled files are not modified or deleted.
43+
build-process-tests:
44+
name: ${{ contains( inputs.os, 'macos-' ) && 'MacOS' || contains( inputs.os, 'windows-' ) && 'Windows' || 'Linux' }}
45+
permissions:
46+
contents: read
47+
runs-on: ${{ inputs.os }}
48+
timeout-minutes: 30
49+
50+
steps:
51+
- name: Checkout repository
52+
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
53+
with:
54+
show-progress: ${{ runner.debug == '1' && 'true' || 'false' }}
55+
persist-credentials: false
56+
57+
- name: Checkout Gutenberg plugin
58+
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
59+
with:
60+
repository: 'WordPress/gutenberg'
61+
path: ${{ env.GUTENBERG_DIRECTORY }}
62+
show-progress: ${{ runner.debug == '1' && 'true' || 'false' }}
63+
persist-credentials: false
64+
65+
- name: Set up Node.js
66+
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
67+
with:
68+
node-version-file: '.nvmrc'
69+
cache: npm
70+
cache-dependency-path: |
71+
package-lock.json
72+
${{ env.GUTENBERG_DIRECTORY }}/package-lock.json
73+
74+
- name: Log debug information
75+
run: |
76+
npm --version
77+
node --version
78+
curl --version
79+
git --version
80+
81+
- name: Install Gutenberg Dependencies
82+
run: npm ci
83+
working-directory: ${{ env.GUTENBERG_DIRECTORY }}
84+
85+
- name: Build Gutenberg
86+
run: npm run build
87+
working-directory: ${{ env.GUTENBERG_DIRECTORY }}
88+
89+
- name: Install Core Dependencies
90+
run: npm ci
91+
92+
- name: Build WordPress to run from ${{ inputs.directory }}
93+
run: npm run ${{ inputs.directory == 'src' && 'build:dev' || 'build' }}
94+
95+
- name: Run Gutenberg build script after building Core to run from ${{ inputs.directory }}
96+
run: npm run build
97+
working-directory: ${{ env.GUTENBERG_DIRECTORY }}
98+
99+
- name: Ensure version-controlled files are not modified or deleted during building
100+
run: git diff --exit-code

src/js/_enqueues/wp/code-editor.js

Lines changed: 44 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
* @output wp-admin/js/code-editor.js
33
*/
44

5+
/* eslint-env es2020 */
6+
57
if ( 'undefined' === typeof window.wp ) {
68
/**
79
* @namespace wp
@@ -44,7 +46,7 @@ if ( 'undefined' === typeof window.wp.codeEditor ) {
4446
* @param {Function} settings.onChangeLintingErrors - Callback for when there are changes to linting errors.
4547
* @param {Function} settings.onUpdateErrorNotice - Callback to update error notice.
4648
*
47-
* @return {void}
49+
* @return {Function} Update error notice function.
4850
*/
4951
function configureLinting( editor, settings ) { // eslint-disable-line complexity
5052
var currentErrorAnnotations = [], previouslyShownErrorAnnotations = [];
@@ -80,7 +82,7 @@ if ( 'undefined' === typeof window.wp.codeEditor ) {
8082
}
8183

8284
/*
83-
* Note that rules must be sent in the "deprecated" lint.options property
85+
* Note that rules must be sent in the "deprecated" lint.options property
8486
* to prevent linter from complaining about unrecognized options.
8587
* See <https://github.com/codemirror/CodeMirror/pull/4944>.
8688
*/
@@ -207,6 +209,8 @@ if ( 'undefined' === typeof window.wp.codeEditor ) {
207209
updateErrorNotice();
208210
}
209211
});
212+
213+
return updateErrorNotice;
210214
}
211215

212216
/**
@@ -259,6 +263,7 @@ if ( 'undefined' === typeof window.wp.codeEditor ) {
259263
* @typedef {object} wp.codeEditor~CodeEditorInstance
260264
* @property {object} settings - The code editor settings.
261265
* @property {CodeMirror} codemirror - The CodeMirror instance.
266+
* @property {Function} updateErrorNotice - Force update the error notice.
262267
*/
263268

264269
/**
@@ -280,7 +285,7 @@ if ( 'undefined' === typeof window.wp.codeEditor ) {
280285
* @return {CodeEditorInstance} Instance.
281286
*/
282287
wp.codeEditor.initialize = function initialize( textarea, settings ) {
283-
var $textarea, codemirror, instanceSettings, instance;
288+
var $textarea, codemirror, instanceSettings, instance, updateErrorNotice;
284289
if ( 'string' === typeof textarea ) {
285290
$textarea = $( '#' + textarea );
286291
} else {
@@ -292,16 +297,33 @@ if ( 'undefined' === typeof window.wp.codeEditor ) {
292297

293298
codemirror = wp.CodeMirror.fromTextArea( $textarea[0], instanceSettings.codemirror );
294299

295-
configureLinting( codemirror, instanceSettings );
300+
updateErrorNotice = configureLinting( codemirror, instanceSettings );
296301

297302
instance = {
298303
settings: instanceSettings,
299-
codemirror: codemirror
304+
codemirror,
305+
updateErrorNotice,
300306
};
301307

302308
if ( codemirror.showHint ) {
303-
codemirror.on( 'keyup', function( editor, event ) { // eslint-disable-line complexity
304-
var shouldAutocomplete, isAlphaKey = /^[a-zA-Z]$/.test( event.key ), lineBeforeCursor, innerMode, token;
309+
codemirror.on( 'inputRead', function( editor, change ) {
310+
var shouldAutocomplete, isAlphaKey, lineBeforeCursor, innerMode, token, char;
311+
312+
// Only trigger autocompletion for typed input or IME composition.
313+
if ( '+input' !== change.origin && ! change.origin.startsWith( '*compose' ) ) {
314+
return;
315+
}
316+
317+
// Only trigger autocompletion for single-character inputs.
318+
// The text property is an array of strings, one for each line.
319+
// We check that there is only one line and that line has only one character.
320+
if ( 1 !== change.text.length || 1 !== change.text[0].length ) {
321+
return;
322+
}
323+
324+
char = change.text[0];
325+
isAlphaKey = /^[a-zA-Z]$/.test( char );
326+
305327
if ( codemirror.state.completionActive && isAlphaKey ) {
306328
return;
307329
}
@@ -315,26 +337,30 @@ if ( 'undefined' === typeof window.wp.codeEditor ) {
315337
innerMode = wp.CodeMirror.innerMode( codemirror.getMode(), token.state ).mode.name;
316338
lineBeforeCursor = codemirror.doc.getLine( codemirror.doc.getCursor().line ).substr( 0, codemirror.doc.getCursor().ch );
317339
if ( 'html' === innerMode || 'xml' === innerMode ) {
318-
shouldAutocomplete =
319-
'<' === event.key ||
320-
'/' === event.key && 'tag' === token.type ||
321-
isAlphaKey && 'tag' === token.type ||
322-
isAlphaKey && 'attribute' === token.type ||
323-
'=' === token.string && token.state.htmlState && token.state.htmlState.tagName;
340+
shouldAutocomplete = (
341+
'<' === char ||
342+
( '/' === char && 'tag' === token.type ) ||
343+
( isAlphaKey && 'tag' === token.type ) ||
344+
( isAlphaKey && 'attribute' === token.type ) ||
345+
( '=' === char && (
346+
token.state.htmlState?.tagName ||
347+
token.state.curState?.htmlState?.tagName
348+
) )
349+
);
324350
} else if ( 'css' === innerMode ) {
325351
shouldAutocomplete =
326352
isAlphaKey ||
327-
':' === event.key ||
328-
' ' === event.key && /:\s+$/.test( lineBeforeCursor );
353+
':' === char ||
354+
( ' ' === char && /:\s+$/.test( lineBeforeCursor ) );
329355
} else if ( 'javascript' === innerMode ) {
330-
shouldAutocomplete = isAlphaKey || '.' === event.key;
356+
shouldAutocomplete = isAlphaKey || '.' === char;
331357
} else if ( 'clike' === innerMode && 'php' === codemirror.options.mode ) {
332-
shouldAutocomplete = 'keyword' === token.type || 'variable' === token.type;
358+
shouldAutocomplete = isAlphaKey && ( 'keyword' === token.type || 'variable' === token.type );
333359
}
334360
if ( shouldAutocomplete ) {
335361
codemirror.showHint( { completeSingle: false } );
336362
}
337-
});
363+
} );
338364
}
339365

340366
// Facilitate tabbing out of the editor.

src/js/_enqueues/wp/sanitize.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@
2323
* @return {string} Stripped text.
2424
*/
2525
stripTags: function( text ) {
26+
if ( ! text ) {
27+
return '';
28+
}
29+
2630
const domParser = new DOMParser();
2731
const htmlDocument = domParser.parseFromString(
2832
text,

src/js/_enqueues/wp/theme-plugin-editor.js

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
* @output wp-admin/js/theme-plugin-editor.js
33
*/
44

5-
/* eslint no-magic-numbers: ["error", { "ignore": [-1, 0, 1] }] */
5+
/* eslint-env es2020 */
6+
7+
/* eslint no-magic-numbers: ["error", { "ignore": [-1, 0, 1, 9, 1000] }] */
68

79
if ( ! window.wp ) {
810
window.wp = {};
@@ -81,6 +83,18 @@ wp.themePluginEditor = (function( $ ) {
8183
component.docsLookUpButton.prop( 'disabled', false );
8284
}
8385
} );
86+
87+
// Initiate saving the file when not focused in CodeMirror or when the user has syntax highlighting turned off.
88+
$( window ).on( 'keydown', function( event ) {
89+
if (
90+
( event.ctrlKey || event.metaKey ) &&
91+
( 's' === event.key.toLowerCase() ) &&
92+
( ! component.instance || ! component.instance.codemirror.hasFocus() )
93+
) {
94+
event.preventDefault();
95+
component.form.trigger( 'submit' );
96+
}
97+
} );
8498
};
8599

86100
/**
@@ -191,6 +205,10 @@ wp.themePluginEditor = (function( $ ) {
191205
return;
192206
}
193207

208+
if ( component.instance && component.instance.updateErrorNotice ) {
209+
component.instance.updateErrorNotice();
210+
}
211+
194212
// Scroll to the line that has the error.
195213
if ( component.lintErrors.length ) {
196214
component.instance.codemirror.setCursor( component.lintErrors[0].from.line );
@@ -399,6 +417,16 @@ wp.themePluginEditor = (function( $ ) {
399417
editor = wp.codeEditor.initialize( $( '#newcontent' ), codeEditorSettings );
400418
editor.codemirror.on( 'change', component.onChange );
401419

420+
function onSaveShortcut() {
421+
component.form.trigger( 'submit' );
422+
}
423+
424+
editor.codemirror.setOption( 'extraKeys', {
425+
...( editor.codemirror.getOption( 'extraKeys' ) || {} ),
426+
'Ctrl-S': onSaveShortcut,
427+
'Cmd-S': onSaveShortcut,
428+
} );
429+
402430
// Improve the editor accessibility.
403431
$( editor.codemirror.display.lineDiv )
404432
.attr({

src/wp-admin/css/common.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@
139139
.screen-reader-text + .dashicons-external {
140140
margin-top: -1px;
141141
margin-left: 2px;
142+
text-decoration: none;
142143
}
143144

144145
.screen-reader-shortcut {

src/wp-admin/css/themes.css

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1086,9 +1086,7 @@ body.folded .theme-browser ~ .theme-overlay .theme-wrap {
10861086

10871087
.upload-theme .wp-upload-form,
10881088
.upload-plugin .wp-upload-form {
1089-
background: #f6f7f7;
1090-
border: 1px solid #c3c4c7;
1091-
padding: 30px;
1089+
position: relative;
10921090
margin: 30px auto;
10931091
display: inline-flex;
10941092
justify-content: space-between;
@@ -1097,7 +1095,16 @@ body.folded .theme-browser ~ .theme-overlay .theme-wrap {
10971095

10981096
.upload-theme .wp-upload-form input[type="file"],
10991097
.upload-plugin .wp-upload-form input[type="file"] {
1100-
margin-right: 10px;
1098+
background: #f6f7f7;
1099+
border: 1px solid #c3c4c7;
1100+
margin: 0;
1101+
padding: 30px 128px 30px 30px;
1102+
}
1103+
1104+
.upload-plugin .wp-upload-form input[type=submit],
1105+
.upload-theme .wp-upload-form input[type=submit] {
1106+
position: absolute;
1107+
right: 30px;
11011108
}
11021109

11031110
.upload-theme .install-help,
@@ -1131,6 +1138,7 @@ p.no-themes-local {
11311138
}
11321139

11331140
@media only screen and (max-width: 1120px) {
1141+
.upload-plugin .wp-upload-form,
11341142
.upload-theme .wp-upload-form {
11351143
margin: 20px 0;
11361144
max-width: 100%;
@@ -2015,12 +2023,22 @@ body.full-overlay-active {
20152023
padding-bottom: 4px;
20162024
}
20172025

2018-
.upload-theme .wp-upload-form,
2019-
.upload-plugin .wp-upload-form {
2020-
display: block;
2026+
.upload-plugin .wp-upload-form,
2027+
.upload-theme .wp-upload-form {
2028+
width: 100%;
20212029
}
20222030

2023-
:is(.upload-theme, .upload-plugin) .wp-upload-form input[type="submit"] {
2024-
margin-top: 10px;
2031+
.upload-plugin .wp-upload-form input[type=file],
2032+
.upload-theme .wp-upload-form input[type=file] {
2033+
padding: 30px 30px 80px;
2034+
width: 100%;
2035+
}
2036+
2037+
:is(.upload-theme, .upload-plugin) .wp-upload-form input[type="submit"].button {
2038+
right: unset;
2039+
left: 50%;
2040+
transform: translateX(-50%) !important;
2041+
top: calc( 1.4em + 42px ); /* Line height of control + gap + top padding. */
2042+
margin: 10px 0 0;
20252043
}
20262044
}

0 commit comments

Comments
 (0)