[ Index ] |
PHP Cross Reference of WordPress |
[Summary view] [Print] [Text view]
1 /** 2 * @output wp-admin/js/theme-plugin-editor.js 3 */ 4 5 /* eslint no-magic-numbers: ["error", { "ignore": [-1, 0, 1] }] */ 6 7 if ( ! window.wp ) { 8 window.wp = {}; 9 } 10 11 wp.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 ot 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: __( 'Something went wrong. Your change may not have been saved. Please try again. There is also a chance that you may need to manually fix and upload the file over 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 */ 1012 wp.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 1026 wp.themePluginEditor.l10n = window.wp.deprecateL10nObject( 'wp.themePluginEditor.l10n', wp.themePluginEditor.l10n, '5.5.0' );
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Tue Dec 3 01:00:02 2024 | Cross-referenced by PHPXref 0.7.1 |