summaryrefslogtreecommitdiff
path: root/www/crm/wp-admin/js
diff options
context:
space:
mode:
Diffstat (limited to 'www/crm/wp-admin/js')
-rw-r--r--www/crm/wp-admin/js/accordion.js94
-rw-r--r--www/crm/wp-admin/js/accordion.min.js1
-rw-r--r--www/crm/wp-admin/js/code-editor.js342
-rw-r--r--www/crm/wp-admin/js/code-editor.min.js1
-rw-r--r--www/crm/wp-admin/js/color-picker.js359
-rw-r--r--www/crm/wp-admin/js/color-picker.min.js1
-rw-r--r--www/crm/wp-admin/js/comment.js101
-rw-r--r--www/crm/wp-admin/js/comment.min.js1
-rw-r--r--www/crm/wp-admin/js/common.js1623
-rw-r--r--www/crm/wp-admin/js/common.min.js1
-rw-r--r--www/crm/wp-admin/js/custom-background.js145
-rw-r--r--www/crm/wp-admin/js/custom-background.min.js1
-rw-r--r--www/crm/wp-admin/js/custom-header.js88
-rw-r--r--www/crm/wp-admin/js/customize-controls.js9270
-rw-r--r--www/crm/wp-admin/js/customize-controls.min.js4
-rw-r--r--www/crm/wp-admin/js/customize-nav-menus.js3464
-rw-r--r--www/crm/wp-admin/js/customize-nav-menus.min.js2
-rw-r--r--www/crm/wp-admin/js/customize-widgets.js2367
-rw-r--r--www/crm/wp-admin/js/customize-widgets.min.js1
-rw-r--r--www/crm/wp-admin/js/dashboard.js593
-rw-r--r--www/crm/wp-admin/js/dashboard.min.js1
-rw-r--r--www/crm/wp-admin/js/edit-comments.js1016
-rw-r--r--www/crm/wp-admin/js/edit-comments.min.js1
-rw-r--r--www/crm/wp-admin/js/editor-expand.js1616
-rw-r--r--www/crm/wp-admin/js/editor-expand.min.js1
-rw-r--r--www/crm/wp-admin/js/editor.js1414
-rw-r--r--www/crm/wp-admin/js/editor.min.js1
-rw-r--r--www/crm/wp-admin/js/farbtastic.js276
-rw-r--r--www/crm/wp-admin/js/gallery.js241
-rw-r--r--www/crm/wp-admin/js/gallery.min.js1
-rw-r--r--www/crm/wp-admin/js/image-edit.js1126
-rw-r--r--www/crm/wp-admin/js/image-edit.min.js1
-rw-r--r--www/crm/wp-admin/js/inline-edit-post.js556
-rw-r--r--www/crm/wp-admin/js/inline-edit-post.min.js1
-rw-r--r--www/crm/wp-admin/js/inline-edit-tax.js294
-rw-r--r--www/crm/wp-admin/js/inline-edit-tax.min.js1
-rw-r--r--www/crm/wp-admin/js/iris.min.js4
-rw-r--r--www/crm/wp-admin/js/language-chooser.js36
-rw-r--r--www/crm/wp-admin/js/language-chooser.min.js1
-rw-r--r--www/crm/wp-admin/js/link.js136
-rw-r--r--www/crm/wp-admin/js/link.min.js1
-rw-r--r--www/crm/wp-admin/js/media-gallery.js41
-rw-r--r--www/crm/wp-admin/js/media-gallery.min.js1
-rw-r--r--www/crm/wp-admin/js/media-upload.js113
-rw-r--r--www/crm/wp-admin/js/media-upload.min.js1
-rw-r--r--www/crm/wp-admin/js/media.js207
-rw-r--r--www/crm/wp-admin/js/media.min.js1
-rw-r--r--www/crm/wp-admin/js/nav-menu.js1302
-rw-r--r--www/crm/wp-admin/js/nav-menu.min.js1
-rw-r--r--www/crm/wp-admin/js/password-strength-meter.js121
-rw-r--r--www/crm/wp-admin/js/password-strength-meter.min.js1
-rw-r--r--www/crm/wp-admin/js/plugin-install.js222
-rw-r--r--www/crm/wp-admin/js/plugin-install.min.js1
-rw-r--r--www/crm/wp-admin/js/post.js1273
-rw-r--r--www/crm/wp-admin/js/post.min.js1
-rw-r--r--www/crm/wp-admin/js/postbox.js445
-rw-r--r--www/crm/wp-admin/js/postbox.min.js1
-rw-r--r--www/crm/wp-admin/js/revisions.js1171
-rw-r--r--www/crm/wp-admin/js/revisions.min.js1
-rw-r--r--www/crm/wp-admin/js/set-post-thumbnail.js28
-rw-r--r--www/crm/wp-admin/js/set-post-thumbnail.min.js1
-rw-r--r--www/crm/wp-admin/js/site-health.js316
-rw-r--r--www/crm/wp-admin/js/site-health.min.js1
-rw-r--r--www/crm/wp-admin/js/svg-painter.js241
-rw-r--r--www/crm/wp-admin/js/svg-painter.min.js1
-rw-r--r--www/crm/wp-admin/js/tags-box.js430
-rw-r--r--www/crm/wp-admin/js/tags-box.min.js1
-rw-r--r--www/crm/wp-admin/js/tags-suggest.js193
-rw-r--r--www/crm/wp-admin/js/tags-suggest.min.js1
-rw-r--r--www/crm/wp-admin/js/tags.js147
-rw-r--r--www/crm/wp-admin/js/tags.min.js1
-rw-r--r--www/crm/wp-admin/js/theme-plugin-editor.js1006
-rw-r--r--www/crm/wp-admin/js/theme-plugin-editor.min.js1
-rw-r--r--www/crm/wp-admin/js/theme.js2067
-rw-r--r--www/crm/wp-admin/js/theme.min.js1
-rw-r--r--www/crm/wp-admin/js/updates.js2465
-rw-r--r--www/crm/wp-admin/js/updates.min.js2
-rw-r--r--www/crm/wp-admin/js/user-profile.js462
-rw-r--r--www/crm/wp-admin/js/user-profile.min.js1
-rw-r--r--www/crm/wp-admin/js/user-suggest.js64
-rw-r--r--www/crm/wp-admin/js/user-suggest.min.js1
-rw-r--r--www/crm/wp-admin/js/widgets.js761
-rw-r--r--www/crm/wp-admin/js/widgets.min.js1
-rw-r--r--www/crm/wp-admin/js/widgets/custom-html-widgets.js456
-rw-r--r--www/crm/wp-admin/js/widgets/custom-html-widgets.min.js1
-rw-r--r--www/crm/wp-admin/js/widgets/media-audio-widget.js154
-rw-r--r--www/crm/wp-admin/js/widgets/media-audio-widget.min.js1
-rw-r--r--www/crm/wp-admin/js/widgets/media-gallery-widget.js341
-rw-r--r--www/crm/wp-admin/js/widgets/media-gallery-widget.min.js1
-rw-r--r--www/crm/wp-admin/js/widgets/media-image-widget.js170
-rw-r--r--www/crm/wp-admin/js/widgets/media-image-widget.min.js1
-rw-r--r--www/crm/wp-admin/js/widgets/media-video-widget.js256
-rw-r--r--www/crm/wp-admin/js/widgets/media-video-widget.min.js1
-rw-r--r--www/crm/wp-admin/js/widgets/media-widgets.js1334
-rw-r--r--www/crm/wp-admin/js/widgets/media-widgets.min.js1
-rw-r--r--www/crm/wp-admin/js/widgets/text-widgets.js552
-rw-r--r--www/crm/wp-admin/js/widgets/text-widgets.min.js1
-rw-r--r--www/crm/wp-admin/js/word-count.js218
-rw-r--r--www/crm/wp-admin/js/word-count.min.js1
-rw-r--r--www/crm/wp-admin/js/wp-fullscreen-stub.js40
-rw-r--r--www/crm/wp-admin/js/wp-fullscreen-stub.min.js1
-rw-r--r--www/crm/wp-admin/js/xfn.js279
-rw-r--r--www/crm/wp-admin/js/xfn.min.js1
103 files changed, 42091 insertions, 0 deletions
diff --git a/www/crm/wp-admin/js/accordion.js b/www/crm/wp-admin/js/accordion.js
new file mode 100644
index 00000000..0bec1ce5
--- /dev/null
+++ b/www/crm/wp-admin/js/accordion.js
@@ -0,0 +1,94 @@
+/**
+ * Accordion-folding functionality.
+ *
+ * Markup with the appropriate classes will be automatically hidden,
+ * with one section opening at a time when its title is clicked.
+ * Use the following markup structure for accordion behavior:
+ *
+ * <div class="accordion-container">
+ * <div class="accordion-section open">
+ * <h3 class="accordion-section-title"></h3>
+ * <div class="accordion-section-content">
+ * </div>
+ * </div>
+ * <div class="accordion-section">
+ * <h3 class="accordion-section-title"></h3>
+ * <div class="accordion-section-content">
+ * </div>
+ * </div>
+ * <div class="accordion-section">
+ * <h3 class="accordion-section-title"></h3>
+ * <div class="accordion-section-content">
+ * </div>
+ * </div>
+ * </div>
+ *
+ * Note that any appropriate tags may be used, as long as the above classes are present.
+ *
+ * @since 3.6.0
+ * @output wp-admin/js/accordion.js
+ */
+
+( function( $ ){
+
+ $( document ).ready( function () {
+
+ // Expand/Collapse accordion sections on click.
+ $( '.accordion-container' ).on( 'click keydown', '.accordion-section-title', function( e ) {
+ if ( e.type === 'keydown' && 13 !== e.which ) { // "return" key
+ return;
+ }
+
+ e.preventDefault(); // Keep this AFTER the key filter above
+
+ accordionSwitch( $( this ) );
+ });
+
+ });
+
+ /**
+ * Close the current accordion section and open a new one.
+ *
+ * @param {Object} el Title element of the accordion section to toggle.
+ * @since 3.6.0
+ */
+ function accordionSwitch ( el ) {
+ var section = el.closest( '.accordion-section' ),
+ sectionToggleControl = section.find( '[aria-expanded]' ).first(),
+ container = section.closest( '.accordion-container' ),
+ siblings = container.find( '.open' ),
+ siblingsToggleControl = siblings.find( '[aria-expanded]' ).first(),
+ content = section.find( '.accordion-section-content' );
+
+ // This section has no content and cannot be expanded.
+ if ( section.hasClass( 'cannot-expand' ) ) {
+ return;
+ }
+
+ // Add a class to the container to let us know something is happening inside.
+ // This helps in cases such as hiding a scrollbar while animations are executing.
+ container.addClass( 'opening' );
+
+ if ( section.hasClass( 'open' ) ) {
+ section.toggleClass( 'open' );
+ content.toggle( true ).slideToggle( 150 );
+ } else {
+ siblingsToggleControl.attr( 'aria-expanded', 'false' );
+ siblings.removeClass( 'open' );
+ siblings.find( '.accordion-section-content' ).show().slideUp( 150 );
+ content.toggle( false ).slideToggle( 150 );
+ section.toggleClass( 'open' );
+ }
+
+ // We have to wait for the animations to finish
+ setTimeout(function(){
+ container.removeClass( 'opening' );
+ }, 150);
+
+ // If there's an element with an aria-expanded attribute, assume it's a toggle control and toggle the aria-expanded value.
+ if ( sectionToggleControl ) {
+ sectionToggleControl.attr( 'aria-expanded', String( sectionToggleControl.attr( 'aria-expanded' ) === 'false' ) );
+ }
+ }
+
+})(jQuery);
diff --git a/www/crm/wp-admin/js/accordion.min.js b/www/crm/wp-admin/js/accordion.min.js
new file mode 100644
index 00000000..6f22cd61
--- /dev/null
+++ b/www/crm/wp-admin/js/accordion.min.js
@@ -0,0 +1 @@
+!function(a){function b(a){var b=a.closest(".accordion-section"),c=b.find("[aria-expanded]").first(),d=b.closest(".accordion-container"),e=d.find(".open"),f=e.find("[aria-expanded]").first(),g=b.find(".accordion-section-content");b.hasClass("cannot-expand")||(d.addClass("opening"),b.hasClass("open")?(b.toggleClass("open"),g.toggle(!0).slideToggle(150)):(f.attr("aria-expanded","false"),e.removeClass("open"),e.find(".accordion-section-content").show().slideUp(150),g.toggle(!1).slideToggle(150),b.toggleClass("open")),setTimeout(function(){d.removeClass("opening")},150),c&&c.attr("aria-expanded",String("false"===c.attr("aria-expanded"))))}a(document).ready(function(){a(".accordion-container").on("click keydown",".accordion-section-title",function(c){"keydown"===c.type&&13!==c.which||(c.preventDefault(),b(a(this)))})})}(jQuery); \ No newline at end of file
diff --git a/www/crm/wp-admin/js/code-editor.js b/www/crm/wp-admin/js/code-editor.js
new file mode 100644
index 00000000..ea04cea2
--- /dev/null
+++ b/www/crm/wp-admin/js/code-editor.js
@@ -0,0 +1,342 @@
+/**
+ * @output wp-admin/js/code-editor.js
+ */
+
+if ( 'undefined' === typeof window.wp ) {
+ /**
+ * @namespace wp
+ */
+ window.wp = {};
+}
+if ( 'undefined' === typeof window.wp.codeEditor ) {
+ /**
+ * @namespace wp.codeEditor
+ */
+ window.wp.codeEditor = {};
+}
+
+( function( $, wp ) {
+ 'use strict';
+
+ /**
+ * Default settings for code editor.
+ *
+ * @since 4.9.0
+ * @type {object}
+ */
+ wp.codeEditor.defaultSettings = {
+ codemirror: {},
+ csslint: {},
+ htmlhint: {},
+ jshint: {},
+ onTabNext: function() {},
+ onTabPrevious: function() {},
+ onChangeLintingErrors: function() {},
+ onUpdateErrorNotice: function() {}
+ };
+
+ /**
+ * Configure linting.
+ *
+ * @param {CodeMirror} editor - Editor.
+ * @param {object} settings - Code editor settings.
+ * @param {object} settings.codeMirror - Settings for CodeMirror.
+ * @param {Function} settings.onChangeLintingErrors - Callback for when there are changes to linting errors.
+ * @param {Function} settings.onUpdateErrorNotice - Callback to update error notice.
+ *
+ * @returns {void}
+ */
+ function configureLinting( editor, settings ) { // eslint-disable-line complexity
+ var currentErrorAnnotations = [], previouslyShownErrorAnnotations = [];
+
+ /**
+ * Call the onUpdateErrorNotice if there are new errors to show.
+ *
+ * @returns {void}
+ */
+ function updateErrorNotice() {
+ if ( settings.onUpdateErrorNotice && ! _.isEqual( currentErrorAnnotations, previouslyShownErrorAnnotations ) ) {
+ settings.onUpdateErrorNotice( currentErrorAnnotations, editor );
+ previouslyShownErrorAnnotations = currentErrorAnnotations;
+ }
+ }
+
+ /**
+ * Get lint options.
+ *
+ * @returns {object} Lint options.
+ */
+ function getLintOptions() { // eslint-disable-line complexity
+ var options = editor.getOption( 'lint' );
+
+ if ( ! options ) {
+ return false;
+ }
+
+ if ( true === options ) {
+ options = {};
+ } else if ( _.isObject( options ) ) {
+ options = $.extend( {}, options );
+ }
+
+ // Note that rules must be sent in the "deprecated" lint.options property to prevent linter from complaining about unrecognized options. See <https://github.com/codemirror/CodeMirror/pull/4944>.
+ if ( ! options.options ) {
+ options.options = {};
+ }
+
+ // Configure JSHint.
+ if ( 'javascript' === settings.codemirror.mode && settings.jshint ) {
+ $.extend( options.options, settings.jshint );
+ }
+
+ // Configure CSSLint.
+ if ( 'css' === settings.codemirror.mode && settings.csslint ) {
+ $.extend( options.options, settings.csslint );
+ }
+
+ // Configure HTMLHint.
+ if ( 'htmlmixed' === settings.codemirror.mode && settings.htmlhint ) {
+ options.options.rules = $.extend( {}, settings.htmlhint );
+
+ if ( settings.jshint ) {
+ options.options.rules.jshint = settings.jshint;
+ }
+ if ( settings.csslint ) {
+ options.options.rules.csslint = settings.csslint;
+ }
+ }
+
+ // Wrap the onUpdateLinting CodeMirror event to route to onChangeLintingErrors and onUpdateErrorNotice.
+ options.onUpdateLinting = (function( onUpdateLintingOverridden ) {
+ return function( annotations, annotationsSorted, cm ) {
+ var errorAnnotations = _.filter( annotations, function( annotation ) {
+ return 'error' === annotation.severity;
+ } );
+
+ if ( onUpdateLintingOverridden ) {
+ onUpdateLintingOverridden.apply( annotations, annotationsSorted, cm );
+ }
+
+ // Skip if there are no changes to the errors.
+ if ( _.isEqual( errorAnnotations, currentErrorAnnotations ) ) {
+ return;
+ }
+
+ currentErrorAnnotations = errorAnnotations;
+
+ if ( settings.onChangeLintingErrors ) {
+ settings.onChangeLintingErrors( errorAnnotations, annotations, annotationsSorted, cm );
+ }
+
+ /*
+ * Update notifications when the editor is not focused to prevent error message
+ * from overwhelming the user during input, unless there are now no errors or there
+ * were previously errors shown. In these cases, update immediately so they can know
+ * that they fixed the errors.
+ */
+ if ( ! editor.state.focused || 0 === currentErrorAnnotations.length || previouslyShownErrorAnnotations.length > 0 ) {
+ updateErrorNotice();
+ }
+ };
+ })( options.onUpdateLinting );
+
+ return options;
+ }
+
+ editor.setOption( 'lint', getLintOptions() );
+
+ // Keep lint options populated.
+ editor.on( 'optionChange', function( cm, option ) {
+ var options, gutters, gutterName = 'CodeMirror-lint-markers';
+ if ( 'lint' !== option ) {
+ return;
+ }
+ gutters = editor.getOption( 'gutters' ) || [];
+ options = editor.getOption( 'lint' );
+ if ( true === options ) {
+ if ( ! _.contains( gutters, gutterName ) ) {
+ editor.setOption( 'gutters', [ gutterName ].concat( gutters ) );
+ }
+ editor.setOption( 'lint', getLintOptions() ); // Expand to include linting options.
+ } else if ( ! options ) {
+ editor.setOption( 'gutters', _.without( gutters, gutterName ) );
+ }
+
+ // Force update on error notice to show or hide.
+ if ( editor.getOption( 'lint' ) ) {
+ editor.performLint();
+ } else {
+ currentErrorAnnotations = [];
+ updateErrorNotice();
+ }
+ } );
+
+ // Update error notice when leaving the editor.
+ editor.on( 'blur', updateErrorNotice );
+
+ // Work around hint selection with mouse causing focus to leave editor.
+ editor.on( 'startCompletion', function() {
+ editor.off( 'blur', updateErrorNotice );
+ } );
+ editor.on( 'endCompletion', function() {
+ var editorRefocusWait = 500;
+ editor.on( 'blur', updateErrorNotice );
+
+ // Wait for editor to possibly get re-focused after selection.
+ _.delay( function() {
+ if ( ! editor.state.focused ) {
+ updateErrorNotice();
+ }
+ }, editorRefocusWait );
+ });
+
+ /*
+ * Make sure setting validities are set if the user tries to click Publish
+ * while an autocomplete dropdown is still open. The Customizer will block
+ * saving when a setting has an error notifications on it. This is only
+ * necessary for mouse interactions because keyboards will have already
+ * blurred the field and cause onUpdateErrorNotice to have already been
+ * called.
+ */
+ $( document.body ).on( 'mousedown', function( event ) {
+ if ( editor.state.focused && ! $.contains( editor.display.wrapper, event.target ) && ! $( event.target ).hasClass( 'CodeMirror-hint' ) ) {
+ updateErrorNotice();
+ }
+ });
+ }
+
+ /**
+ * Configure tabbing.
+ *
+ * @param {CodeMirror} codemirror - Editor.
+ * @param {object} settings - Code editor settings.
+ * @param {object} settings.codeMirror - Settings for CodeMirror.
+ * @param {Function} settings.onTabNext - Callback to handle tabbing to the next tabbable element.
+ * @param {Function} settings.onTabPrevious - Callback to handle tabbing to the previous tabbable element.
+ *
+ * @returns {void}
+ */
+ function configureTabbing( codemirror, settings ) {
+ var $textarea = $( codemirror.getTextArea() );
+
+ codemirror.on( 'blur', function() {
+ $textarea.data( 'next-tab-blurs', false );
+ });
+ codemirror.on( 'keydown', function onKeydown( editor, event ) {
+ var tabKeyCode = 9, escKeyCode = 27;
+
+ // Take note of the ESC keypress so that the next TAB can focus outside the editor.
+ if ( escKeyCode === event.keyCode ) {
+ $textarea.data( 'next-tab-blurs', true );
+ return;
+ }
+
+ // Short-circuit if tab key is not being pressed or the tab key press should move focus.
+ if ( tabKeyCode !== event.keyCode || ! $textarea.data( 'next-tab-blurs' ) ) {
+ return;
+ }
+
+ // Focus on previous or next focusable item.
+ if ( event.shiftKey ) {
+ settings.onTabPrevious( codemirror, event );
+ } else {
+ settings.onTabNext( codemirror, event );
+ }
+
+ // Reset tab state.
+ $textarea.data( 'next-tab-blurs', false );
+
+ // Prevent tab character from being added.
+ event.preventDefault();
+ });
+ }
+
+ /**
+ * @typedef {object} wp.codeEditor~CodeEditorInstance
+ * @property {object} settings - The code editor settings.
+ * @property {CodeMirror} codemirror - The CodeMirror instance.
+ */
+
+ /**
+ * Initialize Code Editor (CodeMirror) for an existing textarea.
+ *
+ * @since 4.9.0
+ *
+ * @param {string|jQuery|Element} textarea - The HTML id, jQuery object, or DOM Element for the textarea that is used for the editor.
+ * @param {object} [settings] - Settings to override defaults.
+ * @param {Function} [settings.onChangeLintingErrors] - Callback for when the linting errors have changed.
+ * @param {Function} [settings.onUpdateErrorNotice] - Callback for when error notice should be displayed.
+ * @param {Function} [settings.onTabPrevious] - Callback to handle tabbing to the previous tabbable element.
+ * @param {Function} [settings.onTabNext] - Callback to handle tabbing to the next tabbable element.
+ * @param {object} [settings.codemirror] - Options for CodeMirror.
+ * @param {object} [settings.csslint] - Rules for CSSLint.
+ * @param {object} [settings.htmlhint] - Rules for HTMLHint.
+ * @param {object} [settings.jshint] - Rules for JSHint.
+ *
+ * @returns {CodeEditorInstance} Instance.
+ */
+ wp.codeEditor.initialize = function initialize( textarea, settings ) {
+ var $textarea, codemirror, instanceSettings, instance;
+ if ( 'string' === typeof textarea ) {
+ $textarea = $( '#' + textarea );
+ } else {
+ $textarea = $( textarea );
+ }
+
+ instanceSettings = $.extend( {}, wp.codeEditor.defaultSettings, settings );
+ instanceSettings.codemirror = $.extend( {}, instanceSettings.codemirror );
+
+ codemirror = wp.CodeMirror.fromTextArea( $textarea[0], instanceSettings.codemirror );
+
+ configureLinting( codemirror, instanceSettings );
+
+ instance = {
+ settings: instanceSettings,
+ codemirror: codemirror
+ };
+
+ if ( codemirror.showHint ) {
+ codemirror.on( 'keyup', function( editor, event ) { // eslint-disable-line complexity
+ var shouldAutocomplete, isAlphaKey = /^[a-zA-Z]$/.test( event.key ), lineBeforeCursor, innerMode, token;
+ if ( codemirror.state.completionActive && isAlphaKey ) {
+ return;
+ }
+
+ // Prevent autocompletion in string literals or comments.
+ token = codemirror.getTokenAt( codemirror.getCursor() );
+ if ( 'string' === token.type || 'comment' === token.type ) {
+ return;
+ }
+
+ innerMode = wp.CodeMirror.innerMode( codemirror.getMode(), token.state ).mode.name;
+ lineBeforeCursor = codemirror.doc.getLine( codemirror.doc.getCursor().line ).substr( 0, codemirror.doc.getCursor().ch );
+ if ( 'html' === innerMode || 'xml' === innerMode ) {
+ shouldAutocomplete =
+ '<' === event.key ||
+ '/' === event.key && 'tag' === token.type ||
+ isAlphaKey && 'tag' === token.type ||
+ isAlphaKey && 'attribute' === token.type ||
+ '=' === token.string && token.state.htmlState && token.state.htmlState.tagName;
+ } else if ( 'css' === innerMode ) {
+ shouldAutocomplete =
+ isAlphaKey ||
+ ':' === event.key ||
+ ' ' === event.key && /:\s+$/.test( lineBeforeCursor );
+ } else if ( 'javascript' === innerMode ) {
+ shouldAutocomplete = isAlphaKey || '.' === event.key;
+ } else if ( 'clike' === innerMode && 'application/x-httpd-php' === codemirror.options.mode ) {
+ shouldAutocomplete = 'keyword' === token.type || 'variable' === token.type;
+ }
+ if ( shouldAutocomplete ) {
+ codemirror.showHint( { completeSingle: false } );
+ }
+ });
+ }
+
+ // Facilitate tabbing out of the editor.
+ configureTabbing( codemirror, settings );
+
+ return instance;
+ };
+
+})( window.jQuery, window.wp );
diff --git a/www/crm/wp-admin/js/code-editor.min.js b/www/crm/wp-admin/js/code-editor.min.js
new file mode 100644
index 00000000..c677ae23
--- /dev/null
+++ b/www/crm/wp-admin/js/code-editor.min.js
@@ -0,0 +1 @@
+"undefined"==typeof window.wp&&(window.wp={}),"undefined"==typeof window.wp.codeEditor&&(window.wp.codeEditor={}),function(a,b){"use strict";function c(b,c){function d(){c.onUpdateErrorNotice&&!_.isEqual(f,g)&&(c.onUpdateErrorNotice(f,b),g=f)}function e(){var e=b.getOption("lint");return!!e&&(!0===e?e={}:_.isObject(e)&&(e=a.extend({},e)),e.options||(e.options={}),"javascript"===c.codemirror.mode&&c.jshint&&a.extend(e.options,c.jshint),"css"===c.codemirror.mode&&c.csslint&&a.extend(e.options,c.csslint),"htmlmixed"===c.codemirror.mode&&c.htmlhint&&(e.options.rules=a.extend({},c.htmlhint),c.jshint&&(e.options.rules.jshint=c.jshint),c.csslint&&(e.options.rules.csslint=c.csslint)),e.onUpdateLinting=function(a){return function(e,h,i){var j=_.filter(e,function(a){return"error"===a.severity});a&&a.apply(e,h,i),_.isEqual(j,f)||(f=j,c.onChangeLintingErrors&&c.onChangeLintingErrors(j,e,h,i),(!b.state.focused||0===f.length||g.length>0)&&d())}}(e.onUpdateLinting),e)}var f=[],g=[];b.setOption("lint",e()),b.on("optionChange",function(a,c){var g,h,i="CodeMirror-lint-markers";"lint"===c&&(h=b.getOption("gutters")||[],g=b.getOption("lint"),!0===g?(_.contains(h,i)||b.setOption("gutters",[i].concat(h)),b.setOption("lint",e())):g||b.setOption("gutters",_.without(h,i)),b.getOption("lint")?b.performLint():(f=[],d()))}),b.on("blur",d),b.on("startCompletion",function(){b.off("blur",d)}),b.on("endCompletion",function(){var a=500;b.on("blur",d),_.delay(function(){b.state.focused||d()},a)}),a(document.body).on("mousedown",function(c){!b.state.focused||a.contains(b.display.wrapper,c.target)||a(c.target).hasClass("CodeMirror-hint")||d()})}function d(b,c){var d=a(b.getTextArea());b.on("blur",function(){d.data("next-tab-blurs",!1)}),b.on("keydown",function(a,e){var f=9,g=27;return g===e.keyCode?void d.data("next-tab-blurs",!0):void(f===e.keyCode&&d.data("next-tab-blurs")&&(e.shiftKey?c.onTabPrevious(b,e):c.onTabNext(b,e),d.data("next-tab-blurs",!1),e.preventDefault()))})}b.codeEditor.defaultSettings={codemirror:{},csslint:{},htmlhint:{},jshint:{},onTabNext:function(){},onTabPrevious:function(){},onChangeLintingErrors:function(){},onUpdateErrorNotice:function(){}},b.codeEditor.initialize=function(e,f){var g,h,i,j;return g=a("string"==typeof e?"#"+e:e),i=a.extend({},b.codeEditor.defaultSettings,f),i.codemirror=a.extend({},i.codemirror),h=b.CodeMirror.fromTextArea(g[0],i.codemirror),c(h,i),j={settings:i,codemirror:h},h.showHint&&h.on("keyup",function(a,c){var d,e,f,g,i=/^[a-zA-Z]$/.test(c.key);h.state.completionActive&&i||(g=h.getTokenAt(h.getCursor()),"string"!==g.type&&"comment"!==g.type&&(f=b.CodeMirror.innerMode(h.getMode(),g.state).mode.name,e=h.doc.getLine(h.doc.getCursor().line).substr(0,h.doc.getCursor().ch),"html"===f||"xml"===f?d="<"===c.key||"/"===c.key&&"tag"===g.type||i&&"tag"===g.type||i&&"attribute"===g.type||"="===g.string&&g.state.htmlState&&g.state.htmlState.tagName:"css"===f?d=i||":"===c.key||" "===c.key&&/:\s+$/.test(e):"javascript"===f?d=i||"."===c.key:"clike"===f&&"application/x-httpd-php"===h.options.mode&&(d="keyword"===g.type||"variable"===g.type),d&&h.showHint({completeSingle:!1})))}),d(h,f),j}}(window.jQuery,window.wp); \ No newline at end of file
diff --git a/www/crm/wp-admin/js/color-picker.js b/www/crm/wp-admin/js/color-picker.js
new file mode 100644
index 00000000..89e6db04
--- /dev/null
+++ b/www/crm/wp-admin/js/color-picker.js
@@ -0,0 +1,359 @@
+/**
+ * @output wp-admin/js/color-picker.js
+ */
+
+/* global wpColorPickerL10n */
+( function( $, undef ) {
+
+ var ColorPicker,
+ _before = '<button type="button" class="button wp-color-result" aria-expanded="false"><span class="wp-color-result-text"></span></button>',
+ _after = '<div class="wp-picker-holder" />',
+ _wrap = '<div class="wp-picker-container" />',
+ _button = '<input type="button" class="button button-small" />',
+ _wrappingLabel = '<label></label>',
+ _wrappingLabelText = '<span class="screen-reader-text"></span>';
+
+ /**
+ * Creates a jQuery UI color picker that is used in the theme customizer.
+ *
+ * @class $.widget.wp.wpColorPicker
+ *
+ * @since 3.5.0
+ */
+ ColorPicker = /** @lends $.widget.wp.wpColorPicker.prototype */{
+ options: {
+ defaultColor: false,
+ change: false,
+ clear: false,
+ hide: true,
+ palettes: true,
+ width: 255,
+ mode: 'hsv',
+ type: 'full',
+ slider: 'horizontal'
+ },
+ /**
+ * Creates a color picker that only allows you to adjust the hue.
+ *
+ * @since 3.5.0
+ *
+ * @access private
+ *
+ * @returns {void}
+ */
+ _createHueOnly: function() {
+ var self = this,
+ el = self.element,
+ color;
+
+ el.hide();
+
+ // Set the saturation to the maximum level.
+ color = 'hsl(' + el.val() + ', 100, 50)';
+
+ // Create an instance of the color picker, using the hsl mode.
+ el.iris( {
+ mode: 'hsl',
+ type: 'hue',
+ hide: false,
+ color: color,
+ /**
+ * Handles the onChange event if one has been defined in the options.
+ *
+ * @ignore
+ *
+ * @param {Event} event The event that's being called.
+ * @param {HTMLElement} ui The HTMLElement containing the color picker.
+ *
+ * @returns {void}
+ */
+ change: function( event, ui ) {
+ if ( $.isFunction( self.options.change ) ) {
+ self.options.change.call( this, event, ui );
+ }
+ },
+ width: self.options.width,
+ slider: self.options.slider
+ } );
+ },
+ /**
+ * Creates the color picker, sets default values, css classes and wraps it all in HTML.
+ *
+ * @since 3.5.0
+ *
+ * @access private
+ *
+ * @returns {void}
+ */
+ _create: function() {
+ // Return early if Iris support is missing.
+ if ( ! $.support.iris ) {
+ return;
+ }
+
+ var self = this,
+ el = self.element;
+
+ // Override default options with options bound to the element.
+ $.extend( self.options, el.data() );
+
+ // Create a color picker which only allows adjustments to the hue.
+ if ( self.options.type === 'hue' ) {
+ return self._createHueOnly();
+ }
+
+ // Bind the close event.
+ self.close = $.proxy( self.close, self );
+
+ self.initialValue = el.val();
+
+ // Add a CSS class to the input field.
+ el.addClass( 'wp-color-picker' );
+
+ /*
+ * Check if there's already a wrapping label, e.g. in the Customizer.
+ * If there's no label, add a default one to match the Customizer template.
+ */
+ if ( ! el.parent( 'label' ).length ) {
+ // Wrap the input field in the default label.
+ el.wrap( _wrappingLabel );
+ // Insert the default label text.
+ self.wrappingLabelText = $( _wrappingLabelText )
+ .insertBefore( el )
+ .text( wpColorPickerL10n.defaultLabel );
+ }
+
+ /*
+ * At this point, either it's the standalone version or the Customizer
+ * one, we have a wrapping label to use as hook in the DOM, let's store it.
+ */
+ self.wrappingLabel = el.parent();
+
+ // Wrap the label in the main wrapper.
+ self.wrappingLabel.wrap( _wrap );
+ // Store a reference to the main wrapper.
+ self.wrap = self.wrappingLabel.parent();
+ // Set up the toggle button and insert it before the wrapping label.
+ self.toggler = $( _before )
+ .insertBefore( self.wrappingLabel )
+ .css( { backgroundColor: self.initialValue } );
+ // Set the toggle button span element text.
+ self.toggler.find( '.wp-color-result-text' ).text( wpColorPickerL10n.pick );
+ // Set up the Iris container and insert it after the wrapping label.
+ self.pickerContainer = $( _after ).insertAfter( self.wrappingLabel );
+ // Store a reference to the Clear/Default button.
+ self.button = $( _button );
+
+ // Set up the Clear/Default button.
+ if ( self.options.defaultColor ) {
+ self.button
+ .addClass( 'wp-picker-default' )
+ .val( wpColorPickerL10n.defaultString )
+ .attr( 'aria-label', wpColorPickerL10n.defaultAriaLabel );
+ } else {
+ self.button
+ .addClass( 'wp-picker-clear' )
+ .val( wpColorPickerL10n.clear )
+ .attr( 'aria-label', wpColorPickerL10n.clearAriaLabel );
+ }
+
+ // Wrap the wrapping label in its wrapper and append the Clear/Default button.
+ self.wrappingLabel
+ .wrap( '<span class="wp-picker-input-wrap hidden" />' )
+ .after( self.button );
+
+ /*
+ * The input wrapper now contains the label+input+Clear/Default button.
+ * Store a reference to the input wrapper: we'll use this to toggle
+ * the controls visibility.
+ */
+ self.inputWrapper = el.closest( '.wp-picker-input-wrap' );
+
+ el.iris( {
+ target: self.pickerContainer,
+ hide: self.options.hide,
+ width: self.options.width,
+ mode: self.options.mode,
+ palettes: self.options.palettes,
+ /**
+ * Handles the onChange event if one has been defined in the options and additionally
+ * sets the background color for the toggler element.
+ *
+ * @since 3.5.0
+ *
+ * @ignore
+ *
+ * @param {Event} event The event that's being called.
+ * @param {HTMLElement} ui The HTMLElement containing the color picker.
+ *
+ * @returns {void}
+ */
+ change: function( event, ui ) {
+ self.toggler.css( { backgroundColor: ui.color.toString() } );
+
+ if ( $.isFunction( self.options.change ) ) {
+ self.options.change.call( this, event, ui );
+ }
+ }
+ } );
+
+ el.val( self.initialValue );
+ self._addListeners();
+
+ // Force the color picker to always be closed on initial load.
+ if ( ! self.options.hide ) {
+ self.toggler.click();
+ }
+ },
+ /**
+ * Binds event listeners to the color picker.
+ *
+ * @since 3.5.0
+ *
+ * @access private
+ *
+ * @returns {void}
+ */
+ _addListeners: function() {
+ var self = this;
+
+ /**
+ * Prevent any clicks inside this widget from leaking to the top and closing it.
+ *
+ * @since 3.5.0
+ *
+ * @param {Event} event The event that's being called.
+ *
+ * @returs {void}
+ */
+ self.wrap.on( 'click.wpcolorpicker', function( event ) {
+ event.stopPropagation();
+ });
+
+ /**
+ * Open or close the color picker depending on the class.
+ *
+ * @since 3.5
+ */
+ self.toggler.click( function(){
+ if ( self.toggler.hasClass( 'wp-picker-open' ) ) {
+ self.close();
+ } else {
+ self.open();
+ }
+ });
+
+ /**
+ * Checks if value is empty when changing the color in the color picker.
+ * If so, the background color is cleared.
+ *
+ * @since 3.5.0
+ *
+ * @param {Event} event The event that's being called.
+ *
+ * @returns {void}
+ */
+ self.element.change( function( event ) {
+ var me = $( this ),
+ val = me.val();
+
+ if ( val === '' || val === '#' ) {
+ self.toggler.css( 'backgroundColor', '' );
+ // Fire clear callback if we have one.
+ if ( $.isFunction( self.options.clear ) ) {
+ self.options.clear.call( this, event );
+ }
+ }
+ });
+
+ /**
+ * Enables the user to either clear the color in the color picker or revert back to the default color.
+ *
+ * @since 3.5.0
+ *
+ * @param {Event} event The event that's being called.
+ *
+ * @returns {void}
+ */
+ self.button.click( function( event ) {
+ var me = $( this );
+ if ( me.hasClass( 'wp-picker-clear' ) ) {
+ self.element.val( '' );
+ self.toggler.css( 'backgroundColor', '' );
+ if ( $.isFunction( self.options.clear ) ) {
+ self.options.clear.call( this, event );
+ }
+ } else if ( me.hasClass( 'wp-picker-default' ) ) {
+ self.element.val( self.options.defaultColor ).change();
+ }
+ });
+ },
+ /**
+ * Opens the color picker dialog.
+ *
+ * @since 3.5.0
+ *
+ * @returns {void}
+ */
+ open: function() {
+ this.element.iris( 'toggle' );
+ this.inputWrapper.removeClass( 'hidden' );
+ this.wrap.addClass( 'wp-picker-active' );
+ this.toggler
+ .addClass( 'wp-picker-open' )
+ .attr( 'aria-expanded', 'true' );
+ $( 'body' ).trigger( 'click.wpcolorpicker' ).on( 'click.wpcolorpicker', this.close );
+ },
+ /**
+ * Closes the color picker dialog.
+ *
+ * @since 3.5.0
+ *
+ * @returns {void}
+ */
+ close: function() {
+ this.element.iris( 'toggle' );
+ this.inputWrapper.addClass( 'hidden' );
+ this.wrap.removeClass( 'wp-picker-active' );
+ this.toggler
+ .removeClass( 'wp-picker-open' )
+ .attr( 'aria-expanded', 'false' );
+ $( 'body' ).off( 'click.wpcolorpicker', this.close );
+ },
+ /**
+ * Returns the iris object if no new color is provided. If a new color is provided, it sets the new color.
+ *
+ * @param newColor {string|*} The new color to use. Can be undefined.
+ *
+ * @since 3.5.0
+ *
+ * @returns {string} The element's color
+ */
+ color: function( newColor ) {
+ if ( newColor === undef ) {
+ return this.element.iris( 'option', 'color' );
+ }
+ this.element.iris( 'option', 'color', newColor );
+ },
+ /**
+ * Returns the iris object if no new default color is provided.
+ * If a new default color is provided, it sets the new default color.
+ *
+ * @param newDefaultColor {string|*} The new default color to use. Can be undefined.
+ *
+ * @since 3.5.0
+ *
+ * @returns {boolean|string} The element's color.
+ */
+ defaultColor: function( newDefaultColor ) {
+ if ( newDefaultColor === undef ) {
+ return this.options.defaultColor;
+ }
+
+ this.options.defaultColor = newDefaultColor;
+ }
+ };
+
+ // Register the color picker as a widget.
+ $.widget( 'wp.wpColorPicker', ColorPicker );
+}( jQuery ) );
diff --git a/www/crm/wp-admin/js/color-picker.min.js b/www/crm/wp-admin/js/color-picker.min.js
new file mode 100644
index 00000000..25d4277c
--- /dev/null
+++ b/www/crm/wp-admin/js/color-picker.min.js
@@ -0,0 +1 @@
+!function(a,b){var c,d='<button type="button" class="button wp-color-result" aria-expanded="false"><span class="wp-color-result-text"></span></button>',e='<div class="wp-picker-holder" />',f='<div class="wp-picker-container" />',g='<input type="button" class="button button-small" />',h="<label></label>",i='<span class="screen-reader-text"></span>';c={options:{defaultColor:!1,change:!1,clear:!1,hide:!0,palettes:!0,width:255,mode:"hsv",type:"full",slider:"horizontal"},_createHueOnly:function(){var b,c=this,d=c.element;d.hide(),b="hsl("+d.val()+", 100, 50)",d.iris({mode:"hsl",type:"hue",hide:!1,color:b,change:function(b,d){a.isFunction(c.options.change)&&c.options.change.call(this,b,d)},width:c.options.width,slider:c.options.slider})},_create:function(){if(a.support.iris){var b=this,c=b.element;if(a.extend(b.options,c.data()),"hue"===b.options.type)return b._createHueOnly();b.close=a.proxy(b.close,b),b.initialValue=c.val(),c.addClass("wp-color-picker"),c.parent("label").length||(c.wrap(h),b.wrappingLabelText=a(i).insertBefore(c).text(wpColorPickerL10n.defaultLabel)),b.wrappingLabel=c.parent(),b.wrappingLabel.wrap(f),b.wrap=b.wrappingLabel.parent(),b.toggler=a(d).insertBefore(b.wrappingLabel).css({backgroundColor:b.initialValue}),b.toggler.find(".wp-color-result-text").text(wpColorPickerL10n.pick),b.pickerContainer=a(e).insertAfter(b.wrappingLabel),b.button=a(g),b.options.defaultColor?b.button.addClass("wp-picker-default").val(wpColorPickerL10n.defaultString).attr("aria-label",wpColorPickerL10n.defaultAriaLabel):b.button.addClass("wp-picker-clear").val(wpColorPickerL10n.clear).attr("aria-label",wpColorPickerL10n.clearAriaLabel),b.wrappingLabel.wrap('<span class="wp-picker-input-wrap hidden" />').after(b.button),b.inputWrapper=c.closest(".wp-picker-input-wrap"),c.iris({target:b.pickerContainer,hide:b.options.hide,width:b.options.width,mode:b.options.mode,palettes:b.options.palettes,change:function(c,d){b.toggler.css({backgroundColor:d.color.toString()}),a.isFunction(b.options.change)&&b.options.change.call(this,c,d)}}),c.val(b.initialValue),b._addListeners(),b.options.hide||b.toggler.click()}},_addListeners:function(){var b=this;b.wrap.on("click.wpcolorpicker",function(a){a.stopPropagation()}),b.toggler.click(function(){b.toggler.hasClass("wp-picker-open")?b.close():b.open()}),b.element.change(function(c){var d=a(this),e=d.val();""!==e&&"#"!==e||(b.toggler.css("backgroundColor",""),a.isFunction(b.options.clear)&&b.options.clear.call(this,c))}),b.button.click(function(c){var d=a(this);d.hasClass("wp-picker-clear")?(b.element.val(""),b.toggler.css("backgroundColor",""),a.isFunction(b.options.clear)&&b.options.clear.call(this,c)):d.hasClass("wp-picker-default")&&b.element.val(b.options.defaultColor).change()})},open:function(){this.element.iris("toggle"),this.inputWrapper.removeClass("hidden"),this.wrap.addClass("wp-picker-active"),this.toggler.addClass("wp-picker-open").attr("aria-expanded","true"),a("body").trigger("click.wpcolorpicker").on("click.wpcolorpicker",this.close)},close:function(){this.element.iris("toggle"),this.inputWrapper.addClass("hidden"),this.wrap.removeClass("wp-picker-active"),this.toggler.removeClass("wp-picker-open").attr("aria-expanded","false"),a("body").off("click.wpcolorpicker",this.close)},color:function(a){return a===b?this.element.iris("option","color"):void this.element.iris("option","color",a)},defaultColor:function(a){return a===b?this.options.defaultColor:void(this.options.defaultColor=a)}},a.widget("wp.wpColorPicker",c)}(jQuery); \ No newline at end of file
diff --git a/www/crm/wp-admin/js/comment.js b/www/crm/wp-admin/js/comment.js
new file mode 100644
index 00000000..04d6e539
--- /dev/null
+++ b/www/crm/wp-admin/js/comment.js
@@ -0,0 +1,101 @@
+/**
+ * @output wp-admin/js/comment.js
+ */
+
+/* global postboxes, commentL10n */
+
+/**
+ * Binds to the document ready event.
+ *
+ * @since 2.5.0
+ *
+ * @param {jQuery} $ The jQuery object.
+ */
+jQuery(document).ready( function($) {
+
+ postboxes.add_postbox_toggles('comment');
+
+ var $timestampdiv = $('#timestampdiv'),
+ $timestamp = $( '#timestamp' ),
+ stamp = $timestamp.html(),
+ $timestampwrap = $timestampdiv.find( '.timestamp-wrap' ),
+ $edittimestamp = $timestampdiv.siblings( 'a.edit-timestamp' );
+
+ /**
+ * Adds event that opens the time stamp form if the form is hidden.
+ *
+ * @listens $edittimestamp:click
+ *
+ * @param {Event} event The event object.
+ * @returns {void}
+ */
+ $edittimestamp.click( function( event ) {
+ if ( $timestampdiv.is( ':hidden' ) ) {
+ // Slide down the form and set focus on the first field.
+ $timestampdiv.slideDown( 'fast', function() {
+ $( 'input, select', $timestampwrap ).first().focus();
+ } );
+ $(this).hide();
+ }
+ event.preventDefault();
+ });
+
+ /**
+ * Resets the time stamp values when the cancel button is clicked.
+ *
+ * @listens .cancel-timestamp:click
+ *
+ * @param {Event} event The event object.
+ * @returns {void}
+ */
+
+ $timestampdiv.find('.cancel-timestamp').click( function( event ) {
+ // Move focus back to the Edit link.
+ $edittimestamp.show().focus();
+ $timestampdiv.slideUp( 'fast' );
+ $('#mm').val($('#hidden_mm').val());
+ $('#jj').val($('#hidden_jj').val());
+ $('#aa').val($('#hidden_aa').val());
+ $('#hh').val($('#hidden_hh').val());
+ $('#mn').val($('#hidden_mn').val());
+ $timestamp.html( stamp );
+ event.preventDefault();
+ });
+
+ /**
+ * Sets the time stamp values when the ok button is clicked.
+ *
+ * @listens .save-timestamp:click
+ *
+ * @param {Event} event The event object.
+ * @returns {void}
+ */
+ $timestampdiv.find('.save-timestamp').click( function( event ) { // crazyhorse - multiple ok cancels
+ var aa = $('#aa').val(), mm = $('#mm').val(), jj = $('#jj').val(), hh = $('#hh').val(), mn = $('#mn').val(),
+ newD = new Date( aa, mm - 1, jj, hh, mn );
+
+ event.preventDefault();
+
+ if ( newD.getFullYear() != aa || (1 + newD.getMonth()) != mm || newD.getDate() != jj || newD.getMinutes() != mn ) {
+ $timestampwrap.addClass( 'form-invalid' );
+ return;
+ } else {
+ $timestampwrap.removeClass( 'form-invalid' );
+ }
+
+ $timestamp.html(
+ commentL10n.submittedOn + ' <b>' +
+ commentL10n.dateFormat
+ .replace( '%1$s', $( 'option[value="' + mm + '"]', '#mm' ).attr( 'data-text' ) )
+ .replace( '%2$s', parseInt( jj, 10 ) )
+ .replace( '%3$s', aa )
+ .replace( '%4$s', ( '00' + hh ).slice( -2 ) )
+ .replace( '%5$s', ( '00' + mn ).slice( -2 ) ) +
+ '</b> '
+ );
+
+ // Move focus back to the Edit link.
+ $edittimestamp.show().focus();
+ $timestampdiv.slideUp( 'fast' );
+ });
+});
diff --git a/www/crm/wp-admin/js/comment.min.js b/www/crm/wp-admin/js/comment.min.js
new file mode 100644
index 00000000..08512640
--- /dev/null
+++ b/www/crm/wp-admin/js/comment.min.js
@@ -0,0 +1 @@
+jQuery(document).ready(function(a){postboxes.add_postbox_toggles("comment");var b=a("#timestampdiv"),c=a("#timestamp"),d=c.html(),e=b.find(".timestamp-wrap"),f=b.siblings("a.edit-timestamp");f.click(function(c){b.is(":hidden")&&(b.slideDown("fast",function(){a("input, select",e).first().focus()}),a(this).hide()),c.preventDefault()}),b.find(".cancel-timestamp").click(function(e){f.show().focus(),b.slideUp("fast"),a("#mm").val(a("#hidden_mm").val()),a("#jj").val(a("#hidden_jj").val()),a("#aa").val(a("#hidden_aa").val()),a("#hh").val(a("#hidden_hh").val()),a("#mn").val(a("#hidden_mn").val()),c.html(d),e.preventDefault()}),b.find(".save-timestamp").click(function(d){var g=a("#aa").val(),h=a("#mm").val(),i=a("#jj").val(),j=a("#hh").val(),k=a("#mn").val(),l=new Date(g,h-1,i,j,k);return d.preventDefault(),l.getFullYear()!=g||1+l.getMonth()!=h||l.getDate()!=i||l.getMinutes()!=k?void e.addClass("form-invalid"):(e.removeClass("form-invalid"),c.html(commentL10n.submittedOn+" <b>"+commentL10n.dateFormat.replace("%1$s",a('option[value="'+h+'"]',"#mm").attr("data-text")).replace("%2$s",parseInt(i,10)).replace("%3$s",g).replace("%4$s",("00"+j).slice(-2)).replace("%5$s",("00"+k).slice(-2))+"</b> "),f.show().focus(),void b.slideUp("fast"))})}); \ No newline at end of file
diff --git a/www/crm/wp-admin/js/common.js b/www/crm/wp-admin/js/common.js
new file mode 100644
index 00000000..2fc0c388
--- /dev/null
+++ b/www/crm/wp-admin/js/common.js
@@ -0,0 +1,1623 @@
+/**
+ * @output wp-admin/js/common.js
+ */
+
+/* global setUserSetting, ajaxurl, commonL10n, alert, confirm, pagenow */
+/* global columns, screenMeta */
+
+/**
+ * Adds common WordPress functionality to the window.
+ *
+ * @param {jQuery} $ jQuery object.
+ * @param {Object} window The window object.
+ * @param {mixed} undefined Unused.
+ */
+( function( $, window, undefined ) {
+ var $document = $( document ),
+ $window = $( window ),
+ $body = $( document.body );
+
+/**
+ * Removed in 3.3.0, needed for back-compatibility.
+ *
+ * @since 2.7.0
+ * @deprecated 3.3.0
+ */
+window.adminMenu = {
+ init : function() {},
+ fold : function() {},
+ restoreMenuState : function() {},
+ toggle : function() {},
+ favorites : function() {}
+};
+
+// Show/hide/save table columns.
+window.columns = {
+
+ /**
+ * Initializes the column toggles in the screen options.
+ *
+ * Binds an onClick event to the checkboxes to show or hide the table columns
+ * based on their toggled state. And persists the toggled state.
+ *
+ * @since 2.7.0
+ *
+ * @returns {void}
+ */
+ init : function() {
+ var that = this;
+ $('.hide-column-tog', '#adv-settings').click( function() {
+ var $t = $(this), column = $t.val();
+ if ( $t.prop('checked') )
+ that.checked(column);
+ else
+ that.unchecked(column);
+
+ columns.saveManageColumnsState();
+ });
+ },
+
+ /**
+ * Saves the toggled state for the columns.
+ *
+ * Saves whether the columns should be shown or hidden on a page.
+ *
+ * @since 3.0.0
+ *
+ * @returns {void}
+ */
+ saveManageColumnsState : function() {
+ var hidden = this.hidden();
+ $.post(ajaxurl, {
+ action: 'hidden-columns',
+ hidden: hidden,
+ screenoptionnonce: $('#screenoptionnonce').val(),
+ page: pagenow
+ });
+ },
+
+ /**
+ * Makes a column visible and adjusts the column span for the table.
+ *
+ * @since 3.0.0
+ * @param {string} column The column name.
+ *
+ * @returns {void}
+ */
+ checked : function(column) {
+ $('.column-' + column).removeClass( 'hidden' );
+ this.colSpanChange(+1);
+ },
+
+ /**
+ * Hides a column and adjusts the column span for the table.
+ *
+ * @since 3.0.0
+ * @param {string} column The column name.
+ *
+ * @returns {void}
+ */
+ unchecked : function(column) {
+ $('.column-' + column).addClass( 'hidden' );
+ this.colSpanChange(-1);
+ },
+
+ /**
+ * Gets all hidden columns.
+ *
+ * @since 3.0.0
+ *
+ * @returns {string} The hidden column names separated by a comma.
+ */
+ hidden : function() {
+ return $( '.manage-column[id]' ).filter( '.hidden' ).map(function() {
+ return this.id;
+ }).get().join( ',' );
+ },
+
+ /**
+ * Gets the checked column toggles from the screen options.
+ *
+ * @since 3.0.0
+ *
+ * @returns {string} String containing the checked column names.
+ */
+ useCheckboxesForHidden : function() {
+ this.hidden = function(){
+ return $('.hide-column-tog').not(':checked').map(function() {
+ var id = this.id;
+ return id.substring( id, id.length - 5 );
+ }).get().join(',');
+ };
+ },
+
+ /**
+ * Adjusts the column span for the table.
+ *
+ * @since 3.1.0
+ *
+ * @param {int} diff The modifier for the column span.
+ */
+ colSpanChange : function(diff) {
+ var $t = $('table').find('.colspanchange'), n;
+ if ( !$t.length )
+ return;
+ n = parseInt( $t.attr('colspan'), 10 ) + diff;
+ $t.attr('colspan', n.toString());
+ }
+};
+
+$document.ready(function(){columns.init();});
+
+/**
+ * Validates that the required form fields are not empty.
+ *
+ * @since 2.9.0
+ *
+ * @param {jQuery} form The form to validate.
+ *
+ * @returns {boolean} Returns true if all required fields are not an empty string.
+ */
+window.validateForm = function( form ) {
+ return !$( form )
+ .find( '.form-required' )
+ .filter( function() { return $( ':input:visible', this ).val() === ''; } )
+ .addClass( 'form-invalid' )
+ .find( ':input:visible' )
+ .change( function() { $( this ).closest( '.form-invalid' ).removeClass( 'form-invalid' ); } )
+ .length;
+};
+
+// stub for doing better warnings
+/**
+ * Shows message pop-up notice or confirmation message.
+ *
+ * @since 2.7.0
+ *
+ * @type {{warn: showNotice.warn, note: showNotice.note}}
+ *
+ * @returns {void}
+ */
+window.showNotice = {
+
+ /**
+ * Shows a delete confirmation pop-up message.
+ *
+ * @since 2.7.0
+ *
+ * @returns {boolean} Returns true if the message is confirmed.
+ */
+ warn : function() {
+ var msg = commonL10n.warnDelete || '';
+ if ( confirm(msg) ) {
+ return true;
+ }
+
+ return false;
+ },
+
+ /**
+ * Shows an alert message.
+ *
+ * @since 2.7.0
+ *
+ * @param text The text to display in the message.
+ */
+ note : function(text) {
+ alert(text);
+ }
+};
+
+/**
+ * Represents the functions for the meta screen options panel.
+ *
+ * @since 3.2.0
+ *
+ * @type {{element: null, toggles: null, page: null, init: screenMeta.init,
+ * toggleEvent: screenMeta.toggleEvent, open: screenMeta.open,
+ * close: screenMeta.close}}
+ *
+ * @returns {void}
+ */
+window.screenMeta = {
+ element: null, // #screen-meta
+ toggles: null, // .screen-meta-toggle
+ page: null, // #wpcontent
+
+ /**
+ * Initializes the screen meta options panel.
+ *
+ * @since 3.2.0
+ *
+ * @returns {void}
+ */
+ init: function() {
+ this.element = $('#screen-meta');
+ this.toggles = $( '#screen-meta-links' ).find( '.show-settings' );
+ this.page = $('#wpcontent');
+
+ this.toggles.click( this.toggleEvent );
+ },
+
+ /**
+ * Toggles the screen meta options panel.
+ *
+ * @since 3.2.0
+ *
+ * @returns {void}
+ */
+ toggleEvent: function() {
+ var panel = $( '#' + $( this ).attr( 'aria-controls' ) );
+
+ if ( !panel.length )
+ return;
+
+ if ( panel.is(':visible') )
+ screenMeta.close( panel, $(this) );
+ else
+ screenMeta.open( panel, $(this) );
+ },
+
+ /**
+ * Opens the screen meta options panel.
+ *
+ * @since 3.2.0
+ *
+ * @param {jQuery} panel The screen meta options panel div.
+ * @param {jQuery} button The toggle button.
+ *
+ * @returns {void}
+ */
+ open: function( panel, button ) {
+
+ $( '#screen-meta-links' ).find( '.screen-meta-toggle' ).not( button.parent() ).css( 'visibility', 'hidden' );
+
+ panel.parent().show();
+
+ /**
+ * Sets the focus to the meta options panel and adds the necessary CSS classes.
+ *
+ * @since 3.2.0
+ *
+ * @returns {void}
+ */
+ panel.slideDown( 'fast', function() {
+ panel.focus();
+ button.addClass( 'screen-meta-active' ).attr( 'aria-expanded', true );
+ });
+
+ $document.trigger( 'screen:options:open' );
+ },
+
+ /**
+ * Closes the screen meta options panel.
+ *
+ * @since 3.2.0
+ *
+ * @param {jQuery} panel The screen meta options panel div.
+ * @param {jQuery} button The toggle button.
+ *
+ * @returns {void}
+ */
+ close: function( panel, button ) {
+ /**
+ * Hides the screen meta options panel.
+ *
+ * @since 3.2.0
+ *
+ * @returns {void}
+ */
+ panel.slideUp( 'fast', function() {
+ button.removeClass( 'screen-meta-active' ).attr( 'aria-expanded', false );
+ $('.screen-meta-toggle').css('visibility', '');
+ panel.parent().hide();
+ });
+
+ $document.trigger( 'screen:options:close' );
+ }
+};
+
+/**
+ * Initializes the help tabs in the help panel.
+ *
+ * @param {Event} e The event object.
+ *
+ * @returns {void}
+ */
+$('.contextual-help-tabs').delegate('a', 'click', function(e) {
+ var link = $(this),
+ panel;
+
+ e.preventDefault();
+
+ // Don't do anything if the click is for the tab already showing.
+ if ( link.is('.active a') )
+ return false;
+
+ // Links
+ $('.contextual-help-tabs .active').removeClass('active');
+ link.parent('li').addClass('active');
+
+ panel = $( link.attr('href') );
+
+ // Panels
+ $('.help-tab-content').not( panel ).removeClass('active').hide();
+ panel.addClass('active').show();
+});
+
+/**
+ * Update custom permalink structure via buttons.
+ */
+var permalinkStructureFocused = false,
+ $permalinkStructure = $( '#permalink_structure' ),
+ $permalinkStructureInputs = $( '.permalink-structure input:radio' ),
+ $permalinkCustomSelection = $( '#custom_selection' ),
+ $availableStructureTags = $( '.form-table.permalink-structure .available-structure-tags button' );
+
+// Change permalink structure input when selecting one of the common structures.
+$permalinkStructureInputs.on( 'change', function() {
+ if ( 'custom' === this.value ) {
+ return;
+ }
+
+ $permalinkStructure.val( this.value );
+
+ // Update button states after selection.
+ $availableStructureTags.each( function() {
+ changeStructureTagButtonState( $( this ) );
+ } );
+} );
+
+$permalinkStructure.on( 'click input', function() {
+ $permalinkCustomSelection.prop( 'checked', true );
+} );
+
+// Check if the permalink structure input field has had focus at least once.
+$permalinkStructure.on( 'focus', function( event ) {
+ permalinkStructureFocused = true;
+ $( this ).off( event );
+} );
+
+/**
+ * Enables or disables a structure tag button depending on its usage.
+ *
+ * If the structure is already used in the custom permalink structure,
+ * it will be disabled.
+ *
+ * @param {object} button Button jQuery object.
+ */
+function changeStructureTagButtonState( button ) {
+ if ( -1 !== $permalinkStructure.val().indexOf( button.text().trim() ) ) {
+ button.attr( 'data-label', button.attr( 'aria-label' ) );
+ button.attr( 'aria-label', button.attr( 'data-used' ) );
+ button.attr( 'aria-pressed', true );
+ button.addClass( 'active' );
+ } else if ( button.attr( 'data-label' ) ) {
+ button.attr( 'aria-label', button.attr( 'data-label' ) );
+ button.attr( 'aria-pressed', false );
+ button.removeClass( 'active' );
+ }
+}
+
+// Check initial button state.
+$availableStructureTags.each( function() {
+ changeStructureTagButtonState( $( this ) );
+} );
+
+// Observe permalink structure field and disable buttons of tags that are already present.
+$permalinkStructure.on( 'change', function() {
+ $availableStructureTags.each( function() {
+ changeStructureTagButtonState( $( this ) );
+ } );
+} );
+
+$availableStructureTags.on( 'click', function() {
+ var permalinkStructureValue = $permalinkStructure.val(),
+ selectionStart = $permalinkStructure[ 0 ].selectionStart,
+ selectionEnd = $permalinkStructure[ 0 ].selectionEnd,
+ textToAppend = $( this ).text().trim(),
+ textToAnnounce = $( this ).attr( 'data-added' ),
+ newSelectionStart;
+
+ // Remove structure tag if already part of the structure.
+ if ( -1 !== permalinkStructureValue.indexOf( textToAppend ) ) {
+ permalinkStructureValue = permalinkStructureValue.replace( textToAppend + '/', '' );
+
+ $permalinkStructure.val( '/' === permalinkStructureValue ? '' : permalinkStructureValue );
+
+ // Announce change to screen readers.
+ $( '#custom_selection_updated' ).text( textToAnnounce );
+
+ // Disable button.
+ changeStructureTagButtonState( $( this ) );
+
+ return;
+ }
+
+ // Input field never had focus, move selection to end of input.
+ if ( ! permalinkStructureFocused && 0 === selectionStart && 0 === selectionEnd ) {
+ selectionStart = selectionEnd = permalinkStructureValue.length;
+ }
+
+ $permalinkCustomSelection.prop( 'checked', true );
+
+ // Prepend and append slashes if necessary.
+ if ( '/' !== permalinkStructureValue.substr( 0, selectionStart ).substr( -1 ) ) {
+ textToAppend = '/' + textToAppend;
+ }
+
+ if ( '/' !== permalinkStructureValue.substr( selectionEnd, 1 ) ) {
+ textToAppend = textToAppend + '/';
+ }
+
+ // Insert structure tag at the specified position.
+ $permalinkStructure.val( permalinkStructureValue.substr( 0, selectionStart ) + textToAppend + permalinkStructureValue.substr( selectionEnd ) );
+
+ // Announce change to screen readers.
+ $( '#custom_selection_updated' ).text( textToAnnounce );
+
+ // Disable button.
+ changeStructureTagButtonState( $( this ) );
+
+ // If input had focus give it back with cursor right after appended text.
+ if ( permalinkStructureFocused && $permalinkStructure[0].setSelectionRange ) {
+ newSelectionStart = ( permalinkStructureValue.substr( 0, selectionStart ) + textToAppend ).length;
+ $permalinkStructure[0].setSelectionRange( newSelectionStart, newSelectionStart );
+ $permalinkStructure.focus();
+ }
+} );
+
+$document.ready( function() {
+ var checks, first, last, checked, sliced, mobileEvent, transitionTimeout, focusedRowActions,
+ lastClicked = false,
+ pageInput = $('input.current-page'),
+ currentPage = pageInput.val(),
+ isIOS = /iPhone|iPad|iPod/.test( navigator.userAgent ),
+ isAndroid = navigator.userAgent.indexOf( 'Android' ) !== -1,
+ isIE8 = $( document.documentElement ).hasClass( 'ie8' ),
+ $adminMenuWrap = $( '#adminmenuwrap' ),
+ $wpwrap = $( '#wpwrap' ),
+ $adminmenu = $( '#adminmenu' ),
+ $overlay = $( '#wp-responsive-overlay' ),
+ $toolbar = $( '#wp-toolbar' ),
+ $toolbarPopups = $toolbar.find( 'a[aria-haspopup="true"]' ),
+ $sortables = $('.meta-box-sortables'),
+ wpResponsiveActive = false,
+ $adminbar = $( '#wpadminbar' ),
+ lastScrollPosition = 0,
+ pinnedMenuTop = false,
+ pinnedMenuBottom = false,
+ menuTop = 0,
+ menuState,
+ menuIsPinned = false,
+ height = {
+ window: $window.height(),
+ wpwrap: $wpwrap.height(),
+ adminbar: $adminbar.height(),
+ menu: $adminMenuWrap.height()
+ },
+ $headerEnd = $( '.wp-header-end' );
+
+ /**
+ * Makes the fly-out submenu header clickable, when the menu is folded.
+ *
+ * @param {Event} e The event object.
+ *
+ * @returns {void}
+ */
+ $adminmenu.on('click.wp-submenu-head', '.wp-submenu-head', function(e){
+ $(e.target).parent().siblings('a').get(0).click();
+ });
+
+ /**
+ * Collapses the admin menu.
+ *
+ * @returns {void}
+ */
+ $( '#collapse-button' ).on( 'click.collapse-menu', function() {
+ var viewportWidth = getViewportWidth() || 961;
+
+ // reset any compensation for submenus near the bottom of the screen
+ $('#adminmenu div.wp-submenu').css('margin-top', '');
+
+ if ( viewportWidth < 960 ) {
+ if ( $body.hasClass('auto-fold') ) {
+ $body.removeClass('auto-fold').removeClass('folded');
+ setUserSetting('unfold', 1);
+ setUserSetting('mfold', 'o');
+ menuState = 'open';
+ } else {
+ $body.addClass('auto-fold');
+ setUserSetting('unfold', 0);
+ menuState = 'folded';
+ }
+ } else {
+ if ( $body.hasClass('folded') ) {
+ $body.removeClass('folded');
+ setUserSetting('mfold', 'o');
+ menuState = 'open';
+ } else {
+ $body.addClass('folded');
+ setUserSetting('mfold', 'f');
+ menuState = 'folded';
+ }
+ }
+
+ $document.trigger( 'wp-collapse-menu', { state: menuState } );
+ });
+
+ /**
+ * Handles the `aria-haspopup` attribute on the current menu item when it has a submenu.
+ *
+ * @since 4.4.0
+ *
+ * @returns {void}
+ */
+ function currentMenuItemHasPopup() {
+ var $current = $( 'a.wp-has-current-submenu' );
+
+ if ( 'folded' === menuState ) {
+ // When folded or auto-folded and not responsive view, the current menu item does have a fly-out sub-menu.
+ $current.attr( 'aria-haspopup', 'true' );
+ } else {
+ // When expanded or in responsive view, reset aria-haspopup.
+ $current.attr( 'aria-haspopup', 'false' );
+ }
+ }
+
+ $document.on( 'wp-menu-state-set wp-collapse-menu wp-responsive-activate wp-responsive-deactivate', currentMenuItemHasPopup );
+
+ /**
+ * Ensures an admin submenu is within the visual viewport.
+ *
+ * @since 4.1.0
+ *
+ * @param {jQuery} $menuItem The parent menu item containing the submenu.
+ *
+ * @returns {void}
+ */
+ function adjustSubmenu( $menuItem ) {
+ var bottomOffset, pageHeight, adjustment, theFold, menutop, wintop, maxtop,
+ $submenu = $menuItem.find( '.wp-submenu' );
+
+ menutop = $menuItem.offset().top;
+ wintop = $window.scrollTop();
+ maxtop = menutop - wintop - 30; // max = make the top of the sub almost touch admin bar
+
+ bottomOffset = menutop + $submenu.height() + 1; // Bottom offset of the menu
+ pageHeight = $wpwrap.height(); // Height of the entire page
+ adjustment = 60 + bottomOffset - pageHeight;
+ theFold = $window.height() + wintop - 50; // The fold
+
+ if ( theFold < ( bottomOffset - adjustment ) ) {
+ adjustment = bottomOffset - theFold;
+ }
+
+ if ( adjustment > maxtop ) {
+ adjustment = maxtop;
+ }
+
+ if ( adjustment > 1 ) {
+ $submenu.css( 'margin-top', '-' + adjustment + 'px' );
+ } else {
+ $submenu.css( 'margin-top', '' );
+ }
+ }
+
+ if ( 'ontouchstart' in window || /IEMobile\/[1-9]/.test(navigator.userAgent) ) { // touch screen device
+ // iOS Safari works with touchstart, the rest work with click
+ mobileEvent = isIOS ? 'touchstart' : 'click';
+
+ /**
+ * Closes any open submenus when touch/click is not on the menu.
+ *
+ * @param {Event} e The event object.
+ *
+ * @returns {void}
+ */
+ $body.on( mobileEvent+'.wp-mobile-hover', function(e) {
+ if ( $adminmenu.data('wp-responsive') ) {
+ return;
+ }
+
+ if ( ! $( e.target ).closest( '#adminmenu' ).length ) {
+ $adminmenu.find( 'li.opensub' ).removeClass( 'opensub' );
+ }
+ });
+
+ /**
+ * Handles the opening or closing the submenu based on the mobile click|touch event.
+ *
+ * @param {Event} event The event object.
+ *
+ * @returns {void}
+ */
+ $adminmenu.find( 'a.wp-has-submenu' ).on( mobileEvent + '.wp-mobile-hover', function( event ) {
+ var $menuItem = $(this).parent();
+
+ if ( $adminmenu.data( 'wp-responsive' ) ) {
+ return;
+ }
+
+ // Show the sub instead of following the link if:
+ // - the submenu is not open
+ // - the submenu is not shown inline or the menu is not folded
+ if ( ! $menuItem.hasClass( 'opensub' ) && ( ! $menuItem.hasClass( 'wp-menu-open' ) || $menuItem.width() < 40 ) ) {
+ event.preventDefault();
+ adjustSubmenu( $menuItem );
+ $adminmenu.find( 'li.opensub' ).removeClass( 'opensub' );
+ $menuItem.addClass('opensub');
+ }
+ });
+ }
+
+ if ( ! isIOS && ! isAndroid ) {
+ $adminmenu.find( 'li.wp-has-submenu' ).hoverIntent({
+
+ /**
+ * Opens the submenu when hovered over the menu item for desktops.
+ *
+ * @returns {void}
+ */
+ over: function() {
+ var $menuItem = $( this ),
+ $submenu = $menuItem.find( '.wp-submenu' ),
+ top = parseInt( $submenu.css( 'top' ), 10 );
+
+ if ( isNaN( top ) || top > -5 ) { // the submenu is visible
+ return;
+ }
+
+ if ( $adminmenu.data( 'wp-responsive' ) ) {
+ // The menu is in responsive mode, bail
+ return;
+ }
+
+ adjustSubmenu( $menuItem );
+ $adminmenu.find( 'li.opensub' ).removeClass( 'opensub' );
+ $menuItem.addClass( 'opensub' );
+ },
+
+ /**
+ * Closes the submenu when no longer hovering the menu item.
+ *
+ * @returns {void}
+ */
+ out: function(){
+ if ( $adminmenu.data( 'wp-responsive' ) ) {
+ // The menu is in responsive mode, bail
+ return;
+ }
+
+ $( this ).removeClass( 'opensub' ).find( '.wp-submenu' ).css( 'margin-top', '' );
+ },
+ timeout: 200,
+ sensitivity: 7,
+ interval: 90
+ });
+
+ /**
+ * Opens the submenu on when focused on the menu item.
+ *
+ * @param {Event} event The event object.
+ *
+ * @returns {void}
+ */
+ $adminmenu.on( 'focus.adminmenu', '.wp-submenu a', function( event ) {
+ if ( $adminmenu.data( 'wp-responsive' ) ) {
+ // The menu is in responsive mode, bail
+ return;
+ }
+
+ $( event.target ).closest( 'li.menu-top' ).addClass( 'opensub' );
+
+ /**
+ * Closes the submenu on blur from the menu item.
+ *
+ * @param {Event} event The event object.
+ *
+ * @returns {void}
+ */
+ }).on( 'blur.adminmenu', '.wp-submenu a', function( event ) {
+ if ( $adminmenu.data( 'wp-responsive' ) ) {
+ return;
+ }
+
+ $( event.target ).closest( 'li.menu-top' ).removeClass( 'opensub' );
+
+ /**
+ * Adjusts the size for the submenu.
+ *
+ * @returns {void}
+ */
+ }).find( 'li.wp-has-submenu.wp-not-current-submenu' ).on( 'focusin.adminmenu', function() {
+ adjustSubmenu( $( this ) );
+ });
+ }
+
+ /*
+ * The `.below-h2` class is here just for backward compatibility with plugins
+ * that are (incorrectly) using it. Do not use. Use `.inline` instead. See #34570.
+ * If '.wp-header-end' is found, append the notices after it otherwise
+ * after the first h1 or h2 heading found within the main content.
+ */
+ if ( ! $headerEnd.length ) {
+ $headerEnd = $( '.wrap h1, .wrap h2' ).first();
+ }
+ $( 'div.updated, div.error, div.notice' ).not( '.inline, .below-h2' ).insertAfter( $headerEnd );
+
+ /**
+ * Makes notices dismissible.
+ *
+ * @since 4.4.0
+ *
+ * @returns {void}
+ */
+ function makeNoticesDismissible() {
+ $( '.notice.is-dismissible' ).each( function() {
+ var $el = $( this ),
+ $button = $( '<button type="button" class="notice-dismiss"><span class="screen-reader-text"></span></button>' ),
+ btnText = commonL10n.dismiss || '';
+
+ // Ensure plain text
+ $button.find( '.screen-reader-text' ).text( btnText );
+ $button.on( 'click.wp-dismiss-notice', function( event ) {
+ event.preventDefault();
+ $el.fadeTo( 100, 0, function() {
+ $el.slideUp( 100, function() {
+ $el.remove();
+ });
+ });
+ });
+
+ $el.append( $button );
+ });
+ }
+
+ $document.on( 'wp-updates-notice-added wp-plugin-install-error wp-plugin-update-error wp-plugin-delete-error wp-theme-install-error wp-theme-delete-error', makeNoticesDismissible );
+
+ // Init screen meta
+ screenMeta.init();
+
+ /**
+ * Checks a checkbox.
+ *
+ * This event needs to be delegated. Ticket #37973.
+ *
+ * @returns {boolean} Returns whether a checkbox is checked or not.
+ */
+ $body.on( 'click', 'tbody > tr > .check-column :checkbox', function( event ) {
+ // Shift click to select a range of checkboxes.
+ if ( 'undefined' == event.shiftKey ) { return true; }
+ if ( event.shiftKey ) {
+ if ( !lastClicked ) { return true; }
+ checks = $( lastClicked ).closest( 'form' ).find( ':checkbox' ).filter( ':visible:enabled' );
+ first = checks.index( lastClicked );
+ last = checks.index( this );
+ checked = $(this).prop('checked');
+ if ( 0 < first && 0 < last && first != last ) {
+ sliced = ( last > first ) ? checks.slice( first, last ) : checks.slice( last, first );
+ sliced.prop( 'checked', function() {
+ if ( $(this).closest('tr').is(':visible') )
+ return checked;
+
+ return false;
+ });
+ }
+ }
+ lastClicked = this;
+
+ // Toggle the "Select all" checkboxes depending if the other ones are all checked or not.
+ var unchecked = $(this).closest('tbody').find(':checkbox').filter(':visible:enabled').not(':checked');
+
+ /**
+ * Determines if all checkboxes are checked.
+ *
+ * @returns {boolean} Returns true if there are no unchecked checkboxes.
+ */
+ $(this).closest('table').children('thead, tfoot').find(':checkbox').prop('checked', function() {
+ return ( 0 === unchecked.length );
+ });
+
+ return true;
+ });
+
+ /**
+ * Controls all the toggles on bulk toggle change.
+ *
+ * When the bulk checkbox is changed, all the checkboxes in the tables are changed accordingly.
+ * When the shift-button is pressed while changing the bulk checkbox the checkboxes in the table are inverted.
+ *
+ * This event needs to be delegated. Ticket #37973.
+ *
+ * @param {Event} event The event object.
+ *
+ * @returns {boolean}
+ */
+ $body.on( 'click.wp-toggle-checkboxes', 'thead .check-column :checkbox, tfoot .check-column :checkbox', function( event ) {
+ var $this = $(this),
+ $table = $this.closest( 'table' ),
+ controlChecked = $this.prop('checked'),
+ toggle = event.shiftKey || $this.data('wp-toggle');
+
+ $table.children( 'tbody' ).filter(':visible')
+ .children().children('.check-column').find(':checkbox')
+ /**
+ * Updates the checked state on the checkbox in the table.
+ *
+ * @returns {boolean} True checks the checkbox, False unchecks the checkbox.
+ */
+ .prop('checked', function() {
+ if ( $(this).is(':hidden,:disabled') ) {
+ return false;
+ }
+
+ if ( toggle ) {
+ return ! $(this).prop( 'checked' );
+ } else if ( controlChecked ) {
+ return true;
+ }
+
+ return false;
+ });
+
+ $table.children('thead, tfoot').filter(':visible')
+ .children().children('.check-column').find(':checkbox')
+
+ /**
+ * Syncs the bulk checkboxes on the top and bottom of the table.
+ *
+ * @returns {boolean} True checks the checkbox, False unchecks the checkbox.
+ */
+ .prop('checked', function() {
+ if ( toggle ) {
+ return false;
+ } else if ( controlChecked ) {
+ return true;
+ }
+
+ return false;
+ });
+ });
+
+ /**
+ * Shows row actions on focus of its parent container element or any other elements contained within.
+ *
+ * @returns {void}
+ */
+ $( '#wpbody-content' ).on({
+ focusin: function() {
+ clearTimeout( transitionTimeout );
+ focusedRowActions = $( this ).find( '.row-actions' );
+ // transitionTimeout is necessary for Firefox, but Chrome won't remove the CSS class without a little help.
+ $( '.row-actions' ).not( this ).removeClass( 'visible' );
+ focusedRowActions.addClass( 'visible' );
+ },
+ focusout: function() {
+ // Tabbing between post title and .row-actions links needs a brief pause, otherwise
+ // the .row-actions div gets hidden in transit in some browsers (ahem, Firefox).
+ transitionTimeout = setTimeout( function() {
+ focusedRowActions.removeClass( 'visible' );
+ }, 30 );
+ }
+ }, '.has-row-actions' );
+
+ // Toggle list table rows on small screens
+ $( 'tbody' ).on( 'click', '.toggle-row', function() {
+ $( this ).closest( 'tr' ).toggleClass( 'is-expanded' );
+ });
+
+ $('#default-password-nag-no').click( function() {
+ setUserSetting('default_password_nag', 'hide');
+ $('div.default-password-nag').hide();
+ return false;
+ });
+
+ /**
+ * Handles tab keypresses in theme and plugin editor textareas.
+ *
+ * @param {Event} e The event object.
+ *
+ * @returns {void}
+ */
+ $('#newcontent').bind('keydown.wpevent_InsertTab', function(e) {
+ var el = e.target, selStart, selEnd, val, scroll, sel;
+
+ // After pressing escape key (keyCode: 27), the tab key should tab out of the textarea.
+ if ( e.keyCode == 27 ) {
+ // when pressing Escape: Opera 12 and 27 blur form fields, IE 8 clears them
+ e.preventDefault();
+ $(el).data('tab-out', true);
+ return;
+ }
+
+ // Only listen for plain tab key (keyCode: 9) without any modifiers.
+ if ( e.keyCode != 9 || e.ctrlKey || e.altKey || e.shiftKey )
+ return;
+
+ // After tabbing out, reset it so next time the tab key can be used again.
+ if ( $(el).data('tab-out') ) {
+ $(el).data('tab-out', false);
+ return;
+ }
+
+ selStart = el.selectionStart;
+ selEnd = el.selectionEnd;
+ val = el.value;
+
+ // If any text is selected, replace the selection with a tab character.
+ if ( document.selection ) {
+ el.focus();
+ sel = document.selection.createRange();
+ sel.text = '\t';
+ } else if ( selStart >= 0 ) {
+ scroll = this.scrollTop;
+ el.value = val.substring(0, selStart).concat('\t', val.substring(selEnd) );
+ el.selectionStart = el.selectionEnd = selStart + 1;
+ this.scrollTop = scroll;
+ }
+
+ // Cancel the regular tab functionality, to prevent losing focus of the textarea.
+ if ( e.stopPropagation )
+ e.stopPropagation();
+ if ( e.preventDefault )
+ e.preventDefault();
+ });
+
+ // Reset page number variable for new filters/searches but not for bulk actions. See #17685.
+ if ( pageInput.length ) {
+
+ /**
+ * Handles pagination variable when filtering the list table.
+ *
+ * Set the pagination argument to the first page when the post-filter form is submitted.
+ * This happens when pressing the 'filter' button on the list table page.
+ *
+ * The pagination argument should not be touched when the bulk action dropdowns are set to do anything.
+ *
+ * The form closest to the pageInput is the post-filter form.
+ *
+ * @returns {void}
+ */
+ pageInput.closest('form').submit( function() {
+ /*
+ * action = bulk action dropdown at the top of the table
+ * action2 = bulk action dropdow at the bottom of the table
+ */
+ if ( $('select[name="action"]').val() == -1 && $('select[name="action2"]').val() == -1 && pageInput.val() == currentPage )
+ pageInput.val('1');
+ });
+ }
+
+ /**
+ * Resets the bulk actions when the search button is clicked.
+ *
+ * @returns {void}
+ */
+ $('.search-box input[type="search"], .search-box input[type="submit"]').mousedown(function () {
+ $('select[name^="action"]').val('-1');
+ });
+
+ /**
+ * Scrolls into view when focus.scroll-into-view is triggered.
+ *
+ * @param {Event} e The event object.
+ *
+ * @returns {void}
+ */
+ $('#contextual-help-link, #show-settings-link').on( 'focus.scroll-into-view', function(e){
+ if ( e.target.scrollIntoView )
+ e.target.scrollIntoView(false);
+ });
+
+ /**
+ * Disables the submit upload buttons when no data is entered.
+ *
+ * @returns {void}
+ */
+ (function(){
+ var button, input, form = $('form.wp-upload-form');
+
+ // Exit when no upload form is found.
+ if ( ! form.length )
+ return;
+
+ button = form.find('input[type="submit"]');
+ input = form.find('input[type="file"]');
+
+ /**
+ * Determines if any data is entered in any file upload input.
+ *
+ * @since 3.5.0
+ *
+ * @returns {void}
+ */
+ function toggleUploadButton() {
+ // When no inputs have a value, disable the upload buttons.
+ button.prop('disabled', '' === input.map( function() {
+ return $(this).val();
+ }).get().join(''));
+ }
+
+ // Update the status initially.
+ toggleUploadButton();
+ // Update the status when any file input changes.
+ input.on('change', toggleUploadButton);
+ })();
+
+ /**
+ * Pins the menu while distraction-free writing is enabled.
+ *
+ * @param {Event} event Event data.
+ *
+ * @since 4.1.0
+ *
+ * @returns {void}
+ */
+ function pinMenu( event ) {
+ var windowPos = $window.scrollTop(),
+ resizing = ! event || event.type !== 'scroll';
+
+ if ( isIOS || isIE8 || $adminmenu.data( 'wp-responsive' ) ) {
+ return;
+ }
+
+ /*
+ * When the menu is higher than the window and smaller than the entire page.
+ * It should be adjusted to be able to see the entire menu.
+ *
+ * Otherwise it can be accessed normally.
+ */
+ if ( height.menu + height.adminbar < height.window ||
+ height.menu + height.adminbar + 20 > height.wpwrap ) {
+ unpinMenu();
+ return;
+ }
+
+ menuIsPinned = true;
+
+ // If the menu is higher than the window, compensate on scroll.
+ if ( height.menu + height.adminbar > height.window ) {
+ // Check for overscrolling, this happens when swiping up at the top of the document in modern browsers.
+ if ( windowPos < 0 ) {
+ // Stick the menu to the top.
+ if ( ! pinnedMenuTop ) {
+ pinnedMenuTop = true;
+ pinnedMenuBottom = false;
+
+ $adminMenuWrap.css({
+ position: 'fixed',
+ top: '',
+ bottom: ''
+ });
+ }
+
+ return;
+ } else if ( windowPos + height.window > $document.height() - 1 ) {
+ // When overscrolling at the bottom, stick the menu to the bottom.
+ if ( ! pinnedMenuBottom ) {
+ pinnedMenuBottom = true;
+ pinnedMenuTop = false;
+
+ $adminMenuWrap.css({
+ position: 'fixed',
+ top: '',
+ bottom: 0
+ });
+ }
+
+ return;
+ }
+
+ if ( windowPos > lastScrollPosition ) {
+ // When a down scroll has been detected.
+
+ // If it was pinned to the top, unpin and calculate relative scroll.
+ if ( pinnedMenuTop ) {
+ pinnedMenuTop = false;
+ // Calculate new offset position.
+ menuTop = $adminMenuWrap.offset().top - height.adminbar - ( windowPos - lastScrollPosition );
+
+ if ( menuTop + height.menu + height.adminbar < windowPos + height.window ) {
+ menuTop = windowPos + height.window - height.menu - height.adminbar;
+ }
+
+ $adminMenuWrap.css({
+ position: 'absolute',
+ top: menuTop,
+ bottom: ''
+ });
+ } else if ( ! pinnedMenuBottom && $adminMenuWrap.offset().top + height.menu < windowPos + height.window ) {
+ // Pin it to the bottom.
+ pinnedMenuBottom = true;
+
+ $adminMenuWrap.css({
+ position: 'fixed',
+ top: '',
+ bottom: 0
+ });
+ }
+ } else if ( windowPos < lastScrollPosition ) {
+ // When a scroll up is detected.
+
+ // If it was pinned to the bottom, unpin and calculate relative scroll.
+ if ( pinnedMenuBottom ) {
+ pinnedMenuBottom = false;
+
+ // Calculate new offset position.
+ menuTop = $adminMenuWrap.offset().top - height.adminbar + ( lastScrollPosition - windowPos );
+
+ if ( menuTop + height.menu > windowPos + height.window ) {
+ menuTop = windowPos;
+ }
+
+ $adminMenuWrap.css({
+ position: 'absolute',
+ top: menuTop,
+ bottom: ''
+ });
+ } else if ( ! pinnedMenuTop && $adminMenuWrap.offset().top >= windowPos + height.adminbar ) {
+
+ // Pin it to the top.
+ pinnedMenuTop = true;
+
+ $adminMenuWrap.css({
+ position: 'fixed',
+ top: '',
+ bottom: ''
+ });
+ }
+ } else if ( resizing ) {
+ // Window is being resized.
+
+ pinnedMenuTop = pinnedMenuBottom = false;
+
+ // Calculate the new offset.
+ menuTop = windowPos + height.window - height.menu - height.adminbar - 1;
+
+ if ( menuTop > 0 ) {
+ $adminMenuWrap.css({
+ position: 'absolute',
+ top: menuTop,
+ bottom: ''
+ });
+ } else {
+ unpinMenu();
+ }
+ }
+ }
+
+ lastScrollPosition = windowPos;
+ }
+
+ /**
+ * Determines the height of certain elements.
+ *
+ * @since 4.1.0
+ *
+ * @returns {void}
+ */
+ function resetHeights() {
+ height = {
+ window: $window.height(),
+ wpwrap: $wpwrap.height(),
+ adminbar: $adminbar.height(),
+ menu: $adminMenuWrap.height()
+ };
+ }
+
+ /**
+ * Unpins the menu.
+ *
+ * @since 4.1.0
+ *
+ * @returns {void}
+ */
+ function unpinMenu() {
+ if ( isIOS || ! menuIsPinned ) {
+ return;
+ }
+
+ pinnedMenuTop = pinnedMenuBottom = menuIsPinned = false;
+ $adminMenuWrap.css({
+ position: '',
+ top: '',
+ bottom: ''
+ });
+ }
+
+ /**
+ * Pins and unpins the menu when applicable.
+ *
+ * @since 4.1.0
+ *
+ * @returns {void}
+ */
+ function setPinMenu() {
+ resetHeights();
+
+ if ( $adminmenu.data('wp-responsive') ) {
+ $body.removeClass( 'sticky-menu' );
+ unpinMenu();
+ } else if ( height.menu + height.adminbar > height.window ) {
+ pinMenu();
+ $body.removeClass( 'sticky-menu' );
+ } else {
+ $body.addClass( 'sticky-menu' );
+ unpinMenu();
+ }
+ }
+
+ if ( ! isIOS ) {
+ $window.on( 'scroll.pin-menu', pinMenu );
+ $document.on( 'tinymce-editor-init.pin-menu', function( event, editor ) {
+ editor.on( 'wp-autoresize', resetHeights );
+ });
+ }
+
+ /**
+ * Changes the sortables and responsiveness of metaboxes.
+ *
+ * @since 3.8.0
+ *
+ *@returns {void}
+ */
+ window.wpResponsive = {
+
+ /**
+ * Initializes the wpResponsive object.
+ *
+ * @since 3.8.0
+ *
+ * @returns {void}
+ */
+ init: function() {
+ var self = this;
+
+ // Modify functionality based on custom activate/deactivate event
+ $document.on( 'wp-responsive-activate.wp-responsive', function() {
+ self.activate();
+ }).on( 'wp-responsive-deactivate.wp-responsive', function() {
+ self.deactivate();
+ });
+
+ $( '#wp-admin-bar-menu-toggle a' ).attr( 'aria-expanded', 'false' );
+
+ // Toggle sidebar when toggle is clicked.
+ $( '#wp-admin-bar-menu-toggle' ).on( 'click.wp-responsive', function( event ) {
+ event.preventDefault();
+
+ // close any open toolbar submenus.
+ $adminbar.find( '.hover' ).removeClass( 'hover' );
+
+ $wpwrap.toggleClass( 'wp-responsive-open' );
+ if ( $wpwrap.hasClass( 'wp-responsive-open' ) ) {
+ $(this).find('a').attr( 'aria-expanded', 'true' );
+ $( '#adminmenu a:first' ).focus();
+ } else {
+ $(this).find('a').attr( 'aria-expanded', 'false' );
+ }
+ } );
+
+ // Add menu events.
+ $adminmenu.on( 'click.wp-responsive', 'li.wp-has-submenu > a', function( event ) {
+ if ( ! $adminmenu.data('wp-responsive') ) {
+ return;
+ }
+
+ $( this ).parent( 'li' ).toggleClass( 'selected' );
+ event.preventDefault();
+ });
+
+ self.trigger();
+ $document.on( 'wp-window-resized.wp-responsive', $.proxy( this.trigger, this ) );
+
+ // This needs to run later as UI Sortable may be initialized later on $(document).ready().
+ $window.on( 'load.wp-responsive', function() {
+ var width = navigator.userAgent.indexOf('AppleWebKit/') > -1 ? $window.width() : window.innerWidth;
+
+ if ( width <= 782 ) {
+ self.disableSortables();
+ }
+ });
+ },
+
+ /**
+ * Changes properties of body and admin menu.
+ *
+ * Pins and unpins the menu and adds the auto-fold class to the body.
+ * Makes the admin menu responsive and disables the metabox sortables.
+ *
+ * @since 3.8.0
+ *
+ * @returns {void}
+ */
+ activate: function() {
+ setPinMenu();
+
+ if ( ! $body.hasClass( 'auto-fold' ) ) {
+ $body.addClass( 'auto-fold' );
+ }
+
+ $adminmenu.data( 'wp-responsive', 1 );
+ this.disableSortables();
+ },
+
+ /**
+ * Changes properties of admin menu and enables metabox sortables.
+ *
+ * Pin and unpin the menu.
+ * Removes the responsiveness of the admin menu and enables the metabox sortables.
+ *
+ * @since 3.8.0
+ *
+ * @returns {void}
+ */
+ deactivate: function() {
+ setPinMenu();
+ $adminmenu.removeData('wp-responsive');
+ this.enableSortables();
+ },
+
+ /**
+ * Sets the responsiveness and enables the overlay based on the viewport width.
+ *
+ * @since 3.8.0
+ *
+ * @returns {void}
+ */
+ trigger: function() {
+ var viewportWidth = getViewportWidth();
+
+ // Exclude IE < 9, it doesn't support @media CSS rules.
+ if ( ! viewportWidth ) {
+ return;
+ }
+
+ if ( viewportWidth <= 782 ) {
+ if ( ! wpResponsiveActive ) {
+ $document.trigger( 'wp-responsive-activate' );
+ wpResponsiveActive = true;
+ }
+ } else {
+ if ( wpResponsiveActive ) {
+ $document.trigger( 'wp-responsive-deactivate' );
+ wpResponsiveActive = false;
+ }
+ }
+
+ if ( viewportWidth <= 480 ) {
+ this.enableOverlay();
+ } else {
+ this.disableOverlay();
+ }
+ },
+
+ /**
+ * Inserts a responsive overlay and toggles the window.
+ *
+ * @since 3.8.0
+ *
+ * @returns {void}
+ */
+ enableOverlay: function() {
+ if ( $overlay.length === 0 ) {
+ $overlay = $( '<div id="wp-responsive-overlay"></div>' )
+ .insertAfter( '#wpcontent' )
+ .hide()
+ .on( 'click.wp-responsive', function() {
+ $toolbar.find( '.menupop.hover' ).removeClass( 'hover' );
+ $( this ).hide();
+ });
+ }
+
+ $toolbarPopups.on( 'click.wp-responsive', function() {
+ $overlay.show();
+ });
+ },
+
+ /**
+ * Disables the responsive overlay and removes the overlay.
+ *
+ * @since 3.8.0
+ *
+ * @returns {void}
+ */
+ disableOverlay: function() {
+ $toolbarPopups.off( 'click.wp-responsive' );
+ $overlay.hide();
+ },
+
+ /**
+ * Disables sortables.
+ *
+ * @since 3.8.0
+ *
+ * @returns {void}
+ */
+ disableSortables: function() {
+ if ( $sortables.length ) {
+ try {
+ $sortables.sortable( 'disable' );
+ } catch ( e ) {}
+ }
+ },
+
+ /**
+ * Enables sortables.
+ *
+ * @since 3.8.0
+ *
+ * @returns {void}
+ */
+ enableSortables: function() {
+ if ( $sortables.length ) {
+ try {
+ $sortables.sortable( 'enable' );
+ } catch ( e ) {}
+ }
+ }
+ };
+
+ /**
+ * Add an ARIA role `button` to elements that behave like UI controls when JavaScript is on.
+ *
+ * @since 4.5.0
+ *
+ * @returns {void}
+ */
+ function aria_button_if_js() {
+ $( '.aria-button-if-js' ).attr( 'role', 'button' );
+ }
+
+ $( document ).ajaxComplete( function() {
+ aria_button_if_js();
+ });
+
+ /**
+ * Get the viewport width.
+ *
+ * @since 4.7.0
+ *
+ * @returns {number|boolean} The current viewport width or false if the
+ * browser doesn't support innerWidth (IE < 9).
+ */
+ function getViewportWidth() {
+ var viewportWidth = false;
+
+ if ( window.innerWidth ) {
+ // On phones, window.innerWidth is affected by zooming.
+ viewportWidth = Math.max( window.innerWidth, document.documentElement.clientWidth );
+ }
+
+ return viewportWidth;
+ }
+
+ /**
+ * Sets the admin menu collapsed/expanded state.
+ *
+ * Sets the global variable `menuState` and triggers a custom event passing
+ * the current menu state.
+ *
+ * @since 4.7.0
+ *
+ * @returns {void}
+ */
+ function setMenuState() {
+ var viewportWidth = getViewportWidth() || 961;
+
+ if ( viewportWidth <= 782 ) {
+ menuState = 'responsive';
+ } else if ( $body.hasClass( 'folded' ) || ( $body.hasClass( 'auto-fold' ) && viewportWidth <= 960 && viewportWidth > 782 ) ) {
+ menuState = 'folded';
+ } else {
+ menuState = 'open';
+ }
+
+ $document.trigger( 'wp-menu-state-set', { state: menuState } );
+ }
+
+ // Set the menu state when the window gets resized.
+ $document.on( 'wp-window-resized.set-menu-state', setMenuState );
+
+ /**
+ * Sets ARIA attributes on the collapse/expand menu button.
+ *
+ * When the admin menu is open or folded, updates the `aria-expanded` and
+ * `aria-label` attributes of the button to give feedback to assistive
+ * technologies. In the responsive view, the button is always hidden.
+ *
+ * @since 4.7.0
+ *
+ * @returns {void}
+ */
+ $document.on( 'wp-menu-state-set wp-collapse-menu', function( event, eventData ) {
+ var $collapseButton = $( '#collapse-button' ),
+ ariaExpanded = 'true',
+ ariaLabelText = commonL10n.collapseMenu;
+
+ if ( 'folded' === eventData.state ) {
+ ariaExpanded = 'false';
+ ariaLabelText = commonL10n.expandMenu;
+ }
+
+ $collapseButton.attr({
+ 'aria-expanded': ariaExpanded,
+ 'aria-label': ariaLabelText
+ });
+ });
+
+ window.wpResponsive.init();
+ setPinMenu();
+ setMenuState();
+ currentMenuItemHasPopup();
+ makeNoticesDismissible();
+ aria_button_if_js();
+
+ $document.on( 'wp-pin-menu wp-window-resized.pin-menu postboxes-columnchange.pin-menu postbox-toggled.pin-menu wp-collapse-menu.pin-menu wp-scroll-start.pin-menu', setPinMenu );
+
+ // Set initial focus on a specific element.
+ $( '.wp-initial-focus' ).focus();
+
+ // Toggle update details on update-core.php.
+ $body.on( 'click', '.js-update-details-toggle', function() {
+ var $updateNotice = $( this ).closest( '.js-update-details' ),
+ $progressDiv = $( '#' + $updateNotice.data( 'update-details' ) );
+
+ /*
+ * When clicking on "Show details" move the progress div below the update
+ * notice. Make sure it gets moved just the first time.
+ */
+ if ( ! $progressDiv.hasClass( 'update-details-moved' ) ) {
+ $progressDiv.insertAfter( $updateNotice ).addClass( 'update-details-moved' );
+ }
+
+ // Toggle the progress div visibility.
+ $progressDiv.toggle();
+ // Toggle the Show Details button expanded state.
+ $( this ).attr( 'aria-expanded', $progressDiv.is( ':visible' ) );
+ });
+});
+
+// Fire a custom jQuery event at the end of window resize.
+( function() {
+ var timeout;
+
+ /**
+ * Triggers the WP window-resize event.
+ *
+ * @since 3.8.0
+ *
+ * @returns {void}
+ */
+ function triggerEvent() {
+ $document.trigger( 'wp-window-resized' );
+ }
+
+ /**
+ * Fires the trigger event again after 200 ms.
+ *
+ * @since 3.8.0
+ *
+ * @returns {void}
+ */
+ function fireOnce() {
+ window.clearTimeout( timeout );
+ timeout = window.setTimeout( triggerEvent, 200 );
+ }
+
+ $window.on( 'resize.wp-fire-once', fireOnce );
+}());
+
+// Make Windows 8 devices play along nicely.
+(function(){
+ if ( '-ms-user-select' in document.documentElement.style && navigator.userAgent.match(/IEMobile\/10\.0/) ) {
+ var msViewportStyle = document.createElement( 'style' );
+ msViewportStyle.appendChild(
+ document.createTextNode( '@-ms-viewport{width:auto!important}' )
+ );
+ document.getElementsByTagName( 'head' )[0].appendChild( msViewportStyle );
+ }
+})();
+
+}( jQuery, window ));
diff --git a/www/crm/wp-admin/js/common.min.js b/www/crm/wp-admin/js/common.min.js
new file mode 100644
index 00000000..8181000a
--- /dev/null
+++ b/www/crm/wp-admin/js/common.min.js
@@ -0,0 +1 @@
+!function(a,b,c){function d(a){-1!==i.val().indexOf(a.text().trim())?(a.attr("data-label",a.attr("aria-label")),a.attr("aria-label",a.attr("data-used")),a.attr("aria-pressed",!0),a.addClass("active")):a.attr("data-label")&&(a.attr("aria-label",a.attr("data-label")),a.attr("aria-pressed",!1),a.removeClass("active"))}var e=a(document),f=a(b),g=a(document.body);b.adminMenu={init:function(){},fold:function(){},restoreMenuState:function(){},toggle:function(){},favorites:function(){}},b.columns={init:function(){var b=this;a(".hide-column-tog","#adv-settings").click(function(){var c=a(this),d=c.val();c.prop("checked")?b.checked(d):b.unchecked(d),columns.saveManageColumnsState()})},saveManageColumnsState:function(){var b=this.hidden();a.post(ajaxurl,{action:"hidden-columns",hidden:b,screenoptionnonce:a("#screenoptionnonce").val(),page:pagenow})},checked:function(b){a(".column-"+b).removeClass("hidden"),this.colSpanChange(1)},unchecked:function(b){a(".column-"+b).addClass("hidden"),this.colSpanChange(-1)},hidden:function(){return a(".manage-column[id]").filter(".hidden").map(function(){return this.id}).get().join(",")},useCheckboxesForHidden:function(){this.hidden=function(){return a(".hide-column-tog").not(":checked").map(function(){var a=this.id;return a.substring(a,a.length-5)}).get().join(",")}},colSpanChange:function(b){var c,d=a("table").find(".colspanchange");d.length&&(c=parseInt(d.attr("colspan"),10)+b,d.attr("colspan",c.toString()))}},e.ready(function(){columns.init()}),b.validateForm=function(b){return!a(b).find(".form-required").filter(function(){return""===a(":input:visible",this).val()}).addClass("form-invalid").find(":input:visible").change(function(){a(this).closest(".form-invalid").removeClass("form-invalid")}).length},b.showNotice={warn:function(){var a=commonL10n.warnDelete||"";return!!confirm(a)},note:function(a){alert(a)}},b.screenMeta={element:null,toggles:null,page:null,init:function(){this.element=a("#screen-meta"),this.toggles=a("#screen-meta-links").find(".show-settings"),this.page=a("#wpcontent"),this.toggles.click(this.toggleEvent)},toggleEvent:function(){var b=a("#"+a(this).attr("aria-controls"));b.length&&(b.is(":visible")?screenMeta.close(b,a(this)):screenMeta.open(b,a(this)))},open:function(b,c){a("#screen-meta-links").find(".screen-meta-toggle").not(c.parent()).css("visibility","hidden"),b.parent().show(),b.slideDown("fast",function(){b.focus(),c.addClass("screen-meta-active").attr("aria-expanded",!0)}),e.trigger("screen:options:open")},close:function(b,c){b.slideUp("fast",function(){c.removeClass("screen-meta-active").attr("aria-expanded",!1),a(".screen-meta-toggle").css("visibility",""),b.parent().hide()}),e.trigger("screen:options:close")}},a(".contextual-help-tabs").delegate("a","click",function(b){var c,d=a(this);return b.preventDefault(),!d.is(".active a")&&(a(".contextual-help-tabs .active").removeClass("active"),d.parent("li").addClass("active"),c=a(d.attr("href")),a(".help-tab-content").not(c).removeClass("active").hide(),void c.addClass("active").show())});var h=!1,i=a("#permalink_structure"),j=a(".permalink-structure input:radio"),k=a("#custom_selection"),l=a(".form-table.permalink-structure .available-structure-tags button");j.on("change",function(){"custom"!==this.value&&(i.val(this.value),l.each(function(){d(a(this))}))}),i.on("click input",function(){k.prop("checked",!0)}),i.on("focus",function(b){h=!0,a(this).off(b)}),l.each(function(){d(a(this))}),i.on("change",function(){l.each(function(){d(a(this))})}),l.on("click",function(){var b,c=i.val(),e=i[0].selectionStart,f=i[0].selectionEnd,g=a(this).text().trim(),j=a(this).attr("data-added");return-1!==c.indexOf(g)?(c=c.replace(g+"/",""),i.val("/"===c?"":c),a("#custom_selection_updated").text(j),void d(a(this))):(h||0!==e||0!==f||(e=f=c.length),k.prop("checked",!0),"/"!==c.substr(0,e).substr(-1)&&(g="/"+g),"/"!==c.substr(f,1)&&(g+="/"),i.val(c.substr(0,e)+g+c.substr(f)),a("#custom_selection_updated").text(j),d(a(this)),void(h&&i[0].setSelectionRange&&(b=(c.substr(0,e)+g).length,i[0].setSelectionRange(b,b),i.focus())))}),e.ready(function(){function c(){var b=a("a.wp-has-current-submenu");"folded"===x?b.attr("aria-haspopup","true"):b.attr("aria-haspopup","false")}function d(a){var b,c,d,e,g,h,i,j=a.find(".wp-submenu");g=a.offset().top,h=f.scrollTop(),i=g-h-30,b=g+j.height()+1,c=F.height(),d=60+b-c,e=f.height()+h-50,e<b-d&&(d=b-e),d>i&&(d=i),d>1?j.css("margin-top","-"+d+"px"):j.css("margin-top","")}function h(){a(".notice.is-dismissible").each(function(){var b=a(this),c=a('<button type="button" class="notice-dismiss"><span class="screen-reader-text"></span></button>'),d=commonL10n.dismiss||"";c.find(".screen-reader-text").text(d),c.on("click.wp-dismiss-notice",function(a){a.preventDefault(),b.fadeTo(100,0,function(){b.slideUp(100,function(){b.remove()})})}),b.append(c)})}function i(a){var b=f.scrollTop(),c=!a||"scroll"!==a.type;if(!(B||D||G.data("wp-responsive"))){if(S.menu+S.adminbar<S.window||S.menu+S.adminbar+20>S.wpwrap)return void k();if(R=!0,S.menu+S.adminbar>S.window){if(b<0)return void(O||(O=!0,P=!1,E.css({position:"fixed",top:"",bottom:""})));if(b+S.window>e.height()-1)return void(P||(P=!0,O=!1,E.css({position:"fixed",top:"",bottom:0})));b>N?O?(O=!1,Q=E.offset().top-S.adminbar-(b-N),Q+S.menu+S.adminbar<b+S.window&&(Q=b+S.window-S.menu-S.adminbar),E.css({position:"absolute",top:Q,bottom:""})):!P&&E.offset().top+S.menu<b+S.window&&(P=!0,E.css({position:"fixed",top:"",bottom:0})):b<N?P?(P=!1,Q=E.offset().top-S.adminbar+(N-b),Q+S.menu>b+S.window&&(Q=b),E.css({position:"absolute",top:Q,bottom:""})):!O&&E.offset().top>=b+S.adminbar&&(O=!0,E.css({position:"fixed",top:"",bottom:""})):c&&(O=P=!1,Q=b+S.window-S.menu-S.adminbar-1,Q>0?E.css({position:"absolute",top:Q,bottom:""}):k())}N=b}}function j(){S={window:f.height(),wpwrap:F.height(),adminbar:M.height(),menu:E.height()}}function k(){!B&&R&&(O=P=R=!1,E.css({position:"",top:"",bottom:""}))}function l(){j(),G.data("wp-responsive")?(g.removeClass("sticky-menu"),k()):S.menu+S.adminbar>S.window?(i(),g.removeClass("sticky-menu")):(g.addClass("sticky-menu"),k())}function m(){a(".aria-button-if-js").attr("role","button")}function n(){var a=!1;return b.innerWidth&&(a=Math.max(b.innerWidth,document.documentElement.clientWidth)),a}function o(){var a=n()||961;x=a<=782?"responsive":g.hasClass("folded")||g.hasClass("auto-fold")&&a<=960&&a>782?"folded":"open",e.trigger("wp-menu-state-set",{state:x})}var p,q,r,s,t,u,v,w,x,y=!1,z=a("input.current-page"),A=z.val(),B=/iPhone|iPad|iPod/.test(navigator.userAgent),C=navigator.userAgent.indexOf("Android")!==-1,D=a(document.documentElement).hasClass("ie8"),E=a("#adminmenuwrap"),F=a("#wpwrap"),G=a("#adminmenu"),H=a("#wp-responsive-overlay"),I=a("#wp-toolbar"),J=I.find('a[aria-haspopup="true"]'),K=a(".meta-box-sortables"),L=!1,M=a("#wpadminbar"),N=0,O=!1,P=!1,Q=0,R=!1,S={window:f.height(),wpwrap:F.height(),adminbar:M.height(),menu:E.height()},T=a(".wp-header-end");G.on("click.wp-submenu-head",".wp-submenu-head",function(b){a(b.target).parent().siblings("a").get(0).click()}),a("#collapse-button").on("click.collapse-menu",function(){var b=n()||961;a("#adminmenu div.wp-submenu").css("margin-top",""),b<960?g.hasClass("auto-fold")?(g.removeClass("auto-fold").removeClass("folded"),setUserSetting("unfold",1),setUserSetting("mfold","o"),x="open"):(g.addClass("auto-fold"),setUserSetting("unfold",0),x="folded"):g.hasClass("folded")?(g.removeClass("folded"),setUserSetting("mfold","o"),x="open"):(g.addClass("folded"),setUserSetting("mfold","f"),x="folded"),e.trigger("wp-collapse-menu",{state:x})}),e.on("wp-menu-state-set wp-collapse-menu wp-responsive-activate wp-responsive-deactivate",c),("ontouchstart"in b||/IEMobile\/[1-9]/.test(navigator.userAgent))&&(u=B?"touchstart":"click",g.on(u+".wp-mobile-hover",function(b){G.data("wp-responsive")||a(b.target).closest("#adminmenu").length||G.find("li.opensub").removeClass("opensub")}),G.find("a.wp-has-submenu").on(u+".wp-mobile-hover",function(b){var c=a(this).parent();G.data("wp-responsive")||c.hasClass("opensub")||c.hasClass("wp-menu-open")&&!(c.width()<40)||(b.preventDefault(),d(c),G.find("li.opensub").removeClass("opensub"),c.addClass("opensub"))})),B||C||(G.find("li.wp-has-submenu").hoverIntent({over:function(){var b=a(this),c=b.find(".wp-submenu"),e=parseInt(c.css("top"),10);isNaN(e)||e>-5||G.data("wp-responsive")||(d(b),G.find("li.opensub").removeClass("opensub"),b.addClass("opensub"))},out:function(){G.data("wp-responsive")||a(this).removeClass("opensub").find(".wp-submenu").css("margin-top","")},timeout:200,sensitivity:7,interval:90}),G.on("focus.adminmenu",".wp-submenu a",function(b){G.data("wp-responsive")||a(b.target).closest("li.menu-top").addClass("opensub")}).on("blur.adminmenu",".wp-submenu a",function(b){G.data("wp-responsive")||a(b.target).closest("li.menu-top").removeClass("opensub")}).find("li.wp-has-submenu.wp-not-current-submenu").on("focusin.adminmenu",function(){d(a(this))})),T.length||(T=a(".wrap h1, .wrap h2").first()),a("div.updated, div.error, div.notice").not(".inline, .below-h2").insertAfter(T),e.on("wp-updates-notice-added wp-plugin-install-error wp-plugin-update-error wp-plugin-delete-error wp-theme-install-error wp-theme-delete-error",h),screenMeta.init(),g.on("click","tbody > tr > .check-column :checkbox",function(b){if("undefined"==b.shiftKey)return!0;if(b.shiftKey){if(!y)return!0;p=a(y).closest("form").find(":checkbox").filter(":visible:enabled"),q=p.index(y),r=p.index(this),s=a(this).prop("checked"),0<q&&0<r&&q!=r&&(t=r>q?p.slice(q,r):p.slice(r,q),t.prop("checked",function(){return!!a(this).closest("tr").is(":visible")&&s}))}y=this;var c=a(this).closest("tbody").find(":checkbox").filter(":visible:enabled").not(":checked");return a(this).closest("table").children("thead, tfoot").find(":checkbox").prop("checked",function(){return 0===c.length}),!0}),g.on("click.wp-toggle-checkboxes","thead .check-column :checkbox, tfoot .check-column :checkbox",function(b){var c=a(this),d=c.closest("table"),e=c.prop("checked"),f=b.shiftKey||c.data("wp-toggle");d.children("tbody").filter(":visible").children().children(".check-column").find(":checkbox").prop("checked",function(){return!a(this).is(":hidden,:disabled")&&(f?!a(this).prop("checked"):!!e)}),d.children("thead, tfoot").filter(":visible").children().children(".check-column").find(":checkbox").prop("checked",function(){return!f&&!!e})}),a("#wpbody-content").on({focusin:function(){clearTimeout(v),w=a(this).find(".row-actions"),a(".row-actions").not(this).removeClass("visible"),w.addClass("visible")},focusout:function(){v=setTimeout(function(){w.removeClass("visible")},30)}},".has-row-actions"),a("tbody").on("click",".toggle-row",function(){a(this).closest("tr").toggleClass("is-expanded")}),a("#default-password-nag-no").click(function(){return setUserSetting("default_password_nag","hide"),a("div.default-password-nag").hide(),!1}),a("#newcontent").bind("keydown.wpevent_InsertTab",function(b){var c,d,e,f,g,h=b.target;if(27==b.keyCode)return b.preventDefault(),void a(h).data("tab-out",!0);if(!(9!=b.keyCode||b.ctrlKey||b.altKey||b.shiftKey)){if(a(h).data("tab-out"))return void a(h).data("tab-out",!1);c=h.selectionStart,d=h.selectionEnd,e=h.value,document.selection?(h.focus(),g=document.selection.createRange(),g.text="\t"):c>=0&&(f=this.scrollTop,h.value=e.substring(0,c).concat("\t",e.substring(d)),h.selectionStart=h.selectionEnd=c+1,this.scrollTop=f),b.stopPropagation&&b.stopPropagation(),b.preventDefault&&b.preventDefault()}}),z.length&&z.closest("form").submit(function(){a('select[name="action"]').val()==-1&&a('select[name="action2"]').val()==-1&&z.val()==A&&z.val("1")}),a('.search-box input[type="search"], .search-box input[type="submit"]').mousedown(function(){a('select[name^="action"]').val("-1")}),a("#contextual-help-link, #show-settings-link").on("focus.scroll-into-view",function(a){a.target.scrollIntoView&&a.target.scrollIntoView(!1)}),function(){function b(){c.prop("disabled",""===d.map(function(){return a(this).val()}).get().join(""))}var c,d,e=a("form.wp-upload-form");e.length&&(c=e.find('input[type="submit"]'),d=e.find('input[type="file"]'),b(),d.on("change",b))}(),B||(f.on("scroll.pin-menu",i),e.on("tinymce-editor-init.pin-menu",function(a,b){b.on("wp-autoresize",j)})),b.wpResponsive={init:function(){var c=this;e.on("wp-responsive-activate.wp-responsive",function(){c.activate()}).on("wp-responsive-deactivate.wp-responsive",function(){c.deactivate()}),a("#wp-admin-bar-menu-toggle a").attr("aria-expanded","false"),a("#wp-admin-bar-menu-toggle").on("click.wp-responsive",function(b){b.preventDefault(),M.find(".hover").removeClass("hover"),F.toggleClass("wp-responsive-open"),F.hasClass("wp-responsive-open")?(a(this).find("a").attr("aria-expanded","true"),a("#adminmenu a:first").focus()):a(this).find("a").attr("aria-expanded","false")}),G.on("click.wp-responsive","li.wp-has-submenu > a",function(b){G.data("wp-responsive")&&(a(this).parent("li").toggleClass("selected"),b.preventDefault())}),c.trigger(),e.on("wp-window-resized.wp-responsive",a.proxy(this.trigger,this)),f.on("load.wp-responsive",function(){var a=navigator.userAgent.indexOf("AppleWebKit/")>-1?f.width():b.innerWidth;a<=782&&c.disableSortables()})},activate:function(){l(),g.hasClass("auto-fold")||g.addClass("auto-fold"),G.data("wp-responsive",1),this.disableSortables()},deactivate:function(){l(),G.removeData("wp-responsive"),this.enableSortables()},trigger:function(){var a=n();a&&(a<=782?L||(e.trigger("wp-responsive-activate"),L=!0):L&&(e.trigger("wp-responsive-deactivate"),L=!1),a<=480?this.enableOverlay():this.disableOverlay())},enableOverlay:function(){0===H.length&&(H=a('<div id="wp-responsive-overlay"></div>').insertAfter("#wpcontent").hide().on("click.wp-responsive",function(){I.find(".menupop.hover").removeClass("hover"),a(this).hide()})),J.on("click.wp-responsive",function(){H.show()})},disableOverlay:function(){J.off("click.wp-responsive"),H.hide()},disableSortables:function(){if(K.length)try{K.sortable("disable")}catch(a){}},enableSortables:function(){if(K.length)try{K.sortable("enable")}catch(a){}}},a(document).ajaxComplete(function(){m()}),e.on("wp-window-resized.set-menu-state",o),e.on("wp-menu-state-set wp-collapse-menu",function(b,c){var d=a("#collapse-button"),e="true",f=commonL10n.collapseMenu;"folded"===c.state&&(e="false",f=commonL10n.expandMenu),d.attr({"aria-expanded":e,"aria-label":f})}),b.wpResponsive.init(),l(),o(),c(),h(),m(),e.on("wp-pin-menu wp-window-resized.pin-menu postboxes-columnchange.pin-menu postbox-toggled.pin-menu wp-collapse-menu.pin-menu wp-scroll-start.pin-menu",l),a(".wp-initial-focus").focus(),g.on("click",".js-update-details-toggle",function(){var b=a(this).closest(".js-update-details"),c=a("#"+b.data("update-details"));c.hasClass("update-details-moved")||c.insertAfter(b).addClass("update-details-moved"),c.toggle(),a(this).attr("aria-expanded",c.is(":visible"))})}),function(){function a(){e.trigger("wp-window-resized")}function c(){b.clearTimeout(d),d=b.setTimeout(a,200)}var d;f.on("resize.wp-fire-once",c)}(),function(){if("-ms-user-select"in document.documentElement.style&&navigator.userAgent.match(/IEMobile\/10\.0/)){var a=document.createElement("style");a.appendChild(document.createTextNode("@-ms-viewport{width:auto!important}")),document.getElementsByTagName("head")[0].appendChild(a)}}()}(jQuery,window); \ No newline at end of file
diff --git a/www/crm/wp-admin/js/custom-background.js b/www/crm/wp-admin/js/custom-background.js
new file mode 100644
index 00000000..a948e780
--- /dev/null
+++ b/www/crm/wp-admin/js/custom-background.js
@@ -0,0 +1,145 @@
+/**
+ * @output wp-admin/js/custom-background.js
+ */
+
+/* global ajaxurl */
+
+/**
+ * Registers all events for customizing the background.
+ *
+ * @since 3.0.0
+ *
+ * @requires jQuery
+ */
+(function($) {
+ $(document).ready(function() {
+ var frame,
+ bgImage = $( '#custom-background-image' );
+
+ /**
+ * Instantiates the WordPress color picker and binds the change and clear events.
+ *
+ * @since 3.5.0
+ *
+ * @returns {void}
+ */
+ $('#background-color').wpColorPicker({
+ change: function( event, ui ) {
+ bgImage.css('background-color', ui.color.toString());
+ },
+ clear: function() {
+ bgImage.css('background-color', '');
+ }
+ });
+
+ /**
+ * Alters the background size CSS property whenever the background size input has changed.
+ *
+ * @since 4.7.0
+ *
+ * @returns {void}
+ */
+ $( 'select[name="background-size"]' ).change( function() {
+ bgImage.css( 'background-size', $( this ).val() );
+ });
+
+ /**
+ * Alters the background position CSS property whenever the background position input has changed.
+ *
+ * @since 4.7.0
+ *
+ * @returns {void}
+ */
+ $( 'input[name="background-position"]' ).change( function() {
+ bgImage.css( 'background-position', $( this ).val() );
+ });
+
+ /**
+ * Alters the background repeat CSS property whenever the background repeat input has changed.
+ *
+ * @since 3.0.0
+ *
+ * @returns {void}
+ */
+ $( 'input[name="background-repeat"]' ).change( function() {
+ bgImage.css( 'background-repeat', $( this ).is( ':checked' ) ? 'repeat' : 'no-repeat' );
+ });
+
+ /**
+ * Alters the background attachment CSS property whenever the background attachment input has changed.
+ *
+ * @since 4.7.0
+ *
+ * @returns {void}
+ */
+ $( 'input[name="background-attachment"]' ).change( function() {
+ bgImage.css( 'background-attachment', $( this ).is( ':checked' ) ? 'scroll' : 'fixed' );
+ });
+
+ /**
+ * Binds the event for opening the WP Media dialog.
+ *
+ * @since 3.5.0
+ *
+ * @returns {void}
+ */
+ $('#choose-from-library-link').click( function( event ) {
+ var $el = $(this);
+
+ event.preventDefault();
+
+ // If the media frame already exists, reopen it.
+ if ( frame ) {
+ frame.open();
+ return;
+ }
+
+ // Create the media frame.
+ frame = wp.media.frames.customBackground = wp.media({
+ // Set the title of the modal.
+ title: $el.data('choose'),
+
+ // Tell the modal to show only images.
+ library: {
+ type: 'image'
+ },
+
+ // Customize the submit button.
+ button: {
+ // Set the text of the button.
+ text: $el.data('update'),
+ /*
+ * Tell the button not to close the modal, since we're
+ * going to refresh the page when the image is selected.
+ */
+ close: false
+ }
+ });
+
+ /**
+ * When an image is selected, run a callback.
+ *
+ * @since 3.5.0
+ *
+ * @returns {void}
+ */
+ frame.on( 'select', function() {
+ // Grab the selected attachment.
+ var attachment = frame.state().get('selection').first();
+
+ // Run an AJAX request to set the background image.
+ $.post( ajaxurl, {
+ action: 'set-background-image',
+ attachment_id: attachment.id,
+ size: 'full'
+ }).done( function() {
+ // When the request completes, reload the window.
+ window.location.reload();
+ });
+ });
+
+ // Finally, open the modal.
+ frame.open();
+ });
+ });
+})(jQuery);
diff --git a/www/crm/wp-admin/js/custom-background.min.js b/www/crm/wp-admin/js/custom-background.min.js
new file mode 100644
index 00000000..5e303882
--- /dev/null
+++ b/www/crm/wp-admin/js/custom-background.min.js
@@ -0,0 +1 @@
+!function(a){a(document).ready(function(){var b,c=a("#custom-background-image");a("#background-color").wpColorPicker({change:function(a,b){c.css("background-color",b.color.toString())},clear:function(){c.css("background-color","")}}),a('select[name="background-size"]').change(function(){c.css("background-size",a(this).val())}),a('input[name="background-position"]').change(function(){c.css("background-position",a(this).val())}),a('input[name="background-repeat"]').change(function(){c.css("background-repeat",a(this).is(":checked")?"repeat":"no-repeat")}),a('input[name="background-attachment"]').change(function(){c.css("background-attachment",a(this).is(":checked")?"scroll":"fixed")}),a("#choose-from-library-link").click(function(c){var d=a(this);return c.preventDefault(),b?void b.open():(b=wp.media.frames.customBackground=wp.media({title:d.data("choose"),library:{type:"image"},button:{text:d.data("update"),close:!1}}),b.on("select",function(){var c=b.state().get("selection").first();a.post(ajaxurl,{action:"set-background-image",attachment_id:c.id,size:"full"}).done(function(){window.location.reload()})}),void b.open())})})}(jQuery); \ No newline at end of file
diff --git a/www/crm/wp-admin/js/custom-header.js b/www/crm/wp-admin/js/custom-header.js
new file mode 100644
index 00000000..e00c70cd
--- /dev/null
+++ b/www/crm/wp-admin/js/custom-header.js
@@ -0,0 +1,88 @@
+/**
+ * @output wp-admin/js/custom-header.js
+ */
+
+/* global isRtl */
+
+/**
+ * Initializes the custom header selection page.
+ *
+ * @since 3.5.0
+ *
+ * @deprecated 4.1.0 The page this is used on is never linked to from the UI.
+ * Setting a custom header is completely handled by the Customizer.
+ */
+(function($) {
+ var frame;
+
+ $( function() {
+ // Fetch available headers.
+ var $headers = $('.available-headers');
+
+ // Apply jQuery.masonry once the images have loaded.
+ $headers.imagesLoaded( function() {
+ $headers.masonry({
+ itemSelector: '.default-header',
+ isRTL: !! ( 'undefined' != typeof isRtl && isRtl )
+ });
+ });
+
+ /**
+ * Opens the 'choose from library' frame and creates it if it doesn't exist.
+ *
+ * @since 3.5.0
+ * @deprecated 4.1.0
+ *
+ * @returns {void}
+ */
+ $('#choose-from-library-link').click( function( event ) {
+ var $el = $(this);
+ event.preventDefault();
+
+ // If the media frame already exists, reopen it.
+ if ( frame ) {
+ frame.open();
+ return;
+ }
+
+ // Create the media frame.
+ frame = wp.media.frames.customHeader = wp.media({
+ // Set the title of the modal.
+ title: $el.data('choose'),
+
+ // Tell the modal to show only images.
+ library: {
+ type: 'image'
+ },
+
+ // Customize the submit button.
+ button: {
+ // Set the text of the button.
+ text: $el.data('update'),
+ // Tell the button not to close the modal, since we're
+ // going to refresh the page when the image is selected.
+ close: false
+ }
+ });
+
+ /**
+ * Updates the window location to include the selected attachment.
+ *
+ * @since 3.5.0
+ * @deprecated 4.1.0
+ *
+ * @returns {void}
+ */
+ frame.on( 'select', function() {
+ // Grab the selected attachment.
+ var attachment = frame.state().get('selection').first(),
+ link = $el.data('updateLink');
+
+ // Tell the browser to navigate to the crop step.
+ window.location = link + '&file=' + attachment.id;
+ });
+
+ frame.open();
+ });
+ });
+}(jQuery));
diff --git a/www/crm/wp-admin/js/customize-controls.js b/www/crm/wp-admin/js/customize-controls.js
new file mode 100644
index 00000000..2edc498e
--- /dev/null
+++ b/www/crm/wp-admin/js/customize-controls.js
@@ -0,0 +1,9270 @@
+/**
+ * @output wp-admin/js/customize-controls.js
+ */
+
+/* global _wpCustomizeHeader, _wpCustomizeBackground, _wpMediaViewsL10n, MediaElementPlayer, console, confirm */
+(function( exports, $ ){
+ var Container, focus, normalizedTransitionendEventName, api = wp.customize;
+
+ api.OverlayNotification = api.Notification.extend(/** @lends wp.customize.OverlayNotification.prototype */{
+
+ /**
+ * Whether the notification should show a loading spinner.
+ *
+ * @since 4.9.0
+ * @var {boolean}
+ */
+ loading: false,
+
+ /**
+ * A notification that is displayed in a full-screen overlay.
+ *
+ * @constructs wp.customize.OverlayNotification
+ * @augments wp.customize.Notification
+ *
+ * @since 4.9.0
+ *
+ * @param {string} code - Code.
+ * @param {object} params - Params.
+ */
+ initialize: function( code, params ) {
+ var notification = this;
+ api.Notification.prototype.initialize.call( notification, code, params );
+ notification.containerClasses += ' notification-overlay';
+ if ( notification.loading ) {
+ notification.containerClasses += ' notification-loading';
+ }
+ },
+
+ /**
+ * Render notification.
+ *
+ * @since 4.9.0
+ *
+ * @return {jQuery} Notification container.
+ */
+ render: function() {
+ var li = api.Notification.prototype.render.call( this );
+ li.on( 'keydown', _.bind( this.handleEscape, this ) );
+ return li;
+ },
+
+ /**
+ * Stop propagation on escape key presses, but also dismiss notification if it is dismissible.
+ *
+ * @since 4.9.0
+ *
+ * @param {jQuery.Event} event - Event.
+ * @returns {void}
+ */
+ handleEscape: function( event ) {
+ var notification = this;
+ if ( 27 === event.which ) {
+ event.stopPropagation();
+ if ( notification.dismissible && notification.parent ) {
+ notification.parent.remove( notification.code );
+ }
+ }
+ }
+ });
+
+ api.Notifications = api.Values.extend(/** @lends wp.customize.Notifications.prototype */{
+
+ /**
+ * Whether the alternative style should be used.
+ *
+ * @since 4.9.0
+ * @type {boolean}
+ */
+ alt: false,
+
+ /**
+ * The default constructor for items of the collection.
+ *
+ * @since 4.9.0
+ * @type {object}
+ */
+ defaultConstructor: api.Notification,
+
+ /**
+ * A collection of observable notifications.
+ *
+ * @since 4.9.0
+ *
+ * @constructs wp.customize.Notifications
+ * @augments wp.customize.Values
+ *
+ * @param {object} options - Options.
+ * @param {jQuery} [options.container] - Container element for notifications. This can be injected later.
+ * @param {boolean} [options.alt] - Whether alternative style should be used when rendering notifications.
+ *
+ * @returns {void}
+ */
+ initialize: function( options ) {
+ var collection = this;
+
+ api.Values.prototype.initialize.call( collection, options );
+
+ _.bindAll( collection, 'constrainFocus' );
+
+ // Keep track of the order in which the notifications were added for sorting purposes.
+ collection._addedIncrement = 0;
+ collection._addedOrder = {};
+
+ // Trigger change event when notification is added or removed.
+ collection.bind( 'add', function( notification ) {
+ collection.trigger( 'change', notification );
+ });
+ collection.bind( 'removed', function( notification ) {
+ collection.trigger( 'change', notification );
+ });
+ },
+
+ /**
+ * Get the number of notifications added.
+ *
+ * @since 4.9.0
+ * @return {number} Count of notifications.
+ */
+ count: function() {
+ return _.size( this._value );
+ },
+
+ /**
+ * Add notification to the collection.
+ *
+ * @since 4.9.0
+ *
+ * @param {string|wp.customize.Notification} notification - Notification object to add. Alternatively code may be supplied, and in that case the second notificationObject argument must be supplied.
+ * @param {wp.customize.Notification} [notificationObject] - Notification to add when first argument is the code string.
+ * @returns {wp.customize.Notification} Added notification (or existing instance if it was already added).
+ */
+ add: function( notification, notificationObject ) {
+ var collection = this, code, instance;
+ if ( 'string' === typeof notification ) {
+ code = notification;
+ instance = notificationObject;
+ } else {
+ code = notification.code;
+ instance = notification;
+ }
+ if ( ! collection.has( code ) ) {
+ collection._addedIncrement += 1;
+ collection._addedOrder[ code ] = collection._addedIncrement;
+ }
+ return api.Values.prototype.add.call( collection, code, instance );
+ },
+
+ /**
+ * Add notification to the collection.
+ *
+ * @since 4.9.0
+ * @param {string} code - Notification code to remove.
+ * @return {api.Notification} Added instance (or existing instance if it was already added).
+ */
+ remove: function( code ) {
+ var collection = this;
+ delete collection._addedOrder[ code ];
+ return api.Values.prototype.remove.call( this, code );
+ },
+
+ /**
+ * Get list of notifications.
+ *
+ * Notifications may be sorted by type followed by added time.
+ *
+ * @since 4.9.0
+ * @param {object} args - Args.
+ * @param {boolean} [args.sort=false] - Whether to return the notifications sorted.
+ * @return {Array.<wp.customize.Notification>} Notifications.
+ */
+ get: function( args ) {
+ var collection = this, notifications, errorTypePriorities, params;
+ notifications = _.values( collection._value );
+
+ params = _.extend(
+ { sort: false },
+ args
+ );
+
+ if ( params.sort ) {
+ errorTypePriorities = { error: 4, warning: 3, success: 2, info: 1 };
+ notifications.sort( function( a, b ) {
+ var aPriority = 0, bPriority = 0;
+ if ( ! _.isUndefined( errorTypePriorities[ a.type ] ) ) {
+ aPriority = errorTypePriorities[ a.type ];
+ }
+ if ( ! _.isUndefined( errorTypePriorities[ b.type ] ) ) {
+ bPriority = errorTypePriorities[ b.type ];
+ }
+ if ( aPriority !== bPriority ) {
+ return bPriority - aPriority; // Show errors first.
+ }
+ return collection._addedOrder[ b.code ] - collection._addedOrder[ a.code ]; // Show newer notifications higher.
+ });
+ }
+
+ return notifications;
+ },
+
+ /**
+ * Render notifications area.
+ *
+ * @since 4.9.0
+ * @returns {void}
+ */
+ render: function() {
+ var collection = this,
+ notifications, hadOverlayNotification = false, hasOverlayNotification, overlayNotifications = [],
+ previousNotificationsByCode = {},
+ listElement, focusableElements;
+
+ // Short-circuit if there are no container to render into.
+ if ( ! collection.container || ! collection.container.length ) {
+ return;
+ }
+
+ notifications = collection.get( { sort: true } );
+ collection.container.toggle( 0 !== notifications.length );
+
+ // Short-circuit if there are no changes to the notifications.
+ if ( collection.container.is( collection.previousContainer ) && _.isEqual( notifications, collection.previousNotifications ) ) {
+ return;
+ }
+
+ // Make sure list is part of the container.
+ listElement = collection.container.children( 'ul' ).first();
+ if ( ! listElement.length ) {
+ listElement = $( '<ul></ul>' );
+ collection.container.append( listElement );
+ }
+
+ // Remove all notifications prior to re-rendering.
+ listElement.find( '> [data-code]' ).remove();
+
+ _.each( collection.previousNotifications, function( notification ) {
+ previousNotificationsByCode[ notification.code ] = notification;
+ });
+
+ // Add all notifications in the sorted order.
+ _.each( notifications, function( notification ) {
+ var notificationContainer;
+ if ( wp.a11y && ( ! previousNotificationsByCode[ notification.code ] || ! _.isEqual( notification.message, previousNotificationsByCode[ notification.code ].message ) ) ) {
+ wp.a11y.speak( notification.message, 'assertive' );
+ }
+ notificationContainer = $( notification.render() );
+ notification.container = notificationContainer;
+ listElement.append( notificationContainer ); // @todo Consider slideDown() as enhancement.
+
+ if ( notification.extended( api.OverlayNotification ) ) {
+ overlayNotifications.push( notification );
+ }
+ });
+ hasOverlayNotification = Boolean( overlayNotifications.length );
+
+ if ( collection.previousNotifications ) {
+ hadOverlayNotification = Boolean( _.find( collection.previousNotifications, function( notification ) {
+ return notification.extended( api.OverlayNotification );
+ } ) );
+ }
+
+ if ( hasOverlayNotification !== hadOverlayNotification ) {
+ $( document.body ).toggleClass( 'customize-loading', hasOverlayNotification );
+ collection.container.toggleClass( 'has-overlay-notifications', hasOverlayNotification );
+ if ( hasOverlayNotification ) {
+ collection.previousActiveElement = document.activeElement;
+ $( document ).on( 'keydown', collection.constrainFocus );
+ } else {
+ $( document ).off( 'keydown', collection.constrainFocus );
+ }
+ }
+
+ if ( hasOverlayNotification ) {
+ collection.focusContainer = overlayNotifications[ overlayNotifications.length - 1 ].container;
+ collection.focusContainer.prop( 'tabIndex', -1 );
+ focusableElements = collection.focusContainer.find( ':focusable' );
+ if ( focusableElements.length ) {
+ focusableElements.first().focus();
+ } else {
+ collection.focusContainer.focus();
+ }
+ } else if ( collection.previousActiveElement ) {
+ $( collection.previousActiveElement ).focus();
+ collection.previousActiveElement = null;
+ }
+
+ collection.previousNotifications = notifications;
+ collection.previousContainer = collection.container;
+ collection.trigger( 'rendered' );
+ },
+
+ /**
+ * Constrain focus on focus container.
+ *
+ * @since 4.9.0
+ *
+ * @param {jQuery.Event} event - Event.
+ * @returns {void}
+ */
+ constrainFocus: function constrainFocus( event ) {
+ var collection = this, focusableElements;
+
+ // Prevent keys from escaping.
+ event.stopPropagation();
+
+ if ( 9 !== event.which ) { // Tab key.
+ return;
+ }
+
+ focusableElements = collection.focusContainer.find( ':focusable' );
+ if ( 0 === focusableElements.length ) {
+ focusableElements = collection.focusContainer;
+ }
+
+ if ( ! $.contains( collection.focusContainer[0], event.target ) || ! $.contains( collection.focusContainer[0], document.activeElement ) ) {
+ event.preventDefault();
+ focusableElements.first().focus();
+ } else if ( focusableElements.last().is( event.target ) && ! event.shiftKey ) {
+ event.preventDefault();
+ focusableElements.first().focus();
+ } else if ( focusableElements.first().is( event.target ) && event.shiftKey ) {
+ event.preventDefault();
+ focusableElements.last().focus();
+ }
+ }
+ });
+
+ api.Setting = api.Value.extend(/** @lends wp.customize.Setting.prototype */{
+
+ /**
+ * Default params.
+ *
+ * @since 4.9.0
+ * @var {object}
+ */
+ defaults: {
+ transport: 'refresh',
+ dirty: false
+ },
+
+ /**
+ * A Customizer Setting.
+ *
+ * A setting is WordPress data (theme mod, option, menu, etc.) that the user can
+ * draft changes to in the Customizer.
+ *
+ * @see PHP class WP_Customize_Setting.
+ *
+ * @constructs wp.customize.Setting
+ * @augments wp.customize.Value
+ *
+ * @since 3.4.0
+ *
+ * @param {string} id - The setting ID.
+ * @param {*} value - The initial value of the setting.
+ * @param {object} [options={}] - Options.
+ * @param {string} [options.transport=refresh] - The transport to use for previewing. Supports 'refresh' and 'postMessage'.
+ * @param {boolean} [options.dirty=false] - Whether the setting should be considered initially dirty.
+ * @param {object} [options.previewer] - The Previewer instance to sync with. Defaults to wp.customize.previewer.
+ */
+ initialize: function( id, value, options ) {
+ var setting = this, params;
+ params = _.extend(
+ { previewer: api.previewer },
+ setting.defaults,
+ options || {}
+ );
+
+ api.Value.prototype.initialize.call( setting, value, params );
+
+ setting.id = id;
+ setting._dirty = params.dirty; // The _dirty property is what the Customizer reads from.
+ setting.notifications = new api.Notifications();
+
+ // Whenever the setting's value changes, refresh the preview.
+ setting.bind( setting.preview );
+ },
+
+ /**
+ * Refresh the preview, respective of the setting's refresh policy.
+ *
+ * If the preview hasn't sent a keep-alive message and is likely
+ * disconnected by having navigated to a non-allowed URL, then the
+ * refresh transport will be forced when postMessage is the transport.
+ * Note that postMessage does not throw an error when the recipient window
+ * fails to match the origin window, so using try/catch around the
+ * previewer.send() call to then fallback to refresh will not work.
+ *
+ * @since 3.4.0
+ * @access public
+ *
+ * @returns {void}
+ */
+ preview: function() {
+ var setting = this, transport;
+ transport = setting.transport;
+
+ if ( 'postMessage' === transport && ! api.state( 'previewerAlive' ).get() ) {
+ transport = 'refresh';
+ }
+
+ if ( 'postMessage' === transport ) {
+ setting.previewer.send( 'setting', [ setting.id, setting() ] );
+ } else if ( 'refresh' === transport ) {
+ setting.previewer.refresh();
+ }
+ },
+
+ /**
+ * Find controls associated with this setting.
+ *
+ * @since 4.6.0
+ * @returns {wp.customize.Control[]} Controls associated with setting.
+ */
+ findControls: function() {
+ var setting = this, controls = [];
+ api.control.each( function( control ) {
+ _.each( control.settings, function( controlSetting ) {
+ if ( controlSetting.id === setting.id ) {
+ controls.push( control );
+ }
+ } );
+ } );
+ return controls;
+ }
+ });
+
+ /**
+ * Current change count.
+ *
+ * @alias wp.customize._latestRevision
+ *
+ * @since 4.7.0
+ * @type {number}
+ * @protected
+ */
+ api._latestRevision = 0;
+
+ /**
+ * Last revision that was saved.
+ *
+ * @alias wp.customize._lastSavedRevision
+ *
+ * @since 4.7.0
+ * @type {number}
+ * @protected
+ */
+ api._lastSavedRevision = 0;
+
+ /**
+ * Latest revisions associated with the updated setting.
+ *
+ * @alias wp.customize._latestSettingRevisions
+ *
+ * @since 4.7.0
+ * @type {object}
+ * @protected
+ */
+ api._latestSettingRevisions = {};
+
+ /*
+ * Keep track of the revision associated with each updated setting so that
+ * requestChangesetUpdate knows which dirty settings to include. Also, once
+ * ready is triggered and all initial settings have been added, increment
+ * revision for each newly-created initially-dirty setting so that it will
+ * also be included in changeset update requests.
+ */
+ api.bind( 'change', function incrementChangedSettingRevision( setting ) {
+ api._latestRevision += 1;
+ api._latestSettingRevisions[ setting.id ] = api._latestRevision;
+ } );
+ api.bind( 'ready', function() {
+ api.bind( 'add', function incrementCreatedSettingRevision( setting ) {
+ if ( setting._dirty ) {
+ api._latestRevision += 1;
+ api._latestSettingRevisions[ setting.id ] = api._latestRevision;
+ }
+ } );
+ } );
+
+ /**
+ * Get the dirty setting values.
+ *
+ * @alias wp.customize.dirtyValues
+ *
+ * @since 4.7.0
+ * @access public
+ *
+ * @param {object} [options] Options.
+ * @param {boolean} [options.unsaved=false] Whether only values not saved yet into a changeset will be returned (differential changes).
+ * @returns {object} Dirty setting values.
+ */
+ api.dirtyValues = function dirtyValues( options ) {
+ var values = {};
+ api.each( function( setting ) {
+ var settingRevision;
+
+ if ( ! setting._dirty ) {
+ return;
+ }
+
+ settingRevision = api._latestSettingRevisions[ setting.id ];
+
+ // Skip including settings that have already been included in the changeset, if only requesting unsaved.
+ if ( api.state( 'changesetStatus' ).get() && ( options && options.unsaved ) && ( _.isUndefined( settingRevision ) || settingRevision <= api._lastSavedRevision ) ) {
+ return;
+ }
+
+ values[ setting.id ] = setting.get();
+ } );
+ return values;
+ };
+
+ /**
+ * Request updates to the changeset.
+ *
+ * @alias wp.customize.requestChangesetUpdate
+ *
+ * @since 4.7.0
+ * @access public
+ *
+ * @param {object} [changes] - Mapping of setting IDs to setting params each normally including a value property, or mapping to null.
+ * If not provided, then the changes will still be obtained from unsaved dirty settings.
+ * @param {object} [args] - Additional options for the save request.
+ * @param {boolean} [args.autosave=false] - Whether changes will be stored in autosave revision if the changeset has been promoted from an auto-draft.
+ * @param {boolean} [args.force=false] - Send request to update even when there are no changes to submit. This can be used to request the latest status of the changeset on the server.
+ * @param {string} [args.title] - Title to update in the changeset. Optional.
+ * @param {string} [args.date] - Date to update in the changeset. Optional.
+ * @returns {jQuery.Promise} Promise resolving with the response data.
+ */
+ api.requestChangesetUpdate = function requestChangesetUpdate( changes, args ) {
+ var deferred, request, submittedChanges = {}, data, submittedArgs;
+ deferred = new $.Deferred();
+
+ // Prevent attempting changeset update while request is being made.
+ if ( 0 !== api.state( 'processing' ).get() ) {
+ deferred.reject( 'already_processing' );
+ return deferred.promise();
+ }
+
+ submittedArgs = _.extend( {
+ title: null,
+ date: null,
+ autosave: false,
+ force: false
+ }, args );
+
+ if ( changes ) {
+ _.extend( submittedChanges, changes );
+ }
+
+ // Ensure all revised settings (changes pending save) are also included, but not if marked for deletion in changes.
+ _.each( api.dirtyValues( { unsaved: true } ), function( dirtyValue, settingId ) {
+ if ( ! changes || null !== changes[ settingId ] ) {
+ submittedChanges[ settingId ] = _.extend(
+ {},
+ submittedChanges[ settingId ] || {},
+ { value: dirtyValue }
+ );
+ }
+ } );
+
+ // Allow plugins to attach additional params to the settings.
+ api.trigger( 'changeset-save', submittedChanges, submittedArgs );
+
+ // Short-circuit when there are no pending changes.
+ if ( ! submittedArgs.force && _.isEmpty( submittedChanges ) && null === submittedArgs.title && null === submittedArgs.date ) {
+ deferred.resolve( {} );
+ return deferred.promise();
+ }
+
+ // A status would cause a revision to be made, and for this wp.customize.previewer.save() should be used. Status is also disallowed for revisions regardless.
+ if ( submittedArgs.status ) {
+ return deferred.reject( { code: 'illegal_status_in_changeset_update' } ).promise();
+ }
+
+ // Dates not beung allowed for revisions are is a technical limitation of post revisions.
+ if ( submittedArgs.date && submittedArgs.autosave ) {
+ return deferred.reject( { code: 'illegal_autosave_with_date_gmt' } ).promise();
+ }
+
+ // Make sure that publishing a changeset waits for all changeset update requests to complete.
+ api.state( 'processing' ).set( api.state( 'processing' ).get() + 1 );
+ deferred.always( function() {
+ api.state( 'processing' ).set( api.state( 'processing' ).get() - 1 );
+ } );
+
+ // Ensure that if any plugins add data to save requests by extending query() that they get included here.
+ data = api.previewer.query( { excludeCustomizedSaved: true } );
+ delete data.customized; // Being sent in customize_changeset_data instead.
+ _.extend( data, {
+ nonce: api.settings.nonce.save,
+ customize_theme: api.settings.theme.stylesheet,
+ customize_changeset_data: JSON.stringify( submittedChanges )
+ } );
+ if ( null !== submittedArgs.title ) {
+ data.customize_changeset_title = submittedArgs.title;
+ }
+ if ( null !== submittedArgs.date ) {
+ data.customize_changeset_date = submittedArgs.date;
+ }
+ if ( false !== submittedArgs.autosave ) {
+ data.customize_changeset_autosave = 'true';
+ }
+
+ // Allow plugins to modify the params included with the save request.
+ api.trigger( 'save-request-params', data );
+
+ request = wp.ajax.post( 'customize_save', data );
+
+ request.done( function requestChangesetUpdateDone( data ) {
+ var savedChangesetValues = {};
+
+ // Ensure that all settings updated subsequently will be included in the next changeset update request.
+ api._lastSavedRevision = Math.max( api._latestRevision, api._lastSavedRevision );
+
+ api.state( 'changesetStatus' ).set( data.changeset_status );
+
+ if ( data.changeset_date ) {
+ api.state( 'changesetDate' ).set( data.changeset_date );
+ }
+
+ deferred.resolve( data );
+ api.trigger( 'changeset-saved', data );
+
+ if ( data.setting_validities ) {
+ _.each( data.setting_validities, function( validity, settingId ) {
+ if ( true === validity && _.isObject( submittedChanges[ settingId ] ) && ! _.isUndefined( submittedChanges[ settingId ].value ) ) {
+ savedChangesetValues[ settingId ] = submittedChanges[ settingId ].value;
+ }
+ } );
+ }
+
+ api.previewer.send( 'changeset-saved', _.extend( {}, data, { saved_changeset_values: savedChangesetValues } ) );
+ } );
+ request.fail( function requestChangesetUpdateFail( data ) {
+ deferred.reject( data );
+ api.trigger( 'changeset-error', data );
+ } );
+ request.always( function( data ) {
+ if ( data.setting_validities ) {
+ api._handleSettingValidities( {
+ settingValidities: data.setting_validities
+ } );
+ }
+ } );
+
+ return deferred.promise();
+ };
+
+ /**
+ * Watch all changes to Value properties, and bubble changes to parent Values instance
+ *
+ * @alias wp.customize.utils.bubbleChildValueChanges
+ *
+ * @since 4.1.0
+ *
+ * @param {wp.customize.Class} instance
+ * @param {Array} properties The names of the Value instances to watch.
+ */
+ api.utils.bubbleChildValueChanges = function ( instance, properties ) {
+ $.each( properties, function ( i, key ) {
+ instance[ key ].bind( function ( to, from ) {
+ if ( instance.parent && to !== from ) {
+ instance.parent.trigger( 'change', instance );
+ }
+ } );
+ } );
+ };
+
+ /**
+ * Expand a panel, section, or control and focus on the first focusable element.
+ *
+ * @alias wp.customize~focus
+ *
+ * @since 4.1.0
+ *
+ * @param {Object} [params]
+ * @param {Function} [params.completeCallback]
+ */
+ focus = function ( params ) {
+ var construct, completeCallback, focus, focusElement;
+ construct = this;
+ params = params || {};
+ focus = function () {
+ var focusContainer;
+ if ( ( construct.extended( api.Panel ) || construct.extended( api.Section ) ) && construct.expanded && construct.expanded() ) {
+ focusContainer = construct.contentContainer;
+ } else {
+ focusContainer = construct.container;
+ }
+
+ focusElement = focusContainer.find( '.control-focus:first' );
+ if ( 0 === focusElement.length ) {
+ // Note that we can't use :focusable due to a jQuery UI issue. See: https://github.com/jquery/jquery-ui/pull/1583
+ focusElement = focusContainer.find( 'input, select, textarea, button, object, a[href], [tabindex]' ).filter( ':visible' ).first();
+ }
+ focusElement.focus();
+ };
+ if ( params.completeCallback ) {
+ completeCallback = params.completeCallback;
+ params.completeCallback = function () {
+ focus();
+ completeCallback();
+ };
+ } else {
+ params.completeCallback = focus;
+ }
+
+ api.state( 'paneVisible' ).set( true );
+ if ( construct.expand ) {
+ construct.expand( params );
+ } else {
+ params.completeCallback();
+ }
+ };
+
+ /**
+ * Stable sort for Panels, Sections, and Controls.
+ *
+ * If a.priority() === b.priority(), then sort by their respective params.instanceNumber.
+ *
+ * @alias wp.customize.utils.prioritySort
+ *
+ * @since 4.1.0
+ *
+ * @param {(wp.customize.Panel|wp.customize.Section|wp.customize.Control)} a
+ * @param {(wp.customize.Panel|wp.customize.Section|wp.customize.Control)} b
+ * @returns {Number}
+ */
+ api.utils.prioritySort = function ( a, b ) {
+ if ( a.priority() === b.priority() && typeof a.params.instanceNumber === 'number' && typeof b.params.instanceNumber === 'number' ) {
+ return a.params.instanceNumber - b.params.instanceNumber;
+ } else {
+ return a.priority() - b.priority();
+ }
+ };
+
+ /**
+ * Return whether the supplied Event object is for a keydown event but not the Enter key.
+ *
+ * @alias wp.customize.utils.isKeydownButNotEnterEvent
+ *
+ * @since 4.1.0
+ *
+ * @param {jQuery.Event} event
+ * @returns {boolean}
+ */
+ api.utils.isKeydownButNotEnterEvent = function ( event ) {
+ return ( 'keydown' === event.type && 13 !== event.which );
+ };
+
+ /**
+ * Return whether the two lists of elements are the same and are in the same order.
+ *
+ * @alias wp.customize.utils.areElementListsEqual
+ *
+ * @since 4.1.0
+ *
+ * @param {Array|jQuery} listA
+ * @param {Array|jQuery} listB
+ * @returns {boolean}
+ */
+ api.utils.areElementListsEqual = function ( listA, listB ) {
+ var equal = (
+ listA.length === listB.length && // if lists are different lengths, then naturally they are not equal
+ -1 === _.indexOf( _.map( // are there any false values in the list returned by map?
+ _.zip( listA, listB ), // pair up each element between the two lists
+ function ( pair ) {
+ return $( pair[0] ).is( pair[1] ); // compare to see if each pair are equal
+ }
+ ), false ) // check for presence of false in map's return value
+ );
+ return equal;
+ };
+
+ /**
+ * Highlight the existence of a button.
+ *
+ * This function reminds the user of a button represented by the specified
+ * UI element, after an optional delay. If the user focuses the element
+ * before the delay passes, the reminder is canceled.
+ *
+ * @alias wp.customize.utils.highlightButton
+ *
+ * @since 4.9.0
+ *
+ * @param {jQuery} button - The element to highlight.
+ * @param {object} [options] - Options.
+ * @param {number} [options.delay=0] - Delay in milliseconds.
+ * @param {jQuery} [options.focusTarget] - A target for user focus that defaults to the highlighted element.
+ * If the user focuses the target before the delay passes, the reminder
+ * is canceled. This option exists to accommodate compound buttons
+ * containing auxiliary UI, such as the Publish button augmented with a
+ * Settings button.
+ * @returns {Function} An idempotent function that cancels the reminder.
+ */
+ api.utils.highlightButton = function highlightButton( button, options ) {
+ var animationClass = 'button-see-me',
+ canceled = false,
+ params;
+
+ params = _.extend(
+ {
+ delay: 0,
+ focusTarget: button
+ },
+ options
+ );
+
+ function cancelReminder() {
+ canceled = true;
+ }
+
+ params.focusTarget.on( 'focusin', cancelReminder );
+ setTimeout( function() {
+ params.focusTarget.off( 'focusin', cancelReminder );
+
+ if ( ! canceled ) {
+ button.addClass( animationClass );
+ button.one( 'animationend', function() {
+ /*
+ * Remove animation class to avoid situations in Customizer where
+ * DOM nodes are moved (re-inserted) and the animation repeats.
+ */
+ button.removeClass( animationClass );
+ } );
+ }
+ }, params.delay );
+
+ return cancelReminder;
+ };
+
+ /**
+ * Get current timestamp adjusted for server clock time.
+ *
+ * Same functionality as the `current_time( 'mysql', false )` function in PHP.
+ *
+ * @alias wp.customize.utils.getCurrentTimestamp
+ *
+ * @since 4.9.0
+ *
+ * @returns {int} Current timestamp.
+ */
+ api.utils.getCurrentTimestamp = function getCurrentTimestamp() {
+ var currentDate, currentClientTimestamp, timestampDifferential;
+ currentClientTimestamp = _.now();
+ currentDate = new Date( api.settings.initialServerDate.replace( /-/g, '/' ) );
+ timestampDifferential = currentClientTimestamp - api.settings.initialClientTimestamp;
+ timestampDifferential += api.settings.initialClientTimestamp - api.settings.initialServerTimestamp;
+ currentDate.setTime( currentDate.getTime() + timestampDifferential );
+ return currentDate.getTime();
+ };
+
+ /**
+ * Get remaining time of when the date is set.
+ *
+ * @alias wp.customize.utils.getRemainingTime
+ *
+ * @since 4.9.0
+ *
+ * @param {string|int|Date} datetime - Date time or timestamp of the future date.
+ * @return {int} remainingTime - Remaining time in milliseconds.
+ */
+ api.utils.getRemainingTime = function getRemainingTime( datetime ) {
+ var millisecondsDivider = 1000, remainingTime, timestamp;
+ if ( datetime instanceof Date ) {
+ timestamp = datetime.getTime();
+ } else if ( 'string' === typeof datetime ) {
+ timestamp = ( new Date( datetime.replace( /-/g, '/' ) ) ).getTime();
+ } else {
+ timestamp = datetime;
+ }
+
+ remainingTime = timestamp - api.utils.getCurrentTimestamp();
+ remainingTime = Math.ceil( remainingTime / millisecondsDivider );
+ return remainingTime;
+ };
+
+ /**
+ * Return browser supported `transitionend` event name.
+ *
+ * @since 4.7.0
+ *
+ * @ignore
+ *
+ * @returns {string|null} Normalized `transitionend` event name or null if CSS transitions are not supported.
+ */
+ normalizedTransitionendEventName = (function () {
+ var el, transitions, prop;
+ el = document.createElement( 'div' );
+ transitions = {
+ 'transition' : 'transitionend',
+ 'OTransition' : 'oTransitionEnd',
+ 'MozTransition' : 'transitionend',
+ 'WebkitTransition': 'webkitTransitionEnd'
+ };
+ prop = _.find( _.keys( transitions ), function( prop ) {
+ return ! _.isUndefined( el.style[ prop ] );
+ } );
+ if ( prop ) {
+ return transitions[ prop ];
+ } else {
+ return null;
+ }
+ })();
+
+ Container = api.Class.extend(/** @lends wp.customize~Container.prototype */{
+ defaultActiveArguments: { duration: 'fast', completeCallback: $.noop },
+ defaultExpandedArguments: { duration: 'fast', completeCallback: $.noop },
+ containerType: 'container',
+ defaults: {
+ title: '',
+ description: '',
+ priority: 100,
+ type: 'default',
+ content: null,
+ active: true,
+ instanceNumber: null
+ },
+
+ /**
+ * Base class for Panel and Section.
+ *
+ * @constructs wp.customize~Container
+ * @augments wp.customize.Class
+ *
+ * @since 4.1.0
+ *
+ * @borrows wp.customize~focus as focus
+ *
+ * @param {string} id - The ID for the container.
+ * @param {object} options - Object containing one property: params.
+ * @param {string} options.title - Title shown when panel is collapsed and expanded.
+ * @param {string} [options.description] - Description shown at the top of the panel.
+ * @param {number} [options.priority=100] - The sort priority for the panel.
+ * @param {string} [options.templateId] - Template selector for container.
+ * @param {string} [options.type=default] - The type of the panel. See wp.customize.panelConstructor.
+ * @param {string} [options.content] - The markup to be used for the panel container. If empty, a JS template is used.
+ * @param {boolean} [options.active=true] - Whether the panel is active or not.
+ * @param {object} [options.params] - Deprecated wrapper for the above properties.
+ */
+ initialize: function ( id, options ) {
+ var container = this;
+ container.id = id;
+
+ if ( ! Container.instanceCounter ) {
+ Container.instanceCounter = 0;
+ }
+ Container.instanceCounter++;
+
+ $.extend( container, {
+ params: _.defaults(
+ options.params || options, // Passing the params is deprecated.
+ container.defaults
+ )
+ } );
+ if ( ! container.params.instanceNumber ) {
+ container.params.instanceNumber = Container.instanceCounter;
+ }
+ container.notifications = new api.Notifications();
+ container.templateSelector = container.params.templateId || 'customize-' + container.containerType + '-' + container.params.type;
+ container.container = $( container.params.content );
+ if ( 0 === container.container.length ) {
+ container.container = $( container.getContainer() );
+ }
+ container.headContainer = container.container;
+ container.contentContainer = container.getContent();
+ container.container = container.container.add( container.contentContainer );
+
+ container.deferred = {
+ embedded: new $.Deferred()
+ };
+ container.priority = new api.Value();
+ container.active = new api.Value();
+ container.activeArgumentsQueue = [];
+ container.expanded = new api.Value();
+ container.expandedArgumentsQueue = [];
+
+ container.active.bind( function ( active ) {
+ var args = container.activeArgumentsQueue.shift();
+ args = $.extend( {}, container.defaultActiveArguments, args );
+ active = ( active && container.isContextuallyActive() );
+ container.onChangeActive( active, args );
+ });
+ container.expanded.bind( function ( expanded ) {
+ var args = container.expandedArgumentsQueue.shift();
+ args = $.extend( {}, container.defaultExpandedArguments, args );
+ container.onChangeExpanded( expanded, args );
+ });
+
+ container.deferred.embedded.done( function () {
+ container.setupNotifications();
+ container.attachEvents();
+ });
+
+ api.utils.bubbleChildValueChanges( container, [ 'priority', 'active' ] );
+
+ container.priority.set( container.params.priority );
+ container.active.set( container.params.active );
+ container.expanded.set( false );
+ },
+
+ /**
+ * Get the element that will contain the notifications.
+ *
+ * @since 4.9.0
+ * @returns {jQuery} Notification container element.
+ */
+ getNotificationsContainerElement: function() {
+ var container = this;
+ return container.contentContainer.find( '.customize-control-notifications-container:first' );
+ },
+
+ /**
+ * Set up notifications.
+ *
+ * @since 4.9.0
+ * @returns {void}
+ */
+ setupNotifications: function() {
+ var container = this, renderNotifications;
+ container.notifications.container = container.getNotificationsContainerElement();
+
+ // Render notifications when they change and when the construct is expanded.
+ renderNotifications = function() {
+ if ( container.expanded.get() ) {
+ container.notifications.render();
+ }
+ };
+ container.expanded.bind( renderNotifications );
+ renderNotifications();
+ container.notifications.bind( 'change', _.debounce( renderNotifications ) );
+ },
+
+ /**
+ * @since 4.1.0
+ *
+ * @abstract
+ */
+ ready: function() {},
+
+ /**
+ * Get the child models associated with this parent, sorting them by their priority Value.
+ *
+ * @since 4.1.0
+ *
+ * @param {String} parentType
+ * @param {String} childType
+ * @returns {Array}
+ */
+ _children: function ( parentType, childType ) {
+ var parent = this,
+ children = [];
+ api[ childType ].each( function ( child ) {
+ if ( child[ parentType ].get() === parent.id ) {
+ children.push( child );
+ }
+ } );
+ children.sort( api.utils.prioritySort );
+ return children;
+ },
+
+ /**
+ * To override by subclass, to return whether the container has active children.
+ *
+ * @since 4.1.0
+ *
+ * @abstract
+ */
+ isContextuallyActive: function () {
+ throw new Error( 'Container.isContextuallyActive() must be overridden in a subclass.' );
+ },
+
+ /**
+ * Active state change handler.
+ *
+ * Shows the container if it is active, hides it if not.
+ *
+ * To override by subclass, update the container's UI to reflect the provided active state.
+ *
+ * @since 4.1.0
+ *
+ * @param {boolean} active - The active state to transiution to.
+ * @param {Object} [args] - Args.
+ * @param {Object} [args.duration] - The duration for the slideUp/slideDown animation.
+ * @param {boolean} [args.unchanged] - Whether the state is already known to not be changed, and so short-circuit with calling completeCallback early.
+ * @param {Function} [args.completeCallback] - Function to call when the slideUp/slideDown has completed.
+ */
+ onChangeActive: function( active, args ) {
+ var construct = this,
+ headContainer = construct.headContainer,
+ duration, expandedOtherPanel;
+
+ if ( args.unchanged ) {
+ if ( args.completeCallback ) {
+ args.completeCallback();
+ }
+ return;
+ }
+
+ duration = ( 'resolved' === api.previewer.deferred.active.state() ? args.duration : 0 );
+
+ if ( construct.extended( api.Panel ) ) {
+ // If this is a panel is not currently expanded but another panel is expanded, do not animate.
+ api.panel.each(function ( panel ) {
+ if ( panel !== construct && panel.expanded() ) {
+ expandedOtherPanel = panel;
+ duration = 0;
+ }
+ });
+
+ // Collapse any expanded sections inside of this panel first before deactivating.
+ if ( ! active ) {
+ _.each( construct.sections(), function( section ) {
+ section.collapse( { duration: 0 } );
+ } );
+ }
+ }
+
+ if ( ! $.contains( document, headContainer.get( 0 ) ) ) {
+ // If the element is not in the DOM, then jQuery.fn.slideUp() does nothing. In this case, a hard toggle is required instead.
+ headContainer.toggle( active );
+ if ( args.completeCallback ) {
+ args.completeCallback();
+ }
+ } else if ( active ) {
+ headContainer.slideDown( duration, args.completeCallback );
+ } else {
+ if ( construct.expanded() ) {
+ construct.collapse({
+ duration: duration,
+ completeCallback: function() {
+ headContainer.slideUp( duration, args.completeCallback );
+ }
+ });
+ } else {
+ headContainer.slideUp( duration, args.completeCallback );
+ }
+ }
+ },
+
+ /**
+ * @since 4.1.0
+ *
+ * @params {Boolean} active
+ * @param {Object} [params]
+ * @returns {Boolean} false if state already applied
+ */
+ _toggleActive: function ( active, params ) {
+ var self = this;
+ params = params || {};
+ if ( ( active && this.active.get() ) || ( ! active && ! this.active.get() ) ) {
+ params.unchanged = true;
+ self.onChangeActive( self.active.get(), params );
+ return false;
+ } else {
+ params.unchanged = false;
+ this.activeArgumentsQueue.push( params );
+ this.active.set( active );
+ return true;
+ }
+ },
+
+ /**
+ * @param {Object} [params]
+ * @returns {Boolean} false if already active
+ */
+ activate: function ( params ) {
+ return this._toggleActive( true, params );
+ },
+
+ /**
+ * @param {Object} [params]
+ * @returns {Boolean} false if already inactive
+ */
+ deactivate: function ( params ) {
+ return this._toggleActive( false, params );
+ },
+
+ /**
+ * To override by subclass, update the container's UI to reflect the provided active state.
+ * @abstract
+ */
+ onChangeExpanded: function () {
+ throw new Error( 'Must override with subclass.' );
+ },
+
+ /**
+ * Handle the toggle logic for expand/collapse.
+ *
+ * @param {Boolean} expanded - The new state to apply.
+ * @param {Object} [params] - Object containing options for expand/collapse.
+ * @param {Function} [params.completeCallback] - Function to call when expansion/collapse is complete.
+ * @returns {Boolean} false if state already applied or active state is false
+ */
+ _toggleExpanded: function( expanded, params ) {
+ var instance = this, previousCompleteCallback;
+ params = params || {};
+ previousCompleteCallback = params.completeCallback;
+
+ // Short-circuit expand() if the instance is not active.
+ if ( expanded && ! instance.active() ) {
+ return false;
+ }
+
+ api.state( 'paneVisible' ).set( true );
+ params.completeCallback = function() {
+ if ( previousCompleteCallback ) {
+ previousCompleteCallback.apply( instance, arguments );
+ }
+ if ( expanded ) {
+ instance.container.trigger( 'expanded' );
+ } else {
+ instance.container.trigger( 'collapsed' );
+ }
+ };
+ if ( ( expanded && instance.expanded.get() ) || ( ! expanded && ! instance.expanded.get() ) ) {
+ params.unchanged = true;
+ instance.onChangeExpanded( instance.expanded.get(), params );
+ return false;
+ } else {
+ params.unchanged = false;
+ instance.expandedArgumentsQueue.push( params );
+ instance.expanded.set( expanded );
+ return true;
+ }
+ },
+
+ /**
+ * @param {Object} [params]
+ * @returns {Boolean} false if already expanded or if inactive.
+ */
+ expand: function ( params ) {
+ return this._toggleExpanded( true, params );
+ },
+
+ /**
+ * @param {Object} [params]
+ * @returns {Boolean} false if already collapsed.
+ */
+ collapse: function ( params ) {
+ return this._toggleExpanded( false, params );
+ },
+
+ /**
+ * Animate container state change if transitions are supported by the browser.
+ *
+ * @since 4.7.0
+ * @private
+ *
+ * @param {function} completeCallback Function to be called after transition is completed.
+ * @returns {void}
+ */
+ _animateChangeExpanded: function( completeCallback ) {
+ // Return if CSS transitions are not supported.
+ if ( ! normalizedTransitionendEventName ) {
+ if ( completeCallback ) {
+ completeCallback();
+ }
+ return;
+ }
+
+ var construct = this,
+ content = construct.contentContainer,
+ overlay = content.closest( '.wp-full-overlay' ),
+ elements, transitionEndCallback, transitionParentPane;
+
+ // Determine set of elements that are affected by the animation.
+ elements = overlay.add( content );
+
+ if ( ! construct.panel || '' === construct.panel() ) {
+ transitionParentPane = true;
+ } else if ( api.panel( construct.panel() ).contentContainer.hasClass( 'skip-transition' ) ) {
+ transitionParentPane = true;
+ } else {
+ transitionParentPane = false;
+ }
+ if ( transitionParentPane ) {
+ elements = elements.add( '#customize-info, .customize-pane-parent' );
+ }
+
+ // Handle `transitionEnd` event.
+ transitionEndCallback = function( e ) {
+ if ( 2 !== e.eventPhase || ! $( e.target ).is( content ) ) {
+ return;
+ }
+ content.off( normalizedTransitionendEventName, transitionEndCallback );
+ elements.removeClass( 'busy' );
+ if ( completeCallback ) {
+ completeCallback();
+ }
+ };
+ content.on( normalizedTransitionendEventName, transitionEndCallback );
+ elements.addClass( 'busy' );
+
+ // Prevent screen flicker when pane has been scrolled before expanding.
+ _.defer( function() {
+ var container = content.closest( '.wp-full-overlay-sidebar-content' ),
+ currentScrollTop = container.scrollTop(),
+ previousScrollTop = content.data( 'previous-scrollTop' ) || 0,
+ expanded = construct.expanded();
+
+ if ( expanded && 0 < currentScrollTop ) {
+ content.css( 'top', currentScrollTop + 'px' );
+ content.data( 'previous-scrollTop', currentScrollTop );
+ } else if ( ! expanded && 0 < currentScrollTop + previousScrollTop ) {
+ content.css( 'top', previousScrollTop - currentScrollTop + 'px' );
+ container.scrollTop( previousScrollTop );
+ }
+ } );
+ },
+
+ /*
+ * is documented using @borrows in the constructor.
+ */
+ focus: focus,
+
+ /**
+ * Return the container html, generated from its JS template, if it exists.
+ *
+ * @since 4.3.0
+ */
+ getContainer: function () {
+ var template,
+ container = this;
+
+ if ( 0 !== $( '#tmpl-' + container.templateSelector ).length ) {
+ template = wp.template( container.templateSelector );
+ } else {
+ template = wp.template( 'customize-' + container.containerType + '-default' );
+ }
+ if ( template && container.container ) {
+ return $.trim( template( _.extend(
+ { id: container.id },
+ container.params
+ ) ) );
+ }
+
+ return '<li></li>';
+ },
+
+ /**
+ * Find content element which is displayed when the section is expanded.
+ *
+ * After a construct is initialized, the return value will be available via the `contentContainer` property.
+ * By default the element will be related it to the parent container with `aria-owns` and detached.
+ * Custom panels and sections (such as the `NewMenuSection`) that do not have a sliding pane should
+ * just return the content element without needing to add the `aria-owns` element or detach it from
+ * the container. Such non-sliding pane custom sections also need to override the `onChangeExpanded`
+ * method to handle animating the panel/section into and out of view.
+ *
+ * @since 4.7.0
+ * @access public
+ *
+ * @returns {jQuery} Detached content element.
+ */
+ getContent: function() {
+ var construct = this,
+ container = construct.container,
+ content = container.find( '.accordion-section-content, .control-panel-content' ).first(),
+ contentId = 'sub-' + container.attr( 'id' ),
+ ownedElements = contentId,
+ alreadyOwnedElements = container.attr( 'aria-owns' );
+
+ if ( alreadyOwnedElements ) {
+ ownedElements = ownedElements + ' ' + alreadyOwnedElements;
+ }
+ container.attr( 'aria-owns', ownedElements );
+
+ return content.detach().attr( {
+ 'id': contentId,
+ 'class': 'customize-pane-child ' + content.attr( 'class' ) + ' ' + container.attr( 'class' )
+ } );
+ }
+ });
+
+ api.Section = Container.extend(/** @lends wp.customize.Section.prototype */{
+ containerType: 'section',
+ containerParent: '#customize-theme-controls',
+ containerPaneParent: '.customize-pane-parent',
+ defaults: {
+ title: '',
+ description: '',
+ priority: 100,
+ type: 'default',
+ content: null,
+ active: true,
+ instanceNumber: null,
+ panel: null,
+ customizeAction: ''
+ },
+
+ /**
+ * @constructs wp.customize.Section
+ * @augments wp.customize~Container
+ *
+ * @since 4.1.0
+ *
+ * @param {string} id - The ID for the section.
+ * @param {object} options - Options.
+ * @param {string} options.title - Title shown when section is collapsed and expanded.
+ * @param {string} [options.description] - Description shown at the top of the section.
+ * @param {number} [options.priority=100] - The sort priority for the section.
+ * @param {string} [options.type=default] - The type of the section. See wp.customize.sectionConstructor.
+ * @param {string} [options.content] - The markup to be used for the section container. If empty, a JS template is used.
+ * @param {boolean} [options.active=true] - Whether the section is active or not.
+ * @param {string} options.panel - The ID for the panel this section is associated with.
+ * @param {string} [options.customizeAction] - Additional context information shown before the section title when expanded.
+ * @param {object} [options.params] - Deprecated wrapper for the above properties.
+ */
+ initialize: function ( id, options ) {
+ var section = this, params;
+ params = options.params || options;
+
+ // Look up the type if one was not supplied.
+ if ( ! params.type ) {
+ _.find( api.sectionConstructor, function( Constructor, type ) {
+ if ( Constructor === section.constructor ) {
+ params.type = type;
+ return true;
+ }
+ return false;
+ } );
+ }
+
+ Container.prototype.initialize.call( section, id, params );
+
+ section.id = id;
+ section.panel = new api.Value();
+ section.panel.bind( function ( id ) {
+ $( section.headContainer ).toggleClass( 'control-subsection', !! id );
+ });
+ section.panel.set( section.params.panel || '' );
+ api.utils.bubbleChildValueChanges( section, [ 'panel' ] );
+
+ section.embed();
+ section.deferred.embedded.done( function () {
+ section.ready();
+ });
+ },
+
+ /**
+ * Embed the container in the DOM when any parent panel is ready.
+ *
+ * @since 4.1.0
+ */
+ embed: function () {
+ var inject,
+ section = this;
+
+ section.containerParent = api.ensure( section.containerParent );
+
+ // Watch for changes to the panel state.
+ inject = function ( panelId ) {
+ var parentContainer;
+ if ( panelId ) {
+ // The panel has been supplied, so wait until the panel object is registered.
+ api.panel( panelId, function ( panel ) {
+ // The panel has been registered, wait for it to become ready/initialized.
+ panel.deferred.embedded.done( function () {
+ parentContainer = panel.contentContainer;
+ if ( ! section.headContainer.parent().is( parentContainer ) ) {
+ parentContainer.append( section.headContainer );
+ }
+ if ( ! section.contentContainer.parent().is( section.headContainer ) ) {
+ section.containerParent.append( section.contentContainer );
+ }
+ section.deferred.embedded.resolve();
+ });
+ } );
+ } else {
+ // There is no panel, so embed the section in the root of the customizer
+ parentContainer = api.ensure( section.containerPaneParent );
+ if ( ! section.headContainer.parent().is( parentContainer ) ) {
+ parentContainer.append( section.headContainer );
+ }
+ if ( ! section.contentContainer.parent().is( section.headContainer ) ) {
+ section.containerParent.append( section.contentContainer );
+ }
+ section.deferred.embedded.resolve();
+ }
+ };
+ section.panel.bind( inject );
+ inject( section.panel.get() ); // Since a section may never get a panel, assume that it won't ever get one.
+ },
+
+ /**
+ * Add behaviors for the accordion section.
+ *
+ * @since 4.1.0
+ */
+ attachEvents: function () {
+ var meta, content, section = this;
+
+ if ( section.container.hasClass( 'cannot-expand' ) ) {
+ return;
+ }
+
+ // Expand/Collapse accordion sections on click.
+ section.container.find( '.accordion-section-title, .customize-section-back' ).on( 'click keydown', function( event ) {
+ if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
+ return;
+ }
+ event.preventDefault(); // Keep this AFTER the key filter above
+
+ if ( section.expanded() ) {
+ section.collapse();
+ } else {
+ section.expand();
+ }
+ });
+
+ // This is very similar to what is found for api.Panel.attachEvents().
+ section.container.find( '.customize-section-title .customize-help-toggle' ).on( 'click', function() {
+
+ meta = section.container.find( '.section-meta' );
+ if ( meta.hasClass( 'cannot-expand' ) ) {
+ return;
+ }
+ content = meta.find( '.customize-section-description:first' );
+ content.toggleClass( 'open' );
+ content.slideToggle( section.defaultExpandedArguments.duration, function() {
+ content.trigger( 'toggled' );
+ } );
+ $( this ).attr( 'aria-expanded', function( i, attr ) {
+ return 'true' === attr ? 'false' : 'true';
+ });
+ });
+ },
+
+ /**
+ * Return whether this section has any active controls.
+ *
+ * @since 4.1.0
+ *
+ * @returns {Boolean}
+ */
+ isContextuallyActive: function () {
+ var section = this,
+ controls = section.controls(),
+ activeCount = 0;
+ _( controls ).each( function ( control ) {
+ if ( control.active() ) {
+ activeCount += 1;
+ }
+ } );
+ return ( activeCount !== 0 );
+ },
+
+ /**
+ * Get the controls that are associated with this section, sorted by their priority Value.
+ *
+ * @since 4.1.0
+ *
+ * @returns {Array}
+ */
+ controls: function () {
+ return this._children( 'section', 'control' );
+ },
+
+ /**
+ * Update UI to reflect expanded state.
+ *
+ * @since 4.1.0
+ *
+ * @param {Boolean} expanded
+ * @param {Object} args
+ */
+ onChangeExpanded: function ( expanded, args ) {
+ var section = this,
+ container = section.headContainer.closest( '.wp-full-overlay-sidebar-content' ),
+ content = section.contentContainer,
+ overlay = section.headContainer.closest( '.wp-full-overlay' ),
+ backBtn = content.find( '.customize-section-back' ),
+ sectionTitle = section.headContainer.find( '.accordion-section-title' ).first(),
+ expand, panel;
+
+ if ( expanded && ! content.hasClass( 'open' ) ) {
+
+ if ( args.unchanged ) {
+ expand = args.completeCallback;
+ } else {
+ expand = $.proxy( function() {
+ section._animateChangeExpanded( function() {
+ sectionTitle.attr( 'tabindex', '-1' );
+ backBtn.attr( 'tabindex', '0' );
+
+ backBtn.focus();
+ content.css( 'top', '' );
+ container.scrollTop( 0 );
+
+ if ( args.completeCallback ) {
+ args.completeCallback();
+ }
+ } );
+
+ content.addClass( 'open' );
+ overlay.addClass( 'section-open' );
+ api.state( 'expandedSection' ).set( section );
+ }, this );
+ }
+
+ if ( ! args.allowMultiple ) {
+ api.section.each( function ( otherSection ) {
+ if ( otherSection !== section ) {
+ otherSection.collapse( { duration: args.duration } );
+ }
+ });
+ }
+
+ if ( section.panel() ) {
+ api.panel( section.panel() ).expand({
+ duration: args.duration,
+ completeCallback: expand
+ });
+ } else {
+ if ( ! args.allowMultiple ) {
+ api.panel.each( function( panel ) {
+ panel.collapse();
+ });
+ }
+ expand();
+ }
+
+ } else if ( ! expanded && content.hasClass( 'open' ) ) {
+ if ( section.panel() ) {
+ panel = api.panel( section.panel() );
+ if ( panel.contentContainer.hasClass( 'skip-transition' ) ) {
+ panel.collapse();
+ }
+ }
+ section._animateChangeExpanded( function() {
+ backBtn.attr( 'tabindex', '-1' );
+ sectionTitle.attr( 'tabindex', '0' );
+
+ sectionTitle.focus();
+ content.css( 'top', '' );
+
+ if ( args.completeCallback ) {
+ args.completeCallback();
+ }
+ } );
+
+ content.removeClass( 'open' );
+ overlay.removeClass( 'section-open' );
+ if ( section === api.state( 'expandedSection' ).get() ) {
+ api.state( 'expandedSection' ).set( false );
+ }
+
+ } else {
+ if ( args.completeCallback ) {
+ args.completeCallback();
+ }
+ }
+ }
+ });
+
+ api.ThemesSection = api.Section.extend(/** @lends wp.customize.ThemesSection.prototype */{
+ currentTheme: '',
+ overlay: '',
+ template: '',
+ screenshotQueue: null,
+ $window: null,
+ $body: null,
+ loaded: 0,
+ loading: false,
+ fullyLoaded: false,
+ term: '',
+ tags: '',
+ nextTerm: '',
+ nextTags: '',
+ filtersHeight: 0,
+ headerContainer: null,
+ updateCountDebounced: null,
+
+ /**
+ * wp.customize.ThemesSection
+ *
+ * Custom section for themes that loads themes by category, and also
+ * handles the theme-details view rendering and navigation.
+ *
+ * @constructs wp.customize.ThemesSection
+ * @augments wp.customize.Section
+ *
+ * @since 4.9.0
+ *
+ * @param {string} id - ID.
+ * @param {object} options - Options.
+ * @returns {void}
+ */
+ initialize: function( id, options ) {
+ var section = this;
+ section.headerContainer = $();
+ section.$window = $( window );
+ section.$body = $( document.body );
+ api.Section.prototype.initialize.call( section, id, options );
+ section.updateCountDebounced = _.debounce( section.updateCount, 500 );
+ },
+
+ /**
+ * Embed the section in the DOM when the themes panel is ready.
+ *
+ * Insert the section before the themes container. Assume that a themes section is within a panel, but not necessarily the themes panel.
+ *
+ * @since 4.9.0
+ */
+ embed: function() {
+ var inject,
+ section = this;
+
+ // Watch for changes to the panel state
+ inject = function( panelId ) {
+ var parentContainer;
+ api.panel( panelId, function( panel ) {
+
+ // The panel has been registered, wait for it to become ready/initialized
+ panel.deferred.embedded.done( function() {
+ parentContainer = panel.contentContainer;
+ if ( ! section.headContainer.parent().is( parentContainer ) ) {
+ parentContainer.find( '.customize-themes-full-container-container' ).before( section.headContainer );
+ }
+ if ( ! section.contentContainer.parent().is( section.headContainer ) ) {
+ section.containerParent.append( section.contentContainer );
+ }
+ section.deferred.embedded.resolve();
+ });
+ } );
+ };
+ section.panel.bind( inject );
+ inject( section.panel.get() ); // Since a section may never get a panel, assume that it won't ever get one
+ },
+
+ /**
+ * Set up.
+ *
+ * @since 4.2.0
+ *
+ * @returns {void}
+ */
+ ready: function() {
+ var section = this;
+ section.overlay = section.container.find( '.theme-overlay' );
+ section.template = wp.template( 'customize-themes-details-view' );
+
+ // Bind global keyboard events.
+ section.container.on( 'keydown', function( event ) {
+ if ( ! section.overlay.find( '.theme-wrap' ).is( ':visible' ) ) {
+ return;
+ }
+
+ // Pressing the right arrow key fires a theme:next event
+ if ( 39 === event.keyCode ) {
+ section.nextTheme();
+ }
+
+ // Pressing the left arrow key fires a theme:previous event
+ if ( 37 === event.keyCode ) {
+ section.previousTheme();
+ }
+
+ // Pressing the escape key fires a theme:collapse event
+ if ( 27 === event.keyCode ) {
+ if ( section.$body.hasClass( 'modal-open' ) ) {
+
+ // Escape from the details modal.
+ section.closeDetails();
+ } else {
+
+ // Escape from the inifinite scroll list.
+ section.headerContainer.find( '.customize-themes-section-title' ).focus();
+ }
+ event.stopPropagation(); // Prevent section from being collapsed.
+ }
+ });
+
+ section.renderScreenshots = _.throttle( section.renderScreenshots, 100 );
+
+ _.bindAll( section, 'renderScreenshots', 'loadMore', 'checkTerm', 'filtersChecked' );
+ },
+
+ /**
+ * Override Section.isContextuallyActive method.
+ *
+ * Ignore the active states' of the contained theme controls, and just
+ * use the section's own active state instead. This prevents empty search
+ * results for theme sections from causing the section to become inactive.
+ *
+ * @since 4.2.0
+ *
+ * @returns {Boolean}
+ */
+ isContextuallyActive: function () {
+ return this.active();
+ },
+
+ /**
+ * Attach events.
+ *
+ * @since 4.2.0
+ *
+ * @returns {void}
+ */
+ attachEvents: function () {
+ var section = this, debounced;
+
+ // Expand/Collapse accordion sections on click.
+ section.container.find( '.customize-section-back' ).on( 'click keydown', function( event ) {
+ if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
+ return;
+ }
+ event.preventDefault(); // Keep this AFTER the key filter above
+ section.collapse();
+ });
+
+ section.headerContainer = $( '#accordion-section-' + section.id );
+
+ // Expand section/panel. Only collapse when opening another section.
+ section.headerContainer.on( 'click', '.customize-themes-section-title', function() {
+
+ // Toggle accordion filters under section headers.
+ if ( section.headerContainer.find( '.filter-details' ).length ) {
+ section.headerContainer.find( '.customize-themes-section-title' )
+ .toggleClass( 'details-open' )
+ .attr( 'aria-expanded', function( i, attr ) {
+ return 'true' === attr ? 'false' : 'true';
+ });
+ section.headerContainer.find( '.filter-details' ).slideToggle( 180 );
+ }
+
+ // Open the section.
+ if ( ! section.expanded() ) {
+ section.expand();
+ }
+ });
+
+ // Preview installed themes.
+ section.container.on( 'click', '.theme-actions .preview-theme', function() {
+ api.panel( 'themes' ).loadThemePreview( $( this ).data( 'slug' ) );
+ });
+
+ // Theme navigation in details view.
+ section.container.on( 'click', '.left', function() {
+ section.previousTheme();
+ });
+
+ section.container.on( 'click', '.right', function() {
+ section.nextTheme();
+ });
+
+ section.container.on( 'click', '.theme-backdrop, .close', function() {
+ section.closeDetails();
+ });
+
+ if ( 'local' === section.params.filter_type ) {
+
+ // Filter-search all theme objects loaded in the section.
+ section.container.on( 'input', '.wp-filter-search-themes', function( event ) {
+ section.filterSearch( event.currentTarget.value );
+ });
+
+ } else if ( 'remote' === section.params.filter_type ) {
+
+ // Event listeners for remote queries with user-entered terms.
+ // Search terms.
+ debounced = _.debounce( section.checkTerm, 500 ); // Wait until there is no input for 500 milliseconds to initiate a search.
+ section.contentContainer.on( 'input', '.wp-filter-search', function() {
+ if ( ! api.panel( 'themes' ).expanded() ) {
+ return;
+ }
+ debounced( section );
+ if ( ! section.expanded() ) {
+ section.expand();
+ }
+ });
+
+ // Feature filters.
+ section.contentContainer.on( 'click', '.filter-group input', function() {
+ section.filtersChecked();
+ section.checkTerm( section );
+ });
+ }
+
+ // Toggle feature filters.
+ section.contentContainer.on( 'click', '.feature-filter-toggle', function( e ) {
+ var $themeContainer = $( '.customize-themes-full-container' ),
+ $filterToggle = $( e.currentTarget );
+ section.filtersHeight = $filterToggle.parent().next( '.filter-drawer' ).height();
+
+ if ( 0 < $themeContainer.scrollTop() ) {
+ $themeContainer.animate( { scrollTop: 0 }, 400 );
+
+ if ( $filterToggle.hasClass( 'open' ) ) {
+ return;
+ }
+ }
+
+ $filterToggle
+ .toggleClass( 'open' )
+ .attr( 'aria-expanded', function( i, attr ) {
+ return 'true' === attr ? 'false' : 'true';
+ })
+ .parent().next( '.filter-drawer' ).slideToggle( 180, 'linear' );
+
+ if ( $filterToggle.hasClass( 'open' ) ) {
+ var marginOffset = 1018 < window.innerWidth ? 50 : 76;
+
+ section.contentContainer.find( '.themes' ).css( 'margin-top', section.filtersHeight + marginOffset );
+ } else {
+ section.contentContainer.find( '.themes' ).css( 'margin-top', 0 );
+ }
+ });
+
+ // Setup section cross-linking.
+ section.contentContainer.on( 'click', '.no-themes-local .search-dotorg-themes', function() {
+ api.section( 'wporg_themes' ).focus();
+ });
+
+ function updateSelectedState() {
+ var el = section.headerContainer.find( '.customize-themes-section-title' );
+ el.toggleClass( 'selected', section.expanded() );
+ el.attr( 'aria-expanded', section.expanded() ? 'true' : 'false' );
+ if ( ! section.expanded() ) {
+ el.removeClass( 'details-open' );
+ }
+ }
+ section.expanded.bind( updateSelectedState );
+ updateSelectedState();
+
+ // Move section controls to the themes area.
+ api.bind( 'ready', function () {
+ section.contentContainer = section.container.find( '.customize-themes-section' );
+ section.contentContainer.appendTo( $( '.customize-themes-full-container' ) );
+ section.container.add( section.headerContainer );
+ });
+ },
+
+ /**
+ * Update UI to reflect expanded state
+ *
+ * @since 4.2.0
+ *
+ * @param {Boolean} expanded
+ * @param {Object} args
+ * @param {Boolean} args.unchanged
+ * @param {Function} args.completeCallback
+ * @returns {void}
+ */
+ onChangeExpanded: function ( expanded, args ) {
+
+ // Note: there is a second argument 'args' passed
+ var section = this,
+ container = section.contentContainer.closest( '.customize-themes-full-container' );
+
+ // Immediately call the complete callback if there were no changes
+ if ( args.unchanged ) {
+ if ( args.completeCallback ) {
+ args.completeCallback();
+ }
+ return;
+ }
+
+ function expand() {
+
+ // Try to load controls if none are loaded yet.
+ if ( 0 === section.loaded ) {
+ section.loadThemes();
+ }
+
+ // Collapse any sibling sections/panels
+ api.section.each( function ( otherSection ) {
+ var searchTerm;
+
+ if ( otherSection !== section ) {
+
+ // Try to sync the current search term to the new section.
+ if ( 'themes' === otherSection.params.type ) {
+ searchTerm = otherSection.contentContainer.find( '.wp-filter-search' ).val();
+ section.contentContainer.find( '.wp-filter-search' ).val( searchTerm );
+
+ // Directly initialize an empty remote search to avoid a race condition.
+ if ( '' === searchTerm && '' !== section.term && 'local' !== section.params.filter_type ) {
+ section.term = '';
+ section.initializeNewQuery( section.term, section.tags );
+ } else {
+ if ( 'remote' === section.params.filter_type ) {
+ section.checkTerm( section );
+ } else if ( 'local' === section.params.filter_type ) {
+ section.filterSearch( searchTerm );
+ }
+ }
+ otherSection.collapse( { duration: args.duration } );
+ }
+ }
+ });
+
+ section.contentContainer.addClass( 'current-section' );
+ container.scrollTop();
+
+ container.on( 'scroll', _.throttle( section.renderScreenshots, 300 ) );
+ container.on( 'scroll', _.throttle( section.loadMore, 300 ) );
+
+ if ( args.completeCallback ) {
+ args.completeCallback();
+ }
+ section.updateCount(); // Show this section's count.
+ }
+
+ if ( expanded ) {
+ if ( section.panel() && api.panel.has( section.panel() ) ) {
+ api.panel( section.panel() ).expand({
+ duration: args.duration,
+ completeCallback: expand
+ });
+ } else {
+ expand();
+ }
+ } else {
+ section.contentContainer.removeClass( 'current-section' );
+
+ // Always hide, even if they don't exist or are already hidden.
+ section.headerContainer.find( '.filter-details' ).slideUp( 180 );
+
+ container.off( 'scroll' );
+
+ if ( args.completeCallback ) {
+ args.completeCallback();
+ }
+ }
+ },
+
+ /**
+ * Return the section's content element without detaching from the parent.
+ *
+ * @since 4.9.0
+ *
+ * @returns {jQuery}
+ */
+ getContent: function() {
+ return this.container.find( '.control-section-content' );
+ },
+
+ /**
+ * Load theme data via Ajax and add themes to the section as controls.
+ *
+ * @since 4.9.0
+ *
+ * @returns {void}
+ */
+ loadThemes: function() {
+ var section = this, params, page, request;
+
+ if ( section.loading ) {
+ return; // We're already loading a batch of themes.
+ }
+
+ // Parameters for every API query. Additional params are set in PHP.
+ page = Math.ceil( section.loaded / 100 ) + 1;
+ params = {
+ 'nonce': api.settings.nonce.switch_themes,
+ 'wp_customize': 'on',
+ 'theme_action': section.params.action,
+ 'customized_theme': api.settings.theme.stylesheet,
+ 'page': page
+ };
+
+ // Add fields for remote filtering.
+ if ( 'remote' === section.params.filter_type ) {
+ params.search = section.term;
+ params.tags = section.tags;
+ }
+
+ // Load themes.
+ section.headContainer.closest( '.wp-full-overlay' ).addClass( 'loading' );
+ section.loading = true;
+ section.container.find( '.no-themes' ).hide();
+ request = wp.ajax.post( 'customize_load_themes', params );
+ request.done(function( data ) {
+ var themes = data.themes;
+
+ // Stop and try again if the term changed while loading.
+ if ( '' !== section.nextTerm || '' !== section.nextTags ) {
+ if ( section.nextTerm ) {
+ section.term = section.nextTerm;
+ }
+ if ( section.nextTags ) {
+ section.tags = section.nextTags;
+ }
+ section.nextTerm = '';
+ section.nextTags = '';
+ section.loading = false;
+ section.loadThemes();
+ return;
+ }
+
+ if ( 0 !== themes.length ) {
+
+ section.loadControls( themes, page );
+
+ if ( 1 === page ) {
+
+ // Pre-load the first 3 theme screenshots.
+ _.each( section.controls().slice( 0, 3 ), function( control ) {
+ var img, src = control.params.theme.screenshot[0];
+ if ( src ) {
+ img = new Image();
+ img.src = src;
+ }
+ });
+ if ( 'local' !== section.params.filter_type ) {
+ wp.a11y.speak( api.settings.l10n.themeSearchResults.replace( '%d', data.info.results ) );
+ }
+ }
+
+ _.delay( section.renderScreenshots, 100 ); // Wait for the controls to become visible.
+
+ if ( 'local' === section.params.filter_type || 100 > themes.length ) { // If we have less than the requested 100 themes, it's the end of the list.
+ section.fullyLoaded = true;
+ }
+ } else {
+ if ( 0 === section.loaded ) {
+ section.container.find( '.no-themes' ).show();
+ wp.a11y.speak( section.container.find( '.no-themes' ).text() );
+ } else {
+ section.fullyLoaded = true;
+ }
+ }
+ if ( 'local' === section.params.filter_type ) {
+ section.updateCount(); // Count of visible theme controls.
+ } else {
+ section.updateCount( data.info.results ); // Total number of results including pages not yet loaded.
+ }
+ section.container.find( '.unexpected-error' ).hide(); // Hide error notice in case it was previously shown.
+
+ // This cannot run on request.always, as section.loading may turn false before the new controls load in the success case.
+ section.headContainer.closest( '.wp-full-overlay' ).removeClass( 'loading' );
+ section.loading = false;
+ });
+ request.fail(function( data ) {
+ if ( 'undefined' === typeof data ) {
+ section.container.find( '.unexpected-error' ).show();
+ wp.a11y.speak( section.container.find( '.unexpected-error' ).text() );
+ } else if ( 'undefined' !== typeof console && console.error ) {
+ console.error( data );
+ }
+
+ // This cannot run on request.always, as section.loading may turn false before the new controls load in the success case.
+ section.headContainer.closest( '.wp-full-overlay' ).removeClass( 'loading' );
+ section.loading = false;
+ });
+ },
+
+ /**
+ * Loads controls into the section from data received from loadThemes().
+ *
+ * @since 4.9.0
+ * @param {Array} themes - Array of theme data to create controls with.
+ * @param {integer} page - Page of results being loaded.
+ * @returns {void}
+ */
+ loadControls: function( themes, page ) {
+ var newThemeControls = [],
+ section = this;
+
+ // Add controls for each theme.
+ _.each( themes, function( theme ) {
+ var themeControl = new api.controlConstructor.theme( section.params.action + '_theme_' + theme.id, {
+ type: 'theme',
+ section: section.params.id,
+ theme: theme,
+ priority: section.loaded + 1
+ } );
+
+ api.control.add( themeControl );
+ newThemeControls.push( themeControl );
+ section.loaded = section.loaded + 1;
+ });
+
+ if ( 1 !== page ) {
+ Array.prototype.push.apply( section.screenshotQueue, newThemeControls ); // Add new themes to the screenshot queue.
+ }
+ },
+
+ /**
+ * Determines whether more themes should be loaded, and loads them.
+ *
+ * @since 4.9.0
+ * @returns {void}
+ */
+ loadMore: function() {
+ var section = this, container, bottom, threshold;
+ if ( ! section.fullyLoaded && ! section.loading ) {
+ container = section.container.closest( '.customize-themes-full-container' );
+
+ bottom = container.scrollTop() + container.height();
+ threshold = container.prop( 'scrollHeight' ) - 3000; // Use a fixed distance to the bottom of loaded results to avoid unnecessarily loading results sooner when using a percentage of scroll distance.
+
+ if ( bottom > threshold ) {
+ section.loadThemes();
+ }
+ }
+ },
+
+ /**
+ * Event handler for search input that filters visible controls.
+ *
+ * @since 4.9.0
+ *
+ * @param {string} term - The raw search input value.
+ * @returns {void}
+ */
+ filterSearch: function( term ) {
+ var count = 0,
+ visible = false,
+ section = this,
+ noFilter = ( api.section.has( 'wporg_themes' ) && 'remote' !== section.params.filter_type ) ? '.no-themes-local' : '.no-themes',
+ controls = section.controls(),
+ terms;
+
+ if ( section.loading ) {
+ return;
+ }
+
+ // Standardize search term format and split into an array of individual words.
+ terms = term.toLowerCase().trim().replace( /-/g, ' ' ).split( ' ' );
+
+ _.each( controls, function( control ) {
+ visible = control.filter( terms ); // Shows/hides and sorts control based on the applicability of the search term.
+ if ( visible ) {
+ count = count + 1;
+ }
+ });
+
+ if ( 0 === count ) {
+ section.container.find( noFilter ).show();
+ wp.a11y.speak( section.container.find( noFilter ).text() );
+ } else {
+ section.container.find( noFilter ).hide();
+ }
+
+ section.renderScreenshots();
+ api.reflowPaneContents();
+
+ // Update theme count.
+ section.updateCountDebounced( count );
+ },
+
+ /**
+ * Event handler for search input that determines if the terms have changed and loads new controls as needed.
+ *
+ * @since 4.9.0
+ *
+ * @param {wp.customize.ThemesSection} section - The current theme section, passed through the debouncer.
+ * @returns {void}
+ */
+ checkTerm: function( section ) {
+ var newTerm;
+ if ( 'remote' === section.params.filter_type ) {
+ newTerm = section.contentContainer.find( '.wp-filter-search' ).val();
+ if ( section.term !== newTerm.trim() ) {
+ section.initializeNewQuery( newTerm, section.tags );
+ }
+ }
+ },
+
+ /**
+ * Check for filters checked in the feature filter list and initialize a new query.
+ *
+ * @since 4.9.0
+ *
+ * @returns {void}
+ */
+ filtersChecked: function() {
+ var section = this,
+ items = section.container.find( '.filter-group' ).find( ':checkbox' ),
+ tags = [];
+
+ _.each( items.filter( ':checked' ), function( item ) {
+ tags.push( $( item ).prop( 'value' ) );
+ });
+
+ // When no filters are checked, restore initial state. Update filter count.
+ if ( 0 === tags.length ) {
+ tags = '';
+ section.contentContainer.find( '.feature-filter-toggle .filter-count-0' ).show();
+ section.contentContainer.find( '.feature-filter-toggle .filter-count-filters' ).hide();
+ } else {
+ section.contentContainer.find( '.feature-filter-toggle .theme-filter-count' ).text( tags.length );
+ section.contentContainer.find( '.feature-filter-toggle .filter-count-0' ).hide();
+ section.contentContainer.find( '.feature-filter-toggle .filter-count-filters' ).show();
+ }
+
+ // Check whether tags have changed, and either load or queue them.
+ if ( ! _.isEqual( section.tags, tags ) ) {
+ if ( section.loading ) {
+ section.nextTags = tags;
+ } else {
+ if ( 'remote' === section.params.filter_type ) {
+ section.initializeNewQuery( section.term, tags );
+ } else if ( 'local' === section.params.filter_type ) {
+ section.filterSearch( tags.join( ' ' ) );
+ }
+ }
+ }
+ },
+
+ /**
+ * Reset the current query and load new results.
+ *
+ * @since 4.9.0
+ *
+ * @param {string} newTerm - New term.
+ * @param {Array} newTags - New tags.
+ * @returns {void}
+ */
+ initializeNewQuery: function( newTerm, newTags ) {
+ var section = this;
+
+ // Clear the controls in the section.
+ _.each( section.controls(), function( control ) {
+ control.container.remove();
+ api.control.remove( control.id );
+ });
+ section.loaded = 0;
+ section.fullyLoaded = false;
+ section.screenshotQueue = null;
+
+ // Run a new query, with loadThemes handling paging, etc.
+ if ( ! section.loading ) {
+ section.term = newTerm;
+ section.tags = newTags;
+ section.loadThemes();
+ } else {
+ section.nextTerm = newTerm; // This will reload from loadThemes() with the newest term once the current batch is loaded.
+ section.nextTags = newTags; // This will reload from loadThemes() with the newest tags once the current batch is loaded.
+ }
+ if ( ! section.expanded() ) {
+ section.expand(); // Expand the section if it isn't expanded.
+ }
+ },
+
+ /**
+ * Render control's screenshot if the control comes into view.
+ *
+ * @since 4.2.0
+ *
+ * @returns {void}
+ */
+ renderScreenshots: function() {
+ var section = this;
+
+ // Fill queue initially, or check for more if empty.
+ if ( null === section.screenshotQueue || 0 === section.screenshotQueue.length ) {
+
+ // Add controls that haven't had their screenshots rendered.
+ section.screenshotQueue = _.filter( section.controls(), function( control ) {
+ return ! control.screenshotRendered;
+ });
+ }
+
+ // Are all screenshots rendered (for now)?
+ if ( ! section.screenshotQueue.length ) {
+ return;
+ }
+
+ section.screenshotQueue = _.filter( section.screenshotQueue, function( control ) {
+ var $imageWrapper = control.container.find( '.theme-screenshot' ),
+ $image = $imageWrapper.find( 'img' );
+
+ if ( ! $image.length ) {
+ return false;
+ }
+
+ if ( $image.is( ':hidden' ) ) {
+ return true;
+ }
+
+ // Based on unveil.js.
+ var wt = section.$window.scrollTop(),
+ wb = wt + section.$window.height(),
+ et = $image.offset().top,
+ ih = $imageWrapper.height(),
+ eb = et + ih,
+ threshold = ih * 3,
+ inView = eb >= wt - threshold && et <= wb + threshold;
+
+ if ( inView ) {
+ control.container.trigger( 'render-screenshot' );
+ }
+
+ // If the image is in view return false so it's cleared from the queue.
+ return ! inView;
+ } );
+ },
+
+ /**
+ * Get visible count.
+ *
+ * @since 4.9.0
+ *
+ * @returns {int} Visible count.
+ */
+ getVisibleCount: function() {
+ return this.contentContainer.find( 'li.customize-control:visible' ).length;
+ },
+
+ /**
+ * Update the number of themes in the section.
+ *
+ * @since 4.9.0
+ *
+ * @returns {void}
+ */
+ updateCount: function( count ) {
+ var section = this, countEl, displayed;
+
+ if ( ! count && 0 !== count ) {
+ count = section.getVisibleCount();
+ }
+
+ displayed = section.contentContainer.find( '.themes-displayed' );
+ countEl = section.contentContainer.find( '.theme-count' );
+
+ if ( 0 === count ) {
+ countEl.text( '0' );
+ } else {
+
+ // Animate the count change for emphasis.
+ displayed.fadeOut( 180, function() {
+ countEl.text( count );
+ displayed.fadeIn( 180 );
+ } );
+ wp.a11y.speak( api.settings.l10n.announceThemeCount.replace( '%d', count ) );
+ }
+ },
+
+ /**
+ * Advance the modal to the next theme.
+ *
+ * @since 4.2.0
+ *
+ * @returns {void}
+ */
+ nextTheme: function () {
+ var section = this;
+ if ( section.getNextTheme() ) {
+ section.showDetails( section.getNextTheme(), function() {
+ section.overlay.find( '.right' ).focus();
+ } );
+ }
+ },
+
+ /**
+ * Get the next theme model.
+ *
+ * @since 4.2.0
+ *
+ * @returns {wp.customize.ThemeControl|boolean} Next theme.
+ */
+ getNextTheme: function () {
+ var section = this, control, nextControl, sectionControls, i;
+ control = api.control( section.params.action + '_theme_' + section.currentTheme );
+ sectionControls = section.controls();
+ i = _.indexOf( sectionControls, control );
+ if ( -1 === i ) {
+ return false;
+ }
+
+ nextControl = sectionControls[ i + 1 ];
+ if ( ! nextControl ) {
+ return false;
+ }
+ return nextControl.params.theme;
+ },
+
+ /**
+ * Advance the modal to the previous theme.
+ *
+ * @since 4.2.0
+ * @returns {void}
+ */
+ previousTheme: function () {
+ var section = this;
+ if ( section.getPreviousTheme() ) {
+ section.showDetails( section.getPreviousTheme(), function() {
+ section.overlay.find( '.left' ).focus();
+ } );
+ }
+ },
+
+ /**
+ * Get the previous theme model.
+ *
+ * @since 4.2.0
+ * @returns {wp.customize.ThemeControl|boolean} Previous theme.
+ */
+ getPreviousTheme: function () {
+ var section = this, control, nextControl, sectionControls, i;
+ control = api.control( section.params.action + '_theme_' + section.currentTheme );
+ sectionControls = section.controls();
+ i = _.indexOf( sectionControls, control );
+ if ( -1 === i ) {
+ return false;
+ }
+
+ nextControl = sectionControls[ i - 1 ];
+ if ( ! nextControl ) {
+ return false;
+ }
+ return nextControl.params.theme;
+ },
+
+ /**
+ * Disable buttons when we're viewing the first or last theme.
+ *
+ * @since 4.2.0
+ *
+ * @returns {void}
+ */
+ updateLimits: function () {
+ if ( ! this.getNextTheme() ) {
+ this.overlay.find( '.right' ).addClass( 'disabled' );
+ }
+ if ( ! this.getPreviousTheme() ) {
+ this.overlay.find( '.left' ).addClass( 'disabled' );
+ }
+ },
+
+ /**
+ * Load theme preview.
+ *
+ * @since 4.7.0
+ * @access public
+ *
+ * @deprecated
+ * @param {string} themeId Theme ID.
+ * @returns {jQuery.promise} Promise.
+ */
+ loadThemePreview: function( themeId ) {
+ return api.ThemesPanel.prototype.loadThemePreview.call( this, themeId );
+ },
+
+ /**
+ * Render & show the theme details for a given theme model.
+ *
+ * @since 4.2.0
+ *
+ * @param {object} theme - Theme.
+ * @param {Function} [callback] - Callback once the details have been shown.
+ * @returns {void}
+ */
+ showDetails: function ( theme, callback ) {
+ var section = this, panel = api.panel( 'themes' );
+ section.currentTheme = theme.id;
+ section.overlay.html( section.template( theme ) )
+ .fadeIn( 'fast' )
+ .focus();
+
+ function disableSwitchButtons() {
+ return ! panel.canSwitchTheme( theme.id );
+ }
+
+ // Temporary special function since supplying SFTP credentials does not work yet. See #42184.
+ function disableInstallButtons() {
+ return disableSwitchButtons() || false === api.settings.theme._canInstall || true === api.settings.theme._filesystemCredentialsNeeded;
+ }
+
+ section.overlay.find( 'button.preview, button.preview-theme' ).toggleClass( 'disabled', disableSwitchButtons() );
+ section.overlay.find( 'button.theme-install' ).toggleClass( 'disabled', disableInstallButtons() );
+
+ section.$body.addClass( 'modal-open' );
+ section.containFocus( section.overlay );
+ section.updateLimits();
+ wp.a11y.speak( api.settings.l10n.announceThemeDetails.replace( '%s', theme.name ) );
+ if ( callback ) {
+ callback();
+ }
+ },
+
+ /**
+ * Close the theme details modal.
+ *
+ * @since 4.2.0
+ *
+ * @returns {void}
+ */
+ closeDetails: function () {
+ var section = this;
+ section.$body.removeClass( 'modal-open' );
+ section.overlay.fadeOut( 'fast' );
+ api.control( section.params.action + '_theme_' + section.currentTheme ).container.find( '.theme' ).focus();
+ },
+
+ /**
+ * Keep tab focus within the theme details modal.
+ *
+ * @since 4.2.0
+ *
+ * @param {jQuery} el - Element to contain focus.
+ * @returns {void}
+ */
+ containFocus: function( el ) {
+ var tabbables;
+
+ el.on( 'keydown', function( event ) {
+
+ // Return if it's not the tab key
+ // When navigating with prev/next focus is already handled
+ if ( 9 !== event.keyCode ) {
+ return;
+ }
+
+ // uses jQuery UI to get the tabbable elements
+ tabbables = $( ':tabbable', el );
+
+ // Keep focus within the overlay
+ if ( tabbables.last()[0] === event.target && ! event.shiftKey ) {
+ tabbables.first().focus();
+ return false;
+ } else if ( tabbables.first()[0] === event.target && event.shiftKey ) {
+ tabbables.last().focus();
+ return false;
+ }
+ });
+ }
+ });
+
+ api.OuterSection = api.Section.extend(/** @lends wp.customize.OuterSection.prototype */{
+
+ /**
+ * Class wp.customize.OuterSection.
+ *
+ * Creates section outside of the sidebar, there is no ui to trigger collapse/expand so
+ * it would require custom handling.
+ *
+ * @constructs wp.customize.OuterSection
+ * @augments wp.customize.Section
+ *
+ * @since 4.9.0
+ *
+ * @returns {void}
+ */
+ initialize: function() {
+ var section = this;
+ section.containerParent = '#customize-outer-theme-controls';
+ section.containerPaneParent = '.customize-outer-pane-parent';
+ api.Section.prototype.initialize.apply( section, arguments );
+ },
+
+ /**
+ * Overrides api.Section.prototype.onChangeExpanded to prevent collapse/expand effect
+ * on other sections and panels.
+ *
+ * @since 4.9.0
+ *
+ * @param {Boolean} expanded - The expanded state to transition to.
+ * @param {Object} [args] - Args.
+ * @param {boolean} [args.unchanged] - Whether the state is already known to not be changed, and so short-circuit with calling completeCallback early.
+ * @param {Function} [args.completeCallback] - Function to call when the slideUp/slideDown has completed.
+ * @param {Object} [args.duration] - The duration for the animation.
+ */
+ onChangeExpanded: function( expanded, args ) {
+ var section = this,
+ container = section.headContainer.closest( '.wp-full-overlay-sidebar-content' ),
+ content = section.contentContainer,
+ backBtn = content.find( '.customize-section-back' ),
+ sectionTitle = section.headContainer.find( '.accordion-section-title' ).first(),
+ body = $( document.body ),
+ expand, panel;
+
+ body.toggleClass( 'outer-section-open', expanded );
+ section.container.toggleClass( 'open', expanded );
+ section.container.removeClass( 'busy' );
+ api.section.each( function( _section ) {
+ if ( 'outer' === _section.params.type && _section.id !== section.id ) {
+ _section.container.removeClass( 'open' );
+ }
+ } );
+
+ if ( expanded && ! content.hasClass( 'open' ) ) {
+
+ if ( args.unchanged ) {
+ expand = args.completeCallback;
+ } else {
+ expand = $.proxy( function() {
+ section._animateChangeExpanded( function() {
+ sectionTitle.attr( 'tabindex', '-1' );
+ backBtn.attr( 'tabindex', '0' );
+
+ backBtn.focus();
+ content.css( 'top', '' );
+ container.scrollTop( 0 );
+
+ if ( args.completeCallback ) {
+ args.completeCallback();
+ }
+ } );
+
+ content.addClass( 'open' );
+ }, this );
+ }
+
+ if ( section.panel() ) {
+ api.panel( section.panel() ).expand({
+ duration: args.duration,
+ completeCallback: expand
+ });
+ } else {
+ expand();
+ }
+
+ } else if ( ! expanded && content.hasClass( 'open' ) ) {
+ if ( section.panel() ) {
+ panel = api.panel( section.panel() );
+ if ( panel.contentContainer.hasClass( 'skip-transition' ) ) {
+ panel.collapse();
+ }
+ }
+ section._animateChangeExpanded( function() {
+ backBtn.attr( 'tabindex', '-1' );
+ sectionTitle.attr( 'tabindex', '0' );
+
+ sectionTitle.focus();
+ content.css( 'top', '' );
+
+ if ( args.completeCallback ) {
+ args.completeCallback();
+ }
+ } );
+
+ content.removeClass( 'open' );
+
+ } else {
+ if ( args.completeCallback ) {
+ args.completeCallback();
+ }
+ }
+ }
+ });
+
+ api.Panel = Container.extend(/** @lends wp.customize.Panel.prototype */{
+ containerType: 'panel',
+
+ /**
+ * @constructs wp.customize.Panel
+ * @augments wp.customize~Container
+ *
+ * @since 4.1.0
+ *
+ * @param {string} id - The ID for the panel.
+ * @param {object} options - Object containing one property: params.
+ * @param {string} options.title - Title shown when panel is collapsed and expanded.
+ * @param {string} [options.description] - Description shown at the top of the panel.
+ * @param {number} [options.priority=100] - The sort priority for the panel.
+ * @param {string} [options.type=default] - The type of the panel. See wp.customize.panelConstructor.
+ * @param {string} [options.content] - The markup to be used for the panel container. If empty, a JS template is used.
+ * @param {boolean} [options.active=true] - Whether the panel is active or not.
+ * @param {object} [options.params] - Deprecated wrapper for the above properties.
+ */
+ initialize: function ( id, options ) {
+ var panel = this, params;
+ params = options.params || options;
+
+ // Look up the type if one was not supplied.
+ if ( ! params.type ) {
+ _.find( api.panelConstructor, function( Constructor, type ) {
+ if ( Constructor === panel.constructor ) {
+ params.type = type;
+ return true;
+ }
+ return false;
+ } );
+ }
+
+ Container.prototype.initialize.call( panel, id, params );
+
+ panel.embed();
+ panel.deferred.embedded.done( function () {
+ panel.ready();
+ });
+ },
+
+ /**
+ * Embed the container in the DOM when any parent panel is ready.
+ *
+ * @since 4.1.0
+ */
+ embed: function () {
+ var panel = this,
+ container = $( '#customize-theme-controls' ),
+ parentContainer = $( '.customize-pane-parent' ); // @todo This should be defined elsewhere, and to be configurable
+
+ if ( ! panel.headContainer.parent().is( parentContainer ) ) {
+ parentContainer.append( panel.headContainer );
+ }
+ if ( ! panel.contentContainer.parent().is( panel.headContainer ) ) {
+ container.append( panel.contentContainer );
+ }
+ panel.renderContent();
+
+ panel.deferred.embedded.resolve();
+ },
+
+ /**
+ * @since 4.1.0
+ */
+ attachEvents: function () {
+ var meta, panel = this;
+
+ // Expand/Collapse accordion sections on click.
+ panel.headContainer.find( '.accordion-section-title' ).on( 'click keydown', function( event ) {
+ if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
+ return;
+ }
+ event.preventDefault(); // Keep this AFTER the key filter above
+
+ if ( ! panel.expanded() ) {
+ panel.expand();
+ }
+ });
+
+ // Close panel.
+ panel.container.find( '.customize-panel-back' ).on( 'click keydown', function( event ) {
+ if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
+ return;
+ }
+ event.preventDefault(); // Keep this AFTER the key filter above
+
+ if ( panel.expanded() ) {
+ panel.collapse();
+ }
+ });
+
+ meta = panel.container.find( '.panel-meta:first' );
+
+ meta.find( '> .accordion-section-title .customize-help-toggle' ).on( 'click', function() {
+ if ( meta.hasClass( 'cannot-expand' ) ) {
+ return;
+ }
+
+ var content = meta.find( '.customize-panel-description:first' );
+ if ( meta.hasClass( 'open' ) ) {
+ meta.toggleClass( 'open' );
+ content.slideUp( panel.defaultExpandedArguments.duration, function() {
+ content.trigger( 'toggled' );
+ } );
+ $( this ).attr( 'aria-expanded', false );
+ } else {
+ content.slideDown( panel.defaultExpandedArguments.duration, function() {
+ content.trigger( 'toggled' );
+ } );
+ meta.toggleClass( 'open' );
+ $( this ).attr( 'aria-expanded', true );
+ }
+ });
+
+ },
+
+ /**
+ * Get the sections that are associated with this panel, sorted by their priority Value.
+ *
+ * @since 4.1.0
+ *
+ * @returns {Array}
+ */
+ sections: function () {
+ return this._children( 'panel', 'section' );
+ },
+
+ /**
+ * Return whether this panel has any active sections.
+ *
+ * @since 4.1.0
+ *
+ * @returns {boolean} Whether contextually active.
+ */
+ isContextuallyActive: function () {
+ var panel = this,
+ sections = panel.sections(),
+ activeCount = 0;
+ _( sections ).each( function ( section ) {
+ if ( section.active() && section.isContextuallyActive() ) {
+ activeCount += 1;
+ }
+ } );
+ return ( activeCount !== 0 );
+ },
+
+ /**
+ * Update UI to reflect expanded state.
+ *
+ * @since 4.1.0
+ *
+ * @param {Boolean} expanded
+ * @param {Object} args
+ * @param {Boolean} args.unchanged
+ * @param {Function} args.completeCallback
+ * @returns {void}
+ */
+ onChangeExpanded: function ( expanded, args ) {
+
+ // Immediately call the complete callback if there were no changes
+ if ( args.unchanged ) {
+ if ( args.completeCallback ) {
+ args.completeCallback();
+ }
+ return;
+ }
+
+ // Note: there is a second argument 'args' passed
+ var panel = this,
+ accordionSection = panel.contentContainer,
+ overlay = accordionSection.closest( '.wp-full-overlay' ),
+ container = accordionSection.closest( '.wp-full-overlay-sidebar-content' ),
+ topPanel = panel.headContainer.find( '.accordion-section-title' ),
+ backBtn = accordionSection.find( '.customize-panel-back' ),
+ childSections = panel.sections(),
+ skipTransition;
+
+ if ( expanded && ! accordionSection.hasClass( 'current-panel' ) ) {
+ // Collapse any sibling sections/panels
+ api.section.each( function ( section ) {
+ if ( panel.id !== section.panel() ) {
+ section.collapse( { duration: 0 } );
+ }
+ });
+ api.panel.each( function ( otherPanel ) {
+ if ( panel !== otherPanel ) {
+ otherPanel.collapse( { duration: 0 } );
+ }
+ });
+
+ if ( panel.params.autoExpandSoleSection && 1 === childSections.length && childSections[0].active.get() ) {
+ accordionSection.addClass( 'current-panel skip-transition' );
+ overlay.addClass( 'in-sub-panel' );
+
+ childSections[0].expand( {
+ completeCallback: args.completeCallback
+ } );
+ } else {
+ panel._animateChangeExpanded( function() {
+ topPanel.attr( 'tabindex', '-1' );
+ backBtn.attr( 'tabindex', '0' );
+
+ backBtn.focus();
+ accordionSection.css( 'top', '' );
+ container.scrollTop( 0 );
+
+ if ( args.completeCallback ) {
+ args.completeCallback();
+ }
+ } );
+
+ accordionSection.addClass( 'current-panel' );
+ overlay.addClass( 'in-sub-panel' );
+ }
+
+ api.state( 'expandedPanel' ).set( panel );
+
+ } else if ( ! expanded && accordionSection.hasClass( 'current-panel' ) ) {
+ skipTransition = accordionSection.hasClass( 'skip-transition' );
+ if ( ! skipTransition ) {
+ panel._animateChangeExpanded( function() {
+ topPanel.attr( 'tabindex', '0' );
+ backBtn.attr( 'tabindex', '-1' );
+
+ topPanel.focus();
+ accordionSection.css( 'top', '' );
+
+ if ( args.completeCallback ) {
+ args.completeCallback();
+ }
+ } );
+ } else {
+ accordionSection.removeClass( 'skip-transition' );
+ }
+
+ overlay.removeClass( 'in-sub-panel' );
+ accordionSection.removeClass( 'current-panel' );
+ if ( panel === api.state( 'expandedPanel' ).get() ) {
+ api.state( 'expandedPanel' ).set( false );
+ }
+ }
+ },
+
+ /**
+ * Render the panel from its JS template, if it exists.
+ *
+ * The panel's container must already exist in the DOM.
+ *
+ * @since 4.3.0
+ */
+ renderContent: function () {
+ var template,
+ panel = this;
+
+ // Add the content to the container.
+ if ( 0 !== $( '#tmpl-' + panel.templateSelector + '-content' ).length ) {
+ template = wp.template( panel.templateSelector + '-content' );
+ } else {
+ template = wp.template( 'customize-panel-default-content' );
+ }
+ if ( template && panel.headContainer ) {
+ panel.contentContainer.html( template( _.extend(
+ { id: panel.id },
+ panel.params
+ ) ) );
+ }
+ }
+ });
+
+ api.ThemesPanel = api.Panel.extend(/** @lends wp.customize.ThemsPanel.prototype */{
+
+ /**
+ * Class wp.customize.ThemesPanel.
+ *
+ * Custom section for themes that displays without the customize preview.
+ *
+ * @constructs wp.customize.ThemesPanel
+ * @augments wp.customize.Panel
+ *
+ * @since 4.9.0
+ *
+ * @param {string} id - The ID for the panel.
+ * @param {object} options - Options.
+ * @returns {void}
+ */
+ initialize: function( id, options ) {
+ var panel = this;
+ panel.installingThemes = [];
+ api.Panel.prototype.initialize.call( panel, id, options );
+ },
+
+ /**
+ * Determine whether a given theme can be switched to, or in general.
+ *
+ * @since 4.9.0
+ *
+ * @param {string} [slug] - Theme slug.
+ * @returns {boolean} Whether the theme can be switched to.
+ */
+ canSwitchTheme: function canSwitchTheme( slug ) {
+ if ( slug && slug === api.settings.theme.stylesheet ) {
+ return true;
+ }
+ return 'publish' === api.state( 'selectedChangesetStatus' ).get() && ( '' === api.state( 'changesetStatus' ).get() || 'auto-draft' === api.state( 'changesetStatus' ).get() );
+ },
+
+ /**
+ * Attach events.
+ *
+ * @since 4.9.0
+ * @returns {void}
+ */
+ attachEvents: function() {
+ var panel = this;
+
+ // Attach regular panel events.
+ api.Panel.prototype.attachEvents.apply( panel );
+
+ // Temporary since supplying SFTP credentials does not work yet. See #42184
+ if ( api.settings.theme._canInstall && api.settings.theme._filesystemCredentialsNeeded ) {
+ panel.notifications.add( new api.Notification( 'theme_install_unavailable', {
+ message: api.l10n.themeInstallUnavailable,
+ type: 'info',
+ dismissible: true
+ } ) );
+ }
+
+ function toggleDisabledNotifications() {
+ if ( panel.canSwitchTheme() ) {
+ panel.notifications.remove( 'theme_switch_unavailable' );
+ } else {
+ panel.notifications.add( new api.Notification( 'theme_switch_unavailable', {
+ message: api.l10n.themePreviewUnavailable,
+ type: 'warning'
+ } ) );
+ }
+ }
+ toggleDisabledNotifications();
+ api.state( 'selectedChangesetStatus' ).bind( toggleDisabledNotifications );
+ api.state( 'changesetStatus' ).bind( toggleDisabledNotifications );
+
+ // Collapse panel to customize the current theme.
+ panel.contentContainer.on( 'click', '.customize-theme', function() {
+ panel.collapse();
+ });
+
+ // Toggle between filtering and browsing themes on mobile.
+ panel.contentContainer.on( 'click', '.customize-themes-section-title, .customize-themes-mobile-back', function() {
+ $( '.wp-full-overlay' ).toggleClass( 'showing-themes' );
+ });
+
+ // Install (and maybe preview) a theme.
+ panel.contentContainer.on( 'click', '.theme-install', function( event ) {
+ panel.installTheme( event );
+ });
+
+ // Update a theme. Theme cards have the class, the details modal has the id.
+ panel.contentContainer.on( 'click', '.update-theme, #update-theme', function( event ) {
+
+ // #update-theme is a link.
+ event.preventDefault();
+ event.stopPropagation();
+
+ panel.updateTheme( event );
+ });
+
+ // Delete a theme.
+ panel.contentContainer.on( 'click', '.delete-theme', function( event ) {
+ panel.deleteTheme( event );
+ });
+
+ _.bindAll( panel, 'installTheme', 'updateTheme' );
+ },
+
+ /**
+ * Update UI to reflect expanded state
+ *
+ * @since 4.9.0
+ *
+ * @param {Boolean} expanded - Expanded state.
+ * @param {Object} args - Args.
+ * @param {Boolean} args.unchanged - Whether or not the state changed.
+ * @param {Function} args.completeCallback - Callback to execute when the animation completes.
+ * @returns {void}
+ */
+ onChangeExpanded: function( expanded, args ) {
+ var panel = this, overlay, sections, hasExpandedSection = false;
+
+ // Expand/collapse the panel normally.
+ api.Panel.prototype.onChangeExpanded.apply( this, [ expanded, args ] );
+
+ // Immediately call the complete callback if there were no changes
+ if ( args.unchanged ) {
+ if ( args.completeCallback ) {
+ args.completeCallback();
+ }
+ return;
+ }
+
+ overlay = panel.headContainer.closest( '.wp-full-overlay' );
+
+ if ( expanded ) {
+ overlay
+ .addClass( 'in-themes-panel' )
+ .delay( 200 ).find( '.customize-themes-full-container' ).addClass( 'animate' );
+
+ _.delay( function() {
+ overlay.addClass( 'themes-panel-expanded' );
+ }, 200 );
+
+ // Automatically open the first section (except on small screens), if one isn't already expanded.
+ if ( 600 < window.innerWidth ) {
+ sections = panel.sections();
+ _.each( sections, function( section ) {
+ if ( section.expanded() ) {
+ hasExpandedSection = true;
+ }
+ } );
+ if ( ! hasExpandedSection && sections.length > 0 ) {
+ sections[0].expand();
+ }
+ }
+ } else {
+ overlay
+ .removeClass( 'in-themes-panel themes-panel-expanded' )
+ .find( '.customize-themes-full-container' ).removeClass( 'animate' );
+ }
+ },
+
+ /**
+ * Install a theme via wp.updates.
+ *
+ * @since 4.9.0
+ *
+ * @param {jQuery.Event} event - Event.
+ * @returns {jQuery.promise} Promise.
+ */
+ installTheme: function( event ) {
+ var panel = this, preview, onInstallSuccess, slug = $( event.target ).data( 'slug' ), deferred = $.Deferred(), request;
+ preview = $( event.target ).hasClass( 'preview' );
+
+ // Temporary since supplying SFTP credentials does not work yet. See #42184.
+ if ( api.settings.theme._filesystemCredentialsNeeded ) {
+ deferred.reject({
+ errorCode: 'theme_install_unavailable'
+ });
+ return deferred.promise();
+ }
+
+ // Prevent loading a non-active theme preview when there is a drafted/scheduled changeset.
+ if ( ! panel.canSwitchTheme( slug ) ) {
+ deferred.reject({
+ errorCode: 'theme_switch_unavailable'
+ });
+ return deferred.promise();
+ }
+
+ // Theme is already being installed.
+ if ( _.contains( panel.installingThemes, slug ) ) {
+ deferred.reject({
+ errorCode: 'theme_already_installing'
+ });
+ return deferred.promise();
+ }
+
+ wp.updates.maybeRequestFilesystemCredentials( event );
+
+ onInstallSuccess = function( response ) {
+ var theme = false, themeControl;
+ if ( preview ) {
+ api.notifications.remove( 'theme_installing' );
+
+ panel.loadThemePreview( slug );
+
+ } else {
+ api.control.each( function( control ) {
+ if ( 'theme' === control.params.type && control.params.theme.id === response.slug ) {
+ theme = control.params.theme; // Used below to add theme control.
+ control.rerenderAsInstalled( true );
+ }
+ });
+
+ // Don't add the same theme more than once.
+ if ( ! theme || api.control.has( 'installed_theme_' + theme.id ) ) {
+ deferred.resolve( response );
+ return;
+ }
+
+ // Add theme control to installed section.
+ theme.type = 'installed';
+ themeControl = new api.controlConstructor.theme( 'installed_theme_' + theme.id, {
+ type: 'theme',
+ section: 'installed_themes',
+ theme: theme,
+ priority: 0 // Add all newly-installed themes to the top.
+ } );
+
+ api.control.add( themeControl );
+ api.control( themeControl.id ).container.trigger( 'render-screenshot' );
+
+ // Close the details modal if it's open to the installed theme.
+ api.section.each( function( section ) {
+ if ( 'themes' === section.params.type ) {
+ if ( theme.id === section.currentTheme ) { // Don't close the modal if the user has navigated elsewhere.
+ section.closeDetails();
+ }
+ }
+ });
+ }
+ deferred.resolve( response );
+ };
+
+ panel.installingThemes.push( slug ); // Note: we don't remove elements from installingThemes, since they shouldn't be installed again.
+ request = wp.updates.installTheme( {
+ slug: slug
+ } );
+
+ // Also preview the theme as the event is triggered on Install & Preview.
+ if ( preview ) {
+ api.notifications.add( new api.OverlayNotification( 'theme_installing', {
+ message: api.l10n.themeDownloading,
+ type: 'info',
+ loading: true
+ } ) );
+ }
+
+ request.done( onInstallSuccess );
+ request.fail( function() {
+ api.notifications.remove( 'theme_installing' );
+ } );
+
+ return deferred.promise();
+ },
+
+ /**
+ * Load theme preview.
+ *
+ * @since 4.9.0
+ *
+ * @param {string} themeId Theme ID.
+ * @returns {jQuery.promise} Promise.
+ */
+ loadThemePreview: function( themeId ) {
+ var panel = this, deferred = $.Deferred(), onceProcessingComplete, urlParser, queryParams;
+
+ // Prevent loading a non-active theme preview when there is a drafted/scheduled changeset.
+ if ( ! panel.canSwitchTheme( themeId ) ) {
+ deferred.reject({
+ errorCode: 'theme_switch_unavailable'
+ });
+ return deferred.promise();
+ }
+
+ urlParser = document.createElement( 'a' );
+ urlParser.href = location.href;
+ queryParams = _.extend(
+ api.utils.parseQueryString( urlParser.search.substr( 1 ) ),
+ {
+ theme: themeId,
+ changeset_uuid: api.settings.changeset.uuid,
+ 'return': api.settings.url['return']
+ }
+ );
+
+ // Include autosaved param to load autosave revision without prompting user to restore it.
+ if ( ! api.state( 'saved' ).get() ) {
+ queryParams.customize_autosaved = 'on';
+ }
+
+ urlParser.search = $.param( queryParams );
+
+ // Update loading message. Everything else is handled by reloading the page.
+ api.notifications.add( new api.OverlayNotification( 'theme_previewing', {
+ message: api.l10n.themePreviewWait,
+ type: 'info',
+ loading: true
+ } ) );
+
+ onceProcessingComplete = function() {
+ var request;
+ if ( api.state( 'processing' ).get() > 0 ) {
+ return;
+ }
+
+ api.state( 'processing' ).unbind( onceProcessingComplete );
+
+ request = api.requestChangesetUpdate( {}, { autosave: true } );
+ request.done( function() {
+ deferred.resolve();
+ $( window ).off( 'beforeunload.customize-confirm' );
+ location.replace( urlParser.href );
+ } );
+ request.fail( function() {
+
+ // @todo Show notification regarding failure.
+ api.notifications.remove( 'theme_previewing' );
+
+ deferred.reject();
+ } );
+ };
+
+ if ( 0 === api.state( 'processing' ).get() ) {
+ onceProcessingComplete();
+ } else {
+ api.state( 'processing' ).bind( onceProcessingComplete );
+ }
+
+ return deferred.promise();
+ },
+
+ /**
+ * Update a theme via wp.updates.
+ *
+ * @since 4.9.0
+ *
+ * @param {jQuery.Event} event - Event.
+ * @returns {void}
+ */
+ updateTheme: function( event ) {
+ wp.updates.maybeRequestFilesystemCredentials( event );
+
+ $( document ).one( 'wp-theme-update-success', function( e, response ) {
+
+ // Rerender the control to reflect the update.
+ api.control.each( function( control ) {
+ if ( 'theme' === control.params.type && control.params.theme.id === response.slug ) {
+ control.params.theme.hasUpdate = false;
+ control.params.theme.version = response.newVersion;
+ setTimeout( function() {
+ control.rerenderAsInstalled( true );
+ }, 2000 );
+ }
+ });
+ } );
+
+ wp.updates.updateTheme( {
+ slug: $( event.target ).closest( '.notice' ).data( 'slug' )
+ } );
+ },
+
+ /**
+ * Delete a theme via wp.updates.
+ *
+ * @since 4.9.0
+ *
+ * @param {jQuery.Event} event - Event.
+ * @returns {void}
+ */
+ deleteTheme: function( event ) {
+ var theme, section;
+ theme = $( event.target ).data( 'slug' );
+ section = api.section( 'installed_themes' );
+
+ event.preventDefault();
+
+ // Temporary since supplying SFTP credentials does not work yet. See #42184.
+ if ( api.settings.theme._filesystemCredentialsNeeded ) {
+ return;
+ }
+
+ // Confirmation dialog for deleting a theme.
+ if ( ! window.confirm( api.settings.l10n.confirmDeleteTheme ) ) {
+ return;
+ }
+
+ wp.updates.maybeRequestFilesystemCredentials( event );
+
+ $( document ).one( 'wp-theme-delete-success', function() {
+ var control = api.control( 'installed_theme_' + theme );
+
+ // Remove theme control.
+ control.container.remove();
+ api.control.remove( control.id );
+
+ // Update installed count.
+ section.loaded = section.loaded - 1;
+ section.updateCount();
+
+ // Rerender any other theme controls as uninstalled.
+ api.control.each( function( control ) {
+ if ( 'theme' === control.params.type && control.params.theme.id === theme ) {
+ control.rerenderAsInstalled( false );
+ }
+ });
+ } );
+
+ wp.updates.deleteTheme( {
+ slug: theme
+ } );
+
+ // Close modal and focus the section.
+ section.closeDetails();
+ section.focus();
+ }
+ });
+
+ api.Control = api.Class.extend(/** @lends wp.customize.Control.prototype */{
+ defaultActiveArguments: { duration: 'fast', completeCallback: $.noop },
+
+ /**
+ * Default params.
+ *
+ * @since 4.9.0
+ * @var {object}
+ */
+ defaults: {
+ label: '',
+ description: '',
+ active: true,
+ priority: 10
+ },
+
+ /**
+ * A Customizer Control.
+ *
+ * A control provides a UI element that allows a user to modify a Customizer Setting.
+ *
+ * @see PHP class WP_Customize_Control.
+ *
+ * @constructs wp.customize.Control
+ * @augments wp.customize.Class
+ *
+ * @borrows wp.customize~focus as this#focus
+ * @borrows wp.customize~Container#activate as this#activate
+ * @borrows wp.customize~Container#deactivate as this#deactivate
+ * @borrows wp.customize~Container#_toggleActive as this#_toggleActive
+ *
+ * @param {string} id - Unique identifier for the control instance.
+ * @param {object} options - Options hash for the control instance.
+ * @param {object} options.type - Type of control (e.g. text, radio, dropdown-pages, etc.)
+ * @param {string} [options.content] - The HTML content for the control or at least its container. This should normally be left blank and instead supplying a templateId.
+ * @param {string} [options.templateId] - Template ID for control's content.
+ * @param {string} [options.priority=10] - Order of priority to show the control within the section.
+ * @param {string} [options.active=true] - Whether the control is active.
+ * @param {string} options.section - The ID of the section the control belongs to.
+ * @param {mixed} [options.setting] - The ID of the main setting or an instance of this setting.
+ * @param {mixed} options.settings - An object with keys (e.g. default) that maps to setting IDs or Setting/Value objects, or an array of setting IDs or Setting/Value objects.
+ * @param {mixed} options.settings.default - The ID of the setting the control relates to.
+ * @param {string} options.settings.data - @todo Is this used?
+ * @param {string} options.label - Label.
+ * @param {string} options.description - Description.
+ * @param {number} [options.instanceNumber] - Order in which this instance was created in relation to other instances.
+ * @param {object} [options.params] - Deprecated wrapper for the above properties.
+ * @returns {void}
+ */
+ initialize: function( id, options ) {
+ var control = this, deferredSettingIds = [], settings, gatherSettings;
+
+ control.params = _.extend(
+ {},
+ control.defaults,
+ control.params || {}, // In case sub-class already defines.
+ options.params || options || {} // The options.params property is deprecated, but it is checked first for back-compat.
+ );
+
+ if ( ! api.Control.instanceCounter ) {
+ api.Control.instanceCounter = 0;
+ }
+ api.Control.instanceCounter++;
+ if ( ! control.params.instanceNumber ) {
+ control.params.instanceNumber = api.Control.instanceCounter;
+ }
+
+ // Look up the type if one was not supplied.
+ if ( ! control.params.type ) {
+ _.find( api.controlConstructor, function( Constructor, type ) {
+ if ( Constructor === control.constructor ) {
+ control.params.type = type;
+ return true;
+ }
+ return false;
+ } );
+ }
+
+ if ( ! control.params.content ) {
+ control.params.content = $( '<li></li>', {
+ id: 'customize-control-' + id.replace( /]/g, '' ).replace( /\[/g, '-' ),
+ 'class': 'customize-control customize-control-' + control.params.type
+ } );
+ }
+
+ control.id = id;
+ control.selector = '#customize-control-' + id.replace( /\]/g, '' ).replace( /\[/g, '-' ); // Deprecated, likely dead code from time before #28709.
+ if ( control.params.content ) {
+ control.container = $( control.params.content );
+ } else {
+ control.container = $( control.selector ); // Likely dead, per above. See #28709.
+ }
+
+ if ( control.params.templateId ) {
+ control.templateSelector = control.params.templateId;
+ } else {
+ control.templateSelector = 'customize-control-' + control.params.type + '-content';
+ }
+
+ control.deferred = _.extend( control.deferred || {}, {
+ embedded: new $.Deferred()
+ } );
+ control.section = new api.Value();
+ control.priority = new api.Value();
+ control.active = new api.Value();
+ control.activeArgumentsQueue = [];
+ control.notifications = new api.Notifications({
+ alt: control.altNotice
+ });
+
+ control.elements = [];
+
+ control.active.bind( function ( active ) {
+ var args = control.activeArgumentsQueue.shift();
+ args = $.extend( {}, control.defaultActiveArguments, args );
+ control.onChangeActive( active, args );
+ } );
+
+ control.section.set( control.params.section );
+ control.priority.set( isNaN( control.params.priority ) ? 10 : control.params.priority );
+ control.active.set( control.params.active );
+
+ api.utils.bubbleChildValueChanges( control, [ 'section', 'priority', 'active' ] );
+
+ control.settings = {};
+
+ settings = {};
+ if ( control.params.setting ) {
+ settings['default'] = control.params.setting;
+ }
+ _.extend( settings, control.params.settings );
+
+ // Note: Settings can be an array or an object, with values being either setting IDs or Setting (or Value) objects.
+ _.each( settings, function( value, key ) {
+ var setting;
+ if ( _.isObject( value ) && _.isFunction( value.extended ) && value.extended( api.Value ) ) {
+ control.settings[ key ] = value;
+ } else if ( _.isString( value ) ) {
+ setting = api( value );
+ if ( setting ) {
+ control.settings[ key ] = setting;
+ } else {
+ deferredSettingIds.push( value );
+ }
+ }
+ } );
+
+ gatherSettings = function() {
+
+ // Fill-in all resolved settings.
+ _.each( settings, function ( settingId, key ) {
+ if ( ! control.settings[ key ] && _.isString( settingId ) ) {
+ control.settings[ key ] = api( settingId );
+ }
+ } );
+
+ // Make sure settings passed as array gets associated with default.
+ if ( control.settings[0] && ! control.settings['default'] ) {
+ control.settings['default'] = control.settings[0];
+ }
+
+ // Identify the main setting.
+ control.setting = control.settings['default'] || null;
+
+ control.linkElements(); // Link initial elements present in server-rendered content.
+ control.embed();
+ };
+
+ if ( 0 === deferredSettingIds.length ) {
+ gatherSettings();
+ } else {
+ api.apply( api, deferredSettingIds.concat( gatherSettings ) );
+ }
+
+ // After the control is embedded on the page, invoke the "ready" method.
+ control.deferred.embedded.done( function () {
+ control.linkElements(); // Link any additional elements after template is rendered by renderContent().
+ control.setupNotifications();
+ control.ready();
+ });
+ },
+
+ /**
+ * Link elements between settings and inputs.
+ *
+ * @since 4.7.0
+ * @access public
+ *
+ * @returns {void}
+ */
+ linkElements: function () {
+ var control = this, nodes, radios, element;
+
+ nodes = control.container.find( '[data-customize-setting-link], [data-customize-setting-key-link]' );
+ radios = {};
+
+ nodes.each( function () {
+ var node = $( this ), name, setting;
+
+ if ( node.data( 'customizeSettingLinked' ) ) {
+ return;
+ }
+ node.data( 'customizeSettingLinked', true ); // Prevent re-linking element.
+
+ if ( node.is( ':radio' ) ) {
+ name = node.prop( 'name' );
+ if ( radios[name] ) {
+ return;
+ }
+
+ radios[name] = true;
+ node = nodes.filter( '[name="' + name + '"]' );
+ }
+
+ // Let link by default refer to setting ID. If it doesn't exist, fallback to looking up by setting key.
+ if ( node.data( 'customizeSettingLink' ) ) {
+ setting = api( node.data( 'customizeSettingLink' ) );
+ } else if ( node.data( 'customizeSettingKeyLink' ) ) {
+ setting = control.settings[ node.data( 'customizeSettingKeyLink' ) ];
+ }
+
+ if ( setting ) {
+ element = new api.Element( node );
+ control.elements.push( element );
+ element.sync( setting );
+ element.set( setting() );
+ }
+ } );
+ },
+
+ /**
+ * Embed the control into the page.
+ */
+ embed: function () {
+ var control = this,
+ inject;
+
+ // Watch for changes to the section state
+ inject = function ( sectionId ) {
+ var parentContainer;
+ if ( ! sectionId ) { // @todo allow a control to be embedded without a section, for instance a control embedded in the front end.
+ return;
+ }
+ // Wait for the section to be registered
+ api.section( sectionId, function ( section ) {
+ // Wait for the section to be ready/initialized
+ section.deferred.embedded.done( function () {
+ parentContainer = ( section.contentContainer.is( 'ul' ) ) ? section.contentContainer : section.contentContainer.find( 'ul:first' );
+ if ( ! control.container.parent().is( parentContainer ) ) {
+ parentContainer.append( control.container );
+ control.renderContent();
+ }
+ control.deferred.embedded.resolve();
+ });
+ });
+ };
+ control.section.bind( inject );
+ inject( control.section.get() );
+ },
+
+ /**
+ * Triggered when the control's markup has been injected into the DOM.
+ *
+ * @returns {void}
+ */
+ ready: function() {
+ var control = this, newItem;
+ if ( 'dropdown-pages' === control.params.type && control.params.allow_addition ) {
+ newItem = control.container.find( '.new-content-item' );
+ newItem.hide(); // Hide in JS to preserve flex display when showing.
+ control.container.on( 'click', '.add-new-toggle', function( e ) {
+ $( e.currentTarget ).slideUp( 180 );
+ newItem.slideDown( 180 );
+ newItem.find( '.create-item-input' ).focus();
+ });
+ control.container.on( 'click', '.add-content', function() {
+ control.addNewPage();
+ });
+ control.container.on( 'keydown', '.create-item-input', function( e ) {
+ if ( 13 === e.which ) { // Enter
+ control.addNewPage();
+ }
+ });
+ }
+ },
+
+ /**
+ * Get the element inside of a control's container that contains the validation error message.
+ *
+ * Control subclasses may override this to return the proper container to render notifications into.
+ * Injects the notification container for existing controls that lack the necessary container,
+ * including special handling for nav menu items and widgets.
+ *
+ * @since 4.6.0
+ * @returns {jQuery} Setting validation message element.
+ */
+ getNotificationsContainerElement: function() {
+ var control = this, controlTitle, notificationsContainer;
+
+ notificationsContainer = control.container.find( '.customize-control-notifications-container:first' );
+ if ( notificationsContainer.length ) {
+ return notificationsContainer;
+ }
+
+ notificationsContainer = $( '<div class="customize-control-notifications-container"></div>' );
+
+ if ( control.container.hasClass( 'customize-control-nav_menu_item' ) ) {
+ control.container.find( '.menu-item-settings:first' ).prepend( notificationsContainer );
+ } else if ( control.container.hasClass( 'customize-control-widget_form' ) ) {
+ control.container.find( '.widget-inside:first' ).prepend( notificationsContainer );
+ } else {
+ controlTitle = control.container.find( '.customize-control-title' );
+ if ( controlTitle.length ) {
+ controlTitle.after( notificationsContainer );
+ } else {
+ control.container.prepend( notificationsContainer );
+ }
+ }
+ return notificationsContainer;
+ },
+
+ /**
+ * Set up notifications.
+ *
+ * @since 4.9.0
+ * @returns {void}
+ */
+ setupNotifications: function() {
+ var control = this, renderNotificationsIfVisible, onSectionAssigned;
+
+ // Add setting notifications to the control notification.
+ _.each( control.settings, function( setting ) {
+ if ( ! setting.notifications ) {
+ return;
+ }
+ setting.notifications.bind( 'add', function( settingNotification ) {
+ var params = _.extend(
+ {},
+ settingNotification,
+ {
+ setting: setting.id
+ }
+ );
+ control.notifications.add( new api.Notification( setting.id + ':' + settingNotification.code, params ) );
+ } );
+ setting.notifications.bind( 'remove', function( settingNotification ) {
+ control.notifications.remove( setting.id + ':' + settingNotification.code );
+ } );
+ } );
+
+ renderNotificationsIfVisible = function() {
+ var sectionId = control.section();
+ if ( ! sectionId || ( api.section.has( sectionId ) && api.section( sectionId ).expanded() ) ) {
+ control.notifications.render();
+ }
+ };
+
+ control.notifications.bind( 'rendered', function() {
+ var notifications = control.notifications.get();
+ control.container.toggleClass( 'has-notifications', 0 !== notifications.length );
+ control.container.toggleClass( 'has-error', 0 !== _.where( notifications, { type: 'error' } ).length );
+ } );
+
+ onSectionAssigned = function( newSectionId, oldSectionId ) {
+ if ( oldSectionId && api.section.has( oldSectionId ) ) {
+ api.section( oldSectionId ).expanded.unbind( renderNotificationsIfVisible );
+ }
+ if ( newSectionId ) {
+ api.section( newSectionId, function( section ) {
+ section.expanded.bind( renderNotificationsIfVisible );
+ renderNotificationsIfVisible();
+ });
+ }
+ };
+
+ control.section.bind( onSectionAssigned );
+ onSectionAssigned( control.section.get() );
+ control.notifications.bind( 'change', _.debounce( renderNotificationsIfVisible ) );
+ },
+
+ /**
+ * Render notifications.
+ *
+ * Renders the `control.notifications` into the control's container.
+ * Control subclasses may override this method to do their own handling
+ * of rendering notifications.
+ *
+ * @deprecated in favor of `control.notifications.render()`
+ * @since 4.6.0
+ * @this {wp.customize.Control}
+ */
+ renderNotifications: function() {
+ var control = this, container, notifications, hasError = false;
+
+ if ( 'undefined' !== typeof console && console.warn ) {
+ console.warn( '[DEPRECATED] wp.customize.Control.prototype.renderNotifications() is deprecated in favor of instantating a wp.customize.Notifications and calling its render() method.' );
+ }
+
+ container = control.getNotificationsContainerElement();
+ if ( ! container || ! container.length ) {
+ return;
+ }
+ notifications = [];
+ control.notifications.each( function( notification ) {
+ notifications.push( notification );
+ if ( 'error' === notification.type ) {
+ hasError = true;
+ }
+ } );
+
+ if ( 0 === notifications.length ) {
+ container.stop().slideUp( 'fast' );
+ } else {
+ container.stop().slideDown( 'fast', null, function() {
+ $( this ).css( 'height', 'auto' );
+ } );
+ }
+
+ if ( ! control.notificationsTemplate ) {
+ control.notificationsTemplate = wp.template( 'customize-control-notifications' );
+ }
+
+ control.container.toggleClass( 'has-notifications', 0 !== notifications.length );
+ control.container.toggleClass( 'has-error', hasError );
+ container.empty().append( $.trim(
+ control.notificationsTemplate( { notifications: notifications, altNotice: Boolean( control.altNotice ) } )
+ ) );
+ },
+
+ /**
+ * Normal controls do not expand, so just expand its parent
+ *
+ * @param {Object} [params]
+ */
+ expand: function ( params ) {
+ api.section( this.section() ).expand( params );
+ },
+
+ /*
+ * Documented using @borrows in the constructor.
+ */
+ focus: focus,
+
+ /**
+ * Update UI in response to a change in the control's active state.
+ * This does not change the active state, it merely handles the behavior
+ * for when it does change.
+ *
+ * @since 4.1.0
+ *
+ * @param {Boolean} active
+ * @param {Object} args
+ * @param {Number} args.duration
+ * @param {Function} args.completeCallback
+ */
+ onChangeActive: function ( active, args ) {
+ if ( args.unchanged ) {
+ if ( args.completeCallback ) {
+ args.completeCallback();
+ }
+ return;
+ }
+
+ if ( ! $.contains( document, this.container[0] ) ) {
+ // jQuery.fn.slideUp is not hiding an element if it is not in the DOM
+ this.container.toggle( active );
+ if ( args.completeCallback ) {
+ args.completeCallback();
+ }
+ } else if ( active ) {
+ this.container.slideDown( args.duration, args.completeCallback );
+ } else {
+ this.container.slideUp( args.duration, args.completeCallback );
+ }
+ },
+
+ /**
+ * @deprecated 4.1.0 Use this.onChangeActive() instead.
+ */
+ toggle: function ( active ) {
+ return this.onChangeActive( active, this.defaultActiveArguments );
+ },
+
+ /*
+ * Documented using @borrows in the constructor
+ */
+ activate: Container.prototype.activate,
+
+ /*
+ * Documented using @borrows in the constructor
+ */
+ deactivate: Container.prototype.deactivate,
+
+ /*
+ * Documented using @borrows in the constructor
+ */
+ _toggleActive: Container.prototype._toggleActive,
+
+ // @todo This function appears to be dead code and can be removed.
+ dropdownInit: function() {
+ var control = this,
+ statuses = this.container.find('.dropdown-status'),
+ params = this.params,
+ toggleFreeze = false,
+ update = function( to ) {
+ if ( 'string' === typeof to && params.statuses && params.statuses[ to ] ) {
+ statuses.html( params.statuses[ to ] ).show();
+ } else {
+ statuses.hide();
+ }
+ };
+
+ // Support the .dropdown class to open/close complex elements
+ this.container.on( 'click keydown', '.dropdown', function( event ) {
+ if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
+ return;
+ }
+
+ event.preventDefault();
+
+ if ( ! toggleFreeze ) {
+ control.container.toggleClass( 'open' );
+ }
+
+ if ( control.container.hasClass( 'open' ) ) {
+ control.container.parent().parent().find( 'li.library-selected' ).focus();
+ }
+
+ // Don't want to fire focus and click at same time
+ toggleFreeze = true;
+ setTimeout(function () {
+ toggleFreeze = false;
+ }, 400);
+ });
+
+ this.setting.bind( update );
+ update( this.setting() );
+ },
+
+ /**
+ * Render the control from its JS template, if it exists.
+ *
+ * The control's container must already exist in the DOM.
+ *
+ * @since 4.1.0
+ */
+ renderContent: function () {
+ var control = this, template, standardTypes, templateId, sectionId;
+
+ standardTypes = [
+ 'button',
+ 'checkbox',
+ 'date',
+ 'datetime-local',
+ 'email',
+ 'month',
+ 'number',
+ 'password',
+ 'radio',
+ 'range',
+ 'search',
+ 'select',
+ 'tel',
+ 'time',
+ 'text',
+ 'textarea',
+ 'week',
+ 'url'
+ ];
+
+ templateId = control.templateSelector;
+
+ // Use default content template when a standard HTML type is used, there isn't a more specific template existing, and the control container is empty.
+ if ( templateId === 'customize-control-' + control.params.type + '-content' &&
+ _.contains( standardTypes, control.params.type ) &&
+ ! document.getElementById( 'tmpl-' + templateId ) &&
+ 0 === control.container.children().length )
+ {
+ templateId = 'customize-control-default-content';
+ }
+
+ // Replace the container element's content with the control.
+ if ( document.getElementById( 'tmpl-' + templateId ) ) {
+ template = wp.template( templateId );
+ if ( template && control.container ) {
+ control.container.html( template( control.params ) );
+ }
+ }
+
+ // Re-render notifications after content has been re-rendered.
+ control.notifications.container = control.getNotificationsContainerElement();
+ sectionId = control.section();
+ if ( ! sectionId || ( api.section.has( sectionId ) && api.section( sectionId ).expanded() ) ) {
+ control.notifications.render();
+ }
+ },
+
+ /**
+ * Add a new page to a dropdown-pages control reusing menus code for this.
+ *
+ * @since 4.7.0
+ * @access private
+ * @returns {void}
+ */
+ addNewPage: function () {
+ var control = this, promise, toggle, container, input, title, select;
+
+ if ( 'dropdown-pages' !== control.params.type || ! control.params.allow_addition || ! api.Menus ) {
+ return;
+ }
+
+ toggle = control.container.find( '.add-new-toggle' );
+ container = control.container.find( '.new-content-item' );
+ input = control.container.find( '.create-item-input' );
+ title = input.val();
+ select = control.container.find( 'select' );
+
+ if ( ! title ) {
+ input.addClass( 'invalid' );
+ return;
+ }
+
+ input.removeClass( 'invalid' );
+ input.attr( 'disabled', 'disabled' );
+
+ // The menus functions add the page, publish when appropriate, and also add the new page to the dropdown-pages controls.
+ promise = api.Menus.insertAutoDraftPost( {
+ post_title: title,
+ post_type: 'page'
+ } );
+ promise.done( function( data ) {
+ var availableItem, $content, itemTemplate;
+
+ // Prepare the new page as an available menu item.
+ // See api.Menus.submitNew().
+ availableItem = new api.Menus.AvailableItemModel( {
+ 'id': 'post-' + data.post_id, // Used for available menu item Backbone models.
+ 'title': title,
+ 'type': 'post_type',
+ 'type_label': api.Menus.data.l10n.page_label,
+ 'object': 'page',
+ 'object_id': data.post_id,
+ 'url': data.url
+ } );
+
+ // Add the new item to the list of available menu items.
+ api.Menus.availableMenuItemsPanel.collection.add( availableItem );
+ $content = $( '#available-menu-items-post_type-page' ).find( '.available-menu-items-list' );
+ itemTemplate = wp.template( 'available-menu-item' );
+ $content.prepend( itemTemplate( availableItem.attributes ) );
+
+ // Focus the select control.
+ select.focus();
+ control.setting.set( String( data.post_id ) ); // Triggers a preview refresh and updates the setting.
+
+ // Reset the create page form.
+ container.slideUp( 180 );
+ toggle.slideDown( 180 );
+ } );
+ promise.always( function() {
+ input.val( '' ).removeAttr( 'disabled' );
+ } );
+ }
+ });
+
+ /**
+ * A colorpicker control.
+ *
+ * @class wp.customize.ColorControl
+ * @augments wp.customize.Control
+ */
+ api.ColorControl = api.Control.extend(/** @lends wp.customize.ColorControl.prototype */{
+ ready: function() {
+ var control = this,
+ isHueSlider = this.params.mode === 'hue',
+ updating = false,
+ picker;
+
+ if ( isHueSlider ) {
+ picker = this.container.find( '.color-picker-hue' );
+ picker.val( control.setting() ).wpColorPicker({
+ change: function( event, ui ) {
+ updating = true;
+ control.setting( ui.color.h() );
+ updating = false;
+ }
+ });
+ } else {
+ picker = this.container.find( '.color-picker-hex' );
+ picker.val( control.setting() ).wpColorPicker({
+ change: function() {
+ updating = true;
+ control.setting.set( picker.wpColorPicker( 'color' ) );
+ updating = false;
+ },
+ clear: function() {
+ updating = true;
+ control.setting.set( '' );
+ updating = false;
+ }
+ });
+ }
+
+ control.setting.bind( function ( value ) {
+ // Bail if the update came from the control itself.
+ if ( updating ) {
+ return;
+ }
+ picker.val( value );
+ picker.wpColorPicker( 'color', value );
+ } );
+
+ // Collapse color picker when hitting Esc instead of collapsing the current section.
+ control.container.on( 'keydown', function( event ) {
+ var pickerContainer;
+ if ( 27 !== event.which ) { // Esc.
+ return;
+ }
+ pickerContainer = control.container.find( '.wp-picker-container' );
+ if ( pickerContainer.hasClass( 'wp-picker-active' ) ) {
+ picker.wpColorPicker( 'close' );
+ control.container.find( '.wp-color-result' ).focus();
+ event.stopPropagation(); // Prevent section from being collapsed.
+ }
+ } );
+ }
+ });
+
+ /**
+ * A control that implements the media modal.
+ *
+ * @class wp.customize.MediaControl
+ * @augments wp.customize.Control
+ */
+ api.MediaControl = api.Control.extend(/** @lends wp.customize.MediaControl.prototype */{
+
+ /**
+ * When the control's DOM structure is ready,
+ * set up internal event bindings.
+ */
+ ready: function() {
+ var control = this;
+ // Shortcut so that we don't have to use _.bind every time we add a callback.
+ _.bindAll( control, 'restoreDefault', 'removeFile', 'openFrame', 'select', 'pausePlayer' );
+
+ // Bind events, with delegation to facilitate re-rendering.
+ control.container.on( 'click keydown', '.upload-button', control.openFrame );
+ control.container.on( 'click keydown', '.upload-button', control.pausePlayer );
+ control.container.on( 'click keydown', '.thumbnail-image img', control.openFrame );
+ control.container.on( 'click keydown', '.default-button', control.restoreDefault );
+ control.container.on( 'click keydown', '.remove-button', control.pausePlayer );
+ control.container.on( 'click keydown', '.remove-button', control.removeFile );
+ control.container.on( 'click keydown', '.remove-button', control.cleanupPlayer );
+
+ // Resize the player controls when it becomes visible (ie when section is expanded)
+ api.section( control.section() ).container
+ .on( 'expanded', function() {
+ if ( control.player ) {
+ control.player.setControlsSize();
+ }
+ })
+ .on( 'collapsed', function() {
+ control.pausePlayer();
+ });
+
+ /**
+ * Set attachment data and render content.
+ *
+ * Note that BackgroundImage.prototype.ready applies this ready method
+ * to itself. Since BackgroundImage is an UploadControl, the value
+ * is the attachment URL instead of the attachment ID. In this case
+ * we skip fetching the attachment data because we have no ID available,
+ * and it is the responsibility of the UploadControl to set the control's
+ * attachmentData before calling the renderContent method.
+ *
+ * @param {number|string} value Attachment
+ */
+ function setAttachmentDataAndRenderContent( value ) {
+ var hasAttachmentData = $.Deferred();
+
+ if ( control.extended( api.UploadControl ) ) {
+ hasAttachmentData.resolve();
+ } else {
+ value = parseInt( value, 10 );
+ if ( _.isNaN( value ) || value <= 0 ) {
+ delete control.params.attachment;
+ hasAttachmentData.resolve();
+ } else if ( control.params.attachment && control.params.attachment.id === value ) {
+ hasAttachmentData.resolve();
+ }
+ }
+
+ // Fetch the attachment data.
+ if ( 'pending' === hasAttachmentData.state() ) {
+ wp.media.attachment( value ).fetch().done( function() {
+ control.params.attachment = this.attributes;
+ hasAttachmentData.resolve();
+
+ // Send attachment information to the preview for possible use in `postMessage` transport.
+ wp.customize.previewer.send( control.setting.id + '-attachment-data', this.attributes );
+ } );
+ }
+
+ hasAttachmentData.done( function() {
+ control.renderContent();
+ } );
+ }
+
+ // Ensure attachment data is initially set (for dynamically-instantiated controls).
+ setAttachmentDataAndRenderContent( control.setting() );
+
+ // Update the attachment data and re-render the control when the setting changes.
+ control.setting.bind( setAttachmentDataAndRenderContent );
+ },
+
+ pausePlayer: function () {
+ this.player && this.player.pause();
+ },
+
+ cleanupPlayer: function () {
+ this.player && wp.media.mixin.removePlayer( this.player );
+ },
+
+ /**
+ * Open the media modal.
+ */
+ openFrame: function( event ) {
+ if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
+ return;
+ }
+
+ event.preventDefault();
+
+ if ( ! this.frame ) {
+ this.initFrame();
+ }
+
+ this.frame.open();
+ },
+
+ /**
+ * Create a media modal select frame, and store it so the instance can be reused when needed.
+ */
+ initFrame: function() {
+ this.frame = wp.media({
+ button: {
+ text: this.params.button_labels.frame_button
+ },
+ states: [
+ new wp.media.controller.Library({
+ title: this.params.button_labels.frame_title,
+ library: wp.media.query({ type: this.params.mime_type }),
+ multiple: false,
+ date: false
+ })
+ ]
+ });
+
+ // When a file is selected, run a callback.
+ this.frame.on( 'select', this.select );
+ },
+
+ /**
+ * Callback handler for when an attachment is selected in the media modal.
+ * Gets the selected image information, and sets it within the control.
+ */
+ select: function() {
+ // Get the attachment from the modal frame.
+ var node,
+ attachment = this.frame.state().get( 'selection' ).first().toJSON(),
+ mejsSettings = window._wpmejsSettings || {};
+
+ this.params.attachment = attachment;
+
+ // Set the Customizer setting; the callback takes care of rendering.
+ this.setting( attachment.id );
+ node = this.container.find( 'audio, video' ).get(0);
+
+ // Initialize audio/video previews.
+ if ( node ) {
+ this.player = new MediaElementPlayer( node, mejsSettings );
+ } else {
+ this.cleanupPlayer();
+ }
+ },
+
+ /**
+ * Reset the setting to the default value.
+ */
+ restoreDefault: function( event ) {
+ if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
+ return;
+ }
+ event.preventDefault();
+
+ this.params.attachment = this.params.defaultAttachment;
+ this.setting( this.params.defaultAttachment.url );
+ },
+
+ /**
+ * Called when the "Remove" link is clicked. Empties the setting.
+ *
+ * @param {object} event jQuery Event object
+ */
+ removeFile: function( event ) {
+ if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
+ return;
+ }
+ event.preventDefault();
+
+ this.params.attachment = {};
+ this.setting( '' );
+ this.renderContent(); // Not bound to setting change when emptying.
+ }
+ });
+
+ /**
+ * An upload control, which utilizes the media modal.
+ *
+ * @class wp.customize.UploadControl
+ * @augments wp.customize.MediaControl
+ */
+ api.UploadControl = api.MediaControl.extend(/** @lends wp.customize.UploadControl.prototype */{
+
+ /**
+ * Callback handler for when an attachment is selected in the media modal.
+ * Gets the selected image information, and sets it within the control.
+ */
+ select: function() {
+ // Get the attachment from the modal frame.
+ var node,
+ attachment = this.frame.state().get( 'selection' ).first().toJSON(),
+ mejsSettings = window._wpmejsSettings || {};
+
+ this.params.attachment = attachment;
+
+ // Set the Customizer setting; the callback takes care of rendering.
+ this.setting( attachment.url );
+ node = this.container.find( 'audio, video' ).get(0);
+
+ // Initialize audio/video previews.
+ if ( node ) {
+ this.player = new MediaElementPlayer( node, mejsSettings );
+ } else {
+ this.cleanupPlayer();
+ }
+ },
+
+ // @deprecated
+ success: function() {},
+
+ // @deprecated
+ removerVisibility: function() {}
+ });
+
+ /**
+ * A control for uploading images.
+ *
+ * This control no longer needs to do anything more
+ * than what the upload control does in JS.
+ *
+ * @class wp.customize.ImageControl
+ * @augments wp.customize.UploadControl
+ */
+ api.ImageControl = api.UploadControl.extend(/** @lends wp.customize.ImageControl.prototype */{
+ // @deprecated
+ thumbnailSrc: function() {}
+ });
+
+ /**
+ * A control for uploading background images.
+ *
+ * @class wp.customize.BackgroundControl
+ * @augments wp.customize.UploadControl
+ */
+ api.BackgroundControl = api.UploadControl.extend(/** @lends wp.customize.BackgroundControl.prototype */{
+
+ /**
+ * When the control's DOM structure is ready,
+ * set up internal event bindings.
+ */
+ ready: function() {
+ api.UploadControl.prototype.ready.apply( this, arguments );
+ },
+
+ /**
+ * Callback handler for when an attachment is selected in the media modal.
+ * Does an additional AJAX request for setting the background context.
+ */
+ select: function() {
+ api.UploadControl.prototype.select.apply( this, arguments );
+
+ wp.ajax.post( 'custom-background-add', {
+ nonce: _wpCustomizeBackground.nonces.add,
+ wp_customize: 'on',
+ customize_theme: api.settings.theme.stylesheet,
+ attachment_id: this.params.attachment.id
+ } );
+ }
+ });
+
+ /**
+ * A control for positioning a background image.
+ *
+ * @since 4.7.0
+ *
+ * @class wp.customize.BackgroundPositionControl
+ * @augments wp.customize.Control
+ */
+ api.BackgroundPositionControl = api.Control.extend(/** @lends wp.customize.BackgroundPositionControl.prototype */{
+
+ /**
+ * Set up control UI once embedded in DOM and settings are created.
+ *
+ * @since 4.7.0
+ * @access public
+ */
+ ready: function() {
+ var control = this, updateRadios;
+
+ control.container.on( 'change', 'input[name="background-position"]', function() {
+ var position = $( this ).val().split( ' ' );
+ control.settings.x( position[0] );
+ control.settings.y( position[1] );
+ } );
+
+ updateRadios = _.debounce( function() {
+ var x, y, radioInput, inputValue;
+ x = control.settings.x.get();
+ y = control.settings.y.get();
+ inputValue = String( x ) + ' ' + String( y );
+ radioInput = control.container.find( 'input[name="background-position"][value="' + inputValue + '"]' );
+ radioInput.click();
+ } );
+ control.settings.x.bind( updateRadios );
+ control.settings.y.bind( updateRadios );
+
+ updateRadios(); // Set initial UI.
+ }
+ } );
+
+ /**
+ * A control for selecting and cropping an image.
+ *
+ * @class wp.customize.CroppedImageControl
+ * @augments wp.customize.MediaControl
+ */
+ api.CroppedImageControl = api.MediaControl.extend(/** @lends wp.customize.CroppedImageControl.prototype */{
+
+ /**
+ * Open the media modal to the library state.
+ */
+ openFrame: function( event ) {
+ if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
+ return;
+ }
+
+ this.initFrame();
+ this.frame.setState( 'library' ).open();
+ },
+
+ /**
+ * Create a media modal select frame, and store it so the instance can be reused when needed.
+ */
+ initFrame: function() {
+ var l10n = _wpMediaViewsL10n;
+
+ this.frame = wp.media({
+ button: {
+ text: l10n.select,
+ close: false
+ },
+ states: [
+ new wp.media.controller.Library({
+ title: this.params.button_labels.frame_title,
+ library: wp.media.query({ type: 'image' }),
+ multiple: false,
+ date: false,
+ priority: 20,
+ suggestedWidth: this.params.width,
+ suggestedHeight: this.params.height
+ }),
+ new wp.media.controller.CustomizeImageCropper({
+ imgSelectOptions: this.calculateImageSelectOptions,
+ control: this
+ })
+ ]
+ });
+
+ this.frame.on( 'select', this.onSelect, this );
+ this.frame.on( 'cropped', this.onCropped, this );
+ this.frame.on( 'skippedcrop', this.onSkippedCrop, this );
+ },
+
+ /**
+ * After an image is selected in the media modal, switch to the cropper
+ * state if the image isn't the right size.
+ */
+ onSelect: function() {
+ var attachment = this.frame.state().get( 'selection' ).first().toJSON();
+
+ if ( this.params.width === attachment.width && this.params.height === attachment.height && ! this.params.flex_width && ! this.params.flex_height ) {
+ this.setImageFromAttachment( attachment );
+ this.frame.close();
+ } else {
+ this.frame.setState( 'cropper' );
+ }
+ },
+
+ /**
+ * After the image has been cropped, apply the cropped image data to the setting.
+ *
+ * @param {object} croppedImage Cropped attachment data.
+ */
+ onCropped: function( croppedImage ) {
+ this.setImageFromAttachment( croppedImage );
+ },
+
+ /**
+ * Returns a set of options, computed from the attached image data and
+ * control-specific data, to be fed to the imgAreaSelect plugin in
+ * wp.media.view.Cropper.
+ *
+ * @param {wp.media.model.Attachment} attachment
+ * @param {wp.media.controller.Cropper} controller
+ * @returns {Object} Options
+ */
+ calculateImageSelectOptions: function( attachment, controller ) {
+ var control = controller.get( 'control' ),
+ flexWidth = !! parseInt( control.params.flex_width, 10 ),
+ flexHeight = !! parseInt( control.params.flex_height, 10 ),
+ realWidth = attachment.get( 'width' ),
+ realHeight = attachment.get( 'height' ),
+ xInit = parseInt( control.params.width, 10 ),
+ yInit = parseInt( control.params.height, 10 ),
+ ratio = xInit / yInit,
+ xImg = xInit,
+ yImg = yInit,
+ x1, y1, imgSelectOptions;
+
+ controller.set( 'canSkipCrop', ! control.mustBeCropped( flexWidth, flexHeight, xInit, yInit, realWidth, realHeight ) );
+
+ if ( realWidth / realHeight > ratio ) {
+ yInit = realHeight;
+ xInit = yInit * ratio;
+ } else {
+ xInit = realWidth;
+ yInit = xInit / ratio;
+ }
+
+ x1 = ( realWidth - xInit ) / 2;
+ y1 = ( realHeight - yInit ) / 2;
+
+ imgSelectOptions = {
+ handles: true,
+ keys: true,
+ instance: true,
+ persistent: true,
+ imageWidth: realWidth,
+ imageHeight: realHeight,
+ minWidth: xImg > xInit ? xInit : xImg,
+ minHeight: yImg > yInit ? yInit : yImg,
+ x1: x1,
+ y1: y1,
+ x2: xInit + x1,
+ y2: yInit + y1
+ };
+
+ if ( flexHeight === false && flexWidth === false ) {
+ imgSelectOptions.aspectRatio = xInit + ':' + yInit;
+ }
+
+ if ( true === flexHeight ) {
+ delete imgSelectOptions.minHeight;
+ imgSelectOptions.maxWidth = realWidth;
+ }
+
+ if ( true === flexWidth ) {
+ delete imgSelectOptions.minWidth;
+ imgSelectOptions.maxHeight = realHeight;
+ }
+
+ return imgSelectOptions;
+ },
+
+ /**
+ * Return whether the image must be cropped, based on required dimensions.
+ *
+ * @param {bool} flexW
+ * @param {bool} flexH
+ * @param {int} dstW
+ * @param {int} dstH
+ * @param {int} imgW
+ * @param {int} imgH
+ * @return {bool}
+ */
+ mustBeCropped: function( flexW, flexH, dstW, dstH, imgW, imgH ) {
+ if ( true === flexW && true === flexH ) {
+ return false;
+ }
+
+ if ( true === flexW && dstH === imgH ) {
+ return false;
+ }
+
+ if ( true === flexH && dstW === imgW ) {
+ return false;
+ }
+
+ if ( dstW === imgW && dstH === imgH ) {
+ return false;
+ }
+
+ if ( imgW <= dstW ) {
+ return false;
+ }
+
+ return true;
+ },
+
+ /**
+ * If cropping was skipped, apply the image data directly to the setting.
+ */
+ onSkippedCrop: function() {
+ var attachment = this.frame.state().get( 'selection' ).first().toJSON();
+ this.setImageFromAttachment( attachment );
+ },
+
+ /**
+ * Updates the setting and re-renders the control UI.
+ *
+ * @param {object} attachment
+ */
+ setImageFromAttachment: function( attachment ) {
+ this.params.attachment = attachment;
+
+ // Set the Customizer setting; the callback takes care of rendering.
+ this.setting( attachment.id );
+ }
+ });
+
+ /**
+ * A control for selecting and cropping Site Icons.
+ *
+ * @class wp.customize.SiteIconControl
+ * @augments wp.customize.CroppedImageControl
+ */
+ api.SiteIconControl = api.CroppedImageControl.extend(/** @lends wp.customize.SiteIconControl.prototype */{
+
+ /**
+ * Create a media modal select frame, and store it so the instance can be reused when needed.
+ */
+ initFrame: function() {
+ var l10n = _wpMediaViewsL10n;
+
+ this.frame = wp.media({
+ button: {
+ text: l10n.select,
+ close: false
+ },
+ states: [
+ new wp.media.controller.Library({
+ title: this.params.button_labels.frame_title,
+ library: wp.media.query({ type: 'image' }),
+ multiple: false,
+ date: false,
+ priority: 20,
+ suggestedWidth: this.params.width,
+ suggestedHeight: this.params.height
+ }),
+ new wp.media.controller.SiteIconCropper({
+ imgSelectOptions: this.calculateImageSelectOptions,
+ control: this
+ })
+ ]
+ });
+
+ this.frame.on( 'select', this.onSelect, this );
+ this.frame.on( 'cropped', this.onCropped, this );
+ this.frame.on( 'skippedcrop', this.onSkippedCrop, this );
+ },
+
+ /**
+ * After an image is selected in the media modal, switch to the cropper
+ * state if the image isn't the right size.
+ */
+ onSelect: function() {
+ var attachment = this.frame.state().get( 'selection' ).first().toJSON(),
+ controller = this;
+
+ if ( this.params.width === attachment.width && this.params.height === attachment.height && ! this.params.flex_width && ! this.params.flex_height ) {
+ wp.ajax.post( 'crop-image', {
+ nonce: attachment.nonces.edit,
+ id: attachment.id,
+ context: 'site-icon',
+ cropDetails: {
+ x1: 0,
+ y1: 0,
+ width: this.params.width,
+ height: this.params.height,
+ dst_width: this.params.width,
+ dst_height: this.params.height
+ }
+ } ).done( function( croppedImage ) {
+ controller.setImageFromAttachment( croppedImage );
+ controller.frame.close();
+ } ).fail( function() {
+ controller.frame.trigger('content:error:crop');
+ } );
+ } else {
+ this.frame.setState( 'cropper' );
+ }
+ },
+
+ /**
+ * Updates the setting and re-renders the control UI.
+ *
+ * @param {object} attachment
+ */
+ setImageFromAttachment: function( attachment ) {
+ var sizes = [ 'site_icon-32', 'thumbnail', 'full' ], link,
+ icon;
+
+ _.each( sizes, function( size ) {
+ if ( ! icon && ! _.isUndefined ( attachment.sizes[ size ] ) ) {
+ icon = attachment.sizes[ size ];
+ }
+ } );
+
+ this.params.attachment = attachment;
+
+ // Set the Customizer setting; the callback takes care of rendering.
+ this.setting( attachment.id );
+
+ if ( ! icon ) {
+ return;
+ }
+
+ // Update the icon in-browser.
+ link = $( 'link[rel="icon"][sizes="32x32"]' );
+ link.attr( 'href', icon.url );
+ },
+
+ /**
+ * Called when the "Remove" link is clicked. Empties the setting.
+ *
+ * @param {object} event jQuery Event object
+ */
+ removeFile: function( event ) {
+ if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
+ return;
+ }
+ event.preventDefault();
+
+ this.params.attachment = {};
+ this.setting( '' );
+ this.renderContent(); // Not bound to setting change when emptying.
+ $( 'link[rel="icon"][sizes="32x32"]' ).attr( 'href', '/favicon.ico' ); // Set to default.
+ }
+ });
+
+ /**
+ * @class wp.customize.HeaderControl
+ * @augments wp.customize.Control
+ */
+ api.HeaderControl = api.Control.extend(/** @lends wp.customize.HeaderControl.prototype */{
+ ready: function() {
+ this.btnRemove = $('#customize-control-header_image .actions .remove');
+ this.btnNew = $('#customize-control-header_image .actions .new');
+
+ _.bindAll(this, 'openMedia', 'removeImage');
+
+ this.btnNew.on( 'click', this.openMedia );
+ this.btnRemove.on( 'click', this.removeImage );
+
+ api.HeaderTool.currentHeader = this.getInitialHeaderImage();
+
+ new api.HeaderTool.CurrentView({
+ model: api.HeaderTool.currentHeader,
+ el: '#customize-control-header_image .current .container'
+ });
+
+ new api.HeaderTool.ChoiceListView({
+ collection: api.HeaderTool.UploadsList = new api.HeaderTool.ChoiceList(),
+ el: '#customize-control-header_image .choices .uploaded .list'
+ });
+
+ new api.HeaderTool.ChoiceListView({
+ collection: api.HeaderTool.DefaultsList = new api.HeaderTool.DefaultsList(),
+ el: '#customize-control-header_image .choices .default .list'
+ });
+
+ api.HeaderTool.combinedList = api.HeaderTool.CombinedList = new api.HeaderTool.CombinedList([
+ api.HeaderTool.UploadsList,
+ api.HeaderTool.DefaultsList
+ ]);
+
+ // Ensure custom-header-crop Ajax requests bootstrap the Customizer to activate the previewed theme.
+ wp.media.controller.Cropper.prototype.defaults.doCropArgs.wp_customize = 'on';
+ wp.media.controller.Cropper.prototype.defaults.doCropArgs.customize_theme = api.settings.theme.stylesheet;
+ },
+
+ /**
+ * Returns a new instance of api.HeaderTool.ImageModel based on the currently
+ * saved header image (if any).
+ *
+ * @since 4.2.0
+ *
+ * @returns {Object} Options
+ */
+ getInitialHeaderImage: function() {
+ if ( ! api.get().header_image || ! api.get().header_image_data || _.contains( [ 'remove-header', 'random-default-image', 'random-uploaded-image' ], api.get().header_image ) ) {
+ return new api.HeaderTool.ImageModel();
+ }
+
+ // Get the matching uploaded image object.
+ var currentHeaderObject = _.find( _wpCustomizeHeader.uploads, function( imageObj ) {
+ return ( imageObj.attachment_id === api.get().header_image_data.attachment_id );
+ } );
+ // Fall back to raw current header image.
+ if ( ! currentHeaderObject ) {
+ currentHeaderObject = {
+ url: api.get().header_image,
+ thumbnail_url: api.get().header_image,
+ attachment_id: api.get().header_image_data.attachment_id
+ };
+ }
+
+ return new api.HeaderTool.ImageModel({
+ header: currentHeaderObject,
+ choice: currentHeaderObject.url.split( '/' ).pop()
+ });
+ },
+
+ /**
+ * Returns a set of options, computed from the attached image data and
+ * theme-specific data, to be fed to the imgAreaSelect plugin in
+ * wp.media.view.Cropper.
+ *
+ * @param {wp.media.model.Attachment} attachment
+ * @param {wp.media.controller.Cropper} controller
+ * @returns {Object} Options
+ */
+ calculateImageSelectOptions: function(attachment, controller) {
+ var xInit = parseInt(_wpCustomizeHeader.data.width, 10),
+ yInit = parseInt(_wpCustomizeHeader.data.height, 10),
+ flexWidth = !! parseInt(_wpCustomizeHeader.data['flex-width'], 10),
+ flexHeight = !! parseInt(_wpCustomizeHeader.data['flex-height'], 10),
+ ratio, xImg, yImg, realHeight, realWidth,
+ imgSelectOptions;
+
+ realWidth = attachment.get('width');
+ realHeight = attachment.get('height');
+
+ this.headerImage = new api.HeaderTool.ImageModel();
+ this.headerImage.set({
+ themeWidth: xInit,
+ themeHeight: yInit,
+ themeFlexWidth: flexWidth,
+ themeFlexHeight: flexHeight,
+ imageWidth: realWidth,
+ imageHeight: realHeight
+ });
+
+ controller.set( 'canSkipCrop', ! this.headerImage.shouldBeCropped() );
+
+ ratio = xInit / yInit;
+ xImg = realWidth;
+ yImg = realHeight;
+
+ if ( xImg / yImg > ratio ) {
+ yInit = yImg;
+ xInit = yInit * ratio;
+ } else {
+ xInit = xImg;
+ yInit = xInit / ratio;
+ }
+
+ imgSelectOptions = {
+ handles: true,
+ keys: true,
+ instance: true,
+ persistent: true,
+ imageWidth: realWidth,
+ imageHeight: realHeight,
+ x1: 0,
+ y1: 0,
+ x2: xInit,
+ y2: yInit
+ };
+
+ if (flexHeight === false && flexWidth === false) {
+ imgSelectOptions.aspectRatio = xInit + ':' + yInit;
+ }
+ if (flexHeight === false ) {
+ imgSelectOptions.maxHeight = yInit;
+ }
+ if (flexWidth === false ) {
+ imgSelectOptions.maxWidth = xInit;
+ }
+
+ return imgSelectOptions;
+ },
+
+ /**
+ * Sets up and opens the Media Manager in order to select an image.
+ * Depending on both the size of the image and the properties of the
+ * current theme, a cropping step after selection may be required or
+ * skippable.
+ *
+ * @param {event} event
+ */
+ openMedia: function(event) {
+ var l10n = _wpMediaViewsL10n;
+
+ event.preventDefault();
+
+ this.frame = wp.media({
+ button: {
+ text: l10n.selectAndCrop,
+ close: false
+ },
+ states: [
+ new wp.media.controller.Library({
+ title: l10n.chooseImage,
+ library: wp.media.query({ type: 'image' }),
+ multiple: false,
+ date: false,
+ priority: 20,
+ suggestedWidth: _wpCustomizeHeader.data.width,
+ suggestedHeight: _wpCustomizeHeader.data.height
+ }),
+ new wp.media.controller.Cropper({
+ imgSelectOptions: this.calculateImageSelectOptions
+ })
+ ]
+ });
+
+ this.frame.on('select', this.onSelect, this);
+ this.frame.on('cropped', this.onCropped, this);
+ this.frame.on('skippedcrop', this.onSkippedCrop, this);
+
+ this.frame.open();
+ },
+
+ /**
+ * After an image is selected in the media modal,
+ * switch to the cropper state.
+ */
+ onSelect: function() {
+ this.frame.setState('cropper');
+ },
+
+ /**
+ * After the image has been cropped, apply the cropped image data to the setting.
+ *
+ * @param {object} croppedImage Cropped attachment data.
+ */
+ onCropped: function(croppedImage) {
+ var url = croppedImage.url,
+ attachmentId = croppedImage.attachment_id,
+ w = croppedImage.width,
+ h = croppedImage.height;
+ this.setImageFromURL(url, attachmentId, w, h);
+ },
+
+ /**
+ * If cropping was skipped, apply the image data directly to the setting.
+ *
+ * @param {object} selection
+ */
+ onSkippedCrop: function(selection) {
+ var url = selection.get('url'),
+ w = selection.get('width'),
+ h = selection.get('height');
+ this.setImageFromURL(url, selection.id, w, h);
+ },
+
+ /**
+ * Creates a new wp.customize.HeaderTool.ImageModel from provided
+ * header image data and inserts it into the user-uploaded headers
+ * collection.
+ *
+ * @param {String} url
+ * @param {Number} attachmentId
+ * @param {Number} width
+ * @param {Number} height
+ */
+ setImageFromURL: function(url, attachmentId, width, height) {
+ var choice, data = {};
+
+ data.url = url;
+ data.thumbnail_url = url;
+ data.timestamp = _.now();
+
+ if (attachmentId) {
+ data.attachment_id = attachmentId;
+ }
+
+ if (width) {
+ data.width = width;
+ }
+
+ if (height) {
+ data.height = height;
+ }
+
+ choice = new api.HeaderTool.ImageModel({
+ header: data,
+ choice: url.split('/').pop()
+ });
+ api.HeaderTool.UploadsList.add(choice);
+ api.HeaderTool.currentHeader.set(choice.toJSON());
+ choice.save();
+ choice.importImage();
+ },
+
+ /**
+ * Triggers the necessary events to deselect an image which was set as
+ * the currently selected one.
+ */
+ removeImage: function() {
+ api.HeaderTool.currentHeader.trigger('hide');
+ api.HeaderTool.CombinedList.trigger('control:removeImage');
+ }
+
+ });
+
+ /**
+ * wp.customize.ThemeControl
+ *
+ * @class wp.customize.ThemeControl
+ * @augments wp.customize.Control
+ */
+ api.ThemeControl = api.Control.extend(/** @lends wp.customize.ThemeControl.prototype */{
+
+ touchDrag: false,
+ screenshotRendered: false,
+
+ /**
+ * @since 4.2.0
+ */
+ ready: function() {
+ var control = this, panel = api.panel( 'themes' );
+
+ function disableSwitchButtons() {
+ return ! panel.canSwitchTheme( control.params.theme.id );
+ }
+
+ // Temporary special function since supplying SFTP credentials does not work yet. See #42184.
+ function disableInstallButtons() {
+ return disableSwitchButtons() || false === api.settings.theme._canInstall || true === api.settings.theme._filesystemCredentialsNeeded;
+ }
+ function updateButtons() {
+ control.container.find( 'button.preview, button.preview-theme' ).toggleClass( 'disabled', disableSwitchButtons() );
+ control.container.find( 'button.theme-install' ).toggleClass( 'disabled', disableInstallButtons() );
+ }
+
+ api.state( 'selectedChangesetStatus' ).bind( updateButtons );
+ api.state( 'changesetStatus' ).bind( updateButtons );
+ updateButtons();
+
+ control.container.on( 'touchmove', '.theme', function() {
+ control.touchDrag = true;
+ });
+
+ // Bind details view trigger.
+ control.container.on( 'click keydown touchend', '.theme', function( event ) {
+ var section;
+ if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
+ return;
+ }
+
+ // Bail if the user scrolled on a touch device.
+ if ( control.touchDrag === true ) {
+ return control.touchDrag = false;
+ }
+
+ // Prevent the modal from showing when the user clicks the action button.
+ if ( $( event.target ).is( '.theme-actions .button, .update-theme' ) ) {
+ return;
+ }
+
+ event.preventDefault(); // Keep this AFTER the key filter above
+ section = api.section( control.section() );
+ section.showDetails( control.params.theme, function() {
+
+ // Temporary special function since supplying SFTP credentials does not work yet. See #42184.
+ if ( api.settings.theme._filesystemCredentialsNeeded ) {
+ section.overlay.find( '.theme-actions .delete-theme' ).remove();
+ }
+ } );
+ });
+
+ control.container.on( 'render-screenshot', function() {
+ var $screenshot = $( this ).find( 'img' ),
+ source = $screenshot.data( 'src' );
+
+ if ( source ) {
+ $screenshot.attr( 'src', source );
+ }
+ control.screenshotRendered = true;
+ });
+ },
+
+ /**
+ * Show or hide the theme based on the presence of the term in the title, description, tags, and author.
+ *
+ * @since 4.2.0
+ * @param {Array} terms - An array of terms to search for.
+ * @returns {boolean} Whether a theme control was activated or not.
+ */
+ filter: function( terms ) {
+ var control = this,
+ matchCount = 0,
+ haystack = control.params.theme.name + ' ' +
+ control.params.theme.description + ' ' +
+ control.params.theme.tags + ' ' +
+ control.params.theme.author + ' ';
+ haystack = haystack.toLowerCase().replace( '-', ' ' );
+
+ // Back-compat for behavior in WordPress 4.2.0 to 4.8.X.
+ if ( ! _.isArray( terms ) ) {
+ terms = [ terms ];
+ }
+
+ // Always give exact name matches highest ranking.
+ if ( control.params.theme.name.toLowerCase() === terms.join( ' ' ) ) {
+ matchCount = 100;
+ } else {
+
+ // Search for and weight (by 10) complete term matches.
+ matchCount = matchCount + 10 * ( haystack.split( terms.join( ' ' ) ).length - 1 );
+
+ // Search for each term individually (as whole-word and partial match) and sum weighted match counts.
+ _.each( terms, function( term ) {
+ matchCount = matchCount + 2 * ( haystack.split( term + ' ' ).length - 1 ); // Whole-word, double-weighted.
+ matchCount = matchCount + haystack.split( term ).length - 1; // Partial word, to minimize empty intermediate searches while typing.
+ });
+
+ // Upper limit on match ranking.
+ if ( matchCount > 99 ) {
+ matchCount = 99;
+ }
+ }
+
+ if ( 0 !== matchCount ) {
+ control.activate();
+ control.params.priority = 101 - matchCount; // Sort results by match count.
+ return true;
+ } else {
+ control.deactivate(); // Hide control
+ control.params.priority = 101;
+ return false;
+ }
+ },
+
+ /**
+ * Rerender the theme from its JS template with the installed type.
+ *
+ * @since 4.9.0
+ *
+ * @returns {void}
+ */
+ rerenderAsInstalled: function( installed ) {
+ var control = this, section;
+ if ( installed ) {
+ control.params.theme.type = 'installed';
+ } else {
+ section = api.section( control.params.section );
+ control.params.theme.type = section.params.action;
+ }
+ control.renderContent(); // Replaces existing content.
+ control.container.trigger( 'render-screenshot' );
+ }
+ });
+
+ /**
+ * Class wp.customize.CodeEditorControl
+ *
+ * @since 4.9.0
+ *
+ * @class wp.customize.CodeEditorControl
+ * @augments wp.customize.Control
+ */
+ api.CodeEditorControl = api.Control.extend(/** @lends wp.customize.CodeEditorControl.prototype */{
+
+ /**
+ * Initialize.
+ *
+ * @since 4.9.0
+ * @param {string} id - Unique identifier for the control instance.
+ * @param {object} options - Options hash for the control instance.
+ * @returns {void}
+ */
+ initialize: function( id, options ) {
+ var control = this;
+ control.deferred = _.extend( control.deferred || {}, {
+ codemirror: $.Deferred()
+ } );
+ api.Control.prototype.initialize.call( control, id, options );
+
+ // Note that rendering is debounced so the props will be used when rendering happens after add event.
+ control.notifications.bind( 'add', function( notification ) {
+
+ // Skip if control notification is not from setting csslint_error notification.
+ if ( notification.code !== control.setting.id + ':csslint_error' ) {
+ return;
+ }
+
+ // Customize the template and behavior of csslint_error notifications.
+ notification.templateId = 'customize-code-editor-lint-error-notification';
+ notification.render = (function( render ) {
+ return function() {
+ var li = render.call( this );
+ li.find( 'input[type=checkbox]' ).on( 'click', function() {
+ control.setting.notifications.remove( 'csslint_error' );
+ } );
+ return li;
+ };
+ })( notification.render );
+ } );
+ },
+
+ /**
+ * Initialize the editor when the containing section is ready and expanded.
+ *
+ * @since 4.9.0
+ * @returns {void}
+ */
+ ready: function() {
+ var control = this;
+ if ( ! control.section() ) {
+ control.initEditor();
+ return;
+ }
+
+ // Wait to initialize editor until section is embedded and expanded.
+ api.section( control.section(), function( section ) {
+ section.deferred.embedded.done( function() {
+ var onceExpanded;
+ if ( section.expanded() ) {
+ control.initEditor();
+ } else {
+ onceExpanded = function( isExpanded ) {
+ if ( isExpanded ) {
+ control.initEditor();
+ section.expanded.unbind( onceExpanded );
+ }
+ };
+ section.expanded.bind( onceExpanded );
+ }
+ } );
+ } );
+ },
+
+ /**
+ * Initialize editor.
+ *
+ * @since 4.9.0
+ * @returns {void}
+ */
+ initEditor: function() {
+ var control = this, element, editorSettings = false;
+
+ // Obtain editorSettings for instantiation.
+ if ( wp.codeEditor && ( _.isUndefined( control.params.editor_settings ) || false !== control.params.editor_settings ) ) {
+
+ // Obtain default editor settings.
+ editorSettings = wp.codeEditor.defaultSettings ? _.clone( wp.codeEditor.defaultSettings ) : {};
+ editorSettings.codemirror = _.extend(
+ {},
+ editorSettings.codemirror,
+ {
+ indentUnit: 2,
+ tabSize: 2
+ }
+ );
+
+ // Merge editor_settings param on top of defaults.
+ if ( _.isObject( control.params.editor_settings ) ) {
+ _.each( control.params.editor_settings, function( value, key ) {
+ if ( _.isObject( value ) ) {
+ editorSettings[ key ] = _.extend(
+ {},
+ editorSettings[ key ],
+ value
+ );
+ }
+ } );
+ }
+ }
+
+ element = new api.Element( control.container.find( 'textarea' ) );
+ control.elements.push( element );
+ element.sync( control.setting );
+ element.set( control.setting() );
+
+ if ( editorSettings ) {
+ control.initSyntaxHighlightingEditor( editorSettings );
+ } else {
+ control.initPlainTextareaEditor();
+ }
+ },
+
+ /**
+ * Make sure editor gets focused when control is focused.
+ *
+ * @since 4.9.0
+ * @param {Object} [params] - Focus params.
+ * @param {Function} [params.completeCallback] - Function to call when expansion is complete.
+ * @returns {void}
+ */
+ focus: function( params ) {
+ var control = this, extendedParams = _.extend( {}, params ), originalCompleteCallback;
+ originalCompleteCallback = extendedParams.completeCallback;
+ extendedParams.completeCallback = function() {
+ if ( originalCompleteCallback ) {
+ originalCompleteCallback();
+ }
+ if ( control.editor ) {
+ control.editor.codemirror.focus();
+ }
+ };
+ api.Control.prototype.focus.call( control, extendedParams );
+ },
+
+ /**
+ * Initialize syntax-highlighting editor.
+ *
+ * @since 4.9.0
+ * @param {object} codeEditorSettings - Code editor settings.
+ * @returns {void}
+ */
+ initSyntaxHighlightingEditor: function( codeEditorSettings ) {
+ var control = this, $textarea = control.container.find( 'textarea' ), settings, suspendEditorUpdate = false;
+
+ settings = _.extend( {}, codeEditorSettings, {
+ onTabNext: _.bind( control.onTabNext, control ),
+ onTabPrevious: _.bind( control.onTabPrevious, control ),
+ onUpdateErrorNotice: _.bind( control.onUpdateErrorNotice, control )
+ });
+
+ control.editor = wp.codeEditor.initialize( $textarea, settings );
+
+ // Improve the editor accessibility.
+ $( control.editor.codemirror.display.lineDiv )
+ .attr({
+ role: 'textbox',
+ 'aria-multiline': 'true',
+ 'aria-label': control.params.label,
+ 'aria-describedby': 'editor-keyboard-trap-help-1 editor-keyboard-trap-help-2 editor-keyboard-trap-help-3 editor-keyboard-trap-help-4'
+ });
+
+ // Focus the editor when clicking on its label.
+ control.container.find( 'label' ).on( 'click', function() {
+ control.editor.codemirror.focus();
+ });
+
+ /*
+ * When the CodeMirror instance changes, mirror to the textarea,
+ * where we have our "true" change event handler bound.
+ */
+ control.editor.codemirror.on( 'change', function( codemirror ) {
+ suspendEditorUpdate = true;
+ $textarea.val( codemirror.getValue() ).trigger( 'change' );
+ suspendEditorUpdate = false;
+ });
+
+ // Update CodeMirror when the setting is changed by another plugin.
+ control.setting.bind( function( value ) {
+ if ( ! suspendEditorUpdate ) {
+ control.editor.codemirror.setValue( value );
+ }
+ });
+
+ // Prevent collapsing section when hitting Esc to tab out of editor.
+ control.editor.codemirror.on( 'keydown', function onKeydown( codemirror, event ) {
+ var escKeyCode = 27;
+ if ( escKeyCode === event.keyCode ) {
+ event.stopPropagation();
+ }
+ });
+
+ control.deferred.codemirror.resolveWith( control, [ control.editor.codemirror ] );
+ },
+
+ /**
+ * Handle tabbing to the field after the editor.
+ *
+ * @since 4.9.0
+ * @returns {void}
+ */
+ onTabNext: function onTabNext() {
+ var control = this, controls, controlIndex, section;
+ section = api.section( control.section() );
+ controls = section.controls();
+ controlIndex = controls.indexOf( control );
+ if ( controls.length === controlIndex + 1 ) {
+ $( '#customize-footer-actions .collapse-sidebar' ).focus();
+ } else {
+ controls[ controlIndex + 1 ].container.find( ':focusable:first' ).focus();
+ }
+ },
+
+ /**
+ * Handle tabbing to the field before the editor.
+ *
+ * @since 4.9.0
+ * @returns {void}
+ */
+ onTabPrevious: function onTabPrevious() {
+ var control = this, controls, controlIndex, section;
+ section = api.section( control.section() );
+ controls = section.controls();
+ controlIndex = controls.indexOf( control );
+ if ( 0 === controlIndex ) {
+ section.contentContainer.find( '.customize-section-title .customize-help-toggle, .customize-section-title .customize-section-description.open .section-description-close' ).last().focus();
+ } else {
+ controls[ controlIndex - 1 ].contentContainer.find( ':focusable:first' ).focus();
+ }
+ },
+
+ /**
+ * Update error notice.
+ *
+ * @since 4.9.0
+ * @param {Array} errorAnnotations - Error annotations.
+ * @returns {void}
+ */
+ onUpdateErrorNotice: function onUpdateErrorNotice( errorAnnotations ) {
+ var control = this, message;
+ control.setting.notifications.remove( 'csslint_error' );
+
+ if ( 0 !== errorAnnotations.length ) {
+ if ( 1 === errorAnnotations.length ) {
+ message = api.l10n.customCssError.singular.replace( '%d', '1' );
+ } else {
+ message = api.l10n.customCssError.plural.replace( '%d', String( errorAnnotations.length ) );
+ }
+ control.setting.notifications.add( new api.Notification( 'csslint_error', {
+ message: message,
+ type: 'error'
+ } ) );
+ }
+ },
+
+ /**
+ * Initialize plain-textarea editor when syntax highlighting is disabled.
+ *
+ * @since 4.9.0
+ * @returns {void}
+ */
+ initPlainTextareaEditor: function() {
+ var control = this, $textarea = control.container.find( 'textarea' ), textarea = $textarea[0];
+
+ $textarea.on( 'blur', function onBlur() {
+ $textarea.data( 'next-tab-blurs', false );
+ } );
+
+ $textarea.on( 'keydown', function onKeydown( event ) {
+ var selectionStart, selectionEnd, value, tabKeyCode = 9, escKeyCode = 27;
+
+ if ( escKeyCode === event.keyCode ) {
+ if ( ! $textarea.data( 'next-tab-blurs' ) ) {
+ $textarea.data( 'next-tab-blurs', true );
+ event.stopPropagation(); // Prevent collapsing the section.
+ }
+ return;
+ }
+
+ // Short-circuit if tab key is not being pressed or if a modifier key *is* being pressed.
+ if ( tabKeyCode !== event.keyCode || event.ctrlKey || event.altKey || event.shiftKey ) {
+ return;
+ }
+
+ // Prevent capturing Tab characters if Esc was pressed.
+ if ( $textarea.data( 'next-tab-blurs' ) ) {
+ return;
+ }
+
+ selectionStart = textarea.selectionStart;
+ selectionEnd = textarea.selectionEnd;
+ value = textarea.value;
+
+ if ( selectionStart >= 0 ) {
+ textarea.value = value.substring( 0, selectionStart ).concat( '\t', value.substring( selectionEnd ) );
+ $textarea.selectionStart = textarea.selectionEnd = selectionStart + 1;
+ }
+
+ event.stopPropagation();
+ event.preventDefault();
+ });
+
+ control.deferred.codemirror.rejectWith( control );
+ }
+ });
+
+ /**
+ * Class wp.customize.DateTimeControl.
+ *
+ * @since 4.9.0
+ * @class wp.customize.DateTimeControl
+ * @augments wp.customize.Control
+ */
+ api.DateTimeControl = api.Control.extend(/** @lends wp.customize.DateTimeControl.prototype */{
+
+ /**
+ * Initialize behaviors.
+ *
+ * @since 4.9.0
+ * @returns {void}
+ */
+ ready: function ready() {
+ var control = this;
+
+ control.inputElements = {};
+ control.invalidDate = false;
+
+ _.bindAll( control, 'populateSetting', 'updateDaysForMonth', 'populateDateInputs' );
+
+ if ( ! control.setting ) {
+ throw new Error( 'Missing setting' );
+ }
+
+ control.container.find( '.date-input' ).each( function() {
+ var input = $( this ), component, element;
+ component = input.data( 'component' );
+ element = new api.Element( input );
+ control.inputElements[ component ] = element;
+ control.elements.push( element );
+
+ // Add invalid date error once user changes (and has blurred the input).
+ input.on( 'change', function() {
+ if ( control.invalidDate ) {
+ control.notifications.add( new api.Notification( 'invalid_date', {
+ message: api.l10n.invalidDate
+ } ) );
+ }
+ } );
+
+ // Remove the error immediately after validity change.
+ input.on( 'input', _.debounce( function() {
+ if ( ! control.invalidDate ) {
+ control.notifications.remove( 'invalid_date' );
+ }
+ } ) );
+
+ // Add zero-padding when blurring field.
+ input.on( 'blur', _.debounce( function() {
+ if ( ! control.invalidDate ) {
+ control.populateDateInputs();
+ }
+ } ) );
+ } );
+
+ control.inputElements.month.bind( control.updateDaysForMonth );
+ control.inputElements.year.bind( control.updateDaysForMonth );
+ control.populateDateInputs();
+ control.setting.bind( control.populateDateInputs );
+
+ // Start populating setting after inputs have been populated.
+ _.each( control.inputElements, function( element ) {
+ element.bind( control.populateSetting );
+ } );
+ },
+
+ /**
+ * Parse datetime string.
+ *
+ * @since 4.9.0
+ *
+ * @param {string} datetime - Date/Time string. Accepts Y-m-d[ H:i[:s]] format.
+ * @returns {object|null} Returns object containing date components or null if parse error.
+ */
+ parseDateTime: function parseDateTime( datetime ) {
+ var control = this, matches, date, midDayHour = 12;
+
+ if ( datetime ) {
+ matches = datetime.match( /^(\d\d\d\d)-(\d\d)-(\d\d)(?: (\d\d):(\d\d)(?::(\d\d))?)?$/ );
+ }
+
+ if ( ! matches ) {
+ return null;
+ }
+
+ matches.shift();
+
+ date = {
+ year: matches.shift(),
+ month: matches.shift(),
+ day: matches.shift(),
+ hour: matches.shift() || '00',
+ minute: matches.shift() || '00',
+ second: matches.shift() || '00'
+ };
+
+ if ( control.params.includeTime && control.params.twelveHourFormat ) {
+ date.hour = parseInt( date.hour, 10 );
+ date.meridian = date.hour >= midDayHour ? 'pm' : 'am';
+ date.hour = date.hour % midDayHour ? String( date.hour % midDayHour ) : String( midDayHour );
+ delete date.second; // @todo Why only if twelveHourFormat?
+ }
+
+ return date;
+ },
+
+ /**
+ * Validates if input components have valid date and time.
+ *
+ * @since 4.9.0
+ * @return {boolean} If date input fields has error.
+ */
+ validateInputs: function validateInputs() {
+ var control = this, components, validityInput;
+
+ control.invalidDate = false;
+
+ components = [ 'year', 'day' ];
+ if ( control.params.includeTime ) {
+ components.push( 'hour', 'minute' );
+ }
+
+ _.find( components, function( component ) {
+ var element, max, min, value;
+
+ element = control.inputElements[ component ];
+ validityInput = element.element.get( 0 );
+ max = parseInt( element.element.attr( 'max' ), 10 );
+ min = parseInt( element.element.attr( 'min' ), 10 );
+ value = parseInt( element(), 10 );
+ control.invalidDate = isNaN( value ) || value > max || value < min;
+
+ if ( ! control.invalidDate ) {
+ validityInput.setCustomValidity( '' );
+ }
+
+ return control.invalidDate;
+ } );
+
+ if ( control.inputElements.meridian && ! control.invalidDate ) {
+ validityInput = control.inputElements.meridian.element.get( 0 );
+ if ( 'am' !== control.inputElements.meridian.get() && 'pm' !== control.inputElements.meridian.get() ) {
+ control.invalidDate = true;
+ } else {
+ validityInput.setCustomValidity( '' );
+ }
+ }
+
+ if ( control.invalidDate ) {
+ validityInput.setCustomValidity( api.l10n.invalidValue );
+ } else {
+ validityInput.setCustomValidity( '' );
+ }
+ if ( ! control.section() || api.section.has( control.section() ) && api.section( control.section() ).expanded() ) {
+ _.result( validityInput, 'reportValidity' );
+ }
+
+ return control.invalidDate;
+ },
+
+ /**
+ * Updates number of days according to the month and year selected.
+ *
+ * @since 4.9.0
+ * @return {void}
+ */
+ updateDaysForMonth: function updateDaysForMonth() {
+ var control = this, daysInMonth, year, month, day;
+
+ month = parseInt( control.inputElements.month(), 10 );
+ year = parseInt( control.inputElements.year(), 10 );
+ day = parseInt( control.inputElements.day(), 10 );
+
+ if ( month && year ) {
+ daysInMonth = new Date( year, month, 0 ).getDate();
+ control.inputElements.day.element.attr( 'max', daysInMonth );
+
+ if ( day > daysInMonth ) {
+ control.inputElements.day( String( daysInMonth ) );
+ }
+ }
+ },
+
+ /**
+ * Populate setting value from the inputs.
+ *
+ * @since 4.9.0
+ * @returns {boolean} If setting updated.
+ */
+ populateSetting: function populateSetting() {
+ var control = this, date;
+
+ if ( control.validateInputs() || ! control.params.allowPastDate && ! control.isFutureDate() ) {
+ return false;
+ }
+
+ date = control.convertInputDateToString();
+ control.setting.set( date );
+ return true;
+ },
+
+ /**
+ * Converts input values to string in Y-m-d H:i:s format.
+ *
+ * @since 4.9.0
+ * @return {string} Date string.
+ */
+ convertInputDateToString: function convertInputDateToString() {
+ var control = this, date = '', dateFormat, hourInTwentyFourHourFormat,
+ getElementValue, pad;
+
+ pad = function( number, padding ) {
+ var zeros;
+ if ( String( number ).length < padding ) {
+ zeros = padding - String( number ).length;
+ number = Math.pow( 10, zeros ).toString().substr( 1 ) + String( number );
+ }
+ return number;
+ };
+
+ getElementValue = function( component ) {
+ var value = parseInt( control.inputElements[ component ].get(), 10 );
+
+ if ( _.contains( [ 'month', 'day', 'hour', 'minute' ], component ) ) {
+ value = pad( value, 2 );
+ } else if ( 'year' === component ) {
+ value = pad( value, 4 );
+ }
+ return value;
+ };
+
+ dateFormat = [ 'year', '-', 'month', '-', 'day' ];
+ if ( control.params.includeTime ) {
+ hourInTwentyFourHourFormat = control.inputElements.meridian ? control.convertHourToTwentyFourHourFormat( control.inputElements.hour(), control.inputElements.meridian() ) : control.inputElements.hour();
+ dateFormat = dateFormat.concat( [ ' ', pad( hourInTwentyFourHourFormat, 2 ), ':', 'minute', ':', '00' ] );
+ }
+
+ _.each( dateFormat, function( component ) {
+ date += control.inputElements[ component ] ? getElementValue( component ) : component;
+ } );
+
+ return date;
+ },
+
+ /**
+ * Check if the date is in the future.
+ *
+ * @since 4.9.0
+ * @returns {boolean} True if future date.
+ */
+ isFutureDate: function isFutureDate() {
+ var control = this;
+ return 0 < api.utils.getRemainingTime( control.convertInputDateToString() );
+ },
+
+ /**
+ * Convert hour in twelve hour format to twenty four hour format.
+ *
+ * @since 4.9.0
+ * @param {string} hourInTwelveHourFormat - Hour in twelve hour format.
+ * @param {string} meridian - Either 'am' or 'pm'.
+ * @returns {string} Hour in twenty four hour format.
+ */
+ convertHourToTwentyFourHourFormat: function convertHour( hourInTwelveHourFormat, meridian ) {
+ var hourInTwentyFourHourFormat, hour, midDayHour = 12;
+
+ hour = parseInt( hourInTwelveHourFormat, 10 );
+ if ( isNaN( hour ) ) {
+ return '';
+ }
+
+ if ( 'pm' === meridian && hour < midDayHour ) {
+ hourInTwentyFourHourFormat = hour + midDayHour;
+ } else if ( 'am' === meridian && midDayHour === hour ) {
+ hourInTwentyFourHourFormat = hour - midDayHour;
+ } else {
+ hourInTwentyFourHourFormat = hour;
+ }
+
+ return String( hourInTwentyFourHourFormat );
+ },
+
+ /**
+ * Populates date inputs in date fields.
+ *
+ * @since 4.9.0
+ * @returns {boolean} Whether the inputs were populated.
+ */
+ populateDateInputs: function populateDateInputs() {
+ var control = this, parsed;
+
+ parsed = control.parseDateTime( control.setting.get() );
+
+ if ( ! parsed ) {
+ return false;
+ }
+
+ _.each( control.inputElements, function( element, component ) {
+ var value = parsed[ component ]; // This will be zero-padded string.
+
+ // Set month and meridian regardless of focused state since they are dropdowns.
+ if ( 'month' === component || 'meridian' === component ) {
+
+ // Options in dropdowns are not zero-padded.
+ value = value.replace( /^0/, '' );
+
+ element.set( value );
+ } else {
+
+ value = parseInt( value, 10 );
+ if ( ! element.element.is( document.activeElement ) ) {
+
+ // Populate element with zero-padded value if not focused.
+ element.set( parsed[ component ] );
+ } else if ( value !== parseInt( element(), 10 ) ) {
+
+ // Forcibly update the value if its underlying value changed, regardless of zero-padding.
+ element.set( String( value ) );
+ }
+ }
+ } );
+
+ return true;
+ },
+
+ /**
+ * Toggle future date notification for date control.
+ *
+ * @since 4.9.0
+ * @param {boolean} notify Add or remove the notification.
+ * @return {wp.customize.DateTimeControl}
+ */
+ toggleFutureDateNotification: function toggleFutureDateNotification( notify ) {
+ var control = this, notificationCode, notification;
+
+ notificationCode = 'not_future_date';
+
+ if ( notify ) {
+ notification = new api.Notification( notificationCode, {
+ type: 'error',
+ message: api.l10n.futureDateError
+ } );
+ control.notifications.add( notification );
+ } else {
+ control.notifications.remove( notificationCode );
+ }
+
+ return control;
+ }
+ });
+
+ /**
+ * Class PreviewLinkControl.
+ *
+ * @since 4.9.0
+ * @class wp.customize.PreviewLinkControl
+ * @augments wp.customize.Control
+ */
+ api.PreviewLinkControl = api.Control.extend(/** @lends wp.customize.PreviewLinkControl.prototype */{
+
+ defaults: _.extend( {}, api.Control.prototype.defaults, {
+ templateId: 'customize-preview-link-control'
+ } ),
+
+ /**
+ * Initialize behaviors.
+ *
+ * @since 4.9.0
+ * @returns {void}
+ */
+ ready: function ready() {
+ var control = this, element, component, node, url, input, button;
+
+ _.bindAll( control, 'updatePreviewLink' );
+
+ if ( ! control.setting ) {
+ control.setting = new api.Value();
+ }
+
+ control.previewElements = {};
+
+ control.container.find( '.preview-control-element' ).each( function() {
+ node = $( this );
+ component = node.data( 'component' );
+ element = new api.Element( node );
+ control.previewElements[ component ] = element;
+ control.elements.push( element );
+ } );
+
+ url = control.previewElements.url;
+ input = control.previewElements.input;
+ button = control.previewElements.button;
+
+ input.link( control.setting );
+ url.link( control.setting );
+
+ url.bind( function( value ) {
+ url.element.parent().attr( {
+ href: value,
+ target: api.settings.changeset.uuid
+ } );
+ } );
+
+ api.bind( 'ready', control.updatePreviewLink );
+ api.state( 'saved' ).bind( control.updatePreviewLink );
+ api.state( 'changesetStatus' ).bind( control.updatePreviewLink );
+ api.state( 'activated' ).bind( control.updatePreviewLink );
+ api.previewer.previewUrl.bind( control.updatePreviewLink );
+
+ button.element.on( 'click', function( event ) {
+ event.preventDefault();
+ if ( control.setting() ) {
+ input.element.select();
+ document.execCommand( 'copy' );
+ button( button.element.data( 'copied-text' ) );
+ }
+ } );
+
+ url.element.parent().on( 'click', function( event ) {
+ if ( $( this ).hasClass( 'disabled' ) ) {
+ event.preventDefault();
+ }
+ } );
+
+ button.element.on( 'mouseenter', function() {
+ if ( control.setting() ) {
+ button( button.element.data( 'copy-text' ) );
+ }
+ } );
+ },
+
+ /**
+ * Updates Preview Link
+ *
+ * @since 4.9.0
+ * @return {void}
+ */
+ updatePreviewLink: function updatePreviewLink() {
+ var control = this, unsavedDirtyValues;
+
+ unsavedDirtyValues = ! api.state( 'saved' ).get() || '' === api.state( 'changesetStatus' ).get() || 'auto-draft' === api.state( 'changesetStatus' ).get();
+
+ control.toggleSaveNotification( unsavedDirtyValues );
+ control.previewElements.url.element.parent().toggleClass( 'disabled', unsavedDirtyValues );
+ control.previewElements.button.element.prop( 'disabled', unsavedDirtyValues );
+ control.setting.set( api.previewer.getFrontendPreviewUrl() );
+ },
+
+ /**
+ * Toggles save notification.
+ *
+ * @since 4.9.0
+ * @param {boolean} notify Add or remove notification.
+ * @return {void}
+ */
+ toggleSaveNotification: function toggleSaveNotification( notify ) {
+ var control = this, notificationCode, notification;
+
+ notificationCode = 'changes_not_saved';
+
+ if ( notify ) {
+ notification = new api.Notification( notificationCode, {
+ type: 'info',
+ message: api.l10n.saveBeforeShare
+ } );
+ control.notifications.add( notification );
+ } else {
+ control.notifications.remove( notificationCode );
+ }
+ }
+ });
+
+ /**
+ * Change objects contained within the main customize object to Settings.
+ *
+ * @alias wp.customize.defaultConstructor
+ */
+ api.defaultConstructor = api.Setting;
+
+ /**
+ * Callback for resolved controls.
+ *
+ * @callback wp.customize.deferredControlsCallback
+ * @param {wp.customize.Control[]} controls Resolved controls.
+ */
+
+ /**
+ * Collection of all registered controls.
+ *
+ * @alias wp.customize.control
+ *
+ * @since 3.4.0
+ *
+ * @type {Function}
+ * @param {...string} ids - One or more ids for controls to obtain.
+ * @param {deferredControlsCallback} [callback] - Function called when all supplied controls exist.
+ * @returns {wp.customize.Control|undefined|jQuery.promise} Control instance or undefined (if function called with one id param), or promise resolving to requested controls.
+ *
+ * @example <caption>Loop over all registered controls.</caption>
+ * wp.customize.control.each( function( control ) { ... } );
+ *
+ * @example <caption>Getting `background_color` control instance.</caption>
+ * control = wp.customize.control( 'background_color' );
+ *
+ * @example <caption>Check if control exists.</caption>
+ * hasControl = wp.customize.control.has( 'background_color' );
+ *
+ * @example <caption>Deferred getting of `background_color` control until it exists, using callback.</caption>
+ * wp.customize.control( 'background_color', function( control ) { ... } );
+ *
+ * @example <caption>Get title and tagline controls when they both exist, using promise (only available when multiple IDs are present).</caption>
+ * promise = wp.customize.control( 'blogname', 'blogdescription' );
+ * promise.done( function( titleControl, taglineControl ) { ... } );
+ *
+ * @example <caption>Get title and tagline controls when they both exist, using callback.</caption>
+ * wp.customize.control( 'blogname', 'blogdescription', function( titleControl, taglineControl ) { ... } );
+ *
+ * @example <caption>Getting setting value for `background_color` control.</caption>
+ * value = wp.customize.control( 'background_color ').setting.get();
+ * value = wp.customize( 'background_color' ).get(); // Same as above, since setting ID and control ID are the same.
+ *
+ * @example <caption>Add new control for site title.</caption>
+ * wp.customize.control.add( new wp.customize.Control( 'other_blogname', {
+ * setting: 'blogname',
+ * type: 'text',
+ * label: 'Site title',
+ * section: 'other_site_identify'
+ * } ) );
+ *
+ * @example <caption>Remove control.</caption>
+ * wp.customize.control.remove( 'other_blogname' );
+ *
+ * @example <caption>Listen for control being added.</caption>
+ * wp.customize.control.bind( 'add', function( addedControl ) { ... } )
+ *
+ * @example <caption>Listen for control being removed.</caption>
+ * wp.customize.control.bind( 'removed', function( removedControl ) { ... } )
+ */
+ api.control = new api.Values({ defaultConstructor: api.Control });
+
+ /**
+ * Callback for resolved sections.
+ *
+ * @callback wp.customize.deferredSectionsCallback
+ * @param {wp.customize.Section[]} sections Resolved sections.
+ */
+
+ /**
+ * Collection of all registered sections.
+ *
+ * @alias wp.customize.section
+ *
+ * @since 3.4.0
+ *
+ * @type {Function}
+ * @param {...string} ids - One or more ids for sections to obtain.
+ * @param {deferredSectionsCallback} [callback] - Function called when all supplied sections exist.
+ * @returns {wp.customize.Section|undefined|jQuery.promise} Section instance or undefined (if function called with one id param), or promise resolving to requested sections.
+ *
+ * @example <caption>Loop over all registered sections.</caption>
+ * wp.customize.section.each( function( section ) { ... } )
+ *
+ * @example <caption>Getting `title_tagline` section instance.</caption>
+ * section = wp.customize.section( 'title_tagline' )
+ *
+ * @example <caption>Expand dynamically-created section when it exists.</caption>
+ * wp.customize.section( 'dynamically_created', function( section ) {
+ * section.expand();
+ * } );
+ *
+ * @see {@link wp.customize.control} for further examples of how to interact with {@link wp.customize.Values} instances.
+ */
+ api.section = new api.Values({ defaultConstructor: api.Section });
+
+ /**
+ * Callback for resolved panels.
+ *
+ * @callback wp.customize.deferredPanelsCallback
+ * @param {wp.customize.Panel[]} panels Resolved panels.
+ */
+
+ /**
+ * Collection of all registered panels.
+ *
+ * @alias wp.customize.panel
+ *
+ * @since 4.0.0
+ *
+ * @type {Function}
+ * @param {...string} ids - One or more ids for panels to obtain.
+ * @param {deferredPanelsCallback} [callback] - Function called when all supplied panels exist.
+ * @returns {wp.customize.Panel|undefined|jQuery.promise} Panel instance or undefined (if function called with one id param), or promise resolving to requested panels.
+ *
+ * @example <caption>Loop over all registered panels.</caption>
+ * wp.customize.panel.each( function( panel ) { ... } )
+ *
+ * @example <caption>Getting nav_menus panel instance.</caption>
+ * panel = wp.customize.panel( 'nav_menus' );
+ *
+ * @example <caption>Expand dynamically-created panel when it exists.</caption>
+ * wp.customize.panel( 'dynamically_created', function( panel ) {
+ * panel.expand();
+ * } );
+ *
+ * @see {@link wp.customize.control} for further examples of how to interact with {@link wp.customize.Values} instances.
+ */
+ api.panel = new api.Values({ defaultConstructor: api.Panel });
+
+ /**
+ * Callback for resolved notifications.
+ *
+ * @callback wp.customize.deferredNotificationsCallback
+ * @param {wp.customize.Notification[]} notifications Resolved notifications.
+ */
+
+ /**
+ * Collection of all global notifications.
+ *
+ * @alias wp.customize.notifications
+ *
+ * @since 4.9.0
+ *
+ * @type {Function}
+ * @param {...string} codes - One or more codes for notifications to obtain.
+ * @param {deferredNotificationsCallback} [callback] - Function called when all supplied notifications exist.
+ * @returns {wp.customize.Notification|undefined|jQuery.promise} notification instance or undefined (if function called with one code param), or promise resolving to requested notifications.
+ *
+ * @example <caption>Check if existing notification</caption>
+ * exists = wp.customize.notifications.has( 'a_new_day_arrived' );
+ *
+ * @example <caption>Obtain existing notification</caption>
+ * notification = wp.customize.notifications( 'a_new_day_arrived' );
+ *
+ * @example <caption>Obtain notification that may not exist yet.</caption>
+ * wp.customize.notifications( 'a_new_day_arrived', function( notification ) { ... } );
+ *
+ * @example <caption>Add a warning notification.</caption>
+ * wp.customize.notifications.add( new wp.customize.Notification( 'midnight_almost_here', {
+ * type: 'warning',
+ * message: 'Midnight has almost arrived!',
+ * dismissible: true
+ * } ) );
+ *
+ * @example <caption>Remove a notification.</caption>
+ * wp.customize.notifications.remove( 'a_new_day_arrived' );
+ *
+ * @see {@link wp.customize.control} for further examples of how to interact with {@link wp.customize.Values} instances.
+ */
+ api.notifications = new api.Notifications();
+
+ api.PreviewFrame = api.Messenger.extend(/** @lends wp.customize.PreviewFrame.prototype */{
+ sensitivity: null, // Will get set to api.settings.timeouts.previewFrameSensitivity.
+
+ /**
+ * An object that fetches a preview in the background of the document, which
+ * allows for seamless replacement of an existing preview.
+ *
+ * @constructs wp.customize.PreviewFrame
+ * @augments wp.customize.Messenger
+ *
+ * @param {object} params.container
+ * @param {object} params.previewUrl
+ * @param {object} params.query
+ * @param {object} options
+ */
+ initialize: function( params, options ) {
+ var deferred = $.Deferred();
+
+ /*
+ * Make the instance of the PreviewFrame the promise object
+ * so other objects can easily interact with it.
+ */
+ deferred.promise( this );
+
+ this.container = params.container;
+
+ $.extend( params, { channel: api.PreviewFrame.uuid() });
+
+ api.Messenger.prototype.initialize.call( this, params, options );
+
+ this.add( 'previewUrl', params.previewUrl );
+
+ this.query = $.extend( params.query || {}, { customize_messenger_channel: this.channel() });
+
+ this.run( deferred );
+ },
+
+ /**
+ * Run the preview request.
+ *
+ * @param {object} deferred jQuery Deferred object to be resolved with
+ * the request.
+ */
+ run: function( deferred ) {
+ var previewFrame = this,
+ loaded = false,
+ ready = false,
+ readyData = null,
+ hasPendingChangesetUpdate = '{}' !== previewFrame.query.customized,
+ urlParser,
+ params,
+ form;
+
+ if ( previewFrame._ready ) {
+ previewFrame.unbind( 'ready', previewFrame._ready );
+ }
+
+ previewFrame._ready = function( data ) {
+ ready = true;
+ readyData = data;
+ previewFrame.container.addClass( 'iframe-ready' );
+ if ( ! data ) {
+ return;
+ }
+
+ if ( loaded ) {
+ deferred.resolveWith( previewFrame, [ data ] );
+ }
+ };
+
+ previewFrame.bind( 'ready', previewFrame._ready );
+
+ urlParser = document.createElement( 'a' );
+ urlParser.href = previewFrame.previewUrl();
+
+ params = _.extend(
+ api.utils.parseQueryString( urlParser.search.substr( 1 ) ),
+ {
+ customize_changeset_uuid: previewFrame.query.customize_changeset_uuid,
+ customize_theme: previewFrame.query.customize_theme,
+ customize_messenger_channel: previewFrame.query.customize_messenger_channel
+ }
+ );
+ if ( api.settings.changeset.autosaved || ! api.state( 'saved' ).get() ) {
+ params.customize_autosaved = 'on';
+ }
+
+ urlParser.search = $.param( params );
+ previewFrame.iframe = $( '<iframe />', {
+ title: api.l10n.previewIframeTitle,
+ name: 'customize-' + previewFrame.channel()
+ } );
+ previewFrame.iframe.attr( 'onmousewheel', '' ); // Workaround for Safari bug. See WP Trac #38149.
+ previewFrame.iframe.attr( 'sandbox', 'allow-forms allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-presentation allow-same-origin allow-scripts' );
+
+ if ( ! hasPendingChangesetUpdate ) {
+ previewFrame.iframe.attr( 'src', urlParser.href );
+ } else {
+ previewFrame.iframe.attr( 'data-src', urlParser.href ); // For debugging purposes.
+ }
+
+ previewFrame.iframe.appendTo( previewFrame.container );
+ previewFrame.targetWindow( previewFrame.iframe[0].contentWindow );
+
+ /*
+ * Submit customized data in POST request to preview frame window since
+ * there are setting value changes not yet written to changeset.
+ */
+ if ( hasPendingChangesetUpdate ) {
+ form = $( '<form>', {
+ action: urlParser.href,
+ target: previewFrame.iframe.attr( 'name' ),
+ method: 'post',
+ hidden: 'hidden'
+ } );
+ form.append( $( '<input>', {
+ type: 'hidden',
+ name: '_method',
+ value: 'GET'
+ } ) );
+ _.each( previewFrame.query, function( value, key ) {
+ form.append( $( '<input>', {
+ type: 'hidden',
+ name: key,
+ value: value
+ } ) );
+ } );
+ previewFrame.container.append( form );
+ form.submit();
+ form.remove(); // No need to keep the form around after submitted.
+ }
+
+ previewFrame.bind( 'iframe-loading-error', function( error ) {
+ previewFrame.iframe.remove();
+
+ // Check if the user is not logged in.
+ if ( 0 === error ) {
+ previewFrame.login( deferred );
+ return;
+ }
+
+ // Check for cheaters.
+ if ( -1 === error ) {
+ deferred.rejectWith( previewFrame, [ 'cheatin' ] );
+ return;
+ }
+
+ deferred.rejectWith( previewFrame, [ 'request failure' ] );
+ } );
+
+ previewFrame.iframe.one( 'load', function() {
+ loaded = true;
+
+ if ( ready ) {
+ deferred.resolveWith( previewFrame, [ readyData ] );
+ } else {
+ setTimeout( function() {
+ deferred.rejectWith( previewFrame, [ 'ready timeout' ] );
+ }, previewFrame.sensitivity );
+ }
+ });
+ },
+
+ login: function( deferred ) {
+ var self = this,
+ reject;
+
+ reject = function() {
+ deferred.rejectWith( self, [ 'logged out' ] );
+ };
+
+ if ( this.triedLogin ) {
+ return reject();
+ }
+
+ // Check if we have an admin cookie.
+ $.get( api.settings.url.ajax, {
+ action: 'logged-in'
+ }).fail( reject ).done( function( response ) {
+ var iframe;
+
+ if ( '1' !== response ) {
+ reject();
+ }
+
+ iframe = $( '<iframe />', { 'src': self.previewUrl(), 'title': api.l10n.previewIframeTitle } ).hide();
+ iframe.appendTo( self.container );
+ iframe.on( 'load', function() {
+ self.triedLogin = true;
+
+ iframe.remove();
+ self.run( deferred );
+ });
+ });
+ },
+
+ destroy: function() {
+ api.Messenger.prototype.destroy.call( this );
+
+ if ( this.iframe ) {
+ this.iframe.remove();
+ }
+
+ delete this.iframe;
+ delete this.targetWindow;
+ }
+ });
+
+ (function(){
+ var id = 0;
+ /**
+ * Return an incremented ID for a preview messenger channel.
+ *
+ * This function is named "uuid" for historical reasons, but it is a
+ * misnomer as it is not an actual UUID, and it is not universally unique.
+ * This is not to be confused with `api.settings.changeset.uuid`.
+ *
+ * @return {string}
+ */
+ api.PreviewFrame.uuid = function() {
+ return 'preview-' + String( id++ );
+ };
+ }());
+
+ /**
+ * Set the document title of the customizer.
+ *
+ * @alias wp.customize.setDocumentTitle
+ *
+ * @since 4.1.0
+ *
+ * @param {string} documentTitle
+ */
+ api.setDocumentTitle = function ( documentTitle ) {
+ var tmpl, title;
+ tmpl = api.settings.documentTitleTmpl;
+ title = tmpl.replace( '%s', documentTitle );
+ document.title = title;
+ api.trigger( 'title', title );
+ };
+
+ api.Previewer = api.Messenger.extend(/** @lends wp.customize.Previewer.prototype */{
+ refreshBuffer: null, // Will get set to api.settings.timeouts.windowRefresh.
+
+ /**
+ * @constructs wp.customize.Previewer
+ * @augments wp.customize.Messenger
+ *
+ * @param {array} params.allowedUrls
+ * @param {string} params.container A selector or jQuery element for the preview
+ * frame to be placed.
+ * @param {string} params.form
+ * @param {string} params.previewUrl The URL to preview.
+ * @param {object} options
+ */
+ initialize: function( params, options ) {
+ var previewer = this,
+ urlParser = document.createElement( 'a' );
+
+ $.extend( previewer, options || {} );
+ previewer.deferred = {
+ active: $.Deferred()
+ };
+
+ // Debounce to prevent hammering server and then wait for any pending update requests.
+ previewer.refresh = _.debounce(
+ ( function( originalRefresh ) {
+ return function() {
+ var isProcessingComplete, refreshOnceProcessingComplete;
+ isProcessingComplete = function() {
+ return 0 === api.state( 'processing' ).get();
+ };
+ if ( isProcessingComplete() ) {
+ originalRefresh.call( previewer );
+ } else {
+ refreshOnceProcessingComplete = function() {
+ if ( isProcessingComplete() ) {
+ originalRefresh.call( previewer );
+ api.state( 'processing' ).unbind( refreshOnceProcessingComplete );
+ }
+ };
+ api.state( 'processing' ).bind( refreshOnceProcessingComplete );
+ }
+ };
+ }( previewer.refresh ) ),
+ previewer.refreshBuffer
+ );
+
+ previewer.container = api.ensure( params.container );
+ previewer.allowedUrls = params.allowedUrls;
+
+ params.url = window.location.href;
+
+ api.Messenger.prototype.initialize.call( previewer, params );
+
+ urlParser.href = previewer.origin();
+ previewer.add( 'scheme', urlParser.protocol.replace( /:$/, '' ) );
+
+ // Limit the URL to internal, front-end links.
+ //
+ // If the front end and the admin are served from the same domain, load the
+ // preview over ssl if the Customizer is being loaded over ssl. This avoids
+ // insecure content warnings. This is not attempted if the admin and front end
+ // are on different domains to avoid the case where the front end doesn't have
+ // ssl certs.
+
+ previewer.add( 'previewUrl', params.previewUrl ).setter( function( to ) {
+ var result = null, urlParser, queryParams, parsedAllowedUrl, parsedCandidateUrls = [];
+ urlParser = document.createElement( 'a' );
+ urlParser.href = to;
+
+ // Abort if URL is for admin or (static) files in wp-includes or wp-content.
+ if ( /\/wp-(admin|includes|content)(\/|$)/.test( urlParser.pathname ) ) {
+ return null;
+ }
+
+ // Remove state query params.
+ if ( urlParser.search.length > 1 ) {
+ queryParams = api.utils.parseQueryString( urlParser.search.substr( 1 ) );
+ delete queryParams.customize_changeset_uuid;
+ delete queryParams.customize_theme;
+ delete queryParams.customize_messenger_channel;
+ delete queryParams.customize_autosaved;
+ if ( _.isEmpty( queryParams ) ) {
+ urlParser.search = '';
+ } else {
+ urlParser.search = $.param( queryParams );
+ }
+ }
+
+ parsedCandidateUrls.push( urlParser );
+
+ // Prepend list with URL that matches the scheme/protocol of the iframe.
+ if ( previewer.scheme.get() + ':' !== urlParser.protocol ) {
+ urlParser = document.createElement( 'a' );
+ urlParser.href = parsedCandidateUrls[0].href;
+ urlParser.protocol = previewer.scheme.get() + ':';
+ parsedCandidateUrls.unshift( urlParser );
+ }
+
+ // Attempt to match the URL to the control frame's scheme and check if it's allowed. If not, try the original URL.
+ parsedAllowedUrl = document.createElement( 'a' );
+ _.find( parsedCandidateUrls, function( parsedCandidateUrl ) {
+ return ! _.isUndefined( _.find( previewer.allowedUrls, function( allowedUrl ) {
+ parsedAllowedUrl.href = allowedUrl;
+ if ( urlParser.protocol === parsedAllowedUrl.protocol && urlParser.host === parsedAllowedUrl.host && 0 === urlParser.pathname.indexOf( parsedAllowedUrl.pathname.replace( /\/$/, '' ) ) ) {
+ result = parsedCandidateUrl.href;
+ return true;
+ }
+ } ) );
+ } );
+
+ return result;
+ });
+
+ previewer.bind( 'ready', previewer.ready );
+
+ // Start listening for keep-alive messages when iframe first loads.
+ previewer.deferred.active.done( _.bind( previewer.keepPreviewAlive, previewer ) );
+
+ previewer.bind( 'synced', function() {
+ previewer.send( 'active' );
+ } );
+
+ // Refresh the preview when the URL is changed (but not yet).
+ previewer.previewUrl.bind( previewer.refresh );
+
+ previewer.scroll = 0;
+ previewer.bind( 'scroll', function( distance ) {
+ previewer.scroll = distance;
+ });
+
+ // Update the URL when the iframe sends a URL message, resetting scroll position. If URL is unchanged, then refresh.
+ previewer.bind( 'url', function( url ) {
+ var onUrlChange, urlChanged = false;
+ previewer.scroll = 0;
+ onUrlChange = function() {
+ urlChanged = true;
+ };
+ previewer.previewUrl.bind( onUrlChange );
+ previewer.previewUrl.set( url );
+ previewer.previewUrl.unbind( onUrlChange );
+ if ( ! urlChanged ) {
+ previewer.refresh();
+ }
+ } );
+
+ // Update the document title when the preview changes.
+ previewer.bind( 'documentTitle', function ( title ) {
+ api.setDocumentTitle( title );
+ } );
+ },
+
+ /**
+ * Handle the preview receiving the ready message.
+ *
+ * @since 4.7.0
+ * @access public
+ *
+ * @param {object} data - Data from preview.
+ * @param {string} data.currentUrl - Current URL.
+ * @param {object} data.activePanels - Active panels.
+ * @param {object} data.activeSections Active sections.
+ * @param {object} data.activeControls Active controls.
+ * @returns {void}
+ */
+ ready: function( data ) {
+ var previewer = this, synced = {}, constructs;
+
+ synced.settings = api.get();
+ synced['settings-modified-while-loading'] = previewer.settingsModifiedWhileLoading;
+ if ( 'resolved' !== previewer.deferred.active.state() || previewer.loading ) {
+ synced.scroll = previewer.scroll;
+ }
+ synced['edit-shortcut-visibility'] = api.state( 'editShortcutVisibility' ).get();
+ previewer.send( 'sync', synced );
+
+ // Set the previewUrl without causing the url to set the iframe.
+ if ( data.currentUrl ) {
+ previewer.previewUrl.unbind( previewer.refresh );
+ previewer.previewUrl.set( data.currentUrl );
+ previewer.previewUrl.bind( previewer.refresh );
+ }
+
+ /*
+ * Walk over all panels, sections, and controls and set their
+ * respective active states to true if the preview explicitly
+ * indicates as such.
+ */
+ constructs = {
+ panel: data.activePanels,
+ section: data.activeSections,
+ control: data.activeControls
+ };
+ _( constructs ).each( function ( activeConstructs, type ) {
+ api[ type ].each( function ( construct, id ) {
+ var isDynamicallyCreated = _.isUndefined( api.settings[ type + 's' ][ id ] );
+
+ /*
+ * If the construct was created statically in PHP (not dynamically in JS)
+ * then consider a missing (undefined) value in the activeConstructs to
+ * mean it should be deactivated (since it is gone). But if it is
+ * dynamically created then only toggle activation if the value is defined,
+ * as this means that the construct was also then correspondingly
+ * created statically in PHP and the active callback is available.
+ * Otherwise, dynamically-created constructs should normally have
+ * their active states toggled in JS rather than from PHP.
+ */
+ if ( ! isDynamicallyCreated || ! _.isUndefined( activeConstructs[ id ] ) ) {
+ if ( activeConstructs[ id ] ) {
+ construct.activate();
+ } else {
+ construct.deactivate();
+ }
+ }
+ } );
+ } );
+
+ if ( data.settingValidities ) {
+ api._handleSettingValidities( {
+ settingValidities: data.settingValidities,
+ focusInvalidControl: false
+ } );
+ }
+ },
+
+ /**
+ * Keep the preview alive by listening for ready and keep-alive messages.
+ *
+ * If a message is not received in the allotted time then the iframe will be set back to the last known valid URL.
+ *
+ * @since 4.7.0
+ * @access public
+ *
+ * @returns {void}
+ */
+ keepPreviewAlive: function keepPreviewAlive() {
+ var previewer = this, keepAliveTick, timeoutId, handleMissingKeepAlive, scheduleKeepAliveCheck;
+
+ /**
+ * Schedule a preview keep-alive check.
+ *
+ * Note that if a page load takes longer than keepAliveCheck milliseconds,
+ * the keep-alive messages will still be getting sent from the previous
+ * URL.
+ */
+ scheduleKeepAliveCheck = function() {
+ timeoutId = setTimeout( handleMissingKeepAlive, api.settings.timeouts.keepAliveCheck );
+ };
+
+ /**
+ * Set the previewerAlive state to true when receiving a message from the preview.
+ */
+ keepAliveTick = function() {
+ api.state( 'previewerAlive' ).set( true );
+ clearTimeout( timeoutId );
+ scheduleKeepAliveCheck();
+ };
+
+ /**
+ * Set the previewerAlive state to false if keepAliveCheck milliseconds have transpired without a message.
+ *
+ * This is most likely to happen in the case of a connectivity error, or if the theme causes the browser
+ * to navigate to a non-allowed URL. Setting this state to false will force settings with a postMessage
+ * transport to use refresh instead, causing the preview frame also to be replaced with the current
+ * allowed preview URL.
+ */
+ handleMissingKeepAlive = function() {
+ api.state( 'previewerAlive' ).set( false );
+ };
+ scheduleKeepAliveCheck();
+
+ previewer.bind( 'ready', keepAliveTick );
+ previewer.bind( 'keep-alive', keepAliveTick );
+ },
+
+ /**
+ * Query string data sent with each preview request.
+ *
+ * @abstract
+ */
+ query: function() {},
+
+ abort: function() {
+ if ( this.loading ) {
+ this.loading.destroy();
+ delete this.loading;
+ }
+ },
+
+ /**
+ * Refresh the preview seamlessly.
+ *
+ * @since 3.4.0
+ * @access public
+ * @returns {void}
+ */
+ refresh: function() {
+ var previewer = this, onSettingChange;
+
+ // Display loading indicator
+ previewer.send( 'loading-initiated' );
+
+ previewer.abort();
+
+ previewer.loading = new api.PreviewFrame({
+ url: previewer.url(),
+ previewUrl: previewer.previewUrl(),
+ query: previewer.query( { excludeCustomizedSaved: true } ) || {},
+ container: previewer.container
+ });
+
+ previewer.settingsModifiedWhileLoading = {};
+ onSettingChange = function( setting ) {
+ previewer.settingsModifiedWhileLoading[ setting.id ] = true;
+ };
+ api.bind( 'change', onSettingChange );
+ previewer.loading.always( function() {
+ api.unbind( 'change', onSettingChange );
+ } );
+
+ previewer.loading.done( function( readyData ) {
+ var loadingFrame = this, onceSynced;
+
+ previewer.preview = loadingFrame;
+ previewer.targetWindow( loadingFrame.targetWindow() );
+ previewer.channel( loadingFrame.channel() );
+
+ onceSynced = function() {
+ loadingFrame.unbind( 'synced', onceSynced );
+ if ( previewer._previousPreview ) {
+ previewer._previousPreview.destroy();
+ }
+ previewer._previousPreview = previewer.preview;
+ previewer.deferred.active.resolve();
+ delete previewer.loading;
+ };
+ loadingFrame.bind( 'synced', onceSynced );
+
+ // This event will be received directly by the previewer in normal navigation; this is only needed for seamless refresh.
+ previewer.trigger( 'ready', readyData );
+ });
+
+ previewer.loading.fail( function( reason ) {
+ previewer.send( 'loading-failed' );
+
+ if ( 'logged out' === reason ) {
+ if ( previewer.preview ) {
+ previewer.preview.destroy();
+ delete previewer.preview;
+ }
+
+ previewer.login().done( previewer.refresh );
+ }
+
+ if ( 'cheatin' === reason ) {
+ previewer.cheatin();
+ }
+ });
+ },
+
+ login: function() {
+ var previewer = this,
+ deferred, messenger, iframe;
+
+ if ( this._login ) {
+ return this._login;
+ }
+
+ deferred = $.Deferred();
+ this._login = deferred.promise();
+
+ messenger = new api.Messenger({
+ channel: 'login',
+ url: api.settings.url.login
+ });
+
+ iframe = $( '<iframe />', { 'src': api.settings.url.login, 'title': api.l10n.loginIframeTitle } ).appendTo( this.container );
+
+ messenger.targetWindow( iframe[0].contentWindow );
+
+ messenger.bind( 'login', function () {
+ var refreshNonces = previewer.refreshNonces();
+
+ refreshNonces.always( function() {
+ iframe.remove();
+ messenger.destroy();
+ delete previewer._login;
+ });
+
+ refreshNonces.done( function() {
+ deferred.resolve();
+ });
+
+ refreshNonces.fail( function() {
+ previewer.cheatin();
+ deferred.reject();
+ });
+ });
+
+ return this._login;
+ },
+
+ cheatin: function() {
+ $( document.body ).empty().addClass( 'cheatin' ).append(
+ '<h1>' + api.l10n.notAllowedHeading + '</h1>' +
+ '<p>' + api.l10n.notAllowed + '</p>'
+ );
+ },
+
+ refreshNonces: function() {
+ var request, deferred = $.Deferred();
+
+ deferred.promise();
+
+ request = wp.ajax.post( 'customize_refresh_nonces', {
+ wp_customize: 'on',
+ customize_theme: api.settings.theme.stylesheet
+ });
+
+ request.done( function( response ) {
+ api.trigger( 'nonce-refresh', response );
+ deferred.resolve();
+ });
+
+ request.fail( function() {
+ deferred.reject();
+ });
+
+ return deferred;
+ }
+ });
+
+ api.settingConstructor = {};
+ api.controlConstructor = {
+ color: api.ColorControl,
+ media: api.MediaControl,
+ upload: api.UploadControl,
+ image: api.ImageControl,
+ cropped_image: api.CroppedImageControl,
+ site_icon: api.SiteIconControl,
+ header: api.HeaderControl,
+ background: api.BackgroundControl,
+ background_position: api.BackgroundPositionControl,
+ theme: api.ThemeControl,
+ date_time: api.DateTimeControl,
+ code_editor: api.CodeEditorControl
+ };
+ api.panelConstructor = {
+ themes: api.ThemesPanel
+ };
+ api.sectionConstructor = {
+ themes: api.ThemesSection,
+ outer: api.OuterSection
+ };
+
+ /**
+ * Handle setting_validities in an error response for the customize-save request.
+ *
+ * Add notifications to the settings and focus on the first control that has an invalid setting.
+ *
+ * @alias wp.customize._handleSettingValidities
+ *
+ * @since 4.6.0
+ * @private
+ *
+ * @param {object} args
+ * @param {object} args.settingValidities
+ * @param {boolean} [args.focusInvalidControl=false]
+ * @returns {void}
+ */
+ api._handleSettingValidities = function handleSettingValidities( args ) {
+ var invalidSettingControls, invalidSettings = [], wasFocused = false;
+
+ // Find the controls that correspond to each invalid setting.
+ _.each( args.settingValidities, function( validity, settingId ) {
+ var setting = api( settingId );
+ if ( setting ) {
+
+ // Add notifications for invalidities.
+ if ( _.isObject( validity ) ) {
+ _.each( validity, function( params, code ) {
+ var notification, existingNotification, needsReplacement = false;
+ notification = new api.Notification( code, _.extend( { fromServer: true }, params ) );
+
+ // Remove existing notification if already exists for code but differs in parameters.
+ existingNotification = setting.notifications( notification.code );
+ if ( existingNotification ) {
+ needsReplacement = notification.type !== existingNotification.type || notification.message !== existingNotification.message || ! _.isEqual( notification.data, existingNotification.data );
+ }
+ if ( needsReplacement ) {
+ setting.notifications.remove( code );
+ }
+
+ if ( ! setting.notifications.has( notification.code ) ) {
+ setting.notifications.add( notification );
+ }
+ invalidSettings.push( setting.id );
+ } );
+ }
+
+ // Remove notification errors that are no longer valid.
+ setting.notifications.each( function( notification ) {
+ if ( notification.fromServer && 'error' === notification.type && ( true === validity || ! validity[ notification.code ] ) ) {
+ setting.notifications.remove( notification.code );
+ }
+ } );
+ }
+ } );
+
+ if ( args.focusInvalidControl ) {
+ invalidSettingControls = api.findControlsForSettings( invalidSettings );
+
+ // Focus on the first control that is inside of an expanded section (one that is visible).
+ _( _.values( invalidSettingControls ) ).find( function( controls ) {
+ return _( controls ).find( function( control ) {
+ var isExpanded = control.section() && api.section.has( control.section() ) && api.section( control.section() ).expanded();
+ if ( isExpanded && control.expanded ) {
+ isExpanded = control.expanded();
+ }
+ if ( isExpanded ) {
+ control.focus();
+ wasFocused = true;
+ }
+ return wasFocused;
+ } );
+ } );
+
+ // Focus on the first invalid control.
+ if ( ! wasFocused && ! _.isEmpty( invalidSettingControls ) ) {
+ _.values( invalidSettingControls )[0][0].focus();
+ }
+ }
+ };
+
+ /**
+ * Find all controls associated with the given settings.
+ *
+ * @alias wp.customize.findControlsForSettings
+ *
+ * @since 4.6.0
+ * @param {string[]} settingIds Setting IDs.
+ * @returns {object<string, wp.customize.Control>} Mapping setting ids to arrays of controls.
+ */
+ api.findControlsForSettings = function findControlsForSettings( settingIds ) {
+ var controls = {}, settingControls;
+ _.each( _.unique( settingIds ), function( settingId ) {
+ var setting = api( settingId );
+ if ( setting ) {
+ settingControls = setting.findControls();
+ if ( settingControls && settingControls.length > 0 ) {
+ controls[ settingId ] = settingControls;
+ }
+ }
+ } );
+ return controls;
+ };
+
+ /**
+ * Sort panels, sections, controls by priorities. Hide empty sections and panels.
+ *
+ * @alias wp.customize.reflowPaneContents
+ *
+ * @since 4.1.0
+ */
+ api.reflowPaneContents = _.bind( function () {
+
+ var appendContainer, activeElement, rootHeadContainers, rootNodes = [], wasReflowed = false;
+
+ if ( document.activeElement ) {
+ activeElement = $( document.activeElement );
+ }
+
+ // Sort the sections within each panel
+ api.panel.each( function ( panel ) {
+ if ( 'themes' === panel.id ) {
+ return; // Don't reflow theme sections, as doing so moves them after the themes container.
+ }
+
+ var sections = panel.sections(),
+ sectionHeadContainers = _.pluck( sections, 'headContainer' );
+ rootNodes.push( panel );
+ appendContainer = ( panel.contentContainer.is( 'ul' ) ) ? panel.contentContainer : panel.contentContainer.find( 'ul:first' );
+ if ( ! api.utils.areElementListsEqual( sectionHeadContainers, appendContainer.children( '[id]' ) ) ) {
+ _( sections ).each( function ( section ) {
+ appendContainer.append( section.headContainer );
+ } );
+ wasReflowed = true;
+ }
+ } );
+
+ // Sort the controls within each section
+ api.section.each( function ( section ) {
+ var controls = section.controls(),
+ controlContainers = _.pluck( controls, 'container' );
+ if ( ! section.panel() ) {
+ rootNodes.push( section );
+ }
+ appendContainer = ( section.contentContainer.is( 'ul' ) ) ? section.contentContainer : section.contentContainer.find( 'ul:first' );
+ if ( ! api.utils.areElementListsEqual( controlContainers, appendContainer.children( '[id]' ) ) ) {
+ _( controls ).each( function ( control ) {
+ appendContainer.append( control.container );
+ } );
+ wasReflowed = true;
+ }
+ } );
+
+ // Sort the root panels and sections
+ rootNodes.sort( api.utils.prioritySort );
+ rootHeadContainers = _.pluck( rootNodes, 'headContainer' );
+ appendContainer = $( '#customize-theme-controls .customize-pane-parent' ); // @todo This should be defined elsewhere, and to be configurable
+ if ( ! api.utils.areElementListsEqual( rootHeadContainers, appendContainer.children() ) ) {
+ _( rootNodes ).each( function ( rootNode ) {
+ appendContainer.append( rootNode.headContainer );
+ } );
+ wasReflowed = true;
+ }
+
+ // Now re-trigger the active Value callbacks to that the panels and sections can decide whether they can be rendered
+ api.panel.each( function ( panel ) {
+ var value = panel.active();
+ panel.active.callbacks.fireWith( panel.active, [ value, value ] );
+ } );
+ api.section.each( function ( section ) {
+ var value = section.active();
+ section.active.callbacks.fireWith( section.active, [ value, value ] );
+ } );
+
+ // Restore focus if there was a reflow and there was an active (focused) element
+ if ( wasReflowed && activeElement ) {
+ activeElement.focus();
+ }
+ api.trigger( 'pane-contents-reflowed' );
+ }, api );
+
+ // Define state values.
+ api.state = new api.Values();
+ _.each( [
+ 'saved',
+ 'saving',
+ 'trashing',
+ 'activated',
+ 'processing',
+ 'paneVisible',
+ 'expandedPanel',
+ 'expandedSection',
+ 'changesetDate',
+ 'selectedChangesetDate',
+ 'changesetStatus',
+ 'selectedChangesetStatus',
+ 'remainingTimeToPublish',
+ 'previewerAlive',
+ 'editShortcutVisibility',
+ 'changesetLocked',
+ 'previewedDevice'
+ ], function( name ) {
+ api.state.create( name );
+ });
+
+ $( function() {
+ api.settings = window._wpCustomizeSettings;
+ api.l10n = window._wpCustomizeControlsL10n;
+
+ // Check if we can run the Customizer.
+ if ( ! api.settings ) {
+ return;
+ }
+
+ // Bail if any incompatibilities are found.
+ if ( ! $.support.postMessage || ( ! $.support.cors && api.settings.isCrossDomain ) ) {
+ return;
+ }
+
+ if ( null === api.PreviewFrame.prototype.sensitivity ) {
+ api.PreviewFrame.prototype.sensitivity = api.settings.timeouts.previewFrameSensitivity;
+ }
+ if ( null === api.Previewer.prototype.refreshBuffer ) {
+ api.Previewer.prototype.refreshBuffer = api.settings.timeouts.windowRefresh;
+ }
+
+ var parent,
+ body = $( document.body ),
+ overlay = body.children( '.wp-full-overlay' ),
+ title = $( '#customize-info .panel-title.site-title' ),
+ closeBtn = $( '.customize-controls-close' ),
+ saveBtn = $( '#save' ),
+ btnWrapper = $( '#customize-save-button-wrapper' ),
+ publishSettingsBtn = $( '#publish-settings' ),
+ footerActions = $( '#customize-footer-actions' );
+
+ // Add publish settings section in JS instead of PHP since the Customizer depends on it to function.
+ api.bind( 'ready', function() {
+ api.section.add( new api.OuterSection( 'publish_settings', {
+ title: api.l10n.publishSettings,
+ priority: 0,
+ active: api.settings.theme.active
+ } ) );
+ } );
+
+ // Set up publish settings section and its controls.
+ api.section( 'publish_settings', function( section ) {
+ var updateButtonsState, trashControl, updateSectionActive, isSectionActive, statusControl, dateControl, toggleDateControl, publishWhenTime, pollInterval, updateTimeArrivedPoller, cancelScheduleButtonReminder, timeArrivedPollingInterval = 1000;
+
+ trashControl = new api.Control( 'trash_changeset', {
+ type: 'button',
+ section: section.id,
+ priority: 30,
+ input_attrs: {
+ 'class': 'button-link button-link-delete',
+ value: api.l10n.discardChanges
+ }
+ } );
+ api.control.add( trashControl );
+ trashControl.deferred.embedded.done( function() {
+ trashControl.container.find( '.button-link' ).on( 'click', function() {
+ if ( confirm( api.l10n.trashConfirm ) ) {
+ wp.customize.previewer.trash();
+ }
+ } );
+ } );
+
+ api.control.add( new api.PreviewLinkControl( 'changeset_preview_link', {
+ section: section.id,
+ priority: 100
+ } ) );
+
+ /**
+ * Return whether the pubish settings section should be active.
+ *
+ * @return {boolean} Is section active.
+ */
+ isSectionActive = function() {
+ if ( ! api.state( 'activated' ).get() ) {
+ return false;
+ }
+ if ( api.state( 'trashing' ).get() || 'trash' === api.state( 'changesetStatus' ).get() ) {
+ return false;
+ }
+ if ( '' === api.state( 'changesetStatus' ).get() && api.state( 'saved' ).get() ) {
+ return false;
+ }
+ return true;
+ };
+
+ // Make sure publish settings are not available while the theme is not active and the customizer is in a published state.
+ section.active.validate = isSectionActive;
+ updateSectionActive = function() {
+ section.active.set( isSectionActive() );
+ };
+ api.state( 'activated' ).bind( updateSectionActive );
+ api.state( 'trashing' ).bind( updateSectionActive );
+ api.state( 'saved' ).bind( updateSectionActive );
+ api.state( 'changesetStatus' ).bind( updateSectionActive );
+ updateSectionActive();
+
+ // Bind visibility of the publish settings button to whether the section is active.
+ updateButtonsState = function() {
+ publishSettingsBtn.toggle( section.active.get() );
+ saveBtn.toggleClass( 'has-next-sibling', section.active.get() );
+ };
+ updateButtonsState();
+ section.active.bind( updateButtonsState );
+
+ function highlightScheduleButton() {
+ if ( ! cancelScheduleButtonReminder ) {
+ cancelScheduleButtonReminder = api.utils.highlightButton( btnWrapper, {
+ delay: 1000,
+
+ // Only abort the reminder when the save button is focused.
+ // If the user clicks the settings button to toggle the
+ // settings closed, we'll still remind them.
+ focusTarget: saveBtn
+ } );
+ }
+ }
+ function cancelHighlightScheduleButton() {
+ if ( cancelScheduleButtonReminder ) {
+ cancelScheduleButtonReminder();
+ cancelScheduleButtonReminder = null;
+ }
+ }
+ api.state( 'selectedChangesetStatus' ).bind( cancelHighlightScheduleButton );
+
+ section.contentContainer.find( '.customize-action' ).text( api.l10n.updating );
+ section.contentContainer.find( '.customize-section-back' ).removeAttr( 'tabindex' );
+ publishSettingsBtn.prop( 'disabled', false );
+
+ publishSettingsBtn.on( 'click', function( event ) {
+ event.preventDefault();
+ section.expanded.set( ! section.expanded.get() );
+ } );
+
+ section.expanded.bind( function( isExpanded ) {
+ var defaultChangesetStatus;
+ publishSettingsBtn.attr( 'aria-expanded', String( isExpanded ) );
+ publishSettingsBtn.toggleClass( 'active', isExpanded );
+
+ if ( isExpanded ) {
+ cancelHighlightScheduleButton();
+ return;
+ }
+
+ defaultChangesetStatus = api.state( 'changesetStatus' ).get();
+ if ( '' === defaultChangesetStatus || 'auto-draft' === defaultChangesetStatus ) {
+ defaultChangesetStatus = 'publish';
+ }
+
+ if ( api.state( 'selectedChangesetStatus' ).get() !== defaultChangesetStatus ) {
+ highlightScheduleButton();
+ } else if ( 'future' === api.state( 'selectedChangesetStatus' ).get() && api.state( 'selectedChangesetDate' ).get() !== api.state( 'changesetDate' ).get() ) {
+ highlightScheduleButton();
+ }
+ } );
+
+ statusControl = new api.Control( 'changeset_status', {
+ priority: 10,
+ type: 'radio',
+ section: 'publish_settings',
+ setting: api.state( 'selectedChangesetStatus' ),
+ templateId: 'customize-selected-changeset-status-control',
+ label: api.l10n.action,
+ choices: api.settings.changeset.statusChoices
+ } );
+ api.control.add( statusControl );
+
+ dateControl = new api.DateTimeControl( 'changeset_scheduled_date', {
+ priority: 20,
+ section: 'publish_settings',
+ setting: api.state( 'selectedChangesetDate' ),
+ minYear: ( new Date() ).getFullYear(),
+ allowPastDate: false,
+ includeTime: true,
+ twelveHourFormat: /a/i.test( api.settings.timeFormat ),
+ description: api.l10n.scheduleDescription
+ } );
+ dateControl.notifications.alt = true;
+ api.control.add( dateControl );
+
+ publishWhenTime = function() {
+ api.state( 'selectedChangesetStatus' ).set( 'publish' );
+ api.previewer.save();
+ };
+
+ // Start countdown for when the dateTime arrives, or clear interval when it is .
+ updateTimeArrivedPoller = function() {
+ var shouldPoll = (
+ 'future' === api.state( 'changesetStatus' ).get() &&
+ 'future' === api.state( 'selectedChangesetStatus' ).get() &&
+ api.state( 'changesetDate' ).get() &&
+ api.state( 'selectedChangesetDate' ).get() === api.state( 'changesetDate' ).get() &&
+ api.utils.getRemainingTime( api.state( 'changesetDate' ).get() ) >= 0
+ );
+
+ if ( shouldPoll && ! pollInterval ) {
+ pollInterval = setInterval( function() {
+ var remainingTime = api.utils.getRemainingTime( api.state( 'changesetDate' ).get() );
+ api.state( 'remainingTimeToPublish' ).set( remainingTime );
+ if ( remainingTime <= 0 ) {
+ clearInterval( pollInterval );
+ pollInterval = 0;
+ publishWhenTime();
+ }
+ }, timeArrivedPollingInterval );
+ } else if ( ! shouldPoll && pollInterval ) {
+ clearInterval( pollInterval );
+ pollInterval = 0;
+ }
+ };
+
+ api.state( 'changesetDate' ).bind( updateTimeArrivedPoller );
+ api.state( 'selectedChangesetDate' ).bind( updateTimeArrivedPoller );
+ api.state( 'changesetStatus' ).bind( updateTimeArrivedPoller );
+ api.state( 'selectedChangesetStatus' ).bind( updateTimeArrivedPoller );
+ updateTimeArrivedPoller();
+
+ // Ensure dateControl only appears when selected status is future.
+ dateControl.active.validate = function() {
+ return 'future' === api.state( 'selectedChangesetStatus' ).get();
+ };
+ toggleDateControl = function( value ) {
+ dateControl.active.set( 'future' === value );
+ };
+ toggleDateControl( api.state( 'selectedChangesetStatus' ).get() );
+ api.state( 'selectedChangesetStatus' ).bind( toggleDateControl );
+
+ // Show notification on date control when status is future but it isn't a future date.
+ api.state( 'saving' ).bind( function( isSaving ) {
+ if ( isSaving && 'future' === api.state( 'selectedChangesetStatus' ).get() ) {
+ dateControl.toggleFutureDateNotification( ! dateControl.isFutureDate() );
+ }
+ } );
+ } );
+
+ // Prevent the form from saving when enter is pressed on an input or select element.
+ $('#customize-controls').on( 'keydown', function( e ) {
+ var isEnter = ( 13 === e.which ),
+ $el = $( e.target );
+
+ if ( isEnter && ( $el.is( 'input:not([type=button])' ) || $el.is( 'select' ) ) ) {
+ e.preventDefault();
+ }
+ });
+
+ // Expand/Collapse the main customizer customize info.
+ $( '.customize-info' ).find( '> .accordion-section-title .customize-help-toggle' ).on( 'click', function() {
+ var section = $( this ).closest( '.accordion-section' ),
+ content = section.find( '.customize-panel-description:first' );
+
+ if ( section.hasClass( 'cannot-expand' ) ) {
+ return;
+ }
+
+ if ( section.hasClass( 'open' ) ) {
+ section.toggleClass( 'open' );
+ content.slideUp( api.Panel.prototype.defaultExpandedArguments.duration, function() {
+ content.trigger( 'toggled' );
+ } );
+ $( this ).attr( 'aria-expanded', false );
+ } else {
+ content.slideDown( api.Panel.prototype.defaultExpandedArguments.duration, function() {
+ content.trigger( 'toggled' );
+ } );
+ section.toggleClass( 'open' );
+ $( this ).attr( 'aria-expanded', true );
+ }
+ });
+
+ /**
+ * Initialize Previewer
+ *
+ * @alias wp.customize.previewer
+ */
+ api.previewer = new api.Previewer({
+ container: '#customize-preview',
+ form: '#customize-controls',
+ previewUrl: api.settings.url.preview,
+ allowedUrls: api.settings.url.allowed
+ },/** @lends wp.customize.previewer */{
+
+ nonce: api.settings.nonce,
+
+ /**
+ * Build the query to send along with the Preview request.
+ *
+ * @since 3.4.0
+ * @since 4.7.0 Added options param.
+ * @access public
+ *
+ * @param {object} [options] Options.
+ * @param {boolean} [options.excludeCustomizedSaved=false] Exclude saved settings in customized response (values pending writing to changeset).
+ * @return {object} Query vars.
+ */
+ query: function( options ) {
+ var queryVars = {
+ wp_customize: 'on',
+ customize_theme: api.settings.theme.stylesheet,
+ nonce: this.nonce.preview,
+ customize_changeset_uuid: api.settings.changeset.uuid
+ };
+ if ( api.settings.changeset.autosaved || ! api.state( 'saved' ).get() ) {
+ queryVars.customize_autosaved = 'on';
+ }
+
+ /*
+ * Exclude customized data if requested especially for calls to requestChangesetUpdate.
+ * Changeset updates are differential and so it is a performance waste to send all of
+ * the dirty settings with each update.
+ */
+ queryVars.customized = JSON.stringify( api.dirtyValues( {
+ unsaved: options && options.excludeCustomizedSaved
+ } ) );
+
+ return queryVars;
+ },
+
+ /**
+ * Save (and publish) the customizer changeset.
+ *
+ * Updates to the changeset are transactional. If any of the settings
+ * are invalid then none of them will be written into the changeset.
+ * A revision will be made for the changeset post if revisions support
+ * has been added to the post type.
+ *
+ * @since 3.4.0
+ * @since 4.7.0 Added args param and return value.
+ *
+ * @param {object} [args] Args.
+ * @param {string} [args.status=publish] Status.
+ * @param {string} [args.date] Date, in local time in MySQL format.
+ * @param {string} [args.title] Title
+ * @returns {jQuery.promise} Promise.
+ */
+ save: function( args ) {
+ var previewer = this,
+ deferred = $.Deferred(),
+ changesetStatus = api.state( 'selectedChangesetStatus' ).get(),
+ selectedChangesetDate = api.state( 'selectedChangesetDate' ).get(),
+ processing = api.state( 'processing' ),
+ submitWhenDoneProcessing,
+ submit,
+ modifiedWhileSaving = {},
+ invalidSettings = [],
+ invalidControls = [],
+ invalidSettingLessControls = [];
+
+ if ( args && args.status ) {
+ changesetStatus = args.status;
+ }
+
+ if ( api.state( 'saving' ).get() ) {
+ deferred.reject( 'already_saving' );
+ deferred.promise();
+ }
+
+ api.state( 'saving' ).set( true );
+
+ function captureSettingModifiedDuringSave( setting ) {
+ modifiedWhileSaving[ setting.id ] = true;
+ }
+
+ submit = function () {
+ var request, query, settingInvalidities = {}, latestRevision = api._latestRevision, errorCode = 'client_side_error';
+
+ api.bind( 'change', captureSettingModifiedDuringSave );
+ api.notifications.remove( errorCode );
+
+ /*
+ * Block saving if there are any settings that are marked as
+ * invalid from the client (not from the server). Focus on
+ * the control.
+ */
+ api.each( function( setting ) {
+ setting.notifications.each( function( notification ) {
+ if ( 'error' === notification.type && ! notification.fromServer ) {
+ invalidSettings.push( setting.id );
+ if ( ! settingInvalidities[ setting.id ] ) {
+ settingInvalidities[ setting.id ] = {};
+ }
+ settingInvalidities[ setting.id ][ notification.code ] = notification;
+ }
+ } );
+ } );
+
+ // Find all invalid setting less controls with notification type error.
+ api.control.each( function( control ) {
+ if ( ! control.setting || ! control.setting.id && control.active.get() ) {
+ control.notifications.each( function( notification ) {
+ if ( 'error' === notification.type ) {
+ invalidSettingLessControls.push( [ control ] );
+ }
+ } );
+ }
+ } );
+
+ invalidControls = _.union( invalidSettingLessControls, _.values( api.findControlsForSettings( invalidSettings ) ) );
+ if ( ! _.isEmpty( invalidControls ) ) {
+
+ invalidControls[0][0].focus();
+ api.unbind( 'change', captureSettingModifiedDuringSave );
+
+ if ( invalidSettings.length ) {
+ api.notifications.add( new api.Notification( errorCode, {
+ message: ( 1 === invalidSettings.length ? api.l10n.saveBlockedError.singular : api.l10n.saveBlockedError.plural ).replace( /%s/g, String( invalidSettings.length ) ),
+ type: 'error',
+ dismissible: true,
+ saveFailure: true
+ } ) );
+ }
+
+ deferred.rejectWith( previewer, [
+ { setting_invalidities: settingInvalidities }
+ ] );
+ api.state( 'saving' ).set( false );
+ return deferred.promise();
+ }
+
+ /*
+ * Note that excludeCustomizedSaved is intentionally false so that the entire
+ * set of customized data will be included if bypassed changeset update.
+ */
+ query = $.extend( previewer.query( { excludeCustomizedSaved: false } ), {
+ nonce: previewer.nonce.save,
+ customize_changeset_status: changesetStatus
+ } );
+
+ if ( args && args.date ) {
+ query.customize_changeset_date = args.date;
+ } else if ( 'future' === changesetStatus && selectedChangesetDate ) {
+ query.customize_changeset_date = selectedChangesetDate;
+ }
+
+ if ( args && args.title ) {
+ query.customize_changeset_title = args.title;
+ }
+
+ // Allow plugins to modify the params included with the save request.
+ api.trigger( 'save-request-params', query );
+
+ /*
+ * Note that the dirty customized values will have already been set in the
+ * changeset and so technically query.customized could be deleted. However,
+ * it is remaining here to make sure that any settings that got updated
+ * quietly which may have not triggered an update request will also get
+ * included in the values that get saved to the changeset. This will ensure
+ * that values that get injected via the saved event will be included in
+ * the changeset. This also ensures that setting values that were invalid
+ * will get re-validated, perhaps in the case of settings that are invalid
+ * due to dependencies on other settings.
+ */
+ request = wp.ajax.post( 'customize_save', query );
+ api.state( 'processing' ).set( api.state( 'processing' ).get() + 1 );
+
+ api.trigger( 'save', request );
+
+ request.always( function () {
+ api.state( 'processing' ).set( api.state( 'processing' ).get() - 1 );
+ api.state( 'saving' ).set( false );
+ api.unbind( 'change', captureSettingModifiedDuringSave );
+ } );
+
+ // Remove notifications that were added due to save failures.
+ api.notifications.each( function( notification ) {
+ if ( notification.saveFailure ) {
+ api.notifications.remove( notification.code );
+ }
+ });
+
+ request.fail( function ( response ) {
+ var notification, notificationArgs;
+ notificationArgs = {
+ type: 'error',
+ dismissible: true,
+ fromServer: true,
+ saveFailure: true
+ };
+
+ if ( '0' === response ) {
+ response = 'not_logged_in';
+ } else if ( '-1' === response ) {
+ // Back-compat in case any other check_ajax_referer() call is dying
+ response = 'invalid_nonce';
+ }
+
+ if ( 'invalid_nonce' === response ) {
+ previewer.cheatin();
+ } else if ( 'not_logged_in' === response ) {
+ previewer.preview.iframe.hide();
+ previewer.login().done( function() {
+ previewer.save();
+ previewer.preview.iframe.show();
+ } );
+ } else if ( response.code ) {
+ if ( 'not_future_date' === response.code && api.section.has( 'publish_settings' ) && api.section( 'publish_settings' ).active.get() && api.control.has( 'changeset_scheduled_date' ) ) {
+ api.control( 'changeset_scheduled_date' ).toggleFutureDateNotification( true ).focus();
+ } else if ( 'changeset_locked' !== response.code ) {
+ notification = new api.Notification( response.code, _.extend( notificationArgs, {
+ message: response.message
+ } ) );
+ }
+ } else {
+ notification = new api.Notification( 'unknown_error', _.extend( notificationArgs, {
+ message: api.l10n.unknownRequestFail
+ } ) );
+ }
+
+ if ( notification ) {
+ api.notifications.add( notification );
+ }
+
+ if ( response.setting_validities ) {
+ api._handleSettingValidities( {
+ settingValidities: response.setting_validities,
+ focusInvalidControl: true
+ } );
+ }
+
+ deferred.rejectWith( previewer, [ response ] );
+ api.trigger( 'error', response );
+
+ // Start a new changeset if the underlying changeset was published.
+ if ( 'changeset_already_published' === response.code && response.next_changeset_uuid ) {
+ api.settings.changeset.uuid = response.next_changeset_uuid;
+ api.state( 'changesetStatus' ).set( '' );
+ if ( api.settings.changeset.branching ) {
+ parent.send( 'changeset-uuid', api.settings.changeset.uuid );
+ }
+ api.previewer.send( 'changeset-uuid', api.settings.changeset.uuid );
+ }
+ } );
+
+ request.done( function( response ) {
+
+ previewer.send( 'saved', response );
+
+ api.state( 'changesetStatus' ).set( response.changeset_status );
+ if ( response.changeset_date ) {
+ api.state( 'changesetDate' ).set( response.changeset_date );
+ }
+
+ if ( 'publish' === response.changeset_status ) {
+
+ // Mark all published as clean if they haven't been modified during the request.
+ api.each( function( setting ) {
+ /*
+ * Note that the setting revision will be undefined in the case of setting
+ * values that are marked as dirty when the customizer is loaded, such as
+ * when applying starter content. All other dirty settings will have an
+ * associated revision due to their modification triggering a change event.
+ */
+ if ( setting._dirty && ( _.isUndefined( api._latestSettingRevisions[ setting.id ] ) || api._latestSettingRevisions[ setting.id ] <= latestRevision ) ) {
+ setting._dirty = false;
+ }
+ } );
+
+ api.state( 'changesetStatus' ).set( '' );
+ api.settings.changeset.uuid = response.next_changeset_uuid;
+ if ( api.settings.changeset.branching ) {
+ parent.send( 'changeset-uuid', api.settings.changeset.uuid );
+ }
+ }
+
+ // Prevent subsequent requestChangesetUpdate() calls from including the settings that have been saved.
+ api._lastSavedRevision = Math.max( latestRevision, api._lastSavedRevision );
+
+ if ( response.setting_validities ) {
+ api._handleSettingValidities( {
+ settingValidities: response.setting_validities,
+ focusInvalidControl: true
+ } );
+ }
+
+ deferred.resolveWith( previewer, [ response ] );
+ api.trigger( 'saved', response );
+
+ // Restore the global dirty state if any settings were modified during save.
+ if ( ! _.isEmpty( modifiedWhileSaving ) ) {
+ api.state( 'saved' ).set( false );
+ }
+ } );
+ };
+
+ if ( 0 === processing() ) {
+ submit();
+ } else {
+ submitWhenDoneProcessing = function () {
+ if ( 0 === processing() ) {
+ api.state.unbind( 'change', submitWhenDoneProcessing );
+ submit();
+ }
+ };
+ api.state.bind( 'change', submitWhenDoneProcessing );
+ }
+
+ return deferred.promise();
+ },
+
+ /**
+ * Trash the current changes.
+ *
+ * Revert the Customizer to it's previously-published state.
+ *
+ * @since 4.9.0
+ *
+ * @returns {jQuery.promise} Promise.
+ */
+ trash: function trash() {
+ var request, success, fail;
+
+ api.state( 'trashing' ).set( true );
+ api.state( 'processing' ).set( api.state( 'processing' ).get() + 1 );
+
+ request = wp.ajax.post( 'customize_trash', {
+ customize_changeset_uuid: api.settings.changeset.uuid,
+ nonce: api.settings.nonce.trash
+ } );
+ api.notifications.add( new api.OverlayNotification( 'changeset_trashing', {
+ type: 'info',
+ message: api.l10n.revertingChanges,
+ loading: true
+ } ) );
+
+ success = function() {
+ var urlParser = document.createElement( 'a' ), queryParams;
+
+ api.state( 'changesetStatus' ).set( 'trash' );
+ api.each( function( setting ) {
+ setting._dirty = false;
+ } );
+ api.state( 'saved' ).set( true );
+
+ // Go back to Customizer without changeset.
+ urlParser.href = location.href;
+ queryParams = api.utils.parseQueryString( urlParser.search.substr( 1 ) );
+ delete queryParams.changeset_uuid;
+ queryParams['return'] = api.settings.url['return'];
+ urlParser.search = $.param( queryParams );
+ location.replace( urlParser.href );
+ };
+
+ fail = function( code, message ) {
+ var notificationCode = code || 'unknown_error';
+ api.state( 'processing' ).set( api.state( 'processing' ).get() - 1 );
+ api.state( 'trashing' ).set( false );
+ api.notifications.remove( 'changeset_trashing' );
+ api.notifications.add( new api.Notification( notificationCode, {
+ message: message || api.l10n.unknownError,
+ dismissible: true,
+ type: 'error'
+ } ) );
+ };
+
+ request.done( function( response ) {
+ success( response.message );
+ } );
+
+ request.fail( function( response ) {
+ var code = response.code || 'trashing_failed';
+ if ( response.success || 'non_existent_changeset' === code || 'changeset_already_trashed' === code ) {
+ success( response.message );
+ } else {
+ fail( code, response.message );
+ }
+ } );
+ },
+
+ /**
+ * Builds the front preview url with the current state of customizer.
+ *
+ * @since 4.9
+ *
+ * @return {string} Preview url.
+ */
+ getFrontendPreviewUrl: function() {
+ var previewer = this, params, urlParser;
+ urlParser = document.createElement( 'a' );
+ urlParser.href = previewer.previewUrl.get();
+ params = api.utils.parseQueryString( urlParser.search.substr( 1 ) );
+
+ if ( api.state( 'changesetStatus' ).get() && 'publish' !== api.state( 'changesetStatus' ).get() ) {
+ params.customize_changeset_uuid = api.settings.changeset.uuid;
+ }
+ if ( ! api.state( 'activated' ).get() ) {
+ params.customize_theme = api.settings.theme.stylesheet;
+ }
+
+ urlParser.search = $.param( params );
+ return urlParser.href;
+ }
+ });
+
+ // Ensure preview nonce is included with every customized request, to allow post data to be read.
+ $.ajaxPrefilter( function injectPreviewNonce( options ) {
+ if ( ! /wp_customize=on/.test( options.data ) ) {
+ return;
+ }
+ options.data += '&' + $.param({
+ customize_preview_nonce: api.settings.nonce.preview
+ });
+ });
+
+ // Refresh the nonces if the preview sends updated nonces over.
+ api.previewer.bind( 'nonce', function( nonce ) {
+ $.extend( this.nonce, nonce );
+ });
+
+ // Refresh the nonces if login sends updated nonces over.
+ api.bind( 'nonce-refresh', function( nonce ) {
+ $.extend( api.settings.nonce, nonce );
+ $.extend( api.previewer.nonce, nonce );
+ api.previewer.send( 'nonce-refresh', nonce );
+ });
+
+ // Create Settings
+ $.each( api.settings.settings, function( id, data ) {
+ var Constructor = api.settingConstructor[ data.type ] || api.Setting;
+ api.add( new Constructor( id, data.value, {
+ transport: data.transport,
+ previewer: api.previewer,
+ dirty: !! data.dirty
+ } ) );
+ });
+
+ // Create Panels
+ $.each( api.settings.panels, function ( id, data ) {
+ var Constructor = api.panelConstructor[ data.type ] || api.Panel, options;
+ options = _.extend( { params: data }, data ); // Inclusion of params alias is for back-compat for custom panels that expect to augment this property.
+ api.panel.add( new Constructor( id, options ) );
+ });
+
+ // Create Sections
+ $.each( api.settings.sections, function ( id, data ) {
+ var Constructor = api.sectionConstructor[ data.type ] || api.Section, options;
+ options = _.extend( { params: data }, data ); // Inclusion of params alias is for back-compat for custom sections that expect to augment this property.
+ api.section.add( new Constructor( id, options ) );
+ });
+
+ // Create Controls
+ $.each( api.settings.controls, function( id, data ) {
+ var Constructor = api.controlConstructor[ data.type ] || api.Control, options;
+ options = _.extend( { params: data }, data ); // Inclusion of params alias is for back-compat for custom controls that expect to augment this property.
+ api.control.add( new Constructor( id, options ) );
+ });
+
+ // Focus the autofocused element
+ _.each( [ 'panel', 'section', 'control' ], function( type ) {
+ var id = api.settings.autofocus[ type ];
+ if ( ! id ) {
+ return;
+ }
+
+ /*
+ * Defer focus until:
+ * 1. The panel, section, or control exists (especially for dynamically-created ones).
+ * 2. The instance is embedded in the document (and so is focusable).
+ * 3. The preview has finished loading so that the active states have been set.
+ */
+ api[ type ]( id, function( instance ) {
+ instance.deferred.embedded.done( function() {
+ api.previewer.deferred.active.done( function() {
+ instance.focus();
+ });
+ });
+ });
+ });
+
+ api.bind( 'ready', api.reflowPaneContents );
+ $( [ api.panel, api.section, api.control ] ).each( function ( i, values ) {
+ var debouncedReflowPaneContents = _.debounce( api.reflowPaneContents, api.settings.timeouts.reflowPaneContents );
+ values.bind( 'add', debouncedReflowPaneContents );
+ values.bind( 'change', debouncedReflowPaneContents );
+ values.bind( 'remove', debouncedReflowPaneContents );
+ } );
+
+ // Set up global notifications area.
+ api.bind( 'ready', function setUpGlobalNotificationsArea() {
+ var sidebar, containerHeight, containerInitialTop;
+ api.notifications.container = $( '#customize-notifications-area' );
+
+ api.notifications.bind( 'change', _.debounce( function() {
+ api.notifications.render();
+ } ) );
+
+ sidebar = $( '.wp-full-overlay-sidebar-content' );
+ api.notifications.bind( 'rendered', function updateSidebarTop() {
+ sidebar.css( 'top', '' );
+ if ( 0 !== api.notifications.count() ) {
+ containerHeight = api.notifications.container.outerHeight() + 1;
+ containerInitialTop = parseInt( sidebar.css( 'top' ), 10 );
+ sidebar.css( 'top', containerInitialTop + containerHeight + 'px' );
+ }
+ api.notifications.trigger( 'sidebarTopUpdated' );
+ });
+
+ api.notifications.render();
+ });
+
+ // Save and activated states
+ (function( state ) {
+ var saved = state.instance( 'saved' ),
+ saving = state.instance( 'saving' ),
+ trashing = state.instance( 'trashing' ),
+ activated = state.instance( 'activated' ),
+ processing = state.instance( 'processing' ),
+ paneVisible = state.instance( 'paneVisible' ),
+ expandedPanel = state.instance( 'expandedPanel' ),
+ expandedSection = state.instance( 'expandedSection' ),
+ changesetStatus = state.instance( 'changesetStatus' ),
+ selectedChangesetStatus = state.instance( 'selectedChangesetStatus' ),
+ changesetDate = state.instance( 'changesetDate' ),
+ selectedChangesetDate = state.instance( 'selectedChangesetDate' ),
+ previewerAlive = state.instance( 'previewerAlive' ),
+ editShortcutVisibility = state.instance( 'editShortcutVisibility' ),
+ changesetLocked = state.instance( 'changesetLocked' ),
+ populateChangesetUuidParam, defaultSelectedChangesetStatus;
+
+ state.bind( 'change', function() {
+ var canSave;
+
+ if ( ! activated() ) {
+ saveBtn.val( api.l10n.activate );
+ closeBtn.find( '.screen-reader-text' ).text( api.l10n.cancel );
+
+ } else if ( '' === changesetStatus.get() && saved() ) {
+ if ( api.settings.changeset.currentUserCanPublish ) {
+ saveBtn.val( api.l10n.published );
+ } else {
+ saveBtn.val( api.l10n.saved );
+ }
+ closeBtn.find( '.screen-reader-text' ).text( api.l10n.close );
+
+ } else {
+ if ( 'draft' === selectedChangesetStatus() ) {
+ if ( saved() && selectedChangesetStatus() === changesetStatus() ) {
+ saveBtn.val( api.l10n.draftSaved );
+ } else {
+ saveBtn.val( api.l10n.saveDraft );
+ }
+ } else if ( 'future' === selectedChangesetStatus() ) {
+ if ( saved() && selectedChangesetStatus() === changesetStatus() ) {
+ if ( changesetDate.get() !== selectedChangesetDate.get() ) {
+ saveBtn.val( api.l10n.schedule );
+ } else {
+ saveBtn.val( api.l10n.scheduled );
+ }
+ } else {
+ saveBtn.val( api.l10n.schedule );
+ }
+ } else if ( api.settings.changeset.currentUserCanPublish ) {
+ saveBtn.val( api.l10n.publish );
+ }
+ closeBtn.find( '.screen-reader-text' ).text( api.l10n.cancel );
+ }
+
+ /*
+ * Save (publish) button should be enabled if saving is not currently happening,
+ * and if the theme is not active or the changeset exists but is not published.
+ */
+ canSave = ! saving() && ! trashing() && ! changesetLocked() && ( ! activated() || ! saved() || ( changesetStatus() !== selectedChangesetStatus() && '' !== changesetStatus() ) || ( 'future' === selectedChangesetStatus() && changesetDate.get() !== selectedChangesetDate.get() ) );
+
+ saveBtn.prop( 'disabled', ! canSave );
+ });
+
+ selectedChangesetStatus.validate = function( status ) {
+ if ( '' === status || 'auto-draft' === status ) {
+ return null;
+ }
+ return status;
+ };
+
+ defaultSelectedChangesetStatus = api.settings.changeset.currentUserCanPublish ? 'publish' : 'draft';
+
+ // Set default states.
+ changesetStatus( api.settings.changeset.status );
+ changesetLocked( Boolean( api.settings.changeset.lockUser ) );
+ changesetDate( api.settings.changeset.publishDate );
+ selectedChangesetDate( api.settings.changeset.publishDate );
+ selectedChangesetStatus( '' === api.settings.changeset.status || 'auto-draft' === api.settings.changeset.status ? defaultSelectedChangesetStatus : api.settings.changeset.status );
+ selectedChangesetStatus.link( changesetStatus ); // Ensure that direct updates to status on server via wp.customizer.previewer.save() will update selection.
+ saved( true );
+ if ( '' === changesetStatus() ) { // Handle case for loading starter content.
+ api.each( function( setting ) {
+ if ( setting._dirty ) {
+ saved( false );
+ }
+ } );
+ }
+ saving( false );
+ activated( api.settings.theme.active );
+ processing( 0 );
+ paneVisible( true );
+ expandedPanel( false );
+ expandedSection( false );
+ previewerAlive( true );
+ editShortcutVisibility( 'visible' );
+
+ api.bind( 'change', function() {
+ if ( state( 'saved' ).get() ) {
+ state( 'saved' ).set( false );
+ }
+ });
+
+ // Populate changeset UUID param when state becomes dirty.
+ if ( api.settings.changeset.branching ) {
+ saved.bind( function( isSaved ) {
+ if ( ! isSaved ) {
+ populateChangesetUuidParam( true );
+ }
+ });
+ }
+
+ saving.bind( function( isSaving ) {
+ body.toggleClass( 'saving', isSaving );
+ } );
+ trashing.bind( function( isTrashing ) {
+ body.toggleClass( 'trashing', isTrashing );
+ } );
+
+ api.bind( 'saved', function( response ) {
+ state('saved').set( true );
+ if ( 'publish' === response.changeset_status ) {
+ state( 'activated' ).set( true );
+ }
+ });
+
+ activated.bind( function( to ) {
+ if ( to ) {
+ api.trigger( 'activated' );
+ }
+ });
+
+ /**
+ * Populate URL with UUID via `history.replaceState()`.
+ *
+ * @since 4.7.0
+ * @access private
+ *
+ * @param {boolean} isIncluded Is UUID included.
+ * @returns {void}
+ */
+ populateChangesetUuidParam = function( isIncluded ) {
+ var urlParser, queryParams;
+
+ // Abort on IE9 which doesn't support history management.
+ if ( ! history.replaceState ) {
+ return;
+ }
+
+ urlParser = document.createElement( 'a' );
+ urlParser.href = location.href;
+ queryParams = api.utils.parseQueryString( urlParser.search.substr( 1 ) );
+ if ( isIncluded ) {
+ if ( queryParams.changeset_uuid === api.settings.changeset.uuid ) {
+ return;
+ }
+ queryParams.changeset_uuid = api.settings.changeset.uuid;
+ } else {
+ if ( ! queryParams.changeset_uuid ) {
+ return;
+ }
+ delete queryParams.changeset_uuid;
+ }
+ urlParser.search = $.param( queryParams );
+ history.replaceState( {}, document.title, urlParser.href );
+ };
+
+ // Show changeset UUID in URL when in branching mode and there is a saved changeset.
+ if ( api.settings.changeset.branching ) {
+ changesetStatus.bind( function( newStatus ) {
+ populateChangesetUuidParam( '' !== newStatus && 'publish' !== newStatus && 'trash' !== newStatus );
+ } );
+ }
+ }( api.state ) );
+
+ /**
+ * Handles lock notice and take over request.
+ *
+ * @since 4.9.0
+ */
+ ( function checkAndDisplayLockNotice() {
+
+ var LockedNotification = api.OverlayNotification.extend(/** @lends wp.customize~LockedNotification.prototype */{
+
+ /**
+ * Template ID.
+ *
+ * @type {string}
+ */
+ templateId: 'customize-changeset-locked-notification',
+
+ /**
+ * Lock user.
+ *
+ * @type {object}
+ */
+ lockUser: null,
+
+ /**
+ * A notification that is displayed in a full-screen overlay with information about the locked changeset.
+ *
+ * @constructs wp.customize~LockedNotification
+ * @augments wp.customize.OverlayNotification
+ *
+ * @since 4.9.0
+ *
+ * @param {string} [code] - Code.
+ * @param {object} [params] - Params.
+ */
+ initialize: function( code, params ) {
+ var notification = this, _code, _params;
+ _code = code || 'changeset_locked';
+ _params = _.extend(
+ {
+ type: 'warning',
+ containerClasses: '',
+ lockUser: {}
+ },
+ params
+ );
+ _params.containerClasses += ' notification-changeset-locked';
+ api.OverlayNotification.prototype.initialize.call( notification, _code, _params );
+ },
+
+ /**
+ * Render notification.
+ *
+ * @since 4.9.0
+ *
+ * @return {jQuery} Notification container.
+ */
+ render: function() {
+ var notification = this, li, data, takeOverButton, request;
+ data = _.extend(
+ {
+ allowOverride: false,
+ returnUrl: api.settings.url['return'],
+ previewUrl: api.previewer.previewUrl.get(),
+ frontendPreviewUrl: api.previewer.getFrontendPreviewUrl()
+ },
+ this
+ );
+
+ li = api.OverlayNotification.prototype.render.call( data );
+
+ // Try to autosave the changeset now.
+ api.requestChangesetUpdate( {}, { autosave: true } ).fail( function( response ) {
+ if ( ! response.autosaved ) {
+ li.find( '.notice-error' ).prop( 'hidden', false ).text( response.message || api.l10n.unknownRequestFail );
+ }
+ } );
+
+ takeOverButton = li.find( '.customize-notice-take-over-button' );
+ takeOverButton.on( 'click', function( event ) {
+ event.preventDefault();
+ if ( request ) {
+ return;
+ }
+
+ takeOverButton.addClass( 'disabled' );
+ request = wp.ajax.post( 'customize_override_changeset_lock', {
+ wp_customize: 'on',
+ customize_theme: api.settings.theme.stylesheet,
+ customize_changeset_uuid: api.settings.changeset.uuid,
+ nonce: api.settings.nonce.override_lock
+ } );
+
+ request.done( function() {
+ api.notifications.remove( notification.code ); // Remove self.
+ api.state( 'changesetLocked' ).set( false );
+ } );
+
+ request.fail( function( response ) {
+ var message = response.message || api.l10n.unknownRequestFail;
+ li.find( '.notice-error' ).prop( 'hidden', false ).text( message );
+
+ request.always( function() {
+ takeOverButton.removeClass( 'disabled' );
+ } );
+ } );
+
+ request.always( function() {
+ request = null;
+ } );
+ } );
+
+ return li;
+ }
+ });
+
+ /**
+ * Start lock.
+ *
+ * @since 4.9.0
+ *
+ * @param {object} [args] - Args.
+ * @param {object} [args.lockUser] - Lock user data.
+ * @param {boolean} [args.allowOverride=false] - Whether override is allowed.
+ * @returns {void}
+ */
+ function startLock( args ) {
+ if ( args && args.lockUser ) {
+ api.settings.changeset.lockUser = args.lockUser;
+ }
+ api.state( 'changesetLocked' ).set( true );
+ api.notifications.add( new LockedNotification( 'changeset_locked', {
+ lockUser: api.settings.changeset.lockUser,
+ allowOverride: Boolean( args && args.allowOverride )
+ } ) );
+ }
+
+ // Show initial notification.
+ if ( api.settings.changeset.lockUser ) {
+ startLock( { allowOverride: true } );
+ }
+
+ // Check for lock when sending heartbeat requests.
+ $( document ).on( 'heartbeat-send.update_lock_notice', function( event, data ) {
+ data.check_changeset_lock = true;
+ data.changeset_uuid = api.settings.changeset.uuid;
+ } );
+
+ // Handle heartbeat ticks.
+ $( document ).on( 'heartbeat-tick.update_lock_notice', function( event, data ) {
+ var notification, code = 'changeset_locked';
+ if ( ! data.customize_changeset_lock_user ) {
+ return;
+ }
+
+ // Update notification when a different user takes over.
+ notification = api.notifications( code );
+ if ( notification && notification.lockUser.id !== api.settings.changeset.lockUser.id ) {
+ api.notifications.remove( code );
+ }
+
+ startLock( {
+ lockUser: data.customize_changeset_lock_user
+ } );
+ } );
+
+ // Handle locking in response to changeset save errors.
+ api.bind( 'error', function( response ) {
+ if ( 'changeset_locked' === response.code && response.lock_user ) {
+ startLock( {
+ lockUser: response.lock_user
+ } );
+ }
+ } );
+ } )();
+
+ // Set up initial notifications.
+ (function() {
+ var removedQueryParams = [], autosaveDismissed = false;
+
+ /**
+ * Obtain the URL to restore the autosave.
+ *
+ * @returns {string} Customizer URL.
+ */
+ function getAutosaveRestorationUrl() {
+ var urlParser, queryParams;
+ urlParser = document.createElement( 'a' );
+ urlParser.href = location.href;
+ queryParams = api.utils.parseQueryString( urlParser.search.substr( 1 ) );
+ if ( api.settings.changeset.latestAutoDraftUuid ) {
+ queryParams.changeset_uuid = api.settings.changeset.latestAutoDraftUuid;
+ } else {
+ queryParams.customize_autosaved = 'on';
+ }
+ queryParams['return'] = api.settings.url['return'];
+ urlParser.search = $.param( queryParams );
+ return urlParser.href;
+ }
+
+ /**
+ * Remove parameter from the URL.
+ *
+ * @param {Array} params - Parameter names to remove.
+ * @returns {void}
+ */
+ function stripParamsFromLocation( params ) {
+ var urlParser = document.createElement( 'a' ), queryParams, strippedParams = 0;
+ urlParser.href = location.href;
+ queryParams = api.utils.parseQueryString( urlParser.search.substr( 1 ) );
+ _.each( params, function( param ) {
+ if ( 'undefined' !== typeof queryParams[ param ] ) {
+ strippedParams += 1;
+ delete queryParams[ param ];
+ }
+ } );
+ if ( 0 === strippedParams ) {
+ return;
+ }
+
+ urlParser.search = $.param( queryParams );
+ history.replaceState( {}, document.title, urlParser.href );
+ }
+
+ /**
+ * Dismiss autosave.
+ *
+ * @returns {void}
+ */
+ function dismissAutosave() {
+ if ( autosaveDismissed ) {
+ return;
+ }
+ wp.ajax.post( 'customize_dismiss_autosave_or_lock', {
+ wp_customize: 'on',
+ customize_theme: api.settings.theme.stylesheet,
+ customize_changeset_uuid: api.settings.changeset.uuid,
+ nonce: api.settings.nonce.dismiss_autosave_or_lock,
+ dismiss_autosave: true
+ } );
+ autosaveDismissed = true;
+ }
+
+ /**
+ * Add notification regarding the availability of an autosave to restore.
+ *
+ * @returns {void}
+ */
+ function addAutosaveRestoreNotification() {
+ var code = 'autosave_available', onStateChange;
+
+ // Since there is an autosave revision and the user hasn't loaded with autosaved, add notification to prompt to load autosaved version.
+ api.notifications.add( new api.Notification( code, {
+ message: api.l10n.autosaveNotice,
+ type: 'warning',
+ dismissible: true,
+ render: function() {
+ var li = api.Notification.prototype.render.call( this ), link;
+
+ // Handle clicking on restoration link.
+ link = li.find( 'a' );
+ link.prop( 'href', getAutosaveRestorationUrl() );
+ link.on( 'click', function( event ) {
+ event.preventDefault();
+ location.replace( getAutosaveRestorationUrl() );
+ } );
+
+ // Handle dismissal of notice.
+ li.find( '.notice-dismiss' ).on( 'click', dismissAutosave );
+
+ return li;
+ }
+ } ) );
+
+ // Remove the notification once the user starts making changes.
+ onStateChange = function() {
+ dismissAutosave();
+ api.notifications.remove( code );
+ api.unbind( 'change', onStateChange );
+ api.state( 'changesetStatus' ).unbind( onStateChange );
+ };
+ api.bind( 'change', onStateChange );
+ api.state( 'changesetStatus' ).bind( onStateChange );
+ }
+
+ if ( api.settings.changeset.autosaved ) {
+ api.state( 'saved' ).set( false );
+ removedQueryParams.push( 'customize_autosaved' );
+ }
+ if ( ! api.settings.changeset.branching && ( ! api.settings.changeset.status || 'auto-draft' === api.settings.changeset.status ) ) {
+ removedQueryParams.push( 'changeset_uuid' ); // Remove UUID when restoring autosave auto-draft.
+ }
+ if ( removedQueryParams.length > 0 ) {
+ stripParamsFromLocation( removedQueryParams );
+ }
+ if ( api.settings.changeset.latestAutoDraftUuid || api.settings.changeset.hasAutosaveRevision ) {
+ addAutosaveRestoreNotification();
+ }
+ })();
+
+ // Check if preview url is valid and load the preview frame.
+ if ( api.previewer.previewUrl() ) {
+ api.previewer.refresh();
+ } else {
+ api.previewer.previewUrl( api.settings.url.home );
+ }
+
+ // Button bindings.
+ saveBtn.click( function( event ) {
+ api.previewer.save();
+ event.preventDefault();
+ }).keydown( function( event ) {
+ if ( 9 === event.which ) { // Tab.
+ return;
+ }
+ if ( 13 === event.which ) { // Enter.
+ api.previewer.save();
+ }
+ event.preventDefault();
+ });
+
+ closeBtn.keydown( function( event ) {
+ if ( 9 === event.which ) { // Tab.
+ return;
+ }
+ if ( 13 === event.which ) { // Enter.
+ this.click();
+ }
+ event.preventDefault();
+ });
+
+ $( '.collapse-sidebar' ).on( 'click', function() {
+ api.state( 'paneVisible' ).set( ! api.state( 'paneVisible' ).get() );
+ });
+
+ api.state( 'paneVisible' ).bind( function( paneVisible ) {
+ overlay.toggleClass( 'preview-only', ! paneVisible );
+ overlay.toggleClass( 'expanded', paneVisible );
+ overlay.toggleClass( 'collapsed', ! paneVisible );
+
+ if ( ! paneVisible ) {
+ $( '.collapse-sidebar' ).attr({ 'aria-expanded': 'false', 'aria-label': api.l10n.expandSidebar });
+ } else {
+ $( '.collapse-sidebar' ).attr({ 'aria-expanded': 'true', 'aria-label': api.l10n.collapseSidebar });
+ }
+ });
+
+ // Keyboard shortcuts - esc to exit section/panel.
+ body.on( 'keydown', function( event ) {
+ var collapsedObject, expandedControls = [], expandedSections = [], expandedPanels = [];
+
+ if ( 27 !== event.which ) { // Esc.
+ return;
+ }
+
+ /*
+ * Abort if the event target is not the body (the default) and not inside of #customize-controls.
+ * This ensures that ESC meant to collapse a modal dialog or a TinyMCE toolbar won't collapse something else.
+ */
+ if ( ! $( event.target ).is( 'body' ) && ! $.contains( $( '#customize-controls' )[0], event.target ) ) {
+ return;
+ }
+
+ // Check for expanded expandable controls (e.g. widgets and nav menus items), sections, and panels.
+ api.control.each( function( control ) {
+ if ( control.expanded && control.expanded() && _.isFunction( control.collapse ) ) {
+ expandedControls.push( control );
+ }
+ });
+ api.section.each( function( section ) {
+ if ( section.expanded() ) {
+ expandedSections.push( section );
+ }
+ });
+ api.panel.each( function( panel ) {
+ if ( panel.expanded() ) {
+ expandedPanels.push( panel );
+ }
+ });
+
+ // Skip collapsing expanded controls if there are no expanded sections.
+ if ( expandedControls.length > 0 && 0 === expandedSections.length ) {
+ expandedControls.length = 0;
+ }
+
+ // Collapse the most granular expanded object.
+ collapsedObject = expandedControls[0] || expandedSections[0] || expandedPanels[0];
+ if ( collapsedObject ) {
+ if ( 'themes' === collapsedObject.params.type ) {
+
+ // Themes panel or section.
+ if ( body.hasClass( 'modal-open' ) ) {
+ collapsedObject.closeDetails();
+ } else if ( api.panel.has( 'themes' ) ) {
+
+ // If we're collapsing a section, collapse the panel also.
+ api.panel( 'themes' ).collapse();
+ }
+ return;
+ }
+ collapsedObject.collapse();
+ event.preventDefault();
+ }
+ });
+
+ $( '.customize-controls-preview-toggle' ).on( 'click', function() {
+ api.state( 'paneVisible' ).set( ! api.state( 'paneVisible' ).get() );
+ });
+
+ /*
+ * Sticky header feature.
+ */
+ (function initStickyHeaders() {
+ var parentContainer = $( '.wp-full-overlay-sidebar-content' ),
+ changeContainer, updateHeaderHeight, releaseStickyHeader, resetStickyHeader, positionStickyHeader,
+ activeHeader, lastScrollTop;
+
+ /**
+ * Determine which panel or section is currently expanded.
+ *
+ * @since 4.7.0
+ * @access private
+ *
+ * @param {wp.customize.Panel|wp.customize.Section} container Construct.
+ * @returns {void}
+ */
+ changeContainer = function( container ) {
+ var newInstance = container,
+ expandedSection = api.state( 'expandedSection' ).get(),
+ expandedPanel = api.state( 'expandedPanel' ).get(),
+ headerElement;
+
+ if ( activeHeader && activeHeader.element ) {
+ // Release previously active header element.
+ releaseStickyHeader( activeHeader.element );
+
+ // Remove event listener in the previous panel or section.
+ activeHeader.element.find( '.description' ).off( 'toggled', updateHeaderHeight );
+ }
+
+ if ( ! newInstance ) {
+ if ( ! expandedSection && expandedPanel && expandedPanel.contentContainer ) {
+ newInstance = expandedPanel;
+ } else if ( ! expandedPanel && expandedSection && expandedSection.contentContainer ) {
+ newInstance = expandedSection;
+ } else {
+ activeHeader = false;
+ return;
+ }
+ }
+
+ headerElement = newInstance.contentContainer.find( '.customize-section-title, .panel-meta' ).first();
+ if ( headerElement.length ) {
+ activeHeader = {
+ instance: newInstance,
+ element: headerElement,
+ parent: headerElement.closest( '.customize-pane-child' ),
+ height: headerElement.outerHeight()
+ };
+
+ // Update header height whenever help text is expanded or collapsed.
+ activeHeader.element.find( '.description' ).on( 'toggled', updateHeaderHeight );
+
+ if ( expandedSection ) {
+ resetStickyHeader( activeHeader.element, activeHeader.parent );
+ }
+ } else {
+ activeHeader = false;
+ }
+ };
+ api.state( 'expandedSection' ).bind( changeContainer );
+ api.state( 'expandedPanel' ).bind( changeContainer );
+
+ // Throttled scroll event handler.
+ parentContainer.on( 'scroll', _.throttle( function() {
+ if ( ! activeHeader ) {
+ return;
+ }
+
+ var scrollTop = parentContainer.scrollTop(),
+ scrollDirection;
+
+ if ( ! lastScrollTop ) {
+ scrollDirection = 1;
+ } else {
+ if ( scrollTop === lastScrollTop ) {
+ scrollDirection = 0;
+ } else if ( scrollTop > lastScrollTop ) {
+ scrollDirection = 1;
+ } else {
+ scrollDirection = -1;
+ }
+ }
+ lastScrollTop = scrollTop;
+ if ( 0 !== scrollDirection ) {
+ positionStickyHeader( activeHeader, scrollTop, scrollDirection );
+ }
+ }, 8 ) );
+
+ // Update header position on sidebar layout change.
+ api.notifications.bind( 'sidebarTopUpdated', function() {
+ if ( activeHeader && activeHeader.element.hasClass( 'is-sticky' ) ) {
+ activeHeader.element.css( 'top', parentContainer.css( 'top' ) );
+ }
+ });
+
+ // Release header element if it is sticky.
+ releaseStickyHeader = function( headerElement ) {
+ if ( ! headerElement.hasClass( 'is-sticky' ) ) {
+ return;
+ }
+ headerElement
+ .removeClass( 'is-sticky' )
+ .addClass( 'maybe-sticky is-in-view' )
+ .css( 'top', parentContainer.scrollTop() + 'px' );
+ };
+
+ // Reset position of the sticky header.
+ resetStickyHeader = function( headerElement, headerParent ) {
+ if ( headerElement.hasClass( 'is-in-view' ) ) {
+ headerElement
+ .removeClass( 'maybe-sticky is-in-view' )
+ .css( {
+ width: '',
+ top: ''
+ } );
+ headerParent.css( 'padding-top', '' );
+ }
+ };
+
+ /**
+ * Update active header height.
+ *
+ * @since 4.7.0
+ * @access private
+ *
+ * @returns {void}
+ */
+ updateHeaderHeight = function() {
+ activeHeader.height = activeHeader.element.outerHeight();
+ };
+
+ /**
+ * Reposition header on throttled `scroll` event.
+ *
+ * @since 4.7.0
+ * @access private
+ *
+ * @param {object} header - Header.
+ * @param {number} scrollTop - Scroll top.
+ * @param {number} scrollDirection - Scroll direction, negative number being up and positive being down.
+ * @returns {void}
+ */
+ positionStickyHeader = function( header, scrollTop, scrollDirection ) {
+ var headerElement = header.element,
+ headerParent = header.parent,
+ headerHeight = header.height,
+ headerTop = parseInt( headerElement.css( 'top' ), 10 ),
+ maybeSticky = headerElement.hasClass( 'maybe-sticky' ),
+ isSticky = headerElement.hasClass( 'is-sticky' ),
+ isInView = headerElement.hasClass( 'is-in-view' ),
+ isScrollingUp = ( -1 === scrollDirection );
+
+ // When scrolling down, gradually hide sticky header.
+ if ( ! isScrollingUp ) {
+ if ( isSticky ) {
+ headerTop = scrollTop;
+ headerElement
+ .removeClass( 'is-sticky' )
+ .css( {
+ top: headerTop + 'px',
+ width: ''
+ } );
+ }
+ if ( isInView && scrollTop > headerTop + headerHeight ) {
+ headerElement.removeClass( 'is-in-view' );
+ headerParent.css( 'padding-top', '' );
+ }
+ return;
+ }
+
+ // Scrolling up.
+ if ( ! maybeSticky && scrollTop >= headerHeight ) {
+ maybeSticky = true;
+ headerElement.addClass( 'maybe-sticky' );
+ } else if ( 0 === scrollTop ) {
+ // Reset header in base position.
+ headerElement
+ .removeClass( 'maybe-sticky is-in-view is-sticky' )
+ .css( {
+ top: '',
+ width: ''
+ } );
+ headerParent.css( 'padding-top', '' );
+ return;
+ }
+
+ if ( isInView && ! isSticky ) {
+ // Header is in the view but is not yet sticky.
+ if ( headerTop >= scrollTop ) {
+ // Header is fully visible.
+ headerElement
+ .addClass( 'is-sticky' )
+ .css( {
+ top: parentContainer.css( 'top' ),
+ width: headerParent.outerWidth() + 'px'
+ } );
+ }
+ } else if ( maybeSticky && ! isInView ) {
+ // Header is out of the view.
+ headerElement
+ .addClass( 'is-in-view' )
+ .css( 'top', ( scrollTop - headerHeight ) + 'px' );
+ headerParent.css( 'padding-top', headerHeight + 'px' );
+ }
+ };
+ }());
+
+ // Previewed device bindings. (The api.previewedDevice property is how this Value was first introduced, but since it has moved to api.state.)
+ api.previewedDevice = api.state( 'previewedDevice' );
+
+ // Set the default device.
+ api.bind( 'ready', function() {
+ _.find( api.settings.previewableDevices, function( value, key ) {
+ if ( true === value['default'] ) {
+ api.previewedDevice.set( key );
+ return true;
+ }
+ } );
+ } );
+
+ // Set the toggled device.
+ footerActions.find( '.devices button' ).on( 'click', function( event ) {
+ api.previewedDevice.set( $( event.currentTarget ).data( 'device' ) );
+ });
+
+ // Bind device changes.
+ api.previewedDevice.bind( function( newDevice ) {
+ var overlay = $( '.wp-full-overlay' ),
+ devices = '';
+
+ footerActions.find( '.devices button' )
+ .removeClass( 'active' )
+ .attr( 'aria-pressed', false );
+
+ footerActions.find( '.devices .preview-' + newDevice )
+ .addClass( 'active' )
+ .attr( 'aria-pressed', true );
+
+ $.each( api.settings.previewableDevices, function( device ) {
+ devices += ' preview-' + device;
+ } );
+
+ overlay
+ .removeClass( devices )
+ .addClass( 'preview-' + newDevice );
+ } );
+
+ // Bind site title display to the corresponding field.
+ if ( title.length ) {
+ api( 'blogname', function( setting ) {
+ var updateTitle = function() {
+ title.text( $.trim( setting() ) || api.l10n.untitledBlogName );
+ };
+ setting.bind( updateTitle );
+ updateTitle();
+ } );
+ }
+
+ /*
+ * Create a postMessage connection with a parent frame,
+ * in case the Customizer frame was opened with the Customize loader.
+ *
+ * @see wp.customize.Loader
+ */
+ parent = new api.Messenger({
+ url: api.settings.url.parent,
+ channel: 'loader'
+ });
+
+ // Handle exiting of Customizer.
+ (function() {
+ var isInsideIframe = false;
+
+ function isCleanState() {
+ var defaultChangesetStatus;
+
+ /*
+ * Handle special case of previewing theme switch since some settings (for nav menus and widgets)
+ * are pre-dirty and non-active themes can only ever be auto-drafts.
+ */
+ if ( ! api.state( 'activated' ).get() ) {
+ return 0 === api._latestRevision;
+ }
+
+ // Dirty if the changeset status has been changed but not saved yet.
+ defaultChangesetStatus = api.state( 'changesetStatus' ).get();
+ if ( '' === defaultChangesetStatus || 'auto-draft' === defaultChangesetStatus ) {
+ defaultChangesetStatus = 'publish';
+ }
+ if ( api.state( 'selectedChangesetStatus' ).get() !== defaultChangesetStatus ) {
+ return false;
+ }
+
+ // Dirty if scheduled but the changeset date hasn't been saved yet.
+ if ( 'future' === api.state( 'selectedChangesetStatus' ).get() && api.state( 'selectedChangesetDate' ).get() !== api.state( 'changesetDate' ).get() ) {
+ return false;
+ }
+
+ return api.state( 'saved' ).get() && 'auto-draft' !== api.state( 'changesetStatus' ).get();
+ }
+
+ /*
+ * If we receive a 'back' event, we're inside an iframe.
+ * Send any clicks to the 'Return' link to the parent page.
+ */
+ parent.bind( 'back', function() {
+ isInsideIframe = true;
+ });
+
+ function startPromptingBeforeUnload() {
+ api.unbind( 'change', startPromptingBeforeUnload );
+ api.state( 'selectedChangesetStatus' ).unbind( startPromptingBeforeUnload );
+ api.state( 'selectedChangesetDate' ).unbind( startPromptingBeforeUnload );
+
+ // Prompt user with AYS dialog if leaving the Customizer with unsaved changes
+ $( window ).on( 'beforeunload.customize-confirm', function() {
+ if ( ! isCleanState() && ! api.state( 'changesetLocked' ).get() ) {
+ setTimeout( function() {
+ overlay.removeClass( 'customize-loading' );
+ }, 1 );
+ return api.l10n.saveAlert;
+ }
+ });
+ }
+ api.bind( 'change', startPromptingBeforeUnload );
+ api.state( 'selectedChangesetStatus' ).bind( startPromptingBeforeUnload );
+ api.state( 'selectedChangesetDate' ).bind( startPromptingBeforeUnload );
+
+ function requestClose() {
+ var clearedToClose = $.Deferred(), dismissAutoSave = false, dismissLock = false;
+
+ if ( isCleanState() ) {
+ dismissLock = true;
+ } else if ( confirm( api.l10n.saveAlert ) ) {
+
+ dismissLock = true;
+
+ // Mark all settings as clean to prevent another call to requestChangesetUpdate.
+ api.each( function( setting ) {
+ setting._dirty = false;
+ });
+ $( document ).off( 'visibilitychange.wp-customize-changeset-update' );
+ $( window ).off( 'beforeunload.wp-customize-changeset-update' );
+
+ closeBtn.css( 'cursor', 'progress' );
+ if ( '' !== api.state( 'changesetStatus' ).get() ) {
+ dismissAutoSave = true;
+ }
+ } else {
+ clearedToClose.reject();
+ }
+
+ if ( dismissLock || dismissAutoSave ) {
+ wp.ajax.send( 'customize_dismiss_autosave_or_lock', {
+ timeout: 500, // Don't wait too long.
+ data: {
+ wp_customize: 'on',
+ customize_theme: api.settings.theme.stylesheet,
+ customize_changeset_uuid: api.settings.changeset.uuid,
+ nonce: api.settings.nonce.dismiss_autosave_or_lock,
+ dismiss_autosave: dismissAutoSave,
+ dismiss_lock: dismissLock
+ }
+ } ).always( function() {
+ clearedToClose.resolve();
+ } );
+ }
+
+ return clearedToClose.promise();
+ }
+
+ parent.bind( 'confirm-close', function() {
+ requestClose().done( function() {
+ parent.send( 'confirmed-close', true );
+ } ).fail( function() {
+ parent.send( 'confirmed-close', false );
+ } );
+ } );
+
+ closeBtn.on( 'click.customize-controls-close', function( event ) {
+ event.preventDefault();
+ if ( isInsideIframe ) {
+ parent.send( 'close' ); // See confirm-close logic above.
+ } else {
+ requestClose().done( function() {
+ $( window ).off( 'beforeunload.customize-confirm' );
+ window.location.href = closeBtn.prop( 'href' );
+ } );
+ }
+ });
+ })();
+
+ // Pass events through to the parent.
+ $.each( [ 'saved', 'change' ], function ( i, event ) {
+ api.bind( event, function() {
+ parent.send( event );
+ });
+ } );
+
+ // Pass titles to the parent
+ api.bind( 'title', function( newTitle ) {
+ parent.send( 'title', newTitle );
+ });
+
+ if ( api.settings.changeset.branching ) {
+ parent.send( 'changeset-uuid', api.settings.changeset.uuid );
+ }
+
+ // Initialize the connection with the parent frame.
+ parent.send( 'ready' );
+
+ // Control visibility for default controls
+ $.each({
+ 'background_image': {
+ controls: [ 'background_preset', 'background_position', 'background_size', 'background_repeat', 'background_attachment' ],
+ callback: function( to ) { return !! to; }
+ },
+ 'show_on_front': {
+ controls: [ 'page_on_front', 'page_for_posts' ],
+ callback: function( to ) { return 'page' === to; }
+ },
+ 'header_textcolor': {
+ controls: [ 'header_textcolor' ],
+ callback: function( to ) { return 'blank' !== to; }
+ }
+ }, function( settingId, o ) {
+ api( settingId, function( setting ) {
+ $.each( o.controls, function( i, controlId ) {
+ api.control( controlId, function( control ) {
+ var visibility = function( to ) {
+ control.container.toggle( o.callback( to ) );
+ };
+
+ visibility( setting.get() );
+ setting.bind( visibility );
+ });
+ });
+ });
+ });
+
+ api.control( 'background_preset', function( control ) {
+ var visibility, defaultValues, values, toggleVisibility, updateSettings, preset;
+
+ visibility = { // position, size, repeat, attachment
+ 'default': [ false, false, false, false ],
+ 'fill': [ true, false, false, false ],
+ 'fit': [ true, false, true, false ],
+ 'repeat': [ true, false, false, true ],
+ 'custom': [ true, true, true, true ]
+ };
+
+ defaultValues = [
+ _wpCustomizeBackground.defaults['default-position-x'],
+ _wpCustomizeBackground.defaults['default-position-y'],
+ _wpCustomizeBackground.defaults['default-size'],
+ _wpCustomizeBackground.defaults['default-repeat'],
+ _wpCustomizeBackground.defaults['default-attachment']
+ ];
+
+ values = { // position_x, position_y, size, repeat, attachment
+ 'default': defaultValues,
+ 'fill': [ 'left', 'top', 'cover', 'no-repeat', 'fixed' ],
+ 'fit': [ 'left', 'top', 'contain', 'no-repeat', 'fixed' ],
+ 'repeat': [ 'left', 'top', 'auto', 'repeat', 'scroll' ]
+ };
+
+ // @todo These should actually toggle the active state, but without the preview overriding the state in data.activeControls.
+ toggleVisibility = function( preset ) {
+ _.each( [ 'background_position', 'background_size', 'background_repeat', 'background_attachment' ], function( controlId, i ) {
+ var control = api.control( controlId );
+ if ( control ) {
+ control.container.toggle( visibility[ preset ][ i ] );
+ }
+ } );
+ };
+
+ updateSettings = function( preset ) {
+ _.each( [ 'background_position_x', 'background_position_y', 'background_size', 'background_repeat', 'background_attachment' ], function( settingId, i ) {
+ var setting = api( settingId );
+ if ( setting ) {
+ setting.set( values[ preset ][ i ] );
+ }
+ } );
+ };
+
+ preset = control.setting.get();
+ toggleVisibility( preset );
+
+ control.setting.bind( 'change', function( preset ) {
+ toggleVisibility( preset );
+ if ( 'custom' !== preset ) {
+ updateSettings( preset );
+ }
+ } );
+ } );
+
+ api.control( 'background_repeat', function( control ) {
+ control.elements[0].unsync( api( 'background_repeat' ) );
+
+ control.element = new api.Element( control.container.find( 'input' ) );
+ control.element.set( 'no-repeat' !== control.setting() );
+
+ control.element.bind( function( to ) {
+ control.setting.set( to ? 'repeat' : 'no-repeat' );
+ } );
+
+ control.setting.bind( function( to ) {
+ control.element.set( 'no-repeat' !== to );
+ } );
+ } );
+
+ api.control( 'background_attachment', function( control ) {
+ control.elements[0].unsync( api( 'background_attachment' ) );
+
+ control.element = new api.Element( control.container.find( 'input' ) );
+ control.element.set( 'fixed' !== control.setting() );
+
+ control.element.bind( function( to ) {
+ control.setting.set( to ? 'scroll' : 'fixed' );
+ } );
+
+ control.setting.bind( function( to ) {
+ control.element.set( 'fixed' !== to );
+ } );
+ } );
+
+ // Juggle the two controls that use header_textcolor
+ api.control( 'display_header_text', function( control ) {
+ var last = '';
+
+ control.elements[0].unsync( api( 'header_textcolor' ) );
+
+ control.element = new api.Element( control.container.find('input') );
+ control.element.set( 'blank' !== control.setting() );
+
+ control.element.bind( function( to ) {
+ if ( ! to ) {
+ last = api( 'header_textcolor' ).get();
+ }
+
+ control.setting.set( to ? last : 'blank' );
+ });
+
+ control.setting.bind( function( to ) {
+ control.element.set( 'blank' !== to );
+ });
+ });
+
+ // Add behaviors to the static front page controls.
+ api( 'show_on_front', 'page_on_front', 'page_for_posts', function( showOnFront, pageOnFront, pageForPosts ) {
+ var handleChange = function() {
+ var setting = this, pageOnFrontId, pageForPostsId, errorCode = 'show_on_front_page_collision';
+ pageOnFrontId = parseInt( pageOnFront(), 10 );
+ pageForPostsId = parseInt( pageForPosts(), 10 );
+
+ if ( 'page' === showOnFront() ) {
+
+ // Change previewed URL to the homepage when changing the page_on_front.
+ if ( setting === pageOnFront && pageOnFrontId > 0 ) {
+ api.previewer.previewUrl.set( api.settings.url.home );
+ }
+
+ // Change the previewed URL to the selected page when changing the page_for_posts.
+ if ( setting === pageForPosts && pageForPostsId > 0 ) {
+ api.previewer.previewUrl.set( api.settings.url.home + '?page_id=' + pageForPostsId );
+ }
+ }
+
+ // Toggle notification when the homepage and posts page are both set and the same.
+ if ( 'page' === showOnFront() && pageOnFrontId && pageForPostsId && pageOnFrontId === pageForPostsId ) {
+ showOnFront.notifications.add( new api.Notification( errorCode, {
+ type: 'error',
+ message: api.l10n.pageOnFrontError
+ } ) );
+ } else {
+ showOnFront.notifications.remove( errorCode );
+ }
+ };
+ showOnFront.bind( handleChange );
+ pageOnFront.bind( handleChange );
+ pageForPosts.bind( handleChange );
+ handleChange.call( showOnFront, showOnFront() ); // Make sure initial notification is added after loading existing changeset.
+
+ // Move notifications container to the bottom.
+ api.control( 'show_on_front', function( showOnFrontControl ) {
+ showOnFrontControl.deferred.embedded.done( function() {
+ showOnFrontControl.container.append( showOnFrontControl.getNotificationsContainerElement() );
+ });
+ });
+ });
+
+ // Add code editor for Custom CSS.
+ (function() {
+ var sectionReady = $.Deferred();
+
+ api.section( 'custom_css', function( section ) {
+ section.deferred.embedded.done( function() {
+ if ( section.expanded() ) {
+ sectionReady.resolve( section );
+ } else {
+ section.expanded.bind( function( isExpanded ) {
+ if ( isExpanded ) {
+ sectionReady.resolve( section );
+ }
+ } );
+ }
+ });
+ });
+
+ // Set up the section description behaviors.
+ sectionReady.done( function setupSectionDescription( section ) {
+ var control = api.control( 'custom_css' );
+
+ // Hide redundant label for visual users.
+ control.container.find( '.customize-control-title:first' ).addClass( 'screen-reader-text' );
+
+ // Close the section description when clicking the close button.
+ section.container.find( '.section-description-buttons .section-description-close' ).on( 'click', function() {
+ section.container.find( '.section-meta .customize-section-description:first' )
+ .removeClass( 'open' )
+ .slideUp();
+
+ section.container.find( '.customize-help-toggle' )
+ .attr( 'aria-expanded', 'false' )
+ .focus(); // Avoid focus loss.
+ });
+
+ // Reveal help text if setting is empty.
+ if ( control && ! control.setting.get() ) {
+ section.container.find( '.section-meta .customize-section-description:first' )
+ .addClass( 'open' )
+ .show()
+ .trigger( 'toggled' );
+
+ section.container.find( '.customize-help-toggle' ).attr( 'aria-expanded', 'true' );
+ }
+ });
+ })();
+
+ // Toggle visibility of Header Video notice when active state change.
+ api.control( 'header_video', function( headerVideoControl ) {
+ headerVideoControl.deferred.embedded.done( function() {
+ var toggleNotice = function() {
+ var section = api.section( headerVideoControl.section() ), noticeCode = 'video_header_not_available';
+ if ( ! section ) {
+ return;
+ }
+ if ( headerVideoControl.active.get() ) {
+ section.notifications.remove( noticeCode );
+ } else {
+ section.notifications.add( new api.Notification( noticeCode, {
+ type: 'info',
+ message: api.l10n.videoHeaderNotice
+ } ) );
+ }
+ };
+ toggleNotice();
+ headerVideoControl.active.bind( toggleNotice );
+ } );
+ } );
+
+ // Update the setting validities.
+ api.previewer.bind( 'selective-refresh-setting-validities', function handleSelectiveRefreshedSettingValidities( settingValidities ) {
+ api._handleSettingValidities( {
+ settingValidities: settingValidities,
+ focusInvalidControl: false
+ } );
+ } );
+
+ // Focus on the control that is associated with the given setting.
+ api.previewer.bind( 'focus-control-for-setting', function( settingId ) {
+ var matchedControls = [];
+ api.control.each( function( control ) {
+ var settingIds = _.pluck( control.settings, 'id' );
+ if ( -1 !== _.indexOf( settingIds, settingId ) ) {
+ matchedControls.push( control );
+ }
+ } );
+
+ // Focus on the matched control with the lowest priority (appearing higher).
+ if ( matchedControls.length ) {
+ matchedControls.sort( function( a, b ) {
+ return a.priority() - b.priority();
+ } );
+ matchedControls[0].focus();
+ }
+ } );
+
+ // Refresh the preview when it requests.
+ api.previewer.bind( 'refresh', function() {
+ api.previewer.refresh();
+ });
+
+ // Update the edit shortcut visibility state.
+ api.state( 'paneVisible' ).bind( function( isPaneVisible ) {
+ var isMobileScreen;
+ if ( window.matchMedia ) {
+ isMobileScreen = window.matchMedia( 'screen and ( max-width: 640px )' ).matches;
+ } else {
+ isMobileScreen = $( window ).width() <= 640;
+ }
+ api.state( 'editShortcutVisibility' ).set( isPaneVisible || isMobileScreen ? 'visible' : 'hidden' );
+ } );
+ if ( window.matchMedia ) {
+ window.matchMedia( 'screen and ( max-width: 640px )' ).addListener( function() {
+ var state = api.state( 'paneVisible' );
+ state.callbacks.fireWith( state, [ state.get(), state.get() ] );
+ } );
+ }
+ api.previewer.bind( 'edit-shortcut-visibility', function( visibility ) {
+ api.state( 'editShortcutVisibility' ).set( visibility );
+ } );
+ api.state( 'editShortcutVisibility' ).bind( function( visibility ) {
+ api.previewer.send( 'edit-shortcut-visibility', visibility );
+ } );
+
+ // Autosave changeset.
+ function startAutosaving() {
+ var timeoutId, updateChangesetWithReschedule, scheduleChangesetUpdate, updatePending = false;
+
+ api.unbind( 'change', startAutosaving ); // Ensure startAutosaving only fires once.
+
+ function onChangeSaved( isSaved ) {
+ if ( ! isSaved && ! api.settings.changeset.autosaved ) {
+ api.settings.changeset.autosaved = true; // Once a change is made then autosaving kicks in.
+ api.previewer.send( 'autosaving' );
+ }
+ }
+ api.state( 'saved' ).bind( onChangeSaved );
+ onChangeSaved( api.state( 'saved' ).get() );
+
+ /**
+ * Request changeset update and then re-schedule the next changeset update time.
+ *
+ * @since 4.7.0
+ * @private
+ */
+ updateChangesetWithReschedule = function() {
+ if ( ! updatePending ) {
+ updatePending = true;
+ api.requestChangesetUpdate( {}, { autosave: true } ).always( function() {
+ updatePending = false;
+ } );
+ }
+ scheduleChangesetUpdate();
+ };
+
+ /**
+ * Schedule changeset update.
+ *
+ * @since 4.7.0
+ * @private
+ */
+ scheduleChangesetUpdate = function() {
+ clearTimeout( timeoutId );
+ timeoutId = setTimeout( function() {
+ updateChangesetWithReschedule();
+ }, api.settings.timeouts.changesetAutoSave );
+ };
+
+ // Start auto-save interval for updating changeset.
+ scheduleChangesetUpdate();
+
+ // Save changeset when focus removed from window.
+ $( document ).on( 'visibilitychange.wp-customize-changeset-update', function() {
+ if ( document.hidden ) {
+ updateChangesetWithReschedule();
+ }
+ } );
+
+ // Save changeset before unloading window.
+ $( window ).on( 'beforeunload.wp-customize-changeset-update', function() {
+ updateChangesetWithReschedule();
+ } );
+ }
+ api.bind( 'change', startAutosaving );
+
+ // Make sure TinyMCE dialogs appear above Customizer UI.
+ $( document ).one( 'tinymce-editor-setup', function() {
+ if ( window.tinymce.ui.FloatPanel && ( ! window.tinymce.ui.FloatPanel.zIndex || window.tinymce.ui.FloatPanel.zIndex < 500001 ) ) {
+ window.tinymce.ui.FloatPanel.zIndex = 500001;
+ }
+ } );
+
+ body.addClass( 'ready' );
+ api.trigger( 'ready' );
+ });
+
+})( wp, jQuery );
diff --git a/www/crm/wp-admin/js/customize-controls.min.js b/www/crm/wp-admin/js/customize-controls.min.js
new file mode 100644
index 00000000..66e10d32
--- /dev/null
+++ b/www/crm/wp-admin/js/customize-controls.min.js
@@ -0,0 +1,4 @@
+!function(a,b){var c,d,e,f=wp.customize;f.OverlayNotification=f.Notification.extend({loading:!1,initialize:function(a,b){var c=this;f.Notification.prototype.initialize.call(c,a,b),c.containerClasses+=" notification-overlay",c.loading&&(c.containerClasses+=" notification-loading")},render:function(){var a=f.Notification.prototype.render.call(this);return a.on("keydown",_.bind(this.handleEscape,this)),a},handleEscape:function(a){var b=this;27===a.which&&(a.stopPropagation(),b.dismissible&&b.parent&&b.parent.remove(b.code))}}),f.Notifications=f.Values.extend({alt:!1,defaultConstructor:f.Notification,initialize:function(a){var b=this;f.Values.prototype.initialize.call(b,a),_.bindAll(b,"constrainFocus"),b._addedIncrement=0,b._addedOrder={},b.bind("add",function(a){b.trigger("change",a)}),b.bind("removed",function(a){b.trigger("change",a)})},count:function(){return _.size(this._value)},add:function(a,b){var c,d,e=this;return"string"==typeof a?(c=a,d=b):(c=a.code,d=a),e.has(c)||(e._addedIncrement+=1,e._addedOrder[c]=e._addedIncrement),f.Values.prototype.add.call(e,c,d)},remove:function(a){var b=this;return delete b._addedOrder[a],f.Values.prototype.remove.call(this,a)},get:function(a){var b,c,d,e=this;return b=_.values(e._value),d=_.extend({sort:!1},a),d.sort&&(c={error:4,warning:3,success:2,info:1},b.sort(function(a,b){var d=0,f=0;return _.isUndefined(c[a.type])||(d=c[a.type]),_.isUndefined(c[b.type])||(f=c[b.type]),d!==f?f-d:e._addedOrder[b.code]-e._addedOrder[a.code]})),b},render:function(){var a,c,d,e,g=this,h=!1,i=[],j={};g.container&&g.container.length&&(a=g.get({sort:!0}),g.container.toggle(0!==a.length),g.container.is(g.previousContainer)&&_.isEqual(a,g.previousNotifications)||(d=g.container.children("ul").first(),d.length||(d=b("<ul></ul>"),g.container.append(d)),d.find("> [data-code]").remove(),_.each(g.previousNotifications,function(a){j[a.code]=a}),_.each(a,function(a){var c;!wp.a11y||j[a.code]&&_.isEqual(a.message,j[a.code].message)||wp.a11y.speak(a.message,"assertive"),c=b(a.render()),a.container=c,d.append(c),a.extended(f.OverlayNotification)&&i.push(a)}),c=Boolean(i.length),g.previousNotifications&&(h=Boolean(_.find(g.previousNotifications,function(a){return a.extended(f.OverlayNotification)}))),c!==h&&(b(document.body).toggleClass("customize-loading",c),g.container.toggleClass("has-overlay-notifications",c),c?(g.previousActiveElement=document.activeElement,b(document).on("keydown",g.constrainFocus)):b(document).off("keydown",g.constrainFocus)),c?(g.focusContainer=i[i.length-1].container,g.focusContainer.prop("tabIndex",-1),e=g.focusContainer.find(":focusable"),e.length?e.first().focus():g.focusContainer.focus()):g.previousActiveElement&&(b(g.previousActiveElement).focus(),g.previousActiveElement=null),g.previousNotifications=a,g.previousContainer=g.container,g.trigger("rendered")))},constrainFocus:function(a){var c,d=this;a.stopPropagation(),9===a.which&&(c=d.focusContainer.find(":focusable"),0===c.length&&(c=d.focusContainer),b.contains(d.focusContainer[0],a.target)&&b.contains(d.focusContainer[0],document.activeElement)?c.last().is(a.target)&&!a.shiftKey?(a.preventDefault(),c.first().focus()):c.first().is(a.target)&&a.shiftKey&&(a.preventDefault(),c.last().focus()):(a.preventDefault(),c.first().focus()))}}),f.Setting=f.Value.extend({defaults:{transport:"refresh",dirty:!1},initialize:function(a,b,c){var d,e=this;d=_.extend({previewer:f.previewer},e.defaults,c||{}),f.Value.prototype.initialize.call(e,b,d),e.id=a,e._dirty=d.dirty,e.notifications=new f.Notifications,e.bind(e.preview)},preview:function(){var a,b=this;a=b.transport,"postMessage"!==a||f.state("previewerAlive").get()||(a="refresh"),"postMessage"===a?b.previewer.send("setting",[b.id,b()]):"refresh"===a&&b.previewer.refresh()},findControls:function(){var a=this,b=[];return f.control.each(function(c){_.each(c.settings,function(d){d.id===a.id&&b.push(c)})}),b}}),f._latestRevision=0,f._lastSavedRevision=0,f._latestSettingRevisions={},f.bind("change",function(a){f._latestRevision+=1,f._latestSettingRevisions[a.id]=f._latestRevision}),f.bind("ready",function(){f.bind("add",function(a){a._dirty&&(f._latestRevision+=1,f._latestSettingRevisions[a.id]=f._latestRevision)})}),f.dirtyValues=function(a){var b={};return f.each(function(c){var d;c._dirty&&(d=f._latestSettingRevisions[c.id],f.state("changesetStatus").get()&&a&&a.unsaved&&(_.isUndefined(d)||d<=f._lastSavedRevision)||(b[c.id]=c.get()))}),b},f.requestChangesetUpdate=function(a,c){var d,e,g,h,i={};return d=new b.Deferred,0!==f.state("processing").get()?(d.reject("already_processing"),d.promise()):(h=_.extend({title:null,date:null,autosave:!1,force:!1},c),a&&_.extend(i,a),_.each(f.dirtyValues({unsaved:!0}),function(b,c){a&&null===a[c]||(i[c]=_.extend({},i[c]||{},{value:b}))}),f.trigger("changeset-save",i,h),!h.force&&_.isEmpty(i)&&null===h.title&&null===h.date?(d.resolve({}),d.promise()):h.status?d.reject({code:"illegal_status_in_changeset_update"}).promise():h.date&&h.autosave?d.reject({code:"illegal_autosave_with_date_gmt"}).promise():(f.state("processing").set(f.state("processing").get()+1),d.always(function(){f.state("processing").set(f.state("processing").get()-1)}),g=f.previewer.query({excludeCustomizedSaved:!0}),delete g.customized,_.extend(g,{nonce:f.settings.nonce.save,customize_theme:f.settings.theme.stylesheet,customize_changeset_data:JSON.stringify(i)}),null!==h.title&&(g.customize_changeset_title=h.title),null!==h.date&&(g.customize_changeset_date=h.date),!1!==h.autosave&&(g.customize_changeset_autosave="true"),f.trigger("save-request-params",g),e=wp.ajax.post("customize_save",g),e.done(function(a){var b={};f._lastSavedRevision=Math.max(f._latestRevision,f._lastSavedRevision),f.state("changesetStatus").set(a.changeset_status),a.changeset_date&&f.state("changesetDate").set(a.changeset_date),d.resolve(a),f.trigger("changeset-saved",a),a.setting_validities&&_.each(a.setting_validities,function(a,c){!0===a&&_.isObject(i[c])&&!_.isUndefined(i[c].value)&&(b[c]=i[c].value)}),f.previewer.send("changeset-saved",_.extend({},a,{saved_changeset_values:b}))}),e.fail(function(a){d.reject(a),f.trigger("changeset-error",a)}),e.always(function(a){a.setting_validities&&f._handleSettingValidities({settingValidities:a.setting_validities})}),d.promise()))},f.utils.bubbleChildValueChanges=function(a,c){b.each(c,function(b,c){a[c].bind(function(b,c){a.parent&&b!==c&&a.parent.trigger("change",a)})})},d=function(a){var b,c,d,e;b=this,a=a||{},d=function(){var a;a=(b.extended(f.Panel)||b.extended(f.Section))&&b.expanded&&b.expanded()?b.contentContainer:b.container,e=a.find(".control-focus:first"),0===e.length&&(e=a.find("input, select, textarea, button, object, a[href], [tabindex]").filter(":visible").first()),e.focus()},a.completeCallback?(c=a.completeCallback,a.completeCallback=function(){d(),c()}):a.completeCallback=d,f.state("paneVisible").set(!0),b.expand?b.expand(a):a.completeCallback()},f.utils.prioritySort=function(a,b){return a.priority()===b.priority()&&"number"==typeof a.params.instanceNumber&&"number"==typeof b.params.instanceNumber?a.params.instanceNumber-b.params.instanceNumber:a.priority()-b.priority()},f.utils.isKeydownButNotEnterEvent=function(a){return"keydown"===a.type&&13!==a.which},f.utils.areElementListsEqual=function(a,c){var d=a.length===c.length&&-1===_.indexOf(_.map(_.zip(a,c),function(a){return b(a[0]).is(a[1])}),!1);return d},f.utils.highlightButton=function(a,b){function c(){f=!0}var d,e="button-see-me",f=!1;return d=_.extend({delay:0,focusTarget:a},b),d.focusTarget.on("focusin",c),setTimeout(function(){d.focusTarget.off("focusin",c),f||(a.addClass(e),a.one("animationend",function(){a.removeClass(e)}))},d.delay),c},f.utils.getCurrentTimestamp=function(){var a,b,c;return b=_.now(),a=new Date(f.settings.initialServerDate.replace(/-/g,"/")),c=b-f.settings.initialClientTimestamp,c+=f.settings.initialClientTimestamp-f.settings.initialServerTimestamp,a.setTime(a.getTime()+c),a.getTime()},f.utils.getRemainingTime=function(a){var b,c,d=1e3;return c=a instanceof Date?a.getTime():"string"==typeof a?new Date(a.replace(/-/g,"/")).getTime():a,b=c-f.utils.getCurrentTimestamp(),b=Math.ceil(b/d)},e=function(){var a,b,c;return a=document.createElement("div"),b={transition:"transitionend",OTransition:"oTransitionEnd",MozTransition:"transitionend",WebkitTransition:"webkitTransitionEnd"},c=_.find(_.keys(b),function(b){return!_.isUndefined(a.style[b])}),c?b[c]:null}(),c=f.Class.extend({defaultActiveArguments:{duration:"fast",completeCallback:b.noop},defaultExpandedArguments:{duration:"fast",completeCallback:b.noop},containerType:"container",defaults:{title:"",description:"",priority:100,type:"default",content:null,active:!0,instanceNumber:null},initialize:function(a,d){var e=this;e.id=a,c.instanceCounter||(c.instanceCounter=0),c.instanceCounter++,b.extend(e,{params:_.defaults(d.params||d,e.defaults)}),e.params.instanceNumber||(e.params.instanceNumber=c.instanceCounter),e.notifications=new f.Notifications,e.templateSelector=e.params.templateId||"customize-"+e.containerType+"-"+e.params.type,e.container=b(e.params.content),0===e.container.length&&(e.container=b(e.getContainer())),e.headContainer=e.container,e.contentContainer=e.getContent(),e.container=e.container.add(e.contentContainer),e.deferred={embedded:new b.Deferred},e.priority=new f.Value,e.active=new f.Value,e.activeArgumentsQueue=[],e.expanded=new f.Value,e.expandedArgumentsQueue=[],e.active.bind(function(a){var c=e.activeArgumentsQueue.shift();c=b.extend({},e.defaultActiveArguments,c),a=a&&e.isContextuallyActive(),e.onChangeActive(a,c)}),e.expanded.bind(function(a){var c=e.expandedArgumentsQueue.shift();c=b.extend({},e.defaultExpandedArguments,c),e.onChangeExpanded(a,c)}),e.deferred.embedded.done(function(){e.setupNotifications(),e.attachEvents()}),f.utils.bubbleChildValueChanges(e,["priority","active"]),e.priority.set(e.params.priority),e.active.set(e.params.active),e.expanded.set(!1)},getNotificationsContainerElement:function(){var a=this;return a.contentContainer.find(".customize-control-notifications-container:first")},setupNotifications:function(){var a,b=this;b.notifications.container=b.getNotificationsContainerElement(),a=function(){b.expanded.get()&&b.notifications.render()},b.expanded.bind(a),a(),b.notifications.bind("change",_.debounce(a))},ready:function(){},_children:function(a,b){var c=this,d=[];return f[b].each(function(b){b[a].get()===c.id&&d.push(b)}),d.sort(f.utils.prioritySort),d},isContextuallyActive:function(){throw new Error("Container.isContextuallyActive() must be overridden in a subclass.")},onChangeActive:function(a,c){var d,e,g=this,h=g.headContainer;return c.unchanged?void(c.completeCallback&&c.completeCallback()):(d="resolved"===f.previewer.deferred.active.state()?c.duration:0,g.extended(f.Panel)&&(f.panel.each(function(a){a!==g&&a.expanded()&&(e=a,d=0)}),a||_.each(g.sections(),function(a){a.collapse({duration:0})})),void(b.contains(document,h.get(0))?a?h.slideDown(d,c.completeCallback):g.expanded()?g.collapse({duration:d,completeCallback:function(){h.slideUp(d,c.completeCallback)}}):h.slideUp(d,c.completeCallback):(h.toggle(a),c.completeCallback&&c.completeCallback())))},_toggleActive:function(a,b){var c=this;return b=b||{},a&&this.active.get()||!a&&!this.active.get()?(b.unchanged=!0,c.onChangeActive(c.active.get(),b),!1):(b.unchanged=!1,this.activeArgumentsQueue.push(b),this.active.set(a),!0)},activate:function(a){return this._toggleActive(!0,a)},deactivate:function(a){return this._toggleActive(!1,a)},onChangeExpanded:function(){throw new Error("Must override with subclass.")},_toggleExpanded:function(a,b){var c,d=this;return b=b||{},c=b.completeCallback,!(a&&!d.active())&&(f.state("paneVisible").set(!0),b.completeCallback=function(){c&&c.apply(d,arguments),a?d.container.trigger("expanded"):d.container.trigger("collapsed")},a&&d.expanded.get()||!a&&!d.expanded.get()?(b.unchanged=!0,d.onChangeExpanded(d.expanded.get(),b),!1):(b.unchanged=!1,d.expandedArgumentsQueue.push(b),d.expanded.set(a),!0))},expand:function(a){return this._toggleExpanded(!0,a)},collapse:function(a){return this._toggleExpanded(!1,a)},_animateChangeExpanded:function(a){if(!e)return void(a&&a());var c,d,g,h=this,i=h.contentContainer,j=i.closest(".wp-full-overlay");c=j.add(i),g=!h.panel||""===h.panel()||!!f.panel(h.panel()).contentContainer.hasClass("skip-transition"),g&&(c=c.add("#customize-info, .customize-pane-parent")),d=function(f){2===f.eventPhase&&b(f.target).is(i)&&(i.off(e,d),c.removeClass("busy"),a&&a())},i.on(e,d),c.addClass("busy"),_.defer(function(){var a=i.closest(".wp-full-overlay-sidebar-content"),b=a.scrollTop(),c=i.data("previous-scrollTop")||0,d=h.expanded();d&&0<b?(i.css("top",b+"px"),i.data("previous-scrollTop",b)):!d&&0<b+c&&(i.css("top",c-b+"px"),a.scrollTop(c))})},focus:d,getContainer:function(){var a,c=this;return a=0!==b("#tmpl-"+c.templateSelector).length?wp.template(c.templateSelector):wp.template("customize-"+c.containerType+"-default"),a&&c.container?b.trim(a(_.extend({id:c.id},c.params))):"<li></li>"},getContent:function(){var a=this,b=a.container,c=b.find(".accordion-section-content, .control-panel-content").first(),d="sub-"+b.attr("id"),e=d,f=b.attr("aria-owns");return f&&(e=e+" "+f),b.attr("aria-owns",e),c.detach().attr({id:d,"class":"customize-pane-child "+c.attr("class")+" "+b.attr("class")})}}),f.Section=c.extend({containerType:"section",containerParent:"#customize-theme-controls",containerPaneParent:".customize-pane-parent",defaults:{title:"",description:"",priority:100,type:"default",content:null,active:!0,instanceNumber:null,panel:null,customizeAction:""},initialize:function(a,d){var e,g=this;e=d.params||d,e.type||_.find(f.sectionConstructor,function(a,b){return a===g.constructor&&(e.type=b,!0)}),c.prototype.initialize.call(g,a,e),g.id=a,g.panel=new f.Value,g.panel.bind(function(a){b(g.headContainer).toggleClass("control-subsection",!!a)}),g.panel.set(g.params.panel||""),f.utils.bubbleChildValueChanges(g,["panel"]),g.embed(),g.deferred.embedded.done(function(){g.ready()})},embed:function(){var a,b=this;b.containerParent=f.ensure(b.containerParent),a=function(a){var c;a?f.panel(a,function(a){a.deferred.embedded.done(function(){c=a.contentContainer,b.headContainer.parent().is(c)||c.append(b.headContainer),b.contentContainer.parent().is(b.headContainer)||b.containerParent.append(b.contentContainer),b.deferred.embedded.resolve()})}):(c=f.ensure(b.containerPaneParent),b.headContainer.parent().is(c)||c.append(b.headContainer),b.contentContainer.parent().is(b.headContainer)||b.containerParent.append(b.contentContainer),b.deferred.embedded.resolve())},b.panel.bind(a),a(b.panel.get())},attachEvents:function(){var a,c,d=this;d.container.hasClass("cannot-expand")||(d.container.find(".accordion-section-title, .customize-section-back").on("click keydown",function(a){f.utils.isKeydownButNotEnterEvent(a)||(a.preventDefault(),d.expanded()?d.collapse():d.expand())}),d.container.find(".customize-section-title .customize-help-toggle").on("click",function(){a=d.container.find(".section-meta"),a.hasClass("cannot-expand")||(c=a.find(".customize-section-description:first"),c.toggleClass("open"),c.slideToggle(d.defaultExpandedArguments.duration,function(){c.trigger("toggled")}),b(this).attr("aria-expanded",function(a,b){return"true"===b?"false":"true"}))}))},isContextuallyActive:function(){var a=this,b=a.controls(),c=0;return _(b).each(function(a){a.active()&&(c+=1)}),0!==c},controls:function(){return this._children("section","control")},onChangeExpanded:function(a,c){var d,e,g=this,h=g.headContainer.closest(".wp-full-overlay-sidebar-content"),i=g.contentContainer,j=g.headContainer.closest(".wp-full-overlay"),k=i.find(".customize-section-back"),l=g.headContainer.find(".accordion-section-title").first();a&&!i.hasClass("open")?(d=c.unchanged?c.completeCallback:b.proxy(function(){g._animateChangeExpanded(function(){l.attr("tabindex","-1"),k.attr("tabindex","0"),k.focus(),i.css("top",""),h.scrollTop(0),c.completeCallback&&c.completeCallback()}),i.addClass("open"),j.addClass("section-open"),f.state("expandedSection").set(g)},this),c.allowMultiple||f.section.each(function(a){a!==g&&a.collapse({duration:c.duration})}),g.panel()?f.panel(g.panel()).expand({duration:c.duration,completeCallback:d}):(c.allowMultiple||f.panel.each(function(a){a.collapse()}),d())):!a&&i.hasClass("open")?(g.panel()&&(e=f.panel(g.panel()),e.contentContainer.hasClass("skip-transition")&&e.collapse()),g._animateChangeExpanded(function(){k.attr("tabindex","-1"),l.attr("tabindex","0"),l.focus(),i.css("top",""),c.completeCallback&&c.completeCallback()}),i.removeClass("open"),j.removeClass("section-open"),g===f.state("expandedSection").get()&&f.state("expandedSection").set(!1)):c.completeCallback&&c.completeCallback()}}),f.ThemesSection=f.Section.extend({currentTheme:"",overlay:"",template:"",screenshotQueue:null,$window:null,$body:null,loaded:0,loading:!1,fullyLoaded:!1,term:"",tags:"",nextTerm:"",nextTags:"",filtersHeight:0,headerContainer:null,updateCountDebounced:null,initialize:function(a,c){var d=this;d.headerContainer=b(),d.$window=b(window),d.$body=b(document.body),f.Section.prototype.initialize.call(d,a,c),d.updateCountDebounced=_.debounce(d.updateCount,500)},embed:function(){var a,b=this;a=function(a){var c;f.panel(a,function(a){a.deferred.embedded.done(function(){c=a.contentContainer,b.headContainer.parent().is(c)||c.find(".customize-themes-full-container-container").before(b.headContainer),b.contentContainer.parent().is(b.headContainer)||b.containerParent.append(b.contentContainer),b.deferred.embedded.resolve()})})},b.panel.bind(a),a(b.panel.get())},ready:function(){var a=this;a.overlay=a.container.find(".theme-overlay"),a.template=wp.template("customize-themes-details-view"),a.container.on("keydown",function(b){a.overlay.find(".theme-wrap").is(":visible")&&(39===b.keyCode&&a.nextTheme(),37===b.keyCode&&a.previousTheme(),27===b.keyCode&&(a.$body.hasClass("modal-open")?a.closeDetails():a.headerContainer.find(".customize-themes-section-title").focus(),b.stopPropagation()))}),a.renderScreenshots=_.throttle(a.renderScreenshots,100),_.bindAll(a,"renderScreenshots","loadMore","checkTerm","filtersChecked")},isContextuallyActive:function(){return this.active()},attachEvents:function(){function a(){var a=d.headerContainer.find(".customize-themes-section-title");a.toggleClass("selected",d.expanded()),a.attr("aria-expanded",d.expanded()?"true":"false"),d.expanded()||a.removeClass("details-open")}var c,d=this;d.container.find(".customize-section-back").on("click keydown",function(a){f.utils.isKeydownButNotEnterEvent(a)||(a.preventDefault(),d.collapse())}),d.headerContainer=b("#accordion-section-"+d.id),d.headerContainer.on("click",".customize-themes-section-title",function(){d.headerContainer.find(".filter-details").length&&(d.headerContainer.find(".customize-themes-section-title").toggleClass("details-open").attr("aria-expanded",function(a,b){return"true"===b?"false":"true"}),d.headerContainer.find(".filter-details").slideToggle(180)),d.expanded()||d.expand()}),d.container.on("click",".theme-actions .preview-theme",function(){f.panel("themes").loadThemePreview(b(this).data("slug"))}),d.container.on("click",".left",function(){d.previousTheme()}),d.container.on("click",".right",function(){d.nextTheme()}),d.container.on("click",".theme-backdrop, .close",function(){d.closeDetails()}),"local"===d.params.filter_type?d.container.on("input",".wp-filter-search-themes",function(a){d.filterSearch(a.currentTarget.value)}):"remote"===d.params.filter_type&&(c=_.debounce(d.checkTerm,500),d.contentContainer.on("input",".wp-filter-search",function(){f.panel("themes").expanded()&&(c(d),d.expanded()||d.expand())}),d.contentContainer.on("click",".filter-group input",function(){d.filtersChecked(),d.checkTerm(d)})),d.contentContainer.on("click",".feature-filter-toggle",function(a){var c=b(".customize-themes-full-container"),e=b(a.currentTarget);if(d.filtersHeight=e.parent().next(".filter-drawer").height(),!(0<c.scrollTop()&&(c.animate({scrollTop:0},400),e.hasClass("open"))))if(e.toggleClass("open").attr("aria-expanded",function(a,b){return"true"===b?"false":"true"}).parent().next(".filter-drawer").slideToggle(180,"linear"),e.hasClass("open")){var f=1018<window.innerWidth?50:76;d.contentContainer.find(".themes").css("margin-top",d.filtersHeight+f)}else d.contentContainer.find(".themes").css("margin-top",0)}),d.contentContainer.on("click",".no-themes-local .search-dotorg-themes",function(){f.section("wporg_themes").focus()}),d.expanded.bind(a),a(),f.bind("ready",function(){d.contentContainer=d.container.find(".customize-themes-section"),d.contentContainer.appendTo(b(".customize-themes-full-container")),d.container.add(d.headerContainer)})},onChangeExpanded:function(a,b){function c(){0===d.loaded&&d.loadThemes(),f.section.each(function(a){var c;a!==d&&"themes"===a.params.type&&(c=a.contentContainer.find(".wp-filter-search").val(),d.contentContainer.find(".wp-filter-search").val(c),""===c&&""!==d.term&&"local"!==d.params.filter_type?(d.term="",d.initializeNewQuery(d.term,d.tags)):"remote"===d.params.filter_type?d.checkTerm(d):"local"===d.params.filter_type&&d.filterSearch(c),a.collapse({duration:b.duration}))}),d.contentContainer.addClass("current-section"),e.scrollTop(),e.on("scroll",_.throttle(d.renderScreenshots,300)),e.on("scroll",_.throttle(d.loadMore,300)),b.completeCallback&&b.completeCallback(),d.updateCount()}var d=this,e=d.contentContainer.closest(".customize-themes-full-container");return b.unchanged?void(b.completeCallback&&b.completeCallback()):void(a?d.panel()&&f.panel.has(d.panel())?f.panel(d.panel()).expand({duration:b.duration,completeCallback:c}):c():(d.contentContainer.removeClass("current-section"),d.headerContainer.find(".filter-details").slideUp(180),e.off("scroll"),b.completeCallback&&b.completeCallback()))},getContent:function(){return this.container.find(".control-section-content")},loadThemes:function(){var a,b,c,d=this;d.loading||(b=Math.ceil(d.loaded/100)+1,a={nonce:f.settings.nonce.switch_themes,wp_customize:"on",theme_action:d.params.action,customized_theme:f.settings.theme.stylesheet,page:b},"remote"===d.params.filter_type&&(a.search=d.term,a.tags=d.tags),d.headContainer.closest(".wp-full-overlay").addClass("loading"),d.loading=!0,d.container.find(".no-themes").hide(),c=wp.ajax.post("customize_load_themes",a),c.done(function(a){var c=a.themes;return""!==d.nextTerm||""!==d.nextTags?(d.nextTerm&&(d.term=d.nextTerm),d.nextTags&&(d.tags=d.nextTags),d.nextTerm="",d.nextTags="",d.loading=!1,void d.loadThemes()):(0!==c.length?(d.loadControls(c,b),1===b&&(_.each(d.controls().slice(0,3),function(a){var b,c=a.params.theme.screenshot[0];c&&(b=new Image,b.src=c)}),"local"!==d.params.filter_type&&wp.a11y.speak(f.settings.l10n.themeSearchResults.replace("%d",a.info.results))),_.delay(d.renderScreenshots,100),("local"===d.params.filter_type||100>c.length)&&(d.fullyLoaded=!0)):0===d.loaded?(d.container.find(".no-themes").show(),wp.a11y.speak(d.container.find(".no-themes").text())):d.fullyLoaded=!0,"local"===d.params.filter_type?d.updateCount():d.updateCount(a.info.results),d.container.find(".unexpected-error").hide(),d.headContainer.closest(".wp-full-overlay").removeClass("loading"),void(d.loading=!1))}),c.fail(function(a){"undefined"==typeof a?(d.container.find(".unexpected-error").show(),wp.a11y.speak(d.container.find(".unexpected-error").text())):"undefined"!=typeof console&&console.error&&console.error(a),d.headContainer.closest(".wp-full-overlay").removeClass("loading"),d.loading=!1}))},loadControls:function(a,b){var c=[],d=this;_.each(a,function(a){var b=new f.controlConstructor.theme(d.params.action+"_theme_"+a.id,{type:"theme",section:d.params.id,theme:a,priority:d.loaded+1});f.control.add(b),c.push(b),d.loaded=d.loaded+1}),1!==b&&Array.prototype.push.apply(d.screenshotQueue,c)},loadMore:function(){var a,b,c,d=this;d.fullyLoaded||d.loading||(a=d.container.closest(".customize-themes-full-container"),b=a.scrollTop()+a.height(),c=a.prop("scrollHeight")-3e3,b>c&&d.loadThemes())},filterSearch:function(a){var b,c=0,d=!1,e=this,g=f.section.has("wporg_themes")&&"remote"!==e.params.filter_type?".no-themes-local":".no-themes",h=e.controls();e.loading||(b=a.toLowerCase().trim().replace(/-/g," ").split(" "),_.each(h,function(a){d=a.filter(b),d&&(c+=1)}),0===c?(e.container.find(g).show(),wp.a11y.speak(e.container.find(g).text())):e.container.find(g).hide(),e.renderScreenshots(),f.reflowPaneContents(),e.updateCountDebounced(c))},checkTerm:function(a){var b;"remote"===a.params.filter_type&&(b=a.contentContainer.find(".wp-filter-search").val(),a.term!==b.trim()&&a.initializeNewQuery(b,a.tags))},filtersChecked:function(){var a=this,c=a.container.find(".filter-group").find(":checkbox"),d=[];_.each(c.filter(":checked"),function(a){d.push(b(a).prop("value"))}),0===d.length?(d="",a.contentContainer.find(".feature-filter-toggle .filter-count-0").show(),a.contentContainer.find(".feature-filter-toggle .filter-count-filters").hide()):(a.contentContainer.find(".feature-filter-toggle .theme-filter-count").text(d.length),a.contentContainer.find(".feature-filter-toggle .filter-count-0").hide(),a.contentContainer.find(".feature-filter-toggle .filter-count-filters").show()),_.isEqual(a.tags,d)||(a.loading?a.nextTags=d:"remote"===a.params.filter_type?a.initializeNewQuery(a.term,d):"local"===a.params.filter_type&&a.filterSearch(d.join(" ")))},initializeNewQuery:function(a,b){var c=this;_.each(c.controls(),function(a){a.container.remove(),f.control.remove(a.id)}),c.loaded=0,c.fullyLoaded=!1,c.screenshotQueue=null,c.loading?(c.nextTerm=a,c.nextTags=b):(c.term=a,c.tags=b,c.loadThemes()),c.expanded()||c.expand()},renderScreenshots:function(){var a=this;null!==a.screenshotQueue&&0!==a.screenshotQueue.length||(a.screenshotQueue=_.filter(a.controls(),function(a){return!a.screenshotRendered})),a.screenshotQueue.length&&(a.screenshotQueue=_.filter(a.screenshotQueue,function(b){var c=b.container.find(".theme-screenshot"),d=c.find("img");if(!d.length)return!1;if(d.is(":hidden"))return!0;var e=a.$window.scrollTop(),f=e+a.$window.height(),g=d.offset().top,h=c.height(),i=g+h,j=3*h,k=i>=e-j&&g<=f+j;return k&&b.container.trigger("render-screenshot"),!k}))},getVisibleCount:function(){return this.contentContainer.find("li.customize-control:visible").length},updateCount:function(a){var b,c,d=this;a||0===a||(a=d.getVisibleCount()),c=d.contentContainer.find(".themes-displayed"),b=d.contentContainer.find(".theme-count"),0===a?b.text("0"):(c.fadeOut(180,function(){b.text(a),c.fadeIn(180)}),wp.a11y.speak(f.settings.l10n.announceThemeCount.replace("%d",a)))},nextTheme:function(){var a=this;a.getNextTheme()&&a.showDetails(a.getNextTheme(),function(){a.overlay.find(".right").focus()})},getNextTheme:function(){var a,b,c,d,e=this;return a=f.control(e.params.action+"_theme_"+e.currentTheme),c=e.controls(),d=_.indexOf(c,a),-1!==d&&(b=c[d+1],!!b&&b.params.theme)},previousTheme:function(){var a=this;a.getPreviousTheme()&&a.showDetails(a.getPreviousTheme(),function(){a.overlay.find(".left").focus()})},getPreviousTheme:function(){var a,b,c,d,e=this;return a=f.control(e.params.action+"_theme_"+e.currentTheme),c=e.controls(),d=_.indexOf(c,a),-1!==d&&(b=c[d-1],!!b&&b.params.theme)},updateLimits:function(){this.getNextTheme()||this.overlay.find(".right").addClass("disabled"),this.getPreviousTheme()||this.overlay.find(".left").addClass("disabled")},loadThemePreview:function(a){return f.ThemesPanel.prototype.loadThemePreview.call(this,a)},showDetails:function(a,b){function c(){return!g.canSwitchTheme(a.id)}function d(){return c()||!1===f.settings.theme._canInstall||!0===f.settings.theme._filesystemCredentialsNeeded}var e=this,g=f.panel("themes");e.currentTheme=a.id,e.overlay.html(e.template(a)).fadeIn("fast").focus(),e.overlay.find("button.preview, button.preview-theme").toggleClass("disabled",c()),e.overlay.find("button.theme-install").toggleClass("disabled",d()),e.$body.addClass("modal-open"),e.containFocus(e.overlay),e.updateLimits(),wp.a11y.speak(f.settings.l10n.announceThemeDetails.replace("%s",a.name)),b&&b()},closeDetails:function(){var a=this;a.$body.removeClass("modal-open"),a.overlay.fadeOut("fast"),f.control(a.params.action+"_theme_"+a.currentTheme).container.find(".theme").focus()},containFocus:function(a){var c;a.on("keydown",function(d){if(9===d.keyCode)return c=b(":tabbable",a),c.last()[0]!==d.target||d.shiftKey?c.first()[0]===d.target&&d.shiftKey?(c.last().focus(),!1):void 0:(c.first().focus(),!1)})}}),f.OuterSection=f.Section.extend({initialize:function(){var a=this;a.containerParent="#customize-outer-theme-controls",a.containerPaneParent=".customize-outer-pane-parent",f.Section.prototype.initialize.apply(a,arguments)},onChangeExpanded:function(a,c){var d,e,g=this,h=g.headContainer.closest(".wp-full-overlay-sidebar-content"),i=g.contentContainer,j=i.find(".customize-section-back"),k=g.headContainer.find(".accordion-section-title").first(),l=b(document.body);l.toggleClass("outer-section-open",a),g.container.toggleClass("open",a),g.container.removeClass("busy"),f.section.each(function(a){"outer"===a.params.type&&a.id!==g.id&&a.container.removeClass("open")}),a&&!i.hasClass("open")?(d=c.unchanged?c.completeCallback:b.proxy(function(){g._animateChangeExpanded(function(){k.attr("tabindex","-1"),j.attr("tabindex","0"),j.focus(),i.css("top",""),h.scrollTop(0),c.completeCallback&&c.completeCallback()}),i.addClass("open")},this),g.panel()?f.panel(g.panel()).expand({duration:c.duration,completeCallback:d}):d()):!a&&i.hasClass("open")?(g.panel()&&(e=f.panel(g.panel()),e.contentContainer.hasClass("skip-transition")&&e.collapse()),g._animateChangeExpanded(function(){j.attr("tabindex","-1"),k.attr("tabindex","0"),k.focus(),i.css("top",""),c.completeCallback&&c.completeCallback()}),i.removeClass("open")):c.completeCallback&&c.completeCallback()}}),f.Panel=c.extend({containerType:"panel",initialize:function(a,b){var d,e=this;d=b.params||b,d.type||_.find(f.panelConstructor,function(a,b){return a===e.constructor&&(d.type=b,!0)}),c.prototype.initialize.call(e,a,d),e.embed(),e.deferred.embedded.done(function(){e.ready()})},embed:function(){var a=this,c=b("#customize-theme-controls"),d=b(".customize-pane-parent");a.headContainer.parent().is(d)||d.append(a.headContainer),a.contentContainer.parent().is(a.headContainer)||c.append(a.contentContainer),a.renderContent(),a.deferred.embedded.resolve()},attachEvents:function(){var a,c=this;c.headContainer.find(".accordion-section-title").on("click keydown",function(a){f.utils.isKeydownButNotEnterEvent(a)||(a.preventDefault(),c.expanded()||c.expand())}),c.container.find(".customize-panel-back").on("click keydown",function(a){f.utils.isKeydownButNotEnterEvent(a)||(a.preventDefault(),c.expanded()&&c.collapse())}),a=c.container.find(".panel-meta:first"),a.find("> .accordion-section-title .customize-help-toggle").on("click",function(){if(!a.hasClass("cannot-expand")){var d=a.find(".customize-panel-description:first");a.hasClass("open")?(a.toggleClass("open"),d.slideUp(c.defaultExpandedArguments.duration,function(){d.trigger("toggled")}),b(this).attr("aria-expanded",!1)):(d.slideDown(c.defaultExpandedArguments.duration,function(){d.trigger("toggled")}),a.toggleClass("open"),b(this).attr("aria-expanded",!0))}})},sections:function(){return this._children("panel","section")},isContextuallyActive:function(){var a=this,b=a.sections(),c=0;return _(b).each(function(a){a.active()&&a.isContextuallyActive()&&(c+=1)}),0!==c},onChangeExpanded:function(a,b){if(b.unchanged)return void(b.completeCallback&&b.completeCallback());var c,d=this,e=d.contentContainer,g=e.closest(".wp-full-overlay"),h=e.closest(".wp-full-overlay-sidebar-content"),i=d.headContainer.find(".accordion-section-title"),j=e.find(".customize-panel-back"),k=d.sections();a&&!e.hasClass("current-panel")?(f.section.each(function(a){d.id!==a.panel()&&a.collapse({duration:0})}),f.panel.each(function(a){d!==a&&a.collapse({duration:0});
+}),d.params.autoExpandSoleSection&&1===k.length&&k[0].active.get()?(e.addClass("current-panel skip-transition"),g.addClass("in-sub-panel"),k[0].expand({completeCallback:b.completeCallback})):(d._animateChangeExpanded(function(){i.attr("tabindex","-1"),j.attr("tabindex","0"),j.focus(),e.css("top",""),h.scrollTop(0),b.completeCallback&&b.completeCallback()}),e.addClass("current-panel"),g.addClass("in-sub-panel")),f.state("expandedPanel").set(d)):!a&&e.hasClass("current-panel")&&(c=e.hasClass("skip-transition"),c?e.removeClass("skip-transition"):d._animateChangeExpanded(function(){i.attr("tabindex","0"),j.attr("tabindex","-1"),i.focus(),e.css("top",""),b.completeCallback&&b.completeCallback()}),g.removeClass("in-sub-panel"),e.removeClass("current-panel"),d===f.state("expandedPanel").get()&&f.state("expandedPanel").set(!1))},renderContent:function(){var a,c=this;a=0!==b("#tmpl-"+c.templateSelector+"-content").length?wp.template(c.templateSelector+"-content"):wp.template("customize-panel-default-content"),a&&c.headContainer&&c.contentContainer.html(a(_.extend({id:c.id},c.params)))}}),f.ThemesPanel=f.Panel.extend({initialize:function(a,b){var c=this;c.installingThemes=[],f.Panel.prototype.initialize.call(c,a,b)},canSwitchTheme:function(a){return!(!a||a!==f.settings.theme.stylesheet)||"publish"===f.state("selectedChangesetStatus").get()&&(""===f.state("changesetStatus").get()||"auto-draft"===f.state("changesetStatus").get())},attachEvents:function(){function a(){c.canSwitchTheme()?c.notifications.remove("theme_switch_unavailable"):c.notifications.add(new f.Notification("theme_switch_unavailable",{message:f.l10n.themePreviewUnavailable,type:"warning"}))}var c=this;f.Panel.prototype.attachEvents.apply(c),f.settings.theme._canInstall&&f.settings.theme._filesystemCredentialsNeeded&&c.notifications.add(new f.Notification("theme_install_unavailable",{message:f.l10n.themeInstallUnavailable,type:"info",dismissible:!0})),a(),f.state("selectedChangesetStatus").bind(a),f.state("changesetStatus").bind(a),c.contentContainer.on("click",".customize-theme",function(){c.collapse()}),c.contentContainer.on("click",".customize-themes-section-title, .customize-themes-mobile-back",function(){b(".wp-full-overlay").toggleClass("showing-themes")}),c.contentContainer.on("click",".theme-install",function(a){c.installTheme(a)}),c.contentContainer.on("click",".update-theme, #update-theme",function(a){a.preventDefault(),a.stopPropagation(),c.updateTheme(a)}),c.contentContainer.on("click",".delete-theme",function(a){c.deleteTheme(a)}),_.bindAll(c,"installTheme","updateTheme")},onChangeExpanded:function(a,b){var c,d,e=this,g=!1;return f.Panel.prototype.onChangeExpanded.apply(this,[a,b]),b.unchanged?void(b.completeCallback&&b.completeCallback()):(c=e.headContainer.closest(".wp-full-overlay"),void(a?(c.addClass("in-themes-panel").delay(200).find(".customize-themes-full-container").addClass("animate"),_.delay(function(){c.addClass("themes-panel-expanded")},200),600<window.innerWidth&&(d=e.sections(),_.each(d,function(a){a.expanded()&&(g=!0)}),!g&&d.length>0&&d[0].expand())):c.removeClass("in-themes-panel themes-panel-expanded").find(".customize-themes-full-container").removeClass("animate")))},installTheme:function(a){var c,d,e,g=this,h=b(a.target).data("slug"),i=b.Deferred();return c=b(a.target).hasClass("preview"),f.settings.theme._filesystemCredentialsNeeded?(i.reject({errorCode:"theme_install_unavailable"}),i.promise()):g.canSwitchTheme(h)?_.contains(g.installingThemes,h)?(i.reject({errorCode:"theme_already_installing"}),i.promise()):(wp.updates.maybeRequestFilesystemCredentials(a),d=function(a){var b,d=!1;if(c)f.notifications.remove("theme_installing"),g.loadThemePreview(h);else{if(f.control.each(function(b){"theme"===b.params.type&&b.params.theme.id===a.slug&&(d=b.params.theme,b.rerenderAsInstalled(!0))}),!d||f.control.has("installed_theme_"+d.id))return void i.resolve(a);d.type="installed",b=new f.controlConstructor.theme("installed_theme_"+d.id,{type:"theme",section:"installed_themes",theme:d,priority:0}),f.control.add(b),f.control(b.id).container.trigger("render-screenshot"),f.section.each(function(a){"themes"===a.params.type&&d.id===a.currentTheme&&a.closeDetails()})}i.resolve(a)},g.installingThemes.push(h),e=wp.updates.installTheme({slug:h}),c&&f.notifications.add(new f.OverlayNotification("theme_installing",{message:f.l10n.themeDownloading,type:"info",loading:!0})),e.done(d),e.fail(function(){f.notifications.remove("theme_installing")}),i.promise()):(i.reject({errorCode:"theme_switch_unavailable"}),i.promise())},loadThemePreview:function(a){var c,d,e,g=this,h=b.Deferred();return g.canSwitchTheme(a)?(d=document.createElement("a"),d.href=location.href,e=_.extend(f.utils.parseQueryString(d.search.substr(1)),{theme:a,changeset_uuid:f.settings.changeset.uuid,"return":f.settings.url["return"]}),f.state("saved").get()||(e.customize_autosaved="on"),d.search=b.param(e),f.notifications.add(new f.OverlayNotification("theme_previewing",{message:f.l10n.themePreviewWait,type:"info",loading:!0})),c=function(){var a;f.state("processing").get()>0||(f.state("processing").unbind(c),a=f.requestChangesetUpdate({},{autosave:!0}),a.done(function(){h.resolve(),b(window).off("beforeunload.customize-confirm"),location.replace(d.href)}),a.fail(function(){f.notifications.remove("theme_previewing"),h.reject()}))},0===f.state("processing").get()?c():f.state("processing").bind(c),h.promise()):(h.reject({errorCode:"theme_switch_unavailable"}),h.promise())},updateTheme:function(a){wp.updates.maybeRequestFilesystemCredentials(a),b(document).one("wp-theme-update-success",function(a,b){f.control.each(function(a){"theme"===a.params.type&&a.params.theme.id===b.slug&&(a.params.theme.hasUpdate=!1,a.params.theme.version=b.newVersion,setTimeout(function(){a.rerenderAsInstalled(!0)},2e3))})}),wp.updates.updateTheme({slug:b(a.target).closest(".notice").data("slug")})},deleteTheme:function(a){var c,d;c=b(a.target).data("slug"),d=f.section("installed_themes"),a.preventDefault(),f.settings.theme._filesystemCredentialsNeeded||window.confirm(f.settings.l10n.confirmDeleteTheme)&&(wp.updates.maybeRequestFilesystemCredentials(a),b(document).one("wp-theme-delete-success",function(){var a=f.control("installed_theme_"+c);a.container.remove(),f.control.remove(a.id),d.loaded=d.loaded-1,d.updateCount(),f.control.each(function(a){"theme"===a.params.type&&a.params.theme.id===c&&a.rerenderAsInstalled(!1)})}),wp.updates.deleteTheme({slug:c}),d.closeDetails(),d.focus())}}),f.Control=f.Class.extend({defaultActiveArguments:{duration:"fast",completeCallback:b.noop},defaults:{label:"",description:"",active:!0,priority:10},initialize:function(a,c){var d,e,g=this,h=[];g.params=_.extend({},g.defaults,g.params||{},c.params||c||{}),f.Control.instanceCounter||(f.Control.instanceCounter=0),f.Control.instanceCounter++,g.params.instanceNumber||(g.params.instanceNumber=f.Control.instanceCounter),g.params.type||_.find(f.controlConstructor,function(a,b){return a===g.constructor&&(g.params.type=b,!0)}),g.params.content||(g.params.content=b("<li></li>",{id:"customize-control-"+a.replace(/]/g,"").replace(/\[/g,"-"),"class":"customize-control customize-control-"+g.params.type})),g.id=a,g.selector="#customize-control-"+a.replace(/\]/g,"").replace(/\[/g,"-"),g.params.content?g.container=b(g.params.content):g.container=b(g.selector),g.params.templateId?g.templateSelector=g.params.templateId:g.templateSelector="customize-control-"+g.params.type+"-content",g.deferred=_.extend(g.deferred||{},{embedded:new b.Deferred}),g.section=new f.Value,g.priority=new f.Value,g.active=new f.Value,g.activeArgumentsQueue=[],g.notifications=new f.Notifications({alt:g.altNotice}),g.elements=[],g.active.bind(function(a){var c=g.activeArgumentsQueue.shift();c=b.extend({},g.defaultActiveArguments,c),g.onChangeActive(a,c)}),g.section.set(g.params.section),g.priority.set(isNaN(g.params.priority)?10:g.params.priority),g.active.set(g.params.active),f.utils.bubbleChildValueChanges(g,["section","priority","active"]),g.settings={},d={},g.params.setting&&(d["default"]=g.params.setting),_.extend(d,g.params.settings),_.each(d,function(a,b){var c;_.isObject(a)&&_.isFunction(a.extended)&&a.extended(f.Value)?g.settings[b]=a:_.isString(a)&&(c=f(a),c?g.settings[b]=c:h.push(a))}),e=function(){_.each(d,function(a,b){!g.settings[b]&&_.isString(a)&&(g.settings[b]=f(a))}),g.settings[0]&&!g.settings["default"]&&(g.settings["default"]=g.settings[0]),g.setting=g.settings["default"]||null,g.linkElements(),g.embed()},0===h.length?e():f.apply(f,h.concat(e)),g.deferred.embedded.done(function(){g.linkElements(),g.setupNotifications(),g.ready()})},linkElements:function(){var a,c,d,e=this;a=e.container.find("[data-customize-setting-link], [data-customize-setting-key-link]"),c={},a.each(function(){var g,h,i=b(this);if(!i.data("customizeSettingLinked")){if(i.data("customizeSettingLinked",!0),i.is(":radio")){if(g=i.prop("name"),c[g])return;c[g]=!0,i=a.filter('[name="'+g+'"]')}i.data("customizeSettingLink")?h=f(i.data("customizeSettingLink")):i.data("customizeSettingKeyLink")&&(h=e.settings[i.data("customizeSettingKeyLink")]),h&&(d=new f.Element(i),e.elements.push(d),d.sync(h),d.set(h()))}})},embed:function(){var a,b=this;a=function(a){var c;a&&f.section(a,function(a){a.deferred.embedded.done(function(){c=a.contentContainer.is("ul")?a.contentContainer:a.contentContainer.find("ul:first"),b.container.parent().is(c)||(c.append(b.container),b.renderContent()),b.deferred.embedded.resolve()})})},b.section.bind(a),a(b.section.get())},ready:function(){var a,c=this;"dropdown-pages"===c.params.type&&c.params.allow_addition&&(a=c.container.find(".new-content-item"),a.hide(),c.container.on("click",".add-new-toggle",function(c){b(c.currentTarget).slideUp(180),a.slideDown(180),a.find(".create-item-input").focus()}),c.container.on("click",".add-content",function(){c.addNewPage()}),c.container.on("keydown",".create-item-input",function(a){13===a.which&&c.addNewPage()}))},getNotificationsContainerElement:function(){var a,c,d=this;return c=d.container.find(".customize-control-notifications-container:first"),c.length?c:(c=b('<div class="customize-control-notifications-container"></div>'),d.container.hasClass("customize-control-nav_menu_item")?d.container.find(".menu-item-settings:first").prepend(c):d.container.hasClass("customize-control-widget_form")?d.container.find(".widget-inside:first").prepend(c):(a=d.container.find(".customize-control-title"),a.length?a.after(c):d.container.prepend(c)),c)},setupNotifications:function(){var a,b,c=this;_.each(c.settings,function(a){a.notifications&&(a.notifications.bind("add",function(b){var d=_.extend({},b,{setting:a.id});c.notifications.add(new f.Notification(a.id+":"+b.code,d))}),a.notifications.bind("remove",function(b){c.notifications.remove(a.id+":"+b.code)}))}),a=function(){var a=c.section();(!a||f.section.has(a)&&f.section(a).expanded())&&c.notifications.render()},c.notifications.bind("rendered",function(){var a=c.notifications.get();c.container.toggleClass("has-notifications",0!==a.length),c.container.toggleClass("has-error",0!==_.where(a,{type:"error"}).length)}),b=function(b,c){c&&f.section.has(c)&&f.section(c).expanded.unbind(a),b&&f.section(b,function(b){b.expanded.bind(a),a()})},c.section.bind(b),b(c.section.get()),c.notifications.bind("change",_.debounce(a))},renderNotifications:function(){var a,c,d=this,e=!1;"undefined"!=typeof console&&console.warn&&console.warn("[DEPRECATED] wp.customize.Control.prototype.renderNotifications() is deprecated in favor of instantating a wp.customize.Notifications and calling its render() method."),a=d.getNotificationsContainerElement(),a&&a.length&&(c=[],d.notifications.each(function(a){c.push(a),"error"===a.type&&(e=!0)}),0===c.length?a.stop().slideUp("fast"):a.stop().slideDown("fast",null,function(){b(this).css("height","auto")}),d.notificationsTemplate||(d.notificationsTemplate=wp.template("customize-control-notifications")),d.container.toggleClass("has-notifications",0!==c.length),d.container.toggleClass("has-error",e),a.empty().append(b.trim(d.notificationsTemplate({notifications:c,altNotice:Boolean(d.altNotice)}))))},expand:function(a){f.section(this.section()).expand(a)},focus:d,onChangeActive:function(a,c){return c.unchanged?void(c.completeCallback&&c.completeCallback()):void(b.contains(document,this.container[0])?a?this.container.slideDown(c.duration,c.completeCallback):this.container.slideUp(c.duration,c.completeCallback):(this.container.toggle(a),c.completeCallback&&c.completeCallback()))},toggle:function(a){return this.onChangeActive(a,this.defaultActiveArguments)},activate:c.prototype.activate,deactivate:c.prototype.deactivate,_toggleActive:c.prototype._toggleActive,dropdownInit:function(){var a=this,b=this.container.find(".dropdown-status"),c=this.params,d=!1,e=function(a){"string"==typeof a&&c.statuses&&c.statuses[a]?b.html(c.statuses[a]).show():b.hide()};this.container.on("click keydown",".dropdown",function(b){f.utils.isKeydownButNotEnterEvent(b)||(b.preventDefault(),d||a.container.toggleClass("open"),a.container.hasClass("open")&&a.container.parent().parent().find("li.library-selected").focus(),d=!0,setTimeout(function(){d=!1},400))}),this.setting.bind(e),e(this.setting())},renderContent:function(){var a,b,c,d,e=this;b=["button","checkbox","date","datetime-local","email","month","number","password","radio","range","search","select","tel","time","text","textarea","week","url"],c=e.templateSelector,c==="customize-control-"+e.params.type+"-content"&&_.contains(b,e.params.type)&&!document.getElementById("tmpl-"+c)&&0===e.container.children().length&&(c="customize-control-default-content"),document.getElementById("tmpl-"+c)&&(a=wp.template(c),a&&e.container&&e.container.html(a(e.params))),e.notifications.container=e.getNotificationsContainerElement(),d=e.section(),(!d||f.section.has(d)&&f.section(d).expanded())&&e.notifications.render()},addNewPage:function(){var a,c,d,e,g,h,i=this;if("dropdown-pages"===i.params.type&&i.params.allow_addition&&f.Menus){if(c=i.container.find(".add-new-toggle"),d=i.container.find(".new-content-item"),e=i.container.find(".create-item-input"),g=e.val(),h=i.container.find("select"),!g)return void e.addClass("invalid");e.removeClass("invalid"),e.attr("disabled","disabled"),a=f.Menus.insertAutoDraftPost({post_title:g,post_type:"page"}),a.done(function(a){var e,j,k;e=new f.Menus.AvailableItemModel({id:"post-"+a.post_id,title:g,type:"post_type",type_label:f.Menus.data.l10n.page_label,object:"page",object_id:a.post_id,url:a.url}),f.Menus.availableMenuItemsPanel.collection.add(e),j=b("#available-menu-items-post_type-page").find(".available-menu-items-list"),k=wp.template("available-menu-item"),j.prepend(k(e.attributes)),h.focus(),i.setting.set(String(a.post_id)),d.slideUp(180),c.slideDown(180)}),a.always(function(){e.val("").removeAttr("disabled")})}}}),f.ColorControl=f.Control.extend({ready:function(){var a,b=this,c="hue"===this.params.mode,d=!1;c?(a=this.container.find(".color-picker-hue"),a.val(b.setting()).wpColorPicker({change:function(a,c){d=!0,b.setting(c.color.h()),d=!1}})):(a=this.container.find(".color-picker-hex"),a.val(b.setting()).wpColorPicker({change:function(){d=!0,b.setting.set(a.wpColorPicker("color")),d=!1},clear:function(){d=!0,b.setting.set(""),d=!1}})),b.setting.bind(function(b){d||(a.val(b),a.wpColorPicker("color",b))}),b.container.on("keydown",function(c){var d;27===c.which&&(d=b.container.find(".wp-picker-container"),d.hasClass("wp-picker-active")&&(a.wpColorPicker("close"),b.container.find(".wp-color-result").focus(),c.stopPropagation()))})}}),f.MediaControl=f.Control.extend({ready:function(){function a(a){var d=b.Deferred();c.extended(f.UploadControl)?d.resolve():(a=parseInt(a,10),_.isNaN(a)||a<=0?(delete c.params.attachment,d.resolve()):c.params.attachment&&c.params.attachment.id===a&&d.resolve()),"pending"===d.state()&&wp.media.attachment(a).fetch().done(function(){c.params.attachment=this.attributes,d.resolve(),wp.customize.previewer.send(c.setting.id+"-attachment-data",this.attributes)}),d.done(function(){c.renderContent()})}var c=this;_.bindAll(c,"restoreDefault","removeFile","openFrame","select","pausePlayer"),c.container.on("click keydown",".upload-button",c.openFrame),c.container.on("click keydown",".upload-button",c.pausePlayer),c.container.on("click keydown",".thumbnail-image img",c.openFrame),c.container.on("click keydown",".default-button",c.restoreDefault),c.container.on("click keydown",".remove-button",c.pausePlayer),c.container.on("click keydown",".remove-button",c.removeFile),c.container.on("click keydown",".remove-button",c.cleanupPlayer),f.section(c.section()).container.on("expanded",function(){c.player&&c.player.setControlsSize()}).on("collapsed",function(){c.pausePlayer()}),a(c.setting()),c.setting.bind(a)},pausePlayer:function(){this.player&&this.player.pause()},cleanupPlayer:function(){this.player&&wp.media.mixin.removePlayer(this.player)},openFrame:function(a){f.utils.isKeydownButNotEnterEvent(a)||(a.preventDefault(),this.frame||this.initFrame(),this.frame.open())},initFrame:function(){this.frame=wp.media({button:{text:this.params.button_labels.frame_button},states:[new wp.media.controller.Library({title:this.params.button_labels.frame_title,library:wp.media.query({type:this.params.mime_type}),multiple:!1,date:!1})]}),this.frame.on("select",this.select)},select:function(){var a,b=this.frame.state().get("selection").first().toJSON(),c=window._wpmejsSettings||{};this.params.attachment=b,this.setting(b.id),a=this.container.find("audio, video").get(0),a?this.player=new MediaElementPlayer(a,c):this.cleanupPlayer()},restoreDefault:function(a){f.utils.isKeydownButNotEnterEvent(a)||(a.preventDefault(),this.params.attachment=this.params.defaultAttachment,this.setting(this.params.defaultAttachment.url))},removeFile:function(a){f.utils.isKeydownButNotEnterEvent(a)||(a.preventDefault(),this.params.attachment={},this.setting(""),this.renderContent())}}),f.UploadControl=f.MediaControl.extend({select:function(){var a,b=this.frame.state().get("selection").first().toJSON(),c=window._wpmejsSettings||{};this.params.attachment=b,this.setting(b.url),a=this.container.find("audio, video").get(0),a?this.player=new MediaElementPlayer(a,c):this.cleanupPlayer()},success:function(){},removerVisibility:function(){}}),f.ImageControl=f.UploadControl.extend({thumbnailSrc:function(){}}),f.BackgroundControl=f.UploadControl.extend({ready:function(){f.UploadControl.prototype.ready.apply(this,arguments)},select:function(){f.UploadControl.prototype.select.apply(this,arguments),wp.ajax.post("custom-background-add",{nonce:_wpCustomizeBackground.nonces.add,wp_customize:"on",customize_theme:f.settings.theme.stylesheet,attachment_id:this.params.attachment.id})}}),f.BackgroundPositionControl=f.Control.extend({ready:function(){var a,c=this;c.container.on("change",'input[name="background-position"]',function(){var a=b(this).val().split(" ");c.settings.x(a[0]),c.settings.y(a[1])}),a=_.debounce(function(){var a,b,d,e;a=c.settings.x.get(),b=c.settings.y.get(),e=String(a)+" "+String(b),d=c.container.find('input[name="background-position"][value="'+e+'"]'),d.click()}),c.settings.x.bind(a),c.settings.y.bind(a),a()}}),f.CroppedImageControl=f.MediaControl.extend({openFrame:function(a){f.utils.isKeydownButNotEnterEvent(a)||(this.initFrame(),this.frame.setState("library").open())},initFrame:function(){var a=_wpMediaViewsL10n;this.frame=wp.media({button:{text:a.select,close:!1},states:[new wp.media.controller.Library({title:this.params.button_labels.frame_title,library:wp.media.query({type:"image"}),multiple:!1,date:!1,priority:20,suggestedWidth:this.params.width,suggestedHeight:this.params.height}),new wp.media.controller.CustomizeImageCropper({imgSelectOptions:this.calculateImageSelectOptions,control:this})]}),this.frame.on("select",this.onSelect,this),this.frame.on("cropped",this.onCropped,this),this.frame.on("skippedcrop",this.onSkippedCrop,this)},onSelect:function(){var a=this.frame.state().get("selection").first().toJSON();this.params.width!==a.width||this.params.height!==a.height||this.params.flex_width||this.params.flex_height?this.frame.setState("cropper"):(this.setImageFromAttachment(a),this.frame.close())},onCropped:function(a){this.setImageFromAttachment(a)},calculateImageSelectOptions:function(a,b){var c,d,e,f=b.get("control"),g=!!parseInt(f.params.flex_width,10),h=!!parseInt(f.params.flex_height,10),i=a.get("width"),j=a.get("height"),k=parseInt(f.params.width,10),l=parseInt(f.params.height,10),m=k/l,n=k,o=l;return b.set("canSkipCrop",!f.mustBeCropped(g,h,k,l,i,j)),i/j>m?(l=j,k=l*m):(k=i,l=k/m),c=(i-k)/2,d=(j-l)/2,e={handles:!0,keys:!0,instance:!0,persistent:!0,imageWidth:i,imageHeight:j,minWidth:n>k?k:n,minHeight:o>l?l:o,x1:c,y1:d,x2:k+c,y2:l+d},h===!1&&g===!1&&(e.aspectRatio=k+":"+l),!0===h&&(delete e.minHeight,e.maxWidth=i),!0===g&&(delete e.minWidth,e.maxHeight=j),e},mustBeCropped:function(a,b,c,d,e,f){return(!0!==a||!0!==b)&&((!0!==a||d!==f)&&((!0!==b||c!==e)&&((c!==e||d!==f)&&!(e<=c))))},onSkippedCrop:function(){var a=this.frame.state().get("selection").first().toJSON();this.setImageFromAttachment(a)},setImageFromAttachment:function(a){this.params.attachment=a,this.setting(a.id)}}),f.SiteIconControl=f.CroppedImageControl.extend({initFrame:function(){var a=_wpMediaViewsL10n;this.frame=wp.media({button:{text:a.select,close:!1},states:[new wp.media.controller.Library({title:this.params.button_labels.frame_title,library:wp.media.query({type:"image"}),multiple:!1,date:!1,priority:20,suggestedWidth:this.params.width,suggestedHeight:this.params.height}),new wp.media.controller.SiteIconCropper({imgSelectOptions:this.calculateImageSelectOptions,control:this})]}),this.frame.on("select",this.onSelect,this),this.frame.on("cropped",this.onCropped,this),this.frame.on("skippedcrop",this.onSkippedCrop,this)},onSelect:function(){var a=this.frame.state().get("selection").first().toJSON(),b=this;this.params.width!==a.width||this.params.height!==a.height||this.params.flex_width||this.params.flex_height?this.frame.setState("cropper"):wp.ajax.post("crop-image",{nonce:a.nonces.edit,id:a.id,context:"site-icon",cropDetails:{x1:0,y1:0,width:this.params.width,height:this.params.height,dst_width:this.params.width,dst_height:this.params.height}}).done(function(a){b.setImageFromAttachment(a),b.frame.close()}).fail(function(){b.frame.trigger("content:error:crop")})},setImageFromAttachment:function(a){var c,d,e=["site_icon-32","thumbnail","full"];_.each(e,function(b){d||_.isUndefined(a.sizes[b])||(d=a.sizes[b])}),this.params.attachment=a,this.setting(a.id),d&&(c=b('link[rel="icon"][sizes="32x32"]'),c.attr("href",d.url))},removeFile:function(a){f.utils.isKeydownButNotEnterEvent(a)||(a.preventDefault(),this.params.attachment={},this.setting(""),this.renderContent(),b('link[rel="icon"][sizes="32x32"]').attr("href","/favicon.ico"))}}),f.HeaderControl=f.Control.extend({ready:function(){this.btnRemove=b("#customize-control-header_image .actions .remove"),this.btnNew=b("#customize-control-header_image .actions .new"),_.bindAll(this,"openMedia","removeImage"),this.btnNew.on("click",this.openMedia),this.btnRemove.on("click",this.removeImage),f.HeaderTool.currentHeader=this.getInitialHeaderImage(),new f.HeaderTool.CurrentView({model:f.HeaderTool.currentHeader,el:"#customize-control-header_image .current .container"}),new f.HeaderTool.ChoiceListView({collection:f.HeaderTool.UploadsList=new f.HeaderTool.ChoiceList,el:"#customize-control-header_image .choices .uploaded .list"}),new f.HeaderTool.ChoiceListView({collection:f.HeaderTool.DefaultsList=new f.HeaderTool.DefaultsList,el:"#customize-control-header_image .choices .default .list"}),f.HeaderTool.combinedList=f.HeaderTool.CombinedList=new f.HeaderTool.CombinedList([f.HeaderTool.UploadsList,f.HeaderTool.DefaultsList]),wp.media.controller.Cropper.prototype.defaults.doCropArgs.wp_customize="on",wp.media.controller.Cropper.prototype.defaults.doCropArgs.customize_theme=f.settings.theme.stylesheet},getInitialHeaderImage:function(){if(!f.get().header_image||!f.get().header_image_data||_.contains(["remove-header","random-default-image","random-uploaded-image"],f.get().header_image))return new f.HeaderTool.ImageModel;var a=_.find(_wpCustomizeHeader.uploads,function(a){return a.attachment_id===f.get().header_image_data.attachment_id});return a||(a={url:f.get().header_image,thumbnail_url:f.get().header_image,attachment_id:f.get().header_image_data.attachment_id}),new f.HeaderTool.ImageModel({header:a,choice:a.url.split("/").pop()})},calculateImageSelectOptions:function(a,b){var c,d,e,g,h,i,j=parseInt(_wpCustomizeHeader.data.width,10),k=parseInt(_wpCustomizeHeader.data.height,10),l=!!parseInt(_wpCustomizeHeader.data["flex-width"],10),m=!!parseInt(_wpCustomizeHeader.data["flex-height"],10);return h=a.get("width"),g=a.get("height"),this.headerImage=new f.HeaderTool.ImageModel,this.headerImage.set({themeWidth:j,themeHeight:k,themeFlexWidth:l,themeFlexHeight:m,imageWidth:h,imageHeight:g}),b.set("canSkipCrop",!this.headerImage.shouldBeCropped()),c=j/k,d=h,e=g,d/e>c?(k=e,j=k*c):(j=d,k=j/c),i={handles:!0,keys:!0,instance:!0,persistent:!0,imageWidth:h,imageHeight:g,x1:0,y1:0,x2:j,y2:k},m===!1&&l===!1&&(i.aspectRatio=j+":"+k),m===!1&&(i.maxHeight=k),l===!1&&(i.maxWidth=j),i},openMedia:function(a){var b=_wpMediaViewsL10n;a.preventDefault(),this.frame=wp.media({button:{text:b.selectAndCrop,close:!1},states:[new wp.media.controller.Library({title:b.chooseImage,library:wp.media.query({type:"image"}),multiple:!1,date:!1,priority:20,suggestedWidth:_wpCustomizeHeader.data.width,suggestedHeight:_wpCustomizeHeader.data.height}),new wp.media.controller.Cropper({imgSelectOptions:this.calculateImageSelectOptions})]}),this.frame.on("select",this.onSelect,this),this.frame.on("cropped",this.onCropped,this),this.frame.on("skippedcrop",this.onSkippedCrop,this),this.frame.open()},onSelect:function(){this.frame.setState("cropper")},onCropped:function(a){var b=a.url,c=a.attachment_id,d=a.width,e=a.height;this.setImageFromURL(b,c,d,e)},onSkippedCrop:function(a){var b=a.get("url"),c=a.get("width"),d=a.get("height");this.setImageFromURL(b,a.id,c,d)},setImageFromURL:function(a,b,c,d){var e,g={};g.url=a,g.thumbnail_url=a,g.timestamp=_.now(),b&&(g.attachment_id=b),c&&(g.width=c),d&&(g.height=d),e=new f.HeaderTool.ImageModel({header:g,choice:a.split("/").pop()}),f.HeaderTool.UploadsList.add(e),f.HeaderTool.currentHeader.set(e.toJSON()),e.save(),e.importImage()},removeImage:function(){f.HeaderTool.currentHeader.trigger("hide"),f.HeaderTool.CombinedList.trigger("control:removeImage")}}),f.ThemeControl=f.Control.extend({touchDrag:!1,screenshotRendered:!1,ready:function(){function a(){return!g.canSwitchTheme(e.params.theme.id)}function c(){return a()||!1===f.settings.theme._canInstall||!0===f.settings.theme._filesystemCredentialsNeeded}function d(){e.container.find("button.preview, button.preview-theme").toggleClass("disabled",a()),e.container.find("button.theme-install").toggleClass("disabled",c())}var e=this,g=f.panel("themes");f.state("selectedChangesetStatus").bind(d),f.state("changesetStatus").bind(d),d(),e.container.on("touchmove",".theme",function(){e.touchDrag=!0}),e.container.on("click keydown touchend",".theme",function(a){var c;if(!f.utils.isKeydownButNotEnterEvent(a))return e.touchDrag===!0?e.touchDrag=!1:void(b(a.target).is(".theme-actions .button, .update-theme")||(a.preventDefault(),c=f.section(e.section()),c.showDetails(e.params.theme,function(){f.settings.theme._filesystemCredentialsNeeded&&c.overlay.find(".theme-actions .delete-theme").remove()})))}),e.container.on("render-screenshot",function(){var a=b(this).find("img"),c=a.data("src");c&&a.attr("src",c),e.screenshotRendered=!0})},filter:function(a){var b=this,c=0,d=b.params.theme.name+" "+b.params.theme.description+" "+b.params.theme.tags+" "+b.params.theme.author+" ";return d=d.toLowerCase().replace("-"," "),_.isArray(a)||(a=[a]),b.params.theme.name.toLowerCase()===a.join(" ")?c=100:(c+=10*(d.split(a.join(" ")).length-1),_.each(a,function(a){c+=2*(d.split(a+" ").length-1),c=c+d.split(a).length-1}),c>99&&(c=99)),0!==c?(b.activate(),b.params.priority=101-c,!0):(b.deactivate(),b.params.priority=101,!1)},rerenderAsInstalled:function(a){var b,c=this;a?c.params.theme.type="installed":(b=f.section(c.params.section),c.params.theme.type=b.params.action),c.renderContent(),c.container.trigger("render-screenshot")}}),f.CodeEditorControl=f.Control.extend({initialize:function(a,c){var d=this;d.deferred=_.extend(d.deferred||{},{codemirror:b.Deferred()}),f.Control.prototype.initialize.call(d,a,c),d.notifications.bind("add",function(a){a.code===d.setting.id+":csslint_error"&&(a.templateId="customize-code-editor-lint-error-notification",a.render=function(a){return function(){var b=a.call(this);return b.find("input[type=checkbox]").on("click",function(){d.setting.notifications.remove("csslint_error")}),b}}(a.render))})},ready:function(){var a=this;return a.section()?void f.section(a.section(),function(b){b.deferred.embedded.done(function(){var c;b.expanded()?a.initEditor():(c=function(d){d&&(a.initEditor(),b.expanded.unbind(c))},b.expanded.bind(c))})}):void a.initEditor()},initEditor:function(){var a,b=this,c=!1;wp.codeEditor&&(_.isUndefined(b.params.editor_settings)||!1!==b.params.editor_settings)&&(c=wp.codeEditor.defaultSettings?_.clone(wp.codeEditor.defaultSettings):{},c.codemirror=_.extend({},c.codemirror,{indentUnit:2,tabSize:2}),_.isObject(b.params.editor_settings)&&_.each(b.params.editor_settings,function(a,b){_.isObject(a)&&(c[b]=_.extend({},c[b],a))})),a=new f.Element(b.container.find("textarea")),b.elements.push(a),a.sync(b.setting),a.set(b.setting()),c?b.initSyntaxHighlightingEditor(c):b.initPlainTextareaEditor()},focus:function(a){var b,c=this,d=_.extend({},a);b=d.completeCallback,d.completeCallback=function(){b&&b(),c.editor&&c.editor.codemirror.focus()},f.Control.prototype.focus.call(c,d)},initSyntaxHighlightingEditor:function(a){var c,d=this,e=d.container.find("textarea"),f=!1;c=_.extend({},a,{onTabNext:_.bind(d.onTabNext,d),onTabPrevious:_.bind(d.onTabPrevious,d),onUpdateErrorNotice:_.bind(d.onUpdateErrorNotice,d)}),d.editor=wp.codeEditor.initialize(e,c),b(d.editor.codemirror.display.lineDiv).attr({role:"textbox","aria-multiline":"true","aria-label":d.params.label,"aria-describedby":"editor-keyboard-trap-help-1 editor-keyboard-trap-help-2 editor-keyboard-trap-help-3 editor-keyboard-trap-help-4"}),d.container.find("label").on("click",function(){d.editor.codemirror.focus()}),d.editor.codemirror.on("change",function(a){f=!0,e.val(a.getValue()).trigger("change"),f=!1}),d.setting.bind(function(a){f||d.editor.codemirror.setValue(a)}),d.editor.codemirror.on("keydown",function(a,b){var c=27;c===b.keyCode&&b.stopPropagation()}),d.deferred.codemirror.resolveWith(d,[d.editor.codemirror])},onTabNext:function(){var a,c,d,e=this;d=f.section(e.section()),a=d.controls(),c=a.indexOf(e),a.length===c+1?b("#customize-footer-actions .collapse-sidebar").focus():a[c+1].container.find(":focusable:first").focus()},onTabPrevious:function(){var a,b,c,d=this;c=f.section(d.section()),a=c.controls(),b=a.indexOf(d),0===b?c.contentContainer.find(".customize-section-title .customize-help-toggle, .customize-section-title .customize-section-description.open .section-description-close").last().focus():a[b-1].contentContainer.find(":focusable:first").focus()},onUpdateErrorNotice:function(a){var b,c=this;c.setting.notifications.remove("csslint_error"),0!==a.length&&(b=1===a.length?f.l10n.customCssError.singular.replace("%d","1"):f.l10n.customCssError.plural.replace("%d",String(a.length)),c.setting.notifications.add(new f.Notification("csslint_error",{message:b,type:"error"})))},initPlainTextareaEditor:function(){var a=this,b=a.container.find("textarea"),c=b[0];b.on("blur",function(){b.data("next-tab-blurs",!1)}),b.on("keydown",function(a){var d,e,f,g=9,h=27;return h===a.keyCode?void(b.data("next-tab-blurs")||(b.data("next-tab-blurs",!0),a.stopPropagation())):void(g!==a.keyCode||a.ctrlKey||a.altKey||a.shiftKey||b.data("next-tab-blurs")||(d=c.selectionStart,
+e=c.selectionEnd,f=c.value,d>=0&&(c.value=f.substring(0,d).concat("\t",f.substring(e)),b.selectionStart=c.selectionEnd=d+1),a.stopPropagation(),a.preventDefault()))}),a.deferred.codemirror.rejectWith(a)}}),f.DateTimeControl=f.Control.extend({ready:function(){var a=this;if(a.inputElements={},a.invalidDate=!1,_.bindAll(a,"populateSetting","updateDaysForMonth","populateDateInputs"),!a.setting)throw new Error("Missing setting");a.container.find(".date-input").each(function(){var c,d,e=b(this);c=e.data("component"),d=new f.Element(e),a.inputElements[c]=d,a.elements.push(d),e.on("change",function(){a.invalidDate&&a.notifications.add(new f.Notification("invalid_date",{message:f.l10n.invalidDate}))}),e.on("input",_.debounce(function(){a.invalidDate||a.notifications.remove("invalid_date")})),e.on("blur",_.debounce(function(){a.invalidDate||a.populateDateInputs()}))}),a.inputElements.month.bind(a.updateDaysForMonth),a.inputElements.year.bind(a.updateDaysForMonth),a.populateDateInputs(),a.setting.bind(a.populateDateInputs),_.each(a.inputElements,function(b){b.bind(a.populateSetting)})},parseDateTime:function(a){var b,c,d=this,e=12;return a&&(b=a.match(/^(\d\d\d\d)-(\d\d)-(\d\d)(?: (\d\d):(\d\d)(?::(\d\d))?)?$/)),b?(b.shift(),c={year:b.shift(),month:b.shift(),day:b.shift(),hour:b.shift()||"00",minute:b.shift()||"00",second:b.shift()||"00"},d.params.includeTime&&d.params.twelveHourFormat&&(c.hour=parseInt(c.hour,10),c.meridian=c.hour>=e?"pm":"am",c.hour=c.hour%e?String(c.hour%e):String(e),delete c.second),c):null},validateInputs:function(){var a,b,c=this;return c.invalidDate=!1,a=["year","day"],c.params.includeTime&&a.push("hour","minute"),_.find(a,function(a){var d,e,f,g;return d=c.inputElements[a],b=d.element.get(0),e=parseInt(d.element.attr("max"),10),f=parseInt(d.element.attr("min"),10),g=parseInt(d(),10),c.invalidDate=isNaN(g)||g>e||g<f,c.invalidDate||b.setCustomValidity(""),c.invalidDate}),c.inputElements.meridian&&!c.invalidDate&&(b=c.inputElements.meridian.element.get(0),"am"!==c.inputElements.meridian.get()&&"pm"!==c.inputElements.meridian.get()?c.invalidDate=!0:b.setCustomValidity("")),c.invalidDate?b.setCustomValidity(f.l10n.invalidValue):b.setCustomValidity(""),(!c.section()||f.section.has(c.section())&&f.section(c.section()).expanded())&&_.result(b,"reportValidity"),c.invalidDate},updateDaysForMonth:function(){var a,b,c,d,e=this;c=parseInt(e.inputElements.month(),10),b=parseInt(e.inputElements.year(),10),d=parseInt(e.inputElements.day(),10),c&&b&&(a=new Date(b,c,0).getDate(),e.inputElements.day.element.attr("max",a),d>a&&e.inputElements.day(String(a)))},populateSetting:function(){var a,b=this;return!(b.validateInputs()||!b.params.allowPastDate&&!b.isFutureDate())&&(a=b.convertInputDateToString(),b.setting.set(a),!0)},convertInputDateToString:function(){var a,b,c,d,e=this,f="";return d=function(a,b){var c;return String(a).length<b&&(c=b-String(a).length,a=Math.pow(10,c).toString().substr(1)+String(a)),a},c=function(a){var b=parseInt(e.inputElements[a].get(),10);return _.contains(["month","day","hour","minute"],a)?b=d(b,2):"year"===a&&(b=d(b,4)),b},a=["year","-","month","-","day"],e.params.includeTime&&(b=e.inputElements.meridian?e.convertHourToTwentyFourHourFormat(e.inputElements.hour(),e.inputElements.meridian()):e.inputElements.hour(),a=a.concat([" ",d(b,2),":","minute",":","00"])),_.each(a,function(a){f+=e.inputElements[a]?c(a):a}),f},isFutureDate:function(){var a=this;return 0<f.utils.getRemainingTime(a.convertInputDateToString())},convertHourToTwentyFourHourFormat:function(a,b){var c,d,e=12;return d=parseInt(a,10),isNaN(d)?"":(c="pm"===b&&d<e?d+e:"am"===b&&e===d?d-e:d,String(c))},populateDateInputs:function(){var a,b=this;return!!(a=b.parseDateTime(b.setting.get()))&&(_.each(b.inputElements,function(b,c){var d=a[c];"month"===c||"meridian"===c?(d=d.replace(/^0/,""),b.set(d)):(d=parseInt(d,10),b.element.is(document.activeElement)?d!==parseInt(b(),10)&&b.set(String(d)):b.set(a[c]))}),!0)},toggleFutureDateNotification:function(a){var b,c,d=this;return b="not_future_date",a?(c=new f.Notification(b,{type:"error",message:f.l10n.futureDateError}),d.notifications.add(c)):d.notifications.remove(b),d}}),f.PreviewLinkControl=f.Control.extend({defaults:_.extend({},f.Control.prototype.defaults,{templateId:"customize-preview-link-control"}),ready:function(){var a,c,d,e,g,h,i=this;_.bindAll(i,"updatePreviewLink"),i.setting||(i.setting=new f.Value),i.previewElements={},i.container.find(".preview-control-element").each(function(){d=b(this),c=d.data("component"),a=new f.Element(d),i.previewElements[c]=a,i.elements.push(a)}),e=i.previewElements.url,g=i.previewElements.input,h=i.previewElements.button,g.link(i.setting),e.link(i.setting),e.bind(function(a){e.element.parent().attr({href:a,target:f.settings.changeset.uuid})}),f.bind("ready",i.updatePreviewLink),f.state("saved").bind(i.updatePreviewLink),f.state("changesetStatus").bind(i.updatePreviewLink),f.state("activated").bind(i.updatePreviewLink),f.previewer.previewUrl.bind(i.updatePreviewLink),h.element.on("click",function(a){a.preventDefault(),i.setting()&&(g.element.select(),document.execCommand("copy"),h(h.element.data("copied-text")))}),e.element.parent().on("click",function(a){b(this).hasClass("disabled")&&a.preventDefault()}),h.element.on("mouseenter",function(){i.setting()&&h(h.element.data("copy-text"))})},updatePreviewLink:function(){var a,b=this;a=!f.state("saved").get()||""===f.state("changesetStatus").get()||"auto-draft"===f.state("changesetStatus").get(),b.toggleSaveNotification(a),b.previewElements.url.element.parent().toggleClass("disabled",a),b.previewElements.button.element.prop("disabled",a),b.setting.set(f.previewer.getFrontendPreviewUrl())},toggleSaveNotification:function(a){var b,c,d=this;b="changes_not_saved",a?(c=new f.Notification(b,{type:"info",message:f.l10n.saveBeforeShare}),d.notifications.add(c)):d.notifications.remove(b)}}),f.defaultConstructor=f.Setting,f.control=new f.Values({defaultConstructor:f.Control}),f.section=new f.Values({defaultConstructor:f.Section}),f.panel=new f.Values({defaultConstructor:f.Panel}),f.notifications=new f.Notifications,f.PreviewFrame=f.Messenger.extend({sensitivity:null,initialize:function(a,c){var d=b.Deferred();d.promise(this),this.container=a.container,b.extend(a,{channel:f.PreviewFrame.uuid()}),f.Messenger.prototype.initialize.call(this,a,c),this.add("previewUrl",a.previewUrl),this.query=b.extend(a.query||{},{customize_messenger_channel:this.channel()}),this.run(d)},run:function(a){var c,d,e,g=this,h=!1,i=!1,j=null,k="{}"!==g.query.customized;g._ready&&g.unbind("ready",g._ready),g._ready=function(b){i=!0,j=b,g.container.addClass("iframe-ready"),b&&h&&a.resolveWith(g,[b])},g.bind("ready",g._ready),c=document.createElement("a"),c.href=g.previewUrl(),d=_.extend(f.utils.parseQueryString(c.search.substr(1)),{customize_changeset_uuid:g.query.customize_changeset_uuid,customize_theme:g.query.customize_theme,customize_messenger_channel:g.query.customize_messenger_channel}),!f.settings.changeset.autosaved&&f.state("saved").get()||(d.customize_autosaved="on"),c.search=b.param(d),g.iframe=b("<iframe />",{title:f.l10n.previewIframeTitle,name:"customize-"+g.channel()}),g.iframe.attr("onmousewheel",""),g.iframe.attr("sandbox","allow-forms allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-presentation allow-same-origin allow-scripts"),k?g.iframe.attr("data-src",c.href):g.iframe.attr("src",c.href),g.iframe.appendTo(g.container),g.targetWindow(g.iframe[0].contentWindow),k&&(e=b("<form>",{action:c.href,target:g.iframe.attr("name"),method:"post",hidden:"hidden"}),e.append(b("<input>",{type:"hidden",name:"_method",value:"GET"})),_.each(g.query,function(a,c){e.append(b("<input>",{type:"hidden",name:c,value:a}))}),g.container.append(e),e.submit(),e.remove()),g.bind("iframe-loading-error",function(b){return g.iframe.remove(),0===b?void g.login(a):-1===b?void a.rejectWith(g,["cheatin"]):void a.rejectWith(g,["request failure"])}),g.iframe.one("load",function(){h=!0,i?a.resolveWith(g,[j]):setTimeout(function(){a.rejectWith(g,["ready timeout"])},g.sensitivity)})},login:function(a){var c,d=this;return c=function(){a.rejectWith(d,["logged out"])},this.triedLogin?c():void b.get(f.settings.url.ajax,{action:"logged-in"}).fail(c).done(function(e){var g;"1"!==e&&c(),g=b("<iframe />",{src:d.previewUrl(),title:f.l10n.previewIframeTitle}).hide(),g.appendTo(d.container),g.on("load",function(){d.triedLogin=!0,g.remove(),d.run(a)})})},destroy:function(){f.Messenger.prototype.destroy.call(this),this.iframe&&this.iframe.remove(),delete this.iframe,delete this.targetWindow}}),function(){var a=0;f.PreviewFrame.uuid=function(){return"preview-"+String(a++)}}(),f.setDocumentTitle=function(a){var b,c;b=f.settings.documentTitleTmpl,c=b.replace("%s",a),document.title=c,f.trigger("title",c)},f.Previewer=f.Messenger.extend({refreshBuffer:null,initialize:function(a,c){var d=this,e=document.createElement("a");b.extend(d,c||{}),d.deferred={active:b.Deferred()},d.refresh=_.debounce(function(a){return function(){var b,c;b=function(){return 0===f.state("processing").get()},b()?a.call(d):(c=function(){b()&&(a.call(d),f.state("processing").unbind(c))},f.state("processing").bind(c))}}(d.refresh),d.refreshBuffer),d.container=f.ensure(a.container),d.allowedUrls=a.allowedUrls,a.url=window.location.href,f.Messenger.prototype.initialize.call(d,a),e.href=d.origin(),d.add("scheme",e.protocol.replace(/:$/,"")),d.add("previewUrl",a.previewUrl).setter(function(a){var c,e,g,h=null,i=[];return c=document.createElement("a"),c.href=a,/\/wp-(admin|includes|content)(\/|$)/.test(c.pathname)?null:(c.search.length>1&&(e=f.utils.parseQueryString(c.search.substr(1)),delete e.customize_changeset_uuid,delete e.customize_theme,delete e.customize_messenger_channel,delete e.customize_autosaved,_.isEmpty(e)?c.search="":c.search=b.param(e)),i.push(c),d.scheme.get()+":"!==c.protocol&&(c=document.createElement("a"),c.href=i[0].href,c.protocol=d.scheme.get()+":",i.unshift(c)),g=document.createElement("a"),_.find(i,function(a){return!_.isUndefined(_.find(d.allowedUrls,function(b){if(g.href=b,c.protocol===g.protocol&&c.host===g.host&&0===c.pathname.indexOf(g.pathname.replace(/\/$/,"")))return h=a.href,!0}))}),h)}),d.bind("ready",d.ready),d.deferred.active.done(_.bind(d.keepPreviewAlive,d)),d.bind("synced",function(){d.send("active")}),d.previewUrl.bind(d.refresh),d.scroll=0,d.bind("scroll",function(a){d.scroll=a}),d.bind("url",function(a){var b,c=!1;d.scroll=0,b=function(){c=!0},d.previewUrl.bind(b),d.previewUrl.set(a),d.previewUrl.unbind(b),c||d.refresh()}),d.bind("documentTitle",function(a){f.setDocumentTitle(a)})},ready:function(a){var b,c=this,d={};d.settings=f.get(),d["settings-modified-while-loading"]=c.settingsModifiedWhileLoading,("resolved"!==c.deferred.active.state()||c.loading)&&(d.scroll=c.scroll),d["edit-shortcut-visibility"]=f.state("editShortcutVisibility").get(),c.send("sync",d),a.currentUrl&&(c.previewUrl.unbind(c.refresh),c.previewUrl.set(a.currentUrl),c.previewUrl.bind(c.refresh)),b={panel:a.activePanels,section:a.activeSections,control:a.activeControls},_(b).each(function(a,b){f[b].each(function(c,d){var e=_.isUndefined(f.settings[b+"s"][d]);e&&_.isUndefined(a[d])||(a[d]?c.activate():c.deactivate())})}),a.settingValidities&&f._handleSettingValidities({settingValidities:a.settingValidities,focusInvalidControl:!1})},keepPreviewAlive:function(){var a,b,c,d,e=this;d=function(){b=setTimeout(c,f.settings.timeouts.keepAliveCheck)},a=function(){f.state("previewerAlive").set(!0),clearTimeout(b),d()},c=function(){f.state("previewerAlive").set(!1)},d(),e.bind("ready",a),e.bind("keep-alive",a)},query:function(){},abort:function(){this.loading&&(this.loading.destroy(),delete this.loading)},refresh:function(){var a,b=this;b.send("loading-initiated"),b.abort(),b.loading=new f.PreviewFrame({url:b.url(),previewUrl:b.previewUrl(),query:b.query({excludeCustomizedSaved:!0})||{},container:b.container}),b.settingsModifiedWhileLoading={},a=function(a){b.settingsModifiedWhileLoading[a.id]=!0},f.bind("change",a),b.loading.always(function(){f.unbind("change",a)}),b.loading.done(function(a){var c,d=this;b.preview=d,b.targetWindow(d.targetWindow()),b.channel(d.channel()),c=function(){d.unbind("synced",c),b._previousPreview&&b._previousPreview.destroy(),b._previousPreview=b.preview,b.deferred.active.resolve(),delete b.loading},d.bind("synced",c),b.trigger("ready",a)}),b.loading.fail(function(a){b.send("loading-failed"),"logged out"===a&&(b.preview&&(b.preview.destroy(),delete b.preview),b.login().done(b.refresh)),"cheatin"===a&&b.cheatin()})},login:function(){var a,c,d,e=this;return this._login?this._login:(a=b.Deferred(),this._login=a.promise(),c=new f.Messenger({channel:"login",url:f.settings.url.login}),d=b("<iframe />",{src:f.settings.url.login,title:f.l10n.loginIframeTitle}).appendTo(this.container),c.targetWindow(d[0].contentWindow),c.bind("login",function(){var b=e.refreshNonces();b.always(function(){d.remove(),c.destroy(),delete e._login}),b.done(function(){a.resolve()}),b.fail(function(){e.cheatin(),a.reject()})}),this._login)},cheatin:function(){b(document.body).empty().addClass("cheatin").append("<h1>"+f.l10n.notAllowedHeading+"</h1><p>"+f.l10n.notAllowed+"</p>")},refreshNonces:function(){var a,c=b.Deferred();return c.promise(),a=wp.ajax.post("customize_refresh_nonces",{wp_customize:"on",customize_theme:f.settings.theme.stylesheet}),a.done(function(a){f.trigger("nonce-refresh",a),c.resolve()}),a.fail(function(){c.reject()}),c}}),f.settingConstructor={},f.controlConstructor={color:f.ColorControl,media:f.MediaControl,upload:f.UploadControl,image:f.ImageControl,cropped_image:f.CroppedImageControl,site_icon:f.SiteIconControl,header:f.HeaderControl,background:f.BackgroundControl,background_position:f.BackgroundPositionControl,theme:f.ThemeControl,date_time:f.DateTimeControl,code_editor:f.CodeEditorControl},f.panelConstructor={themes:f.ThemesPanel},f.sectionConstructor={themes:f.ThemesSection,outer:f.OuterSection},f._handleSettingValidities=function(a){var b,c=[],d=!1;_.each(a.settingValidities,function(a,b){var d=f(b);d&&(_.isObject(a)&&_.each(a,function(a,b){var e,g,h=!1;e=new f.Notification(b,_.extend({fromServer:!0},a)),g=d.notifications(e.code),g&&(h=e.type!==g.type||e.message!==g.message||!_.isEqual(e.data,g.data)),h&&d.notifications.remove(b),d.notifications.has(e.code)||d.notifications.add(e),c.push(d.id)}),d.notifications.each(function(b){!b.fromServer||"error"!==b.type||!0!==a&&a[b.code]||d.notifications.remove(b.code)}))}),a.focusInvalidControl&&(b=f.findControlsForSettings(c),_(_.values(b)).find(function(a){return _(a).find(function(a){var b=a.section()&&f.section.has(a.section())&&f.section(a.section()).expanded();return b&&a.expanded&&(b=a.expanded()),b&&(a.focus(),d=!0),d})}),d||_.isEmpty(b)||_.values(b)[0][0].focus())},f.findControlsForSettings=function(a){var b,c={};return _.each(_.unique(a),function(a){var d=f(a);d&&(b=d.findControls(),b&&b.length>0&&(c[a]=b))}),c},f.reflowPaneContents=_.bind(function(){var a,c,d,e=[],g=!1;document.activeElement&&(c=b(document.activeElement)),f.panel.each(function(b){if("themes"!==b.id){var c=b.sections(),d=_.pluck(c,"headContainer");e.push(b),a=b.contentContainer.is("ul")?b.contentContainer:b.contentContainer.find("ul:first"),f.utils.areElementListsEqual(d,a.children("[id]"))||(_(c).each(function(b){a.append(b.headContainer)}),g=!0)}}),f.section.each(function(b){var c=b.controls(),d=_.pluck(c,"container");b.panel()||e.push(b),a=b.contentContainer.is("ul")?b.contentContainer:b.contentContainer.find("ul:first"),f.utils.areElementListsEqual(d,a.children("[id]"))||(_(c).each(function(b){a.append(b.container)}),g=!0)}),e.sort(f.utils.prioritySort),d=_.pluck(e,"headContainer"),a=b("#customize-theme-controls .customize-pane-parent"),f.utils.areElementListsEqual(d,a.children())||(_(e).each(function(b){a.append(b.headContainer)}),g=!0),f.panel.each(function(a){var b=a.active();a.active.callbacks.fireWith(a.active,[b,b])}),f.section.each(function(a){var b=a.active();a.active.callbacks.fireWith(a.active,[b,b])}),g&&c&&c.focus(),f.trigger("pane-contents-reflowed")},f),f.state=new f.Values,_.each(["saved","saving","trashing","activated","processing","paneVisible","expandedPanel","expandedSection","changesetDate","selectedChangesetDate","changesetStatus","selectedChangesetStatus","remainingTimeToPublish","previewerAlive","editShortcutVisibility","changesetLocked","previewedDevice"],function(a){f.state.create(a)}),b(function(){function a(){function c(a){a||f.settings.changeset.autosaved||(f.settings.changeset.autosaved=!0,f.previewer.send("autosaving"))}var d,e,g,h=!1;f.unbind("change",a),f.state("saved").bind(c),c(f.state("saved").get()),e=function(){h||(h=!0,f.requestChangesetUpdate({},{autosave:!0}).always(function(){h=!1})),g()},g=function(){clearTimeout(d),d=setTimeout(function(){e()},f.settings.timeouts.changesetAutoSave)},g(),b(document).on("visibilitychange.wp-customize-changeset-update",function(){document.hidden&&e()}),b(window).on("beforeunload.wp-customize-changeset-update",function(){e()})}if(f.settings=window._wpCustomizeSettings,f.l10n=window._wpCustomizeControlsL10n,f.settings&&b.support.postMessage&&(b.support.cors||!f.settings.isCrossDomain)){null===f.PreviewFrame.prototype.sensitivity&&(f.PreviewFrame.prototype.sensitivity=f.settings.timeouts.previewFrameSensitivity),null===f.Previewer.prototype.refreshBuffer&&(f.Previewer.prototype.refreshBuffer=f.settings.timeouts.windowRefresh);var c,d=b(document.body),e=d.children(".wp-full-overlay"),g=b("#customize-info .panel-title.site-title"),h=b(".customize-controls-close"),i=b("#save"),j=b("#customize-save-button-wrapper"),k=b("#publish-settings"),l=b("#customize-footer-actions");f.bind("ready",function(){f.section.add(new f.OuterSection("publish_settings",{title:f.l10n.publishSettings,priority:0,active:f.settings.theme.active}))}),f.section("publish_settings",function(a){function b(){r||(r=f.utils.highlightButton(j,{delay:1e3,focusTarget:i}))}function c(){r&&(r(),r=null)}var d,e,g,h,l,m,n,o,p,q,r,s=1e3;e=new f.Control("trash_changeset",{type:"button",section:a.id,priority:30,input_attrs:{"class":"button-link button-link-delete",value:f.l10n.discardChanges}}),f.control.add(e),e.deferred.embedded.done(function(){e.container.find(".button-link").on("click",function(){confirm(f.l10n.trashConfirm)&&wp.customize.previewer.trash()})}),f.control.add(new f.PreviewLinkControl("changeset_preview_link",{section:a.id,priority:100})),h=function(){return!!f.state("activated").get()&&(!f.state("trashing").get()&&"trash"!==f.state("changesetStatus").get()&&(""!==f.state("changesetStatus").get()||!f.state("saved").get()))},a.active.validate=h,g=function(){a.active.set(h())},f.state("activated").bind(g),f.state("trashing").bind(g),f.state("saved").bind(g),f.state("changesetStatus").bind(g),g(),d=function(){k.toggle(a.active.get()),i.toggleClass("has-next-sibling",a.active.get())},d(),a.active.bind(d),f.state("selectedChangesetStatus").bind(c),a.contentContainer.find(".customize-action").text(f.l10n.updating),a.contentContainer.find(".customize-section-back").removeAttr("tabindex"),k.prop("disabled",!1),k.on("click",function(b){b.preventDefault(),a.expanded.set(!a.expanded.get())}),a.expanded.bind(function(a){var d;return k.attr("aria-expanded",String(a)),k.toggleClass("active",a),a?void c():(d=f.state("changesetStatus").get(),""!==d&&"auto-draft"!==d||(d="publish"),void(f.state("selectedChangesetStatus").get()!==d?b():"future"===f.state("selectedChangesetStatus").get()&&f.state("selectedChangesetDate").get()!==f.state("changesetDate").get()&&b()))}),l=new f.Control("changeset_status",{priority:10,type:"radio",section:"publish_settings",setting:f.state("selectedChangesetStatus"),templateId:"customize-selected-changeset-status-control",label:f.l10n.action,choices:f.settings.changeset.statusChoices}),f.control.add(l),m=new f.DateTimeControl("changeset_scheduled_date",{priority:20,section:"publish_settings",setting:f.state("selectedChangesetDate"),minYear:(new Date).getFullYear(),allowPastDate:!1,includeTime:!0,twelveHourFormat:/a/i.test(f.settings.timeFormat),description:f.l10n.scheduleDescription}),m.notifications.alt=!0,f.control.add(m),o=function(){f.state("selectedChangesetStatus").set("publish"),f.previewer.save()},q=function(){var a="future"===f.state("changesetStatus").get()&&"future"===f.state("selectedChangesetStatus").get()&&f.state("changesetDate").get()&&f.state("selectedChangesetDate").get()===f.state("changesetDate").get()&&f.utils.getRemainingTime(f.state("changesetDate").get())>=0;a&&!p?p=setInterval(function(){var a=f.utils.getRemainingTime(f.state("changesetDate").get());f.state("remainingTimeToPublish").set(a),a<=0&&(clearInterval(p),p=0,o())},s):!a&&p&&(clearInterval(p),p=0)},f.state("changesetDate").bind(q),f.state("selectedChangesetDate").bind(q),f.state("changesetStatus").bind(q),f.state("selectedChangesetStatus").bind(q),q(),m.active.validate=function(){return"future"===f.state("selectedChangesetStatus").get()},n=function(a){m.active.set("future"===a)},n(f.state("selectedChangesetStatus").get()),f.state("selectedChangesetStatus").bind(n),f.state("saving").bind(function(a){a&&"future"===f.state("selectedChangesetStatus").get()&&m.toggleFutureDateNotification(!m.isFutureDate())})}),b("#customize-controls").on("keydown",function(a){var c=13===a.which,d=b(a.target);c&&(d.is("input:not([type=button])")||d.is("select"))&&a.preventDefault()}),b(".customize-info").find("> .accordion-section-title .customize-help-toggle").on("click",function(){var a=b(this).closest(".accordion-section"),c=a.find(".customize-panel-description:first");a.hasClass("cannot-expand")||(a.hasClass("open")?(a.toggleClass("open"),c.slideUp(f.Panel.prototype.defaultExpandedArguments.duration,function(){c.trigger("toggled")}),b(this).attr("aria-expanded",!1)):(c.slideDown(f.Panel.prototype.defaultExpandedArguments.duration,function(){c.trigger("toggled")}),a.toggleClass("open"),b(this).attr("aria-expanded",!0)))}),f.previewer=new f.Previewer({container:"#customize-preview",form:"#customize-controls",previewUrl:f.settings.url.preview,allowedUrls:f.settings.url.allowed},{nonce:f.settings.nonce,query:function(a){var b={wp_customize:"on",customize_theme:f.settings.theme.stylesheet,nonce:this.nonce.preview,customize_changeset_uuid:f.settings.changeset.uuid};return!f.settings.changeset.autosaved&&f.state("saved").get()||(b.customize_autosaved="on"),b.customized=JSON.stringify(f.dirtyValues({unsaved:a&&a.excludeCustomizedSaved})),b},save:function(a){function d(a){m[a.id]=!0}var e,g,h=this,i=b.Deferred(),j=f.state("selectedChangesetStatus").get(),k=f.state("selectedChangesetDate").get(),l=f.state("processing"),m={},n=[],o=[],p=[];return a&&a.status&&(j=a.status),f.state("saving").get()&&(i.reject("already_saving"),i.promise()),f.state("saving").set(!0),g=function(){var e,g,l={},q=f._latestRevision,r="client_side_error";return f.bind("change",d),f.notifications.remove(r),f.each(function(a){a.notifications.each(function(b){"error"!==b.type||b.fromServer||(n.push(a.id),l[a.id]||(l[a.id]={}),l[a.id][b.code]=b)})}),f.control.each(function(a){(!a.setting||!a.setting.id&&a.active.get())&&a.notifications.each(function(b){"error"===b.type&&p.push([a])})}),o=_.union(p,_.values(f.findControlsForSettings(n))),_.isEmpty(o)?(g=b.extend(h.query({excludeCustomizedSaved:!1}),{nonce:h.nonce.save,customize_changeset_status:j}),a&&a.date?g.customize_changeset_date=a.date:"future"===j&&k&&(g.customize_changeset_date=k),a&&a.title&&(g.customize_changeset_title=a.title),f.trigger("save-request-params",g),e=wp.ajax.post("customize_save",g),f.state("processing").set(f.state("processing").get()+1),f.trigger("save",e),e.always(function(){f.state("processing").set(f.state("processing").get()-1),f.state("saving").set(!1),f.unbind("change",d)}),f.notifications.each(function(a){a.saveFailure&&f.notifications.remove(a.code)}),e.fail(function(a){var b,d;d={type:"error",dismissible:!0,fromServer:!0,saveFailure:!0},"0"===a?a="not_logged_in":"-1"===a&&(a="invalid_nonce"),"invalid_nonce"===a?h.cheatin():"not_logged_in"===a?(h.preview.iframe.hide(),h.login().done(function(){h.save(),h.preview.iframe.show()})):a.code?"not_future_date"===a.code&&f.section.has("publish_settings")&&f.section("publish_settings").active.get()&&f.control.has("changeset_scheduled_date")?f.control("changeset_scheduled_date").toggleFutureDateNotification(!0).focus():"changeset_locked"!==a.code&&(b=new f.Notification(a.code,_.extend(d,{message:a.message}))):b=new f.Notification("unknown_error",_.extend(d,{message:f.l10n.unknownRequestFail})),b&&f.notifications.add(b),a.setting_validities&&f._handleSettingValidities({settingValidities:a.setting_validities,focusInvalidControl:!0}),i.rejectWith(h,[a]),f.trigger("error",a),"changeset_already_published"===a.code&&a.next_changeset_uuid&&(f.settings.changeset.uuid=a.next_changeset_uuid,f.state("changesetStatus").set(""),f.settings.changeset.branching&&c.send("changeset-uuid",f.settings.changeset.uuid),f.previewer.send("changeset-uuid",f.settings.changeset.uuid))}),void e.done(function(a){h.send("saved",a),f.state("changesetStatus").set(a.changeset_status),a.changeset_date&&f.state("changesetDate").set(a.changeset_date),"publish"===a.changeset_status&&(f.each(function(a){a._dirty&&(_.isUndefined(f._latestSettingRevisions[a.id])||f._latestSettingRevisions[a.id]<=q)&&(a._dirty=!1)}),f.state("changesetStatus").set(""),f.settings.changeset.uuid=a.next_changeset_uuid,f.settings.changeset.branching&&c.send("changeset-uuid",f.settings.changeset.uuid)),f._lastSavedRevision=Math.max(q,f._lastSavedRevision),a.setting_validities&&f._handleSettingValidities({settingValidities:a.setting_validities,focusInvalidControl:!0}),i.resolveWith(h,[a]),f.trigger("saved",a),_.isEmpty(m)||f.state("saved").set(!1)})):(o[0][0].focus(),f.unbind("change",d),n.length&&f.notifications.add(new f.Notification(r,{message:(1===n.length?f.l10n.saveBlockedError.singular:f.l10n.saveBlockedError.plural).replace(/%s/g,String(n.length)),type:"error",dismissible:!0,saveFailure:!0})),i.rejectWith(h,[{setting_invalidities:l}]),f.state("saving").set(!1),i.promise())},0===l()?g():(e=function(){0===l()&&(f.state.unbind("change",e),g())},f.state.bind("change",e)),i.promise()},trash:function(){var a,c,d;f.state("trashing").set(!0),f.state("processing").set(f.state("processing").get()+1),a=wp.ajax.post("customize_trash",{customize_changeset_uuid:f.settings.changeset.uuid,nonce:f.settings.nonce.trash}),f.notifications.add(new f.OverlayNotification("changeset_trashing",{type:"info",message:f.l10n.revertingChanges,loading:!0})),c=function(){var a,c=document.createElement("a");f.state("changesetStatus").set("trash"),f.each(function(a){a._dirty=!1}),f.state("saved").set(!0),c.href=location.href,a=f.utils.parseQueryString(c.search.substr(1)),delete a.changeset_uuid,a["return"]=f.settings.url["return"],c.search=b.param(a),location.replace(c.href)},d=function(a,b){var c=a||"unknown_error";f.state("processing").set(f.state("processing").get()-1),f.state("trashing").set(!1),f.notifications.remove("changeset_trashing"),f.notifications.add(new f.Notification(c,{message:b||f.l10n.unknownError,dismissible:!0,type:"error"}))},a.done(function(a){c(a.message)}),a.fail(function(a){var b=a.code||"trashing_failed";a.success||"non_existent_changeset"===b||"changeset_already_trashed"===b?c(a.message):d(b,a.message)})},getFrontendPreviewUrl:function(){var a,c,d=this;return c=document.createElement("a"),c.href=d.previewUrl.get(),a=f.utils.parseQueryString(c.search.substr(1)),f.state("changesetStatus").get()&&"publish"!==f.state("changesetStatus").get()&&(a.customize_changeset_uuid=f.settings.changeset.uuid),f.state("activated").get()||(a.customize_theme=f.settings.theme.stylesheet),c.search=b.param(a),c.href}}),b.ajaxPrefilter(function(a){/wp_customize=on/.test(a.data)&&(a.data+="&"+b.param({customize_preview_nonce:f.settings.nonce.preview}))}),f.previewer.bind("nonce",function(a){b.extend(this.nonce,a)}),f.bind("nonce-refresh",function(a){b.extend(f.settings.nonce,a),b.extend(f.previewer.nonce,a),f.previewer.send("nonce-refresh",a)}),b.each(f.settings.settings,function(a,b){var c=f.settingConstructor[b.type]||f.Setting;f.add(new c(a,b.value,{transport:b.transport,previewer:f.previewer,dirty:!!b.dirty}))}),b.each(f.settings.panels,function(a,b){var c,d=f.panelConstructor[b.type]||f.Panel;c=_.extend({params:b},b),f.panel.add(new d(a,c))}),b.each(f.settings.sections,function(a,b){var c,d=f.sectionConstructor[b.type]||f.Section;c=_.extend({params:b},b),f.section.add(new d(a,c))}),b.each(f.settings.controls,function(a,b){var c,d=f.controlConstructor[b.type]||f.Control;c=_.extend({params:b},b),f.control.add(new d(a,c))}),_.each(["panel","section","control"],function(a){var b=f.settings.autofocus[a];b&&f[a](b,function(a){a.deferred.embedded.done(function(){f.previewer.deferred.active.done(function(){a.focus()})})})}),f.bind("ready",f.reflowPaneContents),b([f.panel,f.section,f.control]).each(function(a,b){var c=_.debounce(f.reflowPaneContents,f.settings.timeouts.reflowPaneContents);b.bind("add",c),b.bind("change",c),b.bind("remove",c)}),f.bind("ready",function(){var a,c,d;f.notifications.container=b("#customize-notifications-area"),f.notifications.bind("change",_.debounce(function(){f.notifications.render()})),a=b(".wp-full-overlay-sidebar-content"),f.notifications.bind("rendered",function(){a.css("top",""),0!==f.notifications.count()&&(c=f.notifications.container.outerHeight()+1,d=parseInt(a.css("top"),10),a.css("top",d+c+"px")),f.notifications.trigger("sidebarTopUpdated")}),f.notifications.render()}),function(a){var c,e,g=a.instance("saved"),j=a.instance("saving"),k=a.instance("trashing"),l=a.instance("activated"),m=a.instance("processing"),n=a.instance("paneVisible"),o=a.instance("expandedPanel"),p=a.instance("expandedSection"),q=a.instance("changesetStatus"),r=a.instance("selectedChangesetStatus"),s=a.instance("changesetDate"),t=a.instance("selectedChangesetDate"),u=a.instance("previewerAlive"),v=a.instance("editShortcutVisibility"),w=a.instance("changesetLocked");a.bind("change",function(){var a;l()?""===q.get()&&g()?(f.settings.changeset.currentUserCanPublish?i.val(f.l10n.published):i.val(f.l10n.saved),h.find(".screen-reader-text").text(f.l10n.close)):("draft"===r()?g()&&r()===q()?i.val(f.l10n.draftSaved):i.val(f.l10n.saveDraft):"future"===r()?g()&&r()===q()?s.get()!==t.get()?i.val(f.l10n.schedule):i.val(f.l10n.scheduled):i.val(f.l10n.schedule):f.settings.changeset.currentUserCanPublish&&i.val(f.l10n.publish),h.find(".screen-reader-text").text(f.l10n.cancel)):(i.val(f.l10n.activate),h.find(".screen-reader-text").text(f.l10n.cancel)),a=!j()&&!k()&&!w()&&(!l()||!g()||q()!==r()&&""!==q()||"future"===r()&&s.get()!==t.get()),i.prop("disabled",!a)}),r.validate=function(a){return""===a||"auto-draft"===a?null:a},e=f.settings.changeset.currentUserCanPublish?"publish":"draft",q(f.settings.changeset.status),w(Boolean(f.settings.changeset.lockUser)),s(f.settings.changeset.publishDate),t(f.settings.changeset.publishDate),r(""===f.settings.changeset.status||"auto-draft"===f.settings.changeset.status?e:f.settings.changeset.status),r.link(q),g(!0),""===q()&&f.each(function(a){a._dirty&&g(!1)}),j(!1),l(f.settings.theme.active),m(0),n(!0),o(!1),p(!1),u(!0),v("visible"),f.bind("change",function(){a("saved").get()&&a("saved").set(!1)}),f.settings.changeset.branching&&g.bind(function(a){a||c(!0)}),j.bind(function(a){d.toggleClass("saving",a)}),k.bind(function(a){d.toggleClass("trashing",a)}),f.bind("saved",function(b){a("saved").set(!0),"publish"===b.changeset_status&&a("activated").set(!0)}),l.bind(function(a){a&&f.trigger("activated")}),c=function(a){var c,d;if(history.replaceState){if(c=document.createElement("a"),c.href=location.href,d=f.utils.parseQueryString(c.search.substr(1)),a){if(d.changeset_uuid===f.settings.changeset.uuid)return;d.changeset_uuid=f.settings.changeset.uuid}else{
+if(!d.changeset_uuid)return;delete d.changeset_uuid}c.search=b.param(d),history.replaceState({},document.title,c.href)}},f.settings.changeset.branching&&q.bind(function(a){c(""!==a&&"publish"!==a&&"trash"!==a)})}(f.state),function(){function a(a){a&&a.lockUser&&(f.settings.changeset.lockUser=a.lockUser),f.state("changesetLocked").set(!0),f.notifications.add(new c("changeset_locked",{lockUser:f.settings.changeset.lockUser,allowOverride:Boolean(a&&a.allowOverride)}))}var c=f.OverlayNotification.extend({templateId:"customize-changeset-locked-notification",lockUser:null,initialize:function(a,b){var c,d,e=this;c=a||"changeset_locked",d=_.extend({type:"warning",containerClasses:"",lockUser:{}},b),d.containerClasses+=" notification-changeset-locked",f.OverlayNotification.prototype.initialize.call(e,c,d)},render:function(){var a,b,c,d,e=this;return b=_.extend({allowOverride:!1,returnUrl:f.settings.url["return"],previewUrl:f.previewer.previewUrl.get(),frontendPreviewUrl:f.previewer.getFrontendPreviewUrl()},this),a=f.OverlayNotification.prototype.render.call(b),f.requestChangesetUpdate({},{autosave:!0}).fail(function(b){b.autosaved||a.find(".notice-error").prop("hidden",!1).text(b.message||f.l10n.unknownRequestFail)}),c=a.find(".customize-notice-take-over-button"),c.on("click",function(b){b.preventDefault(),d||(c.addClass("disabled"),d=wp.ajax.post("customize_override_changeset_lock",{wp_customize:"on",customize_theme:f.settings.theme.stylesheet,customize_changeset_uuid:f.settings.changeset.uuid,nonce:f.settings.nonce.override_lock}),d.done(function(){f.notifications.remove(e.code),f.state("changesetLocked").set(!1)}),d.fail(function(b){var e=b.message||f.l10n.unknownRequestFail;a.find(".notice-error").prop("hidden",!1).text(e),d.always(function(){c.removeClass("disabled")})}),d.always(function(){d=null}))}),a}});f.settings.changeset.lockUser&&a({allowOverride:!0}),b(document).on("heartbeat-send.update_lock_notice",function(a,b){b.check_changeset_lock=!0,b.changeset_uuid=f.settings.changeset.uuid}),b(document).on("heartbeat-tick.update_lock_notice",function(b,c){var d,e="changeset_locked";c.customize_changeset_lock_user&&(d=f.notifications(e),d&&d.lockUser.id!==f.settings.changeset.lockUser.id&&f.notifications.remove(e),a({lockUser:c.customize_changeset_lock_user}))}),f.bind("error",function(b){"changeset_locked"===b.code&&b.lock_user&&a({lockUser:b.lock_user})})}(),function(){function a(){var a,c;return a=document.createElement("a"),a.href=location.href,c=f.utils.parseQueryString(a.search.substr(1)),f.settings.changeset.latestAutoDraftUuid?c.changeset_uuid=f.settings.changeset.latestAutoDraftUuid:c.customize_autosaved="on",c["return"]=f.settings.url["return"],a.search=b.param(c),a.href}function c(a){var c,d=document.createElement("a"),e=0;d.href=location.href,c=f.utils.parseQueryString(d.search.substr(1)),_.each(a,function(a){"undefined"!=typeof c[a]&&(e+=1,delete c[a])}),0!==e&&(d.search=b.param(c),history.replaceState({},document.title,d.href))}function d(){h||(wp.ajax.post("customize_dismiss_autosave_or_lock",{wp_customize:"on",customize_theme:f.settings.theme.stylesheet,customize_changeset_uuid:f.settings.changeset.uuid,nonce:f.settings.nonce.dismiss_autosave_or_lock,dismiss_autosave:!0}),h=!0)}function e(){var b,c="autosave_available";f.notifications.add(new f.Notification(c,{message:f.l10n.autosaveNotice,type:"warning",dismissible:!0,render:function(){var b,c=f.Notification.prototype.render.call(this);return b=c.find("a"),b.prop("href",a()),b.on("click",function(b){b.preventDefault(),location.replace(a())}),c.find(".notice-dismiss").on("click",d),c}})),b=function(){d(),f.notifications.remove(c),f.unbind("change",b),f.state("changesetStatus").unbind(b)},f.bind("change",b),f.state("changesetStatus").bind(b)}var g=[],h=!1;f.settings.changeset.autosaved&&(f.state("saved").set(!1),g.push("customize_autosaved")),f.settings.changeset.branching||f.settings.changeset.status&&"auto-draft"!==f.settings.changeset.status||g.push("changeset_uuid"),g.length>0&&c(g),(f.settings.changeset.latestAutoDraftUuid||f.settings.changeset.hasAutosaveRevision)&&e()}(),f.previewer.previewUrl()?f.previewer.refresh():f.previewer.previewUrl(f.settings.url.home),i.click(function(a){f.previewer.save(),a.preventDefault()}).keydown(function(a){9!==a.which&&(13===a.which&&f.previewer.save(),a.preventDefault())}),h.keydown(function(a){9!==a.which&&(13===a.which&&this.click(),a.preventDefault())}),b(".collapse-sidebar").on("click",function(){f.state("paneVisible").set(!f.state("paneVisible").get())}),f.state("paneVisible").bind(function(a){e.toggleClass("preview-only",!a),e.toggleClass("expanded",a),e.toggleClass("collapsed",!a),a?b(".collapse-sidebar").attr({"aria-expanded":"true","aria-label":f.l10n.collapseSidebar}):b(".collapse-sidebar").attr({"aria-expanded":"false","aria-label":f.l10n.expandSidebar})}),d.on("keydown",function(a){var c,e=[],g=[],h=[];if(27===a.which&&(b(a.target).is("body")||b.contains(b("#customize-controls")[0],a.target))&&(f.control.each(function(a){a.expanded&&a.expanded()&&_.isFunction(a.collapse)&&e.push(a)}),f.section.each(function(a){a.expanded()&&g.push(a)}),f.panel.each(function(a){a.expanded()&&h.push(a)}),e.length>0&&0===g.length&&(e.length=0),c=e[0]||g[0]||h[0])){if("themes"===c.params.type)return void(d.hasClass("modal-open")?c.closeDetails():f.panel.has("themes")&&f.panel("themes").collapse());c.collapse(),a.preventDefault()}}),b(".customize-controls-preview-toggle").on("click",function(){f.state("paneVisible").set(!f.state("paneVisible").get())}),function(){var a,c,d,e,g,h,i,j=b(".wp-full-overlay-sidebar-content");a=function(a){var b,g=a,i=f.state("expandedSection").get(),j=f.state("expandedPanel").get();if(h&&h.element&&(d(h.element),h.element.find(".description").off("toggled",c)),!g)if(!i&&j&&j.contentContainer)g=j;else{if(j||!i||!i.contentContainer)return void(h=!1);g=i}b=g.contentContainer.find(".customize-section-title, .panel-meta").first(),b.length?(h={instance:g,element:b,parent:b.closest(".customize-pane-child"),height:b.outerHeight()},h.element.find(".description").on("toggled",c),i&&e(h.element,h.parent)):h=!1},f.state("expandedSection").bind(a),f.state("expandedPanel").bind(a),j.on("scroll",_.throttle(function(){if(h){var a,b=j.scrollTop();a=i?b===i?0:b>i?1:-1:1,i=b,0!==a&&g(h,b,a)}},8)),f.notifications.bind("sidebarTopUpdated",function(){h&&h.element.hasClass("is-sticky")&&h.element.css("top",j.css("top"))}),d=function(a){a.hasClass("is-sticky")&&a.removeClass("is-sticky").addClass("maybe-sticky is-in-view").css("top",j.scrollTop()+"px")},e=function(a,b){a.hasClass("is-in-view")&&(a.removeClass("maybe-sticky is-in-view").css({width:"",top:""}),b.css("padding-top",""))},c=function(){h.height=h.element.outerHeight()},g=function(a,b,c){var d=a.element,e=a.parent,f=a.height,g=parseInt(d.css("top"),10),h=d.hasClass("maybe-sticky"),i=d.hasClass("is-sticky"),k=d.hasClass("is-in-view"),l=-1===c;if(!l)return i&&(g=b,d.removeClass("is-sticky").css({top:g+"px",width:""})),void(k&&b>g+f&&(d.removeClass("is-in-view"),e.css("padding-top","")));if(!h&&b>=f)h=!0,d.addClass("maybe-sticky");else if(0===b)return d.removeClass("maybe-sticky is-in-view is-sticky").css({top:"",width:""}),void e.css("padding-top","");k&&!i?g>=b&&d.addClass("is-sticky").css({top:j.css("top"),width:e.outerWidth()+"px"}):h&&!k&&(d.addClass("is-in-view").css("top",b-f+"px"),e.css("padding-top",f+"px"))}}(),f.previewedDevice=f.state("previewedDevice"),f.bind("ready",function(){_.find(f.settings.previewableDevices,function(a,b){if(!0===a["default"])return f.previewedDevice.set(b),!0})}),l.find(".devices button").on("click",function(a){f.previewedDevice.set(b(a.currentTarget).data("device"))}),f.previewedDevice.bind(function(a){var c=b(".wp-full-overlay"),d="";l.find(".devices button").removeClass("active").attr("aria-pressed",!1),l.find(".devices .preview-"+a).addClass("active").attr("aria-pressed",!0),b.each(f.settings.previewableDevices,function(a){d+=" preview-"+a}),c.removeClass(d).addClass("preview-"+a)}),g.length&&f("blogname",function(a){var c=function(){g.text(b.trim(a())||f.l10n.untitledBlogName)};a.bind(c),c()}),c=new f.Messenger({url:f.settings.url.parent,channel:"loader"}),function(){function a(){var a;return f.state("activated").get()?(a=f.state("changesetStatus").get(),""!==a&&"auto-draft"!==a||(a="publish"),f.state("selectedChangesetStatus").get()===a&&(("future"!==f.state("selectedChangesetStatus").get()||f.state("selectedChangesetDate").get()===f.state("changesetDate").get())&&(f.state("saved").get()&&"auto-draft"!==f.state("changesetStatus").get()))):0===f._latestRevision}function d(){f.unbind("change",d),f.state("selectedChangesetStatus").unbind(d),f.state("selectedChangesetDate").unbind(d),b(window).on("beforeunload.customize-confirm",function(){if(!a()&&!f.state("changesetLocked").get())return setTimeout(function(){e.removeClass("customize-loading")},1),f.l10n.saveAlert})}function g(){var c=b.Deferred(),d=!1,e=!1;return a()?e=!0:confirm(f.l10n.saveAlert)?(e=!0,f.each(function(a){a._dirty=!1}),b(document).off("visibilitychange.wp-customize-changeset-update"),b(window).off("beforeunload.wp-customize-changeset-update"),h.css("cursor","progress"),""!==f.state("changesetStatus").get()&&(d=!0)):c.reject(),(e||d)&&wp.ajax.send("customize_dismiss_autosave_or_lock",{timeout:500,data:{wp_customize:"on",customize_theme:f.settings.theme.stylesheet,customize_changeset_uuid:f.settings.changeset.uuid,nonce:f.settings.nonce.dismiss_autosave_or_lock,dismiss_autosave:d,dismiss_lock:e}}).always(function(){c.resolve()}),c.promise()}var i=!1;c.bind("back",function(){i=!0}),f.bind("change",d),f.state("selectedChangesetStatus").bind(d),f.state("selectedChangesetDate").bind(d),c.bind("confirm-close",function(){g().done(function(){c.send("confirmed-close",!0)}).fail(function(){c.send("confirmed-close",!1)})}),h.on("click.customize-controls-close",function(a){a.preventDefault(),i?c.send("close"):g().done(function(){b(window).off("beforeunload.customize-confirm"),window.location.href=h.prop("href")})})}(),b.each(["saved","change"],function(a,b){f.bind(b,function(){c.send(b)})}),f.bind("title",function(a){c.send("title",a)}),f.settings.changeset.branching&&c.send("changeset-uuid",f.settings.changeset.uuid),c.send("ready"),b.each({background_image:{controls:["background_preset","background_position","background_size","background_repeat","background_attachment"],callback:function(a){return!!a}},show_on_front:{controls:["page_on_front","page_for_posts"],callback:function(a){return"page"===a}},header_textcolor:{controls:["header_textcolor"],callback:function(a){return"blank"!==a}}},function(a,c){f(a,function(a){b.each(c.controls,function(b,d){f.control(d,function(b){var d=function(a){b.container.toggle(c.callback(a))};d(a.get()),a.bind(d)})})})}),f.control("background_preset",function(a){var b,c,d,e,g,h;b={"default":[!1,!1,!1,!1],fill:[!0,!1,!1,!1],fit:[!0,!1,!0,!1],repeat:[!0,!1,!1,!0],custom:[!0,!0,!0,!0]},c=[_wpCustomizeBackground.defaults["default-position-x"],_wpCustomizeBackground.defaults["default-position-y"],_wpCustomizeBackground.defaults["default-size"],_wpCustomizeBackground.defaults["default-repeat"],_wpCustomizeBackground.defaults["default-attachment"]],d={"default":c,fill:["left","top","cover","no-repeat","fixed"],fit:["left","top","contain","no-repeat","fixed"],repeat:["left","top","auto","repeat","scroll"]},e=function(a){_.each(["background_position","background_size","background_repeat","background_attachment"],function(c,d){var e=f.control(c);e&&e.container.toggle(b[a][d])})},g=function(a){_.each(["background_position_x","background_position_y","background_size","background_repeat","background_attachment"],function(b,c){var e=f(b);e&&e.set(d[a][c])})},h=a.setting.get(),e(h),a.setting.bind("change",function(a){e(a),"custom"!==a&&g(a)})}),f.control("background_repeat",function(a){a.elements[0].unsync(f("background_repeat")),a.element=new f.Element(a.container.find("input")),a.element.set("no-repeat"!==a.setting()),a.element.bind(function(b){a.setting.set(b?"repeat":"no-repeat")}),a.setting.bind(function(b){a.element.set("no-repeat"!==b)})}),f.control("background_attachment",function(a){a.elements[0].unsync(f("background_attachment")),a.element=new f.Element(a.container.find("input")),a.element.set("fixed"!==a.setting()),a.element.bind(function(b){a.setting.set(b?"scroll":"fixed")}),a.setting.bind(function(b){a.element.set("fixed"!==b)})}),f.control("display_header_text",function(a){var b="";a.elements[0].unsync(f("header_textcolor")),a.element=new f.Element(a.container.find("input")),a.element.set("blank"!==a.setting()),a.element.bind(function(c){c||(b=f("header_textcolor").get()),a.setting.set(c?b:"blank")}),a.setting.bind(function(b){a.element.set("blank"!==b)})}),f("show_on_front","page_on_front","page_for_posts",function(a,b,c){var d=function(){var d,e,g=this,h="show_on_front_page_collision";d=parseInt(b(),10),e=parseInt(c(),10),"page"===a()&&(g===b&&d>0&&f.previewer.previewUrl.set(f.settings.url.home),g===c&&e>0&&f.previewer.previewUrl.set(f.settings.url.home+"?page_id="+e)),"page"===a()&&d&&e&&d===e?a.notifications.add(new f.Notification(h,{type:"error",message:f.l10n.pageOnFrontError})):a.notifications.remove(h)};a.bind(d),b.bind(d),c.bind(d),d.call(a,a()),f.control("show_on_front",function(a){a.deferred.embedded.done(function(){a.container.append(a.getNotificationsContainerElement())})})}),function(){var a=b.Deferred();f.section("custom_css",function(b){b.deferred.embedded.done(function(){b.expanded()?a.resolve(b):b.expanded.bind(function(c){c&&a.resolve(b)})})}),a.done(function(a){var b=f.control("custom_css");b.container.find(".customize-control-title:first").addClass("screen-reader-text"),a.container.find(".section-description-buttons .section-description-close").on("click",function(){a.container.find(".section-meta .customize-section-description:first").removeClass("open").slideUp(),a.container.find(".customize-help-toggle").attr("aria-expanded","false").focus()}),b&&!b.setting.get()&&(a.container.find(".section-meta .customize-section-description:first").addClass("open").show().trigger("toggled"),a.container.find(".customize-help-toggle").attr("aria-expanded","true"))})}(),f.control("header_video",function(a){a.deferred.embedded.done(function(){var b=function(){var b=f.section(a.section()),c="video_header_not_available";b&&(a.active.get()?b.notifications.remove(c):b.notifications.add(new f.Notification(c,{type:"info",message:f.l10n.videoHeaderNotice})))};b(),a.active.bind(b)})}),f.previewer.bind("selective-refresh-setting-validities",function(a){f._handleSettingValidities({settingValidities:a,focusInvalidControl:!1})}),f.previewer.bind("focus-control-for-setting",function(a){var b=[];f.control.each(function(c){var d=_.pluck(c.settings,"id");-1!==_.indexOf(d,a)&&b.push(c)}),b.length&&(b.sort(function(a,b){return a.priority()-b.priority()}),b[0].focus())}),f.previewer.bind("refresh",function(){f.previewer.refresh()}),f.state("paneVisible").bind(function(a){var c;c=window.matchMedia?window.matchMedia("screen and ( max-width: 640px )").matches:b(window).width()<=640,f.state("editShortcutVisibility").set(a||c?"visible":"hidden")}),window.matchMedia&&window.matchMedia("screen and ( max-width: 640px )").addListener(function(){var a=f.state("paneVisible");a.callbacks.fireWith(a,[a.get(),a.get()])}),f.previewer.bind("edit-shortcut-visibility",function(a){f.state("editShortcutVisibility").set(a)}),f.state("editShortcutVisibility").bind(function(a){f.previewer.send("edit-shortcut-visibility",a)}),f.bind("change",a),b(document).one("tinymce-editor-setup",function(){window.tinymce.ui.FloatPanel&&(!window.tinymce.ui.FloatPanel.zIndex||window.tinymce.ui.FloatPanel.zIndex<500001)&&(window.tinymce.ui.FloatPanel.zIndex=500001)}),d.addClass("ready"),f.trigger("ready")}})}(wp,jQuery); \ No newline at end of file
diff --git a/www/crm/wp-admin/js/customize-nav-menus.js b/www/crm/wp-admin/js/customize-nav-menus.js
new file mode 100644
index 00000000..19962019
--- /dev/null
+++ b/www/crm/wp-admin/js/customize-nav-menus.js
@@ -0,0 +1,3464 @@
+/**
+ * @output wp-admin/js/customize-nav-menus.js
+ */
+
+/* global _wpCustomizeNavMenusSettings, wpNavMenu, console */
+( function( api, wp, $ ) {
+ 'use strict';
+
+ /**
+ * Set up wpNavMenu for drag and drop.
+ */
+ wpNavMenu.originalInit = wpNavMenu.init;
+ wpNavMenu.options.menuItemDepthPerLevel = 20;
+ wpNavMenu.options.sortableItems = '> .customize-control-nav_menu_item';
+ wpNavMenu.options.targetTolerance = 10;
+ wpNavMenu.init = function() {
+ this.jQueryExtensions();
+ };
+
+ /**
+ * @namespace wp.customize.Menus
+ */
+ api.Menus = api.Menus || {};
+
+ // Link settings.
+ api.Menus.data = {
+ itemTypes: [],
+ l10n: {},
+ settingTransport: 'refresh',
+ phpIntMax: 0,
+ defaultSettingValues: {
+ nav_menu: {},
+ nav_menu_item: {}
+ },
+ locationSlugMappedToName: {}
+ };
+ if ( 'undefined' !== typeof _wpCustomizeNavMenusSettings ) {
+ $.extend( api.Menus.data, _wpCustomizeNavMenusSettings );
+ }
+
+ /**
+ * Newly-created Nav Menus and Nav Menu Items have negative integer IDs which
+ * serve as placeholders until Save & Publish happens.
+ *
+ * @alias wp.customize.Menus.generatePlaceholderAutoIncrementId
+ *
+ * @return {number}
+ */
+ api.Menus.generatePlaceholderAutoIncrementId = function() {
+ return -Math.ceil( api.Menus.data.phpIntMax * Math.random() );
+ };
+
+ /**
+ * wp.customize.Menus.AvailableItemModel
+ *
+ * A single available menu item model. See PHP's WP_Customize_Nav_Menu_Item_Setting class.
+ *
+ * @class wp.customize.Menus.AvailableItemModel
+ * @augments Backbone.Model
+ */
+ api.Menus.AvailableItemModel = Backbone.Model.extend( $.extend(
+ {
+ id: null // This is only used by Backbone.
+ },
+ api.Menus.data.defaultSettingValues.nav_menu_item
+ ) );
+
+ /**
+ * wp.customize.Menus.AvailableItemCollection
+ *
+ * Collection for available menu item models.
+ *
+ * @class wp.customize.Menus.AvailableItemCollection
+ * @augments Backbone.Collection
+ */
+ api.Menus.AvailableItemCollection = Backbone.Collection.extend(/** @lends wp.customize.Menus.AvailableItemCollection.prototype */{
+ model: api.Menus.AvailableItemModel,
+
+ sort_key: 'order',
+
+ comparator: function( item ) {
+ return -item.get( this.sort_key );
+ },
+
+ sortByField: function( fieldName ) {
+ this.sort_key = fieldName;
+ this.sort();
+ }
+ });
+ api.Menus.availableMenuItems = new api.Menus.AvailableItemCollection( api.Menus.data.availableMenuItems );
+
+ /**
+ * Insert a new `auto-draft` post.
+ *
+ * @since 4.7.0
+ * @alias wp.customize.Menus.insertAutoDraftPost
+ *
+ * @param {object} params - Parameters for the draft post to create.
+ * @param {string} params.post_type - Post type to add.
+ * @param {string} params.post_title - Post title to use.
+ * @return {jQuery.promise} Promise resolved with the added post.
+ */
+ api.Menus.insertAutoDraftPost = function insertAutoDraftPost( params ) {
+ var request, deferred = $.Deferred();
+
+ request = wp.ajax.post( 'customize-nav-menus-insert-auto-draft', {
+ 'customize-menus-nonce': api.settings.nonce['customize-menus'],
+ 'wp_customize': 'on',
+ 'customize_changeset_uuid': api.settings.changeset.uuid,
+ 'params': params
+ } );
+
+ request.done( function( response ) {
+ if ( response.post_id ) {
+ api( 'nav_menus_created_posts' ).set(
+ api( 'nav_menus_created_posts' ).get().concat( [ response.post_id ] )
+ );
+
+ if ( 'page' === params.post_type ) {
+
+ // Activate static front page controls as this could be the first page created.
+ if ( api.section.has( 'static_front_page' ) ) {
+ api.section( 'static_front_page' ).activate();
+ }
+
+ // Add new page to dropdown-pages controls.
+ api.control.each( function( control ) {
+ var select;
+ if ( 'dropdown-pages' === control.params.type ) {
+ select = control.container.find( 'select[name^="_customize-dropdown-pages-"]' );
+ select.append( new Option( params.post_title, response.post_id ) );
+ }
+ } );
+ }
+ deferred.resolve( response );
+ }
+ } );
+
+ request.fail( function( response ) {
+ var error = response || '';
+
+ if ( 'undefined' !== typeof response.message ) {
+ error = response.message;
+ }
+
+ console.error( error );
+ deferred.rejectWith( error );
+ } );
+
+ return deferred.promise();
+ };
+
+ api.Menus.AvailableMenuItemsPanelView = wp.Backbone.View.extend(/** @lends wp.customize.Menus.AvailableMenuItemsPanelView.prototype */{
+
+ el: '#available-menu-items',
+
+ events: {
+ 'input #menu-items-search': 'debounceSearch',
+ 'focus .menu-item-tpl': 'focus',
+ 'click .menu-item-tpl': '_submit',
+ 'click #custom-menu-item-submit': '_submitLink',
+ 'keypress #custom-menu-item-name': '_submitLink',
+ 'click .new-content-item .add-content': '_submitNew',
+ 'keypress .create-item-input': '_submitNew',
+ 'keydown': 'keyboardAccessible'
+ },
+
+ // Cache current selected menu item.
+ selected: null,
+
+ // Cache menu control that opened the panel.
+ currentMenuControl: null,
+ debounceSearch: null,
+ $search: null,
+ $clearResults: null,
+ searchTerm: '',
+ rendered: false,
+ pages: {},
+ sectionContent: '',
+ loading: false,
+ addingNew: false,
+
+ /**
+ * wp.customize.Menus.AvailableMenuItemsPanelView
+ *
+ * View class for the available menu items panel.
+ *
+ * @constructs wp.customize.Menus.AvailableMenuItemsPanelView
+ * @augments wp.Backbone.View
+ */
+ initialize: function() {
+ var self = this;
+
+ if ( ! api.panel.has( 'nav_menus' ) ) {
+ return;
+ }
+
+ this.$search = $( '#menu-items-search' );
+ this.$clearResults = this.$el.find( '.clear-results' );
+ this.sectionContent = this.$el.find( '.available-menu-items-list' );
+
+ this.debounceSearch = _.debounce( self.search, 500 );
+
+ _.bindAll( this, 'close' );
+
+ // If the available menu items panel is open and the customize controls are
+ // interacted with (other than an item being deleted), then close the
+ // available menu items panel. Also close on back button click.
+ $( '#customize-controls, .customize-section-back' ).on( 'click keydown', function( e ) {
+ var isDeleteBtn = $( e.target ).is( '.item-delete, .item-delete *' ),
+ isAddNewBtn = $( e.target ).is( '.add-new-menu-item, .add-new-menu-item *' );
+ if ( $( 'body' ).hasClass( 'adding-menu-items' ) && ! isDeleteBtn && ! isAddNewBtn ) {
+ self.close();
+ }
+ } );
+
+ // Clear the search results and trigger a `keyup` event to fire a new search.
+ this.$clearResults.on( 'click', function() {
+ self.$search.val( '' ).focus().trigger( 'keyup' );
+ } );
+
+ this.$el.on( 'input', '#custom-menu-item-name.invalid, #custom-menu-item-url.invalid', function() {
+ $( this ).removeClass( 'invalid' );
+ });
+
+ // Load available items if it looks like we'll need them.
+ api.panel( 'nav_menus' ).container.bind( 'expanded', function() {
+ if ( ! self.rendered ) {
+ self.initList();
+ self.rendered = true;
+ }
+ });
+
+ // Load more items.
+ this.sectionContent.scroll( function() {
+ var totalHeight = self.$el.find( '.accordion-section.open .available-menu-items-list' ).prop( 'scrollHeight' ),
+ visibleHeight = self.$el.find( '.accordion-section.open' ).height();
+
+ if ( ! self.loading && $( this ).scrollTop() > 3 / 4 * totalHeight - visibleHeight ) {
+ var type = $( this ).data( 'type' ),
+ object = $( this ).data( 'object' );
+
+ if ( 'search' === type ) {
+ if ( self.searchTerm ) {
+ self.doSearch( self.pages.search );
+ }
+ } else {
+ self.loadItems( [
+ { type: type, object: object }
+ ] );
+ }
+ }
+ });
+
+ // Close the panel if the URL in the preview changes
+ api.previewer.bind( 'url', this.close );
+
+ self.delegateEvents();
+ },
+
+ // Search input change handler.
+ search: function( event ) {
+ var $searchSection = $( '#available-menu-items-search' ),
+ $otherSections = $( '#available-menu-items .accordion-section' ).not( $searchSection );
+
+ if ( ! event ) {
+ return;
+ }
+
+ if ( this.searchTerm === event.target.value ) {
+ return;
+ }
+
+ if ( '' !== event.target.value && ! $searchSection.hasClass( 'open' ) ) {
+ $otherSections.fadeOut( 100 );
+ $searchSection.find( '.accordion-section-content' ).slideDown( 'fast' );
+ $searchSection.addClass( 'open' );
+ this.$clearResults.addClass( 'is-visible' );
+ } else if ( '' === event.target.value ) {
+ $searchSection.removeClass( 'open' );
+ $otherSections.show();
+ this.$clearResults.removeClass( 'is-visible' );
+ }
+
+ this.searchTerm = event.target.value;
+ this.pages.search = 1;
+ this.doSearch( 1 );
+ },
+
+ // Get search results.
+ doSearch: function( page ) {
+ var self = this, params,
+ $section = $( '#available-menu-items-search' ),
+ $content = $section.find( '.accordion-section-content' ),
+ itemTemplate = wp.template( 'available-menu-item' );
+
+ if ( self.currentRequest ) {
+ self.currentRequest.abort();
+ }
+
+ if ( page < 0 ) {
+ return;
+ } else if ( page > 1 ) {
+ $section.addClass( 'loading-more' );
+ $content.attr( 'aria-busy', 'true' );
+ wp.a11y.speak( api.Menus.data.l10n.itemsLoadingMore );
+ } else if ( '' === self.searchTerm ) {
+ $content.html( '' );
+ wp.a11y.speak( '' );
+ return;
+ }
+
+ $section.addClass( 'loading' );
+ self.loading = true;
+
+ params = api.previewer.query( { excludeCustomizedSaved: true } );
+ _.extend( params, {
+ 'customize-menus-nonce': api.settings.nonce['customize-menus'],
+ 'wp_customize': 'on',
+ 'search': self.searchTerm,
+ 'page': page
+ } );
+
+ self.currentRequest = wp.ajax.post( 'search-available-menu-items-customizer', params );
+
+ self.currentRequest.done(function( data ) {
+ var items;
+ if ( 1 === page ) {
+ // Clear previous results as it's a new search.
+ $content.empty();
+ }
+ $section.removeClass( 'loading loading-more' );
+ $content.attr( 'aria-busy', 'false' );
+ $section.addClass( 'open' );
+ self.loading = false;
+ items = new api.Menus.AvailableItemCollection( data.items );
+ self.collection.add( items.models );
+ items.each( function( menuItem ) {
+ $content.append( itemTemplate( menuItem.attributes ) );
+ } );
+ if ( 20 > items.length ) {
+ self.pages.search = -1; // Up to 20 posts and 20 terms in results, if <20, no more results for either.
+ } else {
+ self.pages.search = self.pages.search + 1;
+ }
+ if ( items && page > 1 ) {
+ wp.a11y.speak( api.Menus.data.l10n.itemsFoundMore.replace( '%d', items.length ) );
+ } else if ( items && page === 1 ) {
+ wp.a11y.speak( api.Menus.data.l10n.itemsFound.replace( '%d', items.length ) );
+ }
+ });
+
+ self.currentRequest.fail(function( data ) {
+ // data.message may be undefined, for example when typing slow and the request is aborted.
+ if ( data.message ) {
+ $content.empty().append( $( '<li class="nothing-found"></li>' ).text( data.message ) );
+ wp.a11y.speak( data.message );
+ }
+ self.pages.search = -1;
+ });
+
+ self.currentRequest.always(function() {
+ $section.removeClass( 'loading loading-more' );
+ $content.attr( 'aria-busy', 'false' );
+ self.loading = false;
+ self.currentRequest = null;
+ });
+ },
+
+ // Render the individual items.
+ initList: function() {
+ var self = this;
+
+ // Render the template for each item by type.
+ _.each( api.Menus.data.itemTypes, function( itemType ) {
+ self.pages[ itemType.type + ':' + itemType.object ] = 0;
+ } );
+ self.loadItems( api.Menus.data.itemTypes );
+ },
+
+ /**
+ * Load available nav menu items.
+ *
+ * @since 4.3.0
+ * @since 4.7.0 Changed function signature to take list of item types instead of single type/object.
+ * @access private
+ *
+ * @param {Array.<object>} itemTypes List of objects containing type and key.
+ * @param {string} deprecated Formerly the object parameter.
+ * @returns {void}
+ */
+ loadItems: function( itemTypes, deprecated ) {
+ var self = this, _itemTypes, requestItemTypes = [], params, request, itemTemplate, availableMenuItemContainers = {};
+ itemTemplate = wp.template( 'available-menu-item' );
+
+ if ( _.isString( itemTypes ) && _.isString( deprecated ) ) {
+ _itemTypes = [ { type: itemTypes, object: deprecated } ];
+ } else {
+ _itemTypes = itemTypes;
+ }
+
+ _.each( _itemTypes, function( itemType ) {
+ var container, name = itemType.type + ':' + itemType.object;
+ if ( -1 === self.pages[ name ] ) {
+ return; // Skip types for which there are no more results.
+ }
+ container = $( '#available-menu-items-' + itemType.type + '-' + itemType.object );
+ container.find( '.accordion-section-title' ).addClass( 'loading' );
+ availableMenuItemContainers[ name ] = container;
+
+ requestItemTypes.push( {
+ object: itemType.object,
+ type: itemType.type,
+ page: self.pages[ name ]
+ } );
+ } );
+
+ if ( 0 === requestItemTypes.length ) {
+ return;
+ }
+
+ self.loading = true;
+
+ params = api.previewer.query( { excludeCustomizedSaved: true } );
+ _.extend( params, {
+ 'customize-menus-nonce': api.settings.nonce['customize-menus'],
+ 'wp_customize': 'on',
+ 'item_types': requestItemTypes
+ } );
+
+ request = wp.ajax.post( 'load-available-menu-items-customizer', params );
+
+ request.done(function( data ) {
+ var typeInner;
+ _.each( data.items, function( typeItems, name ) {
+ if ( 0 === typeItems.length ) {
+ if ( 0 === self.pages[ name ] ) {
+ availableMenuItemContainers[ name ].find( '.accordion-section-title' )
+ .addClass( 'cannot-expand' )
+ .removeClass( 'loading' )
+ .find( '.accordion-section-title > button' )
+ .prop( 'tabIndex', -1 );
+ }
+ self.pages[ name ] = -1;
+ return;
+ } else if ( ( 'post_type:page' === name ) && ( ! availableMenuItemContainers[ name ].hasClass( 'open' ) ) ) {
+ availableMenuItemContainers[ name ].find( '.accordion-section-title > button' ).click();
+ }
+ typeItems = new api.Menus.AvailableItemCollection( typeItems ); // @todo Why is this collection created and then thrown away?
+ self.collection.add( typeItems.models );
+ typeInner = availableMenuItemContainers[ name ].find( '.available-menu-items-list' );
+ typeItems.each( function( menuItem ) {
+ typeInner.append( itemTemplate( menuItem.attributes ) );
+ } );
+ self.pages[ name ] += 1;
+ });
+ });
+ request.fail(function( data ) {
+ if ( typeof console !== 'undefined' && console.error ) {
+ console.error( data );
+ }
+ });
+ request.always(function() {
+ _.each( availableMenuItemContainers, function( container ) {
+ container.find( '.accordion-section-title' ).removeClass( 'loading' );
+ } );
+ self.loading = false;
+ });
+ },
+
+ // Adjust the height of each section of items to fit the screen.
+ itemSectionHeight: function() {
+ var sections, lists, totalHeight, accordionHeight, diff;
+ totalHeight = window.innerHeight;
+ sections = this.$el.find( '.accordion-section:not( #available-menu-items-search ) .accordion-section-content' );
+ lists = this.$el.find( '.accordion-section:not( #available-menu-items-search ) .available-menu-items-list:not(":only-child")' );
+ accordionHeight = 46 * ( 1 + sections.length ) + 14; // Magic numbers.
+ diff = totalHeight - accordionHeight;
+ if ( 120 < diff && 290 > diff ) {
+ sections.css( 'max-height', diff );
+ lists.css( 'max-height', ( diff - 60 ) );
+ }
+ },
+
+ // Highlights a menu item.
+ select: function( menuitemTpl ) {
+ this.selected = $( menuitemTpl );
+ this.selected.siblings( '.menu-item-tpl' ).removeClass( 'selected' );
+ this.selected.addClass( 'selected' );
+ },
+
+ // Highlights a menu item on focus.
+ focus: function( event ) {
+ this.select( $( event.currentTarget ) );
+ },
+
+ // Submit handler for keypress and click on menu item.
+ _submit: function( event ) {
+ // Only proceed with keypress if it is Enter or Spacebar
+ if ( 'keypress' === event.type && ( 13 !== event.which && 32 !== event.which ) ) {
+ return;
+ }
+
+ this.submit( $( event.currentTarget ) );
+ },
+
+ // Adds a selected menu item to the menu.
+ submit: function( menuitemTpl ) {
+ var menuitemId, menu_item;
+
+ if ( ! menuitemTpl ) {
+ menuitemTpl = this.selected;
+ }
+
+ if ( ! menuitemTpl || ! this.currentMenuControl ) {
+ return;
+ }
+
+ this.select( menuitemTpl );
+
+ menuitemId = $( this.selected ).data( 'menu-item-id' );
+ menu_item = this.collection.findWhere( { id: menuitemId } );
+ if ( ! menu_item ) {
+ return;
+ }
+
+ this.currentMenuControl.addItemToMenu( menu_item.attributes );
+
+ $( menuitemTpl ).find( '.menu-item-handle' ).addClass( 'item-added' );
+ },
+
+ // Submit handler for keypress and click on custom menu item.
+ _submitLink: function( event ) {
+ // Only proceed with keypress if it is Enter.
+ if ( 'keypress' === event.type && 13 !== event.which ) {
+ return;
+ }
+
+ this.submitLink();
+ },
+
+ // Adds the custom menu item to the menu.
+ submitLink: function() {
+ var menuItem,
+ itemName = $( '#custom-menu-item-name' ),
+ itemUrl = $( '#custom-menu-item-url' ),
+ url = itemUrl.val().trim(),
+ urlRegex;
+
+ if ( ! this.currentMenuControl ) {
+ return;
+ }
+
+ /*
+ * Allow URLs including:
+ * - http://example.com/
+ * - //example.com
+ * - /directory/
+ * - ?query-param
+ * - #target
+ * - mailto:foo@example.com
+ *
+ * Any further validation will be handled on the server when the setting is attempted to be saved,
+ * so this pattern does not need to be complete.
+ */
+ urlRegex = /^((\w+:)?\/\/\w.*|\w+:(?!\/\/$)|\/|\?|#)/;
+
+ if ( '' === itemName.val() ) {
+ itemName.addClass( 'invalid' );
+ return;
+ } else if ( ! urlRegex.test( url ) ) {
+ itemUrl.addClass( 'invalid' );
+ return;
+ }
+
+ menuItem = {
+ 'title': itemName.val(),
+ 'url': url,
+ 'type': 'custom',
+ 'type_label': api.Menus.data.l10n.custom_label,
+ 'object': 'custom'
+ };
+
+ this.currentMenuControl.addItemToMenu( menuItem );
+
+ // Reset the custom link form.
+ itemUrl.val( 'http://' );
+ itemName.val( '' );
+ },
+
+ /**
+ * Submit handler for keypress (enter) on field and click on button.
+ *
+ * @since 4.7.0
+ * @private
+ *
+ * @param {jQuery.Event} event Event.
+ * @returns {void}
+ */
+ _submitNew: function( event ) {
+ var container;
+
+ // Only proceed with keypress if it is Enter.
+ if ( 'keypress' === event.type && 13 !== event.which ) {
+ return;
+ }
+
+ if ( this.addingNew ) {
+ return;
+ }
+
+ container = $( event.target ).closest( '.accordion-section' );
+
+ this.submitNew( container );
+ },
+
+ /**
+ * Creates a new object and adds an associated menu item to the menu.
+ *
+ * @since 4.7.0
+ * @private
+ *
+ * @param {jQuery} container
+ * @returns {void}
+ */
+ submitNew: function( container ) {
+ var panel = this,
+ itemName = container.find( '.create-item-input' ),
+ title = itemName.val(),
+ dataContainer = container.find( '.available-menu-items-list' ),
+ itemType = dataContainer.data( 'type' ),
+ itemObject = dataContainer.data( 'object' ),
+ itemTypeLabel = dataContainer.data( 'type_label' ),
+ promise;
+
+ if ( ! this.currentMenuControl ) {
+ return;
+ }
+
+ // Only posts are supported currently.
+ if ( 'post_type' !== itemType ) {
+ return;
+ }
+
+ if ( '' === $.trim( itemName.val() ) ) {
+ itemName.addClass( 'invalid' );
+ itemName.focus();
+ return;
+ } else {
+ itemName.removeClass( 'invalid' );
+ container.find( '.accordion-section-title' ).addClass( 'loading' );
+ }
+
+ panel.addingNew = true;
+ itemName.attr( 'disabled', 'disabled' );
+ promise = api.Menus.insertAutoDraftPost( {
+ post_title: title,
+ post_type: itemObject
+ } );
+ promise.done( function( data ) {
+ var availableItem, $content, itemElement;
+ availableItem = new api.Menus.AvailableItemModel( {
+ 'id': 'post-' + data.post_id, // Used for available menu item Backbone models.
+ 'title': itemName.val(),
+ 'type': itemType,
+ 'type_label': itemTypeLabel,
+ 'object': itemObject,
+ 'object_id': data.post_id,
+ 'url': data.url
+ } );
+
+ // Add new item to menu.
+ panel.currentMenuControl.addItemToMenu( availableItem.attributes );
+
+ // Add the new item to the list of available items.
+ api.Menus.availableMenuItemsPanel.collection.add( availableItem );
+ $content = container.find( '.available-menu-items-list' );
+ itemElement = $( wp.template( 'available-menu-item' )( availableItem.attributes ) );
+ itemElement.find( '.menu-item-handle:first' ).addClass( 'item-added' );
+ $content.prepend( itemElement );
+ $content.scrollTop();
+
+ // Reset the create content form.
+ itemName.val( '' ).removeAttr( 'disabled' );
+ panel.addingNew = false;
+ container.find( '.accordion-section-title' ).removeClass( 'loading' );
+ } );
+ },
+
+ // Opens the panel.
+ open: function( menuControl ) {
+ var panel = this, close;
+
+ this.currentMenuControl = menuControl;
+
+ this.itemSectionHeight();
+
+ if ( api.section.has( 'publish_settings' ) ) {
+ api.section( 'publish_settings' ).collapse();
+ }
+
+ $( 'body' ).addClass( 'adding-menu-items' );
+
+ close = function() {
+ panel.close();
+ $( this ).off( 'click', close );
+ };
+ $( '#customize-preview' ).on( 'click', close );
+
+ // Collapse all controls.
+ _( this.currentMenuControl.getMenuItemControls() ).each( function( control ) {
+ control.collapseForm();
+ } );
+
+ this.$el.find( '.selected' ).removeClass( 'selected' );
+
+ this.$search.focus();
+ },
+
+ // Closes the panel
+ close: function( options ) {
+ options = options || {};
+
+ if ( options.returnFocus && this.currentMenuControl ) {
+ this.currentMenuControl.container.find( '.add-new-menu-item' ).focus();
+ }
+
+ this.currentMenuControl = null;
+ this.selected = null;
+
+ $( 'body' ).removeClass( 'adding-menu-items' );
+ $( '#available-menu-items .menu-item-handle.item-added' ).removeClass( 'item-added' );
+
+ this.$search.val( '' ).trigger( 'keyup' );
+ },
+
+ // Add a few keyboard enhancements to the panel.
+ keyboardAccessible: function( event ) {
+ var isEnter = ( 13 === event.which ),
+ isEsc = ( 27 === event.which ),
+ isBackTab = ( 9 === event.which && event.shiftKey ),
+ isSearchFocused = $( event.target ).is( this.$search );
+
+ // If enter pressed but nothing entered, don't do anything
+ if ( isEnter && ! this.$search.val() ) {
+ return;
+ }
+
+ if ( isSearchFocused && isBackTab ) {
+ this.currentMenuControl.container.find( '.add-new-menu-item' ).focus();
+ event.preventDefault(); // Avoid additional back-tab.
+ } else if ( isEsc ) {
+ this.close( { returnFocus: true } );
+ }
+ }
+ });
+
+ /**
+ * wp.customize.Menus.MenusPanel
+ *
+ * Customizer panel for menus. This is used only for screen options management.
+ * Note that 'menus' must match the WP_Customize_Menu_Panel::$type.
+ *
+ * @class wp.customize.Menus.MenusPanel
+ * @augments wp.customize.Panel
+ */
+ api.Menus.MenusPanel = api.Panel.extend(/** @lends wp.customize.Menus.MenusPanel.prototype */{
+
+ attachEvents: function() {
+ api.Panel.prototype.attachEvents.call( this );
+
+ var panel = this,
+ panelMeta = panel.container.find( '.panel-meta' ),
+ help = panelMeta.find( '.customize-help-toggle' ),
+ content = panelMeta.find( '.customize-panel-description' ),
+ options = $( '#screen-options-wrap' ),
+ button = panelMeta.find( '.customize-screen-options-toggle' );
+ button.on( 'click keydown', function( event ) {
+ if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
+ return;
+ }
+ event.preventDefault();
+
+ // Hide description
+ if ( content.not( ':hidden' ) ) {
+ content.slideUp( 'fast' );
+ help.attr( 'aria-expanded', 'false' );
+ }
+
+ if ( 'true' === button.attr( 'aria-expanded' ) ) {
+ button.attr( 'aria-expanded', 'false' );
+ panelMeta.removeClass( 'open' );
+ panelMeta.removeClass( 'active-menu-screen-options' );
+ options.slideUp( 'fast' );
+ } else {
+ button.attr( 'aria-expanded', 'true' );
+ panelMeta.addClass( 'open' );
+ panelMeta.addClass( 'active-menu-screen-options' );
+ options.slideDown( 'fast' );
+ }
+
+ return false;
+ } );
+
+ // Help toggle
+ help.on( 'click keydown', function( event ) {
+ if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
+ return;
+ }
+ event.preventDefault();
+
+ if ( 'true' === button.attr( 'aria-expanded' ) ) {
+ button.attr( 'aria-expanded', 'false' );
+ help.attr( 'aria-expanded', 'true' );
+ panelMeta.addClass( 'open' );
+ panelMeta.removeClass( 'active-menu-screen-options' );
+ options.slideUp( 'fast' );
+ content.slideDown( 'fast' );
+ }
+ } );
+ },
+
+ /**
+ * Update field visibility when clicking on the field toggles.
+ */
+ ready: function() {
+ var panel = this;
+ panel.container.find( '.hide-column-tog' ).click( function() {
+ panel.saveManageColumnsState();
+ });
+
+ // Inject additional heading into the menu locations section's head container.
+ api.section( 'menu_locations', function( section ) {
+ section.headContainer.prepend(
+ wp.template( 'nav-menu-locations-header' )( api.Menus.data )
+ );
+ } );
+ },
+
+ /**
+ * Save hidden column states.
+ *
+ * @since 4.3.0
+ * @private
+ *
+ * @returns {void}
+ */
+ saveManageColumnsState: _.debounce( function() {
+ var panel = this;
+ if ( panel._updateHiddenColumnsRequest ) {
+ panel._updateHiddenColumnsRequest.abort();
+ }
+
+ panel._updateHiddenColumnsRequest = wp.ajax.post( 'hidden-columns', {
+ hidden: panel.hidden(),
+ screenoptionnonce: $( '#screenoptionnonce' ).val(),
+ page: 'nav-menus'
+ } );
+ panel._updateHiddenColumnsRequest.always( function() {
+ panel._updateHiddenColumnsRequest = null;
+ } );
+ }, 2000 ),
+
+ /**
+ * @deprecated Since 4.7.0 now that the nav_menu sections are responsible for toggling the classes on their own containers.
+ */
+ checked: function() {},
+
+ /**
+ * @deprecated Since 4.7.0 now that the nav_menu sections are responsible for toggling the classes on their own containers.
+ */
+ unchecked: function() {},
+
+ /**
+ * Get hidden fields.
+ *
+ * @since 4.3.0
+ * @private
+ *
+ * @returns {Array} Fields (columns) that are hidden.
+ */
+ hidden: function() {
+ return $( '.hide-column-tog' ).not( ':checked' ).map( function() {
+ var id = this.id;
+ return id.substring( 0, id.length - 5 );
+ }).get().join( ',' );
+ }
+ } );
+
+ /**
+ * wp.customize.Menus.MenuSection
+ *
+ * Customizer section for menus. This is used only for lazy-loading child controls.
+ * Note that 'nav_menu' must match the WP_Customize_Menu_Section::$type.
+ *
+ * @class wp.customize.Menus.MenuSection
+ * @augments wp.customize.Section
+ */
+ api.Menus.MenuSection = api.Section.extend(/** @lends wp.customize.Menus.MenuSection.prototype */{
+
+ /**
+ * Initialize.
+ *
+ * @since 4.3.0
+ *
+ * @param {String} id
+ * @param {Object} options
+ */
+ initialize: function( id, options ) {
+ var section = this;
+ api.Section.prototype.initialize.call( section, id, options );
+ section.deferred.initSortables = $.Deferred();
+ },
+
+ /**
+ * Ready.
+ */
+ ready: function() {
+ var section = this, fieldActiveToggles, handleFieldActiveToggle;
+
+ if ( 'undefined' === typeof section.params.menu_id ) {
+ throw new Error( 'params.menu_id was not defined' );
+ }
+
+ /*
+ * Since newly created sections won't be registered in PHP, we need to prevent the
+ * preview's sending of the activeSections to result in this control
+ * being deactivated when the preview refreshes. So we can hook onto
+ * the setting that has the same ID and its presence can dictate
+ * whether the section is active.
+ */
+ section.active.validate = function() {
+ if ( ! api.has( section.id ) ) {
+ return false;
+ }
+ return !! api( section.id ).get();
+ };
+
+ section.populateControls();
+
+ section.navMenuLocationSettings = {};
+ section.assignedLocations = new api.Value( [] );
+
+ api.each(function( setting, id ) {
+ var matches = id.match( /^nav_menu_locations\[(.+?)]/ );
+ if ( matches ) {
+ section.navMenuLocationSettings[ matches[1] ] = setting;
+ setting.bind( function() {
+ section.refreshAssignedLocations();
+ });
+ }
+ });
+
+ section.assignedLocations.bind(function( to ) {
+ section.updateAssignedLocationsInSectionTitle( to );
+ });
+
+ section.refreshAssignedLocations();
+
+ api.bind( 'pane-contents-reflowed', function() {
+ // Skip menus that have been removed.
+ if ( ! section.contentContainer.parent().length ) {
+ return;
+ }
+ section.container.find( '.menu-item .menu-item-reorder-nav button' ).attr({ 'tabindex': '0', 'aria-hidden': 'false' });
+ section.container.find( '.menu-item.move-up-disabled .menus-move-up' ).attr({ 'tabindex': '-1', 'aria-hidden': 'true' });
+ section.container.find( '.menu-item.move-down-disabled .menus-move-down' ).attr({ 'tabindex': '-1', 'aria-hidden': 'true' });
+ section.container.find( '.menu-item.move-left-disabled .menus-move-left' ).attr({ 'tabindex': '-1', 'aria-hidden': 'true' });
+ section.container.find( '.menu-item.move-right-disabled .menus-move-right' ).attr({ 'tabindex': '-1', 'aria-hidden': 'true' });
+ } );
+
+ /**
+ * Update the active field class for the content container for a given checkbox toggle.
+ *
+ * @this {jQuery}
+ * @returns {void}
+ */
+ handleFieldActiveToggle = function() {
+ var className = 'field-' + $( this ).val() + '-active';
+ section.contentContainer.toggleClass( className, $( this ).prop( 'checked' ) );
+ };
+ fieldActiveToggles = api.panel( 'nav_menus' ).contentContainer.find( '.metabox-prefs:first' ).find( '.hide-column-tog' );
+ fieldActiveToggles.each( handleFieldActiveToggle );
+ fieldActiveToggles.on( 'click', handleFieldActiveToggle );
+ },
+
+ populateControls: function() {
+ var section = this,
+ menuNameControlId,
+ menuLocationsControlId,
+ menuAutoAddControlId,
+ menuDeleteControlId,
+ menuControl,
+ menuNameControl,
+ menuLocationsControl,
+ menuAutoAddControl,
+ menuDeleteControl;
+
+ // Add the control for managing the menu name.
+ menuNameControlId = section.id + '[name]';
+ menuNameControl = api.control( menuNameControlId );
+ if ( ! menuNameControl ) {
+ menuNameControl = new api.controlConstructor.nav_menu_name( menuNameControlId, {
+ type: 'nav_menu_name',
+ label: api.Menus.data.l10n.menuNameLabel,
+ section: section.id,
+ priority: 0,
+ settings: {
+ 'default': section.id
+ }
+ } );
+ api.control.add( menuNameControl );
+ menuNameControl.active.set( true );
+ }
+
+ // Add the menu control.
+ menuControl = api.control( section.id );
+ if ( ! menuControl ) {
+ menuControl = new api.controlConstructor.nav_menu( section.id, {
+ type: 'nav_menu',
+ section: section.id,
+ priority: 998,
+ settings: {
+ 'default': section.id
+ },
+ menu_id: section.params.menu_id
+ } );
+ api.control.add( menuControl );
+ menuControl.active.set( true );
+ }
+
+ // Add the menu locations control.
+ menuLocationsControlId = section.id + '[locations]';
+ menuLocationsControl = api.control( menuLocationsControlId );
+ if ( ! menuLocationsControl ) {
+ menuLocationsControl = new api.controlConstructor.nav_menu_locations( menuLocationsControlId, {
+ section: section.id,
+ priority: 999,
+ settings: {
+ 'default': section.id
+ },
+ menu_id: section.params.menu_id
+ } );
+ api.control.add( menuLocationsControl.id, menuLocationsControl );
+ menuControl.active.set( true );
+ }
+
+ // Add the control for managing the menu auto_add.
+ menuAutoAddControlId = section.id + '[auto_add]';
+ menuAutoAddControl = api.control( menuAutoAddControlId );
+ if ( ! menuAutoAddControl ) {
+ menuAutoAddControl = new api.controlConstructor.nav_menu_auto_add( menuAutoAddControlId, {
+ type: 'nav_menu_auto_add',
+ label: '',
+ section: section.id,
+ priority: 1000,
+ settings: {
+ 'default': section.id
+ }
+ } );
+ api.control.add( menuAutoAddControl );
+ menuAutoAddControl.active.set( true );
+ }
+
+ // Add the control for deleting the menu
+ menuDeleteControlId = section.id + '[delete]';
+ menuDeleteControl = api.control( menuDeleteControlId );
+ if ( ! menuDeleteControl ) {
+ menuDeleteControl = new api.Control( menuDeleteControlId, {
+ section: section.id,
+ priority: 1001,
+ templateId: 'nav-menu-delete-button'
+ } );
+ api.control.add( menuDeleteControl.id, menuDeleteControl );
+ menuDeleteControl.active.set( true );
+ menuDeleteControl.deferred.embedded.done( function () {
+ menuDeleteControl.container.find( 'button' ).on( 'click', function() {
+ var menuId = section.params.menu_id;
+ var menuControl = api.Menus.getMenuControl( menuId );
+ menuControl.setting.set( false );
+ });
+ } );
+ }
+ },
+
+ /**
+ *
+ */
+ refreshAssignedLocations: function() {
+ var section = this,
+ menuTermId = section.params.menu_id,
+ currentAssignedLocations = [];
+ _.each( section.navMenuLocationSettings, function( setting, themeLocation ) {
+ if ( setting() === menuTermId ) {
+ currentAssignedLocations.push( themeLocation );
+ }
+ });
+ section.assignedLocations.set( currentAssignedLocations );
+ },
+
+ /**
+ * @param {Array} themeLocationSlugs Theme location slugs.
+ */
+ updateAssignedLocationsInSectionTitle: function( themeLocationSlugs ) {
+ var section = this,
+ $title;
+
+ $title = section.container.find( '.accordion-section-title:first' );
+ $title.find( '.menu-in-location' ).remove();
+ _.each( themeLocationSlugs, function( themeLocationSlug ) {
+ var $label, locationName;
+ $label = $( '<span class="menu-in-location"></span>' );
+ locationName = api.Menus.data.locationSlugMappedToName[ themeLocationSlug ];
+ $label.text( api.Menus.data.l10n.menuLocation.replace( '%s', locationName ) );
+ $title.append( $label );
+ });
+
+ section.container.toggleClass( 'assigned-to-menu-location', 0 !== themeLocationSlugs.length );
+
+ },
+
+ onChangeExpanded: function( expanded, args ) {
+ var section = this, completeCallback;
+
+ if ( expanded ) {
+ wpNavMenu.menuList = section.contentContainer;
+ wpNavMenu.targetList = wpNavMenu.menuList;
+
+ // Add attributes needed by wpNavMenu
+ $( '#menu-to-edit' ).removeAttr( 'id' );
+ wpNavMenu.menuList.attr( 'id', 'menu-to-edit' ).addClass( 'menu' );
+
+ _.each( api.section( section.id ).controls(), function( control ) {
+ if ( 'nav_menu_item' === control.params.type ) {
+ control.actuallyEmbed();
+ }
+ } );
+
+ // Make sure Sortables is initialized after the section has been expanded to prevent `offset` issues.
+ if ( args.completeCallback ) {
+ completeCallback = args.completeCallback;
+ }
+ args.completeCallback = function() {
+ if ( 'resolved' !== section.deferred.initSortables.state() ) {
+ wpNavMenu.initSortables(); // Depends on menu-to-edit ID being set above.
+ section.deferred.initSortables.resolve( wpNavMenu.menuList ); // Now MenuControl can extend the sortable.
+
+ // @todo Note that wp.customize.reflowPaneContents() is debounced, so this immediate change will show a slight flicker while priorities get updated.
+ api.control( 'nav_menu[' + String( section.params.menu_id ) + ']' ).reflowMenuItems();
+ }
+ if ( _.isFunction( completeCallback ) ) {
+ completeCallback();
+ }
+ };
+ }
+ api.Section.prototype.onChangeExpanded.call( section, expanded, args );
+ },
+
+ /**
+ * Highlight how a user may create new menu items.
+ *
+ * This method reminds the user to create new menu items and how.
+ * It's exposed this way because this class knows best which UI needs
+ * highlighted but those expanding this section know more about why and
+ * when the affordance should be highlighted.
+ *
+ * @since 4.9.0
+ *
+ * @returns {void}
+ */
+ highlightNewItemButton: function() {
+ api.utils.highlightButton( this.contentContainer.find( '.add-new-menu-item' ), { delay: 2000 } );
+ }
+ });
+
+ /**
+ * Create a nav menu setting and section.
+ *
+ * @since 4.9.0
+ *
+ * @param {string} [name=''] Nav menu name.
+ * @returns {wp.customize.Menus.MenuSection} Added nav menu.
+ */
+ api.Menus.createNavMenu = function createNavMenu( name ) {
+ var customizeId, placeholderId, setting;
+ placeholderId = api.Menus.generatePlaceholderAutoIncrementId();
+
+ customizeId = 'nav_menu[' + String( placeholderId ) + ']';
+
+ // Register the menu control setting.
+ setting = api.create( customizeId, customizeId, {}, {
+ type: 'nav_menu',
+ transport: api.Menus.data.settingTransport,
+ previewer: api.previewer
+ } );
+ setting.set( $.extend(
+ {},
+ api.Menus.data.defaultSettingValues.nav_menu,
+ {
+ name: name || ''
+ }
+ ) );
+
+ /*
+ * Add the menu section (and its controls).
+ * Note that this will automatically create the required controls
+ * inside via the Section's ready method.
+ */
+ return api.section.add( new api.Menus.MenuSection( customizeId, {
+ panel: 'nav_menus',
+ title: displayNavMenuName( name ),
+ customizeAction: api.Menus.data.l10n.customizingMenus,
+ priority: 10,
+ menu_id: placeholderId
+ } ) );
+ };
+
+ /**
+ * wp.customize.Menus.NewMenuSection
+ *
+ * Customizer section for new menus.
+ *
+ * @class wp.customize.Menus.NewMenuSection
+ * @augments wp.customize.Section
+ */
+ api.Menus.NewMenuSection = api.Section.extend(/** @lends wp.customize.Menus.NewMenuSection.prototype */{
+
+ /**
+ * Add behaviors for the accordion section.
+ *
+ * @since 4.3.0
+ */
+ attachEvents: function() {
+ var section = this,
+ container = section.container,
+ contentContainer = section.contentContainer,
+ navMenuSettingPattern = /^nav_menu\[/;
+
+ section.headContainer.find( '.accordion-section-title' ).replaceWith(
+ wp.template( 'nav-menu-create-menu-section-title' )
+ );
+
+ /*
+ * We have to manually handle section expanded because we do not
+ * apply the `accordion-section-title` class to this button-driven section.
+ */
+ container.on( 'click', '.customize-add-menu-button', function() {
+ section.expand();
+ });
+
+ contentContainer.on( 'keydown', '.menu-name-field', function( event ) {
+ if ( 13 === event.which ) { // Enter.
+ section.submit();
+ }
+ } );
+ contentContainer.on( 'click', '#customize-new-menu-submit', function( event ) {
+ section.submit();
+ event.stopPropagation();
+ event.preventDefault();
+ } );
+
+ /**
+ * Get number of non-deleted nav menus.
+ *
+ * @since 4.9.0
+ * @returns {number} Count.
+ */
+ function getNavMenuCount() {
+ var count = 0;
+ api.each( function( setting ) {
+ if ( navMenuSettingPattern.test( setting.id ) && false !== setting.get() ) {
+ count += 1;
+ }
+ } );
+ return count;
+ }
+
+ /**
+ * Update visibility of notice to prompt users to create menus.
+ *
+ * @since 4.9.0
+ * @returns {void}
+ */
+ function updateNoticeVisibility() {
+ container.find( '.add-new-menu-notice' ).prop( 'hidden', getNavMenuCount() > 0 );
+ }
+
+ /**
+ * Handle setting addition.
+ *
+ * @since 4.9.0
+ * @param {wp.customize.Setting} setting - Added setting.
+ * @returns {void}
+ */
+ function addChangeEventListener( setting ) {
+ if ( navMenuSettingPattern.test( setting.id ) ) {
+ setting.bind( updateNoticeVisibility );
+ updateNoticeVisibility();
+ }
+ }
+
+ /**
+ * Handle setting removal.
+ *
+ * @since 4.9.0
+ * @param {wp.customize.Setting} setting - Removed setting.
+ * @returns {void}
+ */
+ function removeChangeEventListener( setting ) {
+ if ( navMenuSettingPattern.test( setting.id ) ) {
+ setting.unbind( updateNoticeVisibility );
+ updateNoticeVisibility();
+ }
+ }
+
+ api.each( addChangeEventListener );
+ api.bind( 'add', addChangeEventListener );
+ api.bind( 'removed', removeChangeEventListener );
+ updateNoticeVisibility();
+
+ api.Section.prototype.attachEvents.apply( section, arguments );
+ },
+
+ /**
+ * Set up the control.
+ *
+ * @since 4.9.0
+ */
+ ready: function() {
+ this.populateControls();
+ },
+
+ /**
+ * Create the controls for this section.
+ *
+ * @since 4.9.0
+ */
+ populateControls: function() {
+ var section = this,
+ menuNameControlId,
+ menuLocationsControlId,
+ newMenuSubmitControlId,
+ menuNameControl,
+ menuLocationsControl,
+ newMenuSubmitControl;
+
+ menuNameControlId = section.id + '[name]';
+ menuNameControl = api.control( menuNameControlId );
+ if ( ! menuNameControl ) {
+ menuNameControl = new api.controlConstructor.nav_menu_name( menuNameControlId, {
+ label: api.Menus.data.l10n.menuNameLabel,
+ description: api.Menus.data.l10n.newMenuNameDescription,
+ section: section.id,
+ priority: 0
+ } );
+ api.control.add( menuNameControl.id, menuNameControl );
+ menuNameControl.active.set( true );
+ }
+
+ menuLocationsControlId = section.id + '[locations]';
+ menuLocationsControl = api.control( menuLocationsControlId );
+ if ( ! menuLocationsControl ) {
+ menuLocationsControl = new api.controlConstructor.nav_menu_locations( menuLocationsControlId, {
+ section: section.id,
+ priority: 1,
+ menu_id: '',
+ isCreating: true
+ } );
+ api.control.add( menuLocationsControlId, menuLocationsControl );
+ menuLocationsControl.active.set( true );
+ }
+
+ newMenuSubmitControlId = section.id + '[submit]';
+ newMenuSubmitControl = api.control( newMenuSubmitControlId );
+ if ( !newMenuSubmitControl ) {
+ newMenuSubmitControl = new api.Control( newMenuSubmitControlId, {
+ section: section.id,
+ priority: 1,
+ templateId: 'nav-menu-submit-new-button'
+ } );
+ api.control.add( newMenuSubmitControlId, newMenuSubmitControl );
+ newMenuSubmitControl.active.set( true );
+ }
+ },
+
+ /**
+ * Create the new menu with name and location supplied by the user.
+ *
+ * @since 4.9.0
+ */
+ submit: function() {
+ var section = this,
+ contentContainer = section.contentContainer,
+ nameInput = contentContainer.find( '.menu-name-field' ).first(),
+ name = nameInput.val(),
+ menuSection;
+
+ if ( ! name ) {
+ nameInput.addClass( 'invalid' );
+ nameInput.focus();
+ return;
+ }
+
+ menuSection = api.Menus.createNavMenu( name );
+
+ // Clear name field.
+ nameInput.val( '' );
+ nameInput.removeClass( 'invalid' );
+
+ contentContainer.find( '.assigned-menu-location input[type=checkbox]' ).each( function() {
+ var checkbox = $( this ),
+ navMenuLocationSetting;
+
+ if ( checkbox.prop( 'checked' ) ) {
+ navMenuLocationSetting = api( 'nav_menu_locations[' + checkbox.data( 'location-id' ) + ']' );
+ navMenuLocationSetting.set( menuSection.params.menu_id );
+
+ // Reset state for next new menu
+ checkbox.prop( 'checked', false );
+ }
+ } );
+
+ wp.a11y.speak( api.Menus.data.l10n.menuAdded );
+
+ // Focus on the new menu section.
+ menuSection.focus( {
+ completeCallback: function() {
+ menuSection.highlightNewItemButton();
+ }
+ } );
+ },
+
+ /**
+ * Select a default location.
+ *
+ * This method selects a single location by default so we can support
+ * creating a menu for a specific menu location.
+ *
+ * @since 4.9.0
+ *
+ * @param {string|null} locationId - The ID of the location to select. `null` clears all selections.
+ * @returns {void}
+ */
+ selectDefaultLocation: function( locationId ) {
+ var locationControl = api.control( this.id + '[locations]' ),
+ locationSelections = {};
+
+ if ( locationId !== null ) {
+ locationSelections[ locationId ] = true;
+ }
+
+ locationControl.setSelections( locationSelections );
+ }
+ });
+
+ /**
+ * wp.customize.Menus.MenuLocationControl
+ *
+ * Customizer control for menu locations (rendered as a <select>).
+ * Note that 'nav_menu_location' must match the WP_Customize_Nav_Menu_Location_Control::$type.
+ *
+ * @class wp.customize.Menus.MenuLocationControl
+ * @augments wp.customize.Control
+ */
+ api.Menus.MenuLocationControl = api.Control.extend(/** @lends wp.customize.Menus.MenuLocationControl.prototype */{
+ initialize: function( id, options ) {
+ var control = this,
+ matches = id.match( /^nav_menu_locations\[(.+?)]/ );
+ control.themeLocation = matches[1];
+ api.Control.prototype.initialize.call( control, id, options );
+ },
+
+ ready: function() {
+ var control = this, navMenuIdRegex = /^nav_menu\[(-?\d+)]/;
+
+ // @todo It would be better if this was added directly on the setting itself, as opposed to the control.
+ control.setting.validate = function( value ) {
+ if ( '' === value ) {
+ return 0;
+ } else {
+ return parseInt( value, 10 );
+ }
+ };
+
+ // Create and Edit menu buttons.
+ control.container.find( '.create-menu' ).on( 'click', function() {
+ var addMenuSection = api.section( 'add_menu' );
+ addMenuSection.selectDefaultLocation( this.dataset.locationId );
+ addMenuSection.focus();
+ } );
+ control.container.find( '.edit-menu' ).on( 'click', function() {
+ var menuId = control.setting();
+ api.section( 'nav_menu[' + menuId + ']' ).focus();
+ });
+ control.setting.bind( 'change', function() {
+ var menuIsSelected = 0 !== control.setting();
+ control.container.find( '.create-menu' ).toggleClass( 'hidden', menuIsSelected );
+ control.container.find( '.edit-menu' ).toggleClass( 'hidden', ! menuIsSelected );
+ });
+
+ // Add/remove menus from the available options when they are added and removed.
+ api.bind( 'add', function( setting ) {
+ var option, menuId, matches = setting.id.match( navMenuIdRegex );
+ if ( ! matches || false === setting() ) {
+ return;
+ }
+ menuId = matches[1];
+ option = new Option( displayNavMenuName( setting().name ), menuId );
+ control.container.find( 'select' ).append( option );
+ });
+ api.bind( 'remove', function( setting ) {
+ var menuId, matches = setting.id.match( navMenuIdRegex );
+ if ( ! matches ) {
+ return;
+ }
+ menuId = parseInt( matches[1], 10 );
+ if ( control.setting() === menuId ) {
+ control.setting.set( '' );
+ }
+ control.container.find( 'option[value=' + menuId + ']' ).remove();
+ });
+ api.bind( 'change', function( setting ) {
+ var menuId, matches = setting.id.match( navMenuIdRegex );
+ if ( ! matches ) {
+ return;
+ }
+ menuId = parseInt( matches[1], 10 );
+ if ( false === setting() ) {
+ if ( control.setting() === menuId ) {
+ control.setting.set( '' );
+ }
+ control.container.find( 'option[value=' + menuId + ']' ).remove();
+ } else {
+ control.container.find( 'option[value=' + menuId + ']' ).text( displayNavMenuName( setting().name ) );
+ }
+ });
+ }
+ });
+
+ api.Menus.MenuItemControl = api.Control.extend(/** @lends wp.customize.Menus.MenuItemControl.prototype */{
+
+ /**
+ * wp.customize.Menus.MenuItemControl
+ *
+ * Customizer control for menu items.
+ * Note that 'menu_item' must match the WP_Customize_Menu_Item_Control::$type.
+ *
+ * @constructs wp.customize.Menus.MenuItemControl
+ * @augments wp.customize.Control
+ *
+ * @inheritDoc
+ */
+ initialize: function( id, options ) {
+ var control = this;
+ control.expanded = new api.Value( false );
+ control.expandedArgumentsQueue = [];
+ control.expanded.bind( function( expanded ) {
+ var args = control.expandedArgumentsQueue.shift();
+ args = $.extend( {}, control.defaultExpandedArguments, args );
+ control.onChangeExpanded( expanded, args );
+ });
+ api.Control.prototype.initialize.call( control, id, options );
+ control.active.validate = function() {
+ var value, section = api.section( control.section() );
+ if ( section ) {
+ value = section.active();
+ } else {
+ value = false;
+ }
+ return value;
+ };
+ },
+
+ /**
+ * Override the embed() method to do nothing,
+ * so that the control isn't embedded on load,
+ * unless the containing section is already expanded.
+ *
+ * @since 4.3.0
+ */
+ embed: function() {
+ var control = this,
+ sectionId = control.section(),
+ section;
+ if ( ! sectionId ) {
+ return;
+ }
+ section = api.section( sectionId );
+ if ( ( section && section.expanded() ) || api.settings.autofocus.control === control.id ) {
+ control.actuallyEmbed();
+ }
+ },
+
+ /**
+ * This function is called in Section.onChangeExpanded() so the control
+ * will only get embedded when the Section is first expanded.
+ *
+ * @since 4.3.0
+ */
+ actuallyEmbed: function() {
+ var control = this;
+ if ( 'resolved' === control.deferred.embedded.state() ) {
+ return;
+ }
+ control.renderContent();
+ control.deferred.embedded.resolve(); // This triggers control.ready().
+ },
+
+ /**
+ * Set up the control.
+ */
+ ready: function() {
+ if ( 'undefined' === typeof this.params.menu_item_id ) {
+ throw new Error( 'params.menu_item_id was not defined' );
+ }
+
+ this._setupControlToggle();
+ this._setupReorderUI();
+ this._setupUpdateUI();
+ this._setupRemoveUI();
+ this._setupLinksUI();
+ this._setupTitleUI();
+ },
+
+ /**
+ * Show/hide the settings when clicking on the menu item handle.
+ */
+ _setupControlToggle: function() {
+ var control = this;
+
+ this.container.find( '.menu-item-handle' ).on( 'click', function( e ) {
+ e.preventDefault();
+ e.stopPropagation();
+ var menuControl = control.getMenuControl(),
+ isDeleteBtn = $( e.target ).is( '.item-delete, .item-delete *' ),
+ isAddNewBtn = $( e.target ).is( '.add-new-menu-item, .add-new-menu-item *' );
+
+ if ( $( 'body' ).hasClass( 'adding-menu-items' ) && ! isDeleteBtn && ! isAddNewBtn ) {
+ api.Menus.availableMenuItemsPanel.close();
+ }
+
+ if ( menuControl.isReordering || menuControl.isSorting ) {
+ return;
+ }
+ control.toggleForm();
+ } );
+ },
+
+ /**
+ * Set up the menu-item-reorder-nav
+ */
+ _setupReorderUI: function() {
+ var control = this, template, $reorderNav;
+
+ template = wp.template( 'menu-item-reorder-nav' );
+
+ // Add the menu item reordering elements to the menu item control.
+ control.container.find( '.item-controls' ).after( template );
+
+ // Handle clicks for up/down/left-right on the reorder nav.
+ $reorderNav = control.container.find( '.menu-item-reorder-nav' );
+ $reorderNav.find( '.menus-move-up, .menus-move-down, .menus-move-left, .menus-move-right' ).on( 'click', function() {
+ var moveBtn = $( this );
+ moveBtn.focus();
+
+ var isMoveUp = moveBtn.is( '.menus-move-up' ),
+ isMoveDown = moveBtn.is( '.menus-move-down' ),
+ isMoveLeft = moveBtn.is( '.menus-move-left' ),
+ isMoveRight = moveBtn.is( '.menus-move-right' );
+
+ if ( isMoveUp ) {
+ control.moveUp();
+ } else if ( isMoveDown ) {
+ control.moveDown();
+ } else if ( isMoveLeft ) {
+ control.moveLeft();
+ } else if ( isMoveRight ) {
+ control.moveRight();
+ }
+
+ moveBtn.focus(); // Re-focus after the container was moved.
+ } );
+ },
+
+ /**
+ * Set up event handlers for menu item updating.
+ */
+ _setupUpdateUI: function() {
+ var control = this,
+ settingValue = control.setting(),
+ updateNotifications;
+
+ control.elements = {};
+ control.elements.url = new api.Element( control.container.find( '.edit-menu-item-url' ) );
+ control.elements.title = new api.Element( control.container.find( '.edit-menu-item-title' ) );
+ control.elements.attr_title = new api.Element( control.container.find( '.edit-menu-item-attr-title' ) );
+ control.elements.target = new api.Element( control.container.find( '.edit-menu-item-target' ) );
+ control.elements.classes = new api.Element( control.container.find( '.edit-menu-item-classes' ) );
+ control.elements.xfn = new api.Element( control.container.find( '.edit-menu-item-xfn' ) );
+ control.elements.description = new api.Element( control.container.find( '.edit-menu-item-description' ) );
+ // @todo allow other elements, added by plugins, to be automatically picked up here; allow additional values to be added to setting array.
+
+ _.each( control.elements, function( element, property ) {
+ element.bind(function( value ) {
+ if ( element.element.is( 'input[type=checkbox]' ) ) {
+ value = ( value ) ? element.element.val() : '';
+ }
+
+ var settingValue = control.setting();
+ if ( settingValue && settingValue[ property ] !== value ) {
+ settingValue = _.clone( settingValue );
+ settingValue[ property ] = value;
+ control.setting.set( settingValue );
+ }
+ });
+ if ( settingValue ) {
+ if ( ( property === 'classes' || property === 'xfn' ) && _.isArray( settingValue[ property ] ) ) {
+ element.set( settingValue[ property ].join( ' ' ) );
+ } else {
+ element.set( settingValue[ property ] );
+ }
+ }
+ });
+
+ control.setting.bind(function( to, from ) {
+ var itemId = control.params.menu_item_id,
+ followingSiblingItemControls = [],
+ childrenItemControls = [],
+ menuControl;
+
+ if ( false === to ) {
+ menuControl = api.control( 'nav_menu[' + String( from.nav_menu_term_id ) + ']' );
+ control.container.remove();
+
+ _.each( menuControl.getMenuItemControls(), function( otherControl ) {
+ if ( from.menu_item_parent === otherControl.setting().menu_item_parent && otherControl.setting().position > from.position ) {
+ followingSiblingItemControls.push( otherControl );
+ } else if ( otherControl.setting().menu_item_parent === itemId ) {
+ childrenItemControls.push( otherControl );
+ }
+ });
+
+ // Shift all following siblings by the number of children this item has.
+ _.each( followingSiblingItemControls, function( followingSiblingItemControl ) {
+ var value = _.clone( followingSiblingItemControl.setting() );
+ value.position += childrenItemControls.length;
+ followingSiblingItemControl.setting.set( value );
+ });
+
+ // Now move the children up to be the new subsequent siblings.
+ _.each( childrenItemControls, function( childrenItemControl, i ) {
+ var value = _.clone( childrenItemControl.setting() );
+ value.position = from.position + i;
+ value.menu_item_parent = from.menu_item_parent;
+ childrenItemControl.setting.set( value );
+ });
+
+ menuControl.debouncedReflowMenuItems();
+ } else {
+ // Update the elements' values to match the new setting properties.
+ _.each( to, function( value, key ) {
+ if ( control.elements[ key] ) {
+ control.elements[ key ].set( to[ key ] );
+ }
+ } );
+ control.container.find( '.menu-item-data-parent-id' ).val( to.menu_item_parent );
+
+ // Handle UI updates when the position or depth (parent) change.
+ if ( to.position !== from.position || to.menu_item_parent !== from.menu_item_parent ) {
+ control.getMenuControl().debouncedReflowMenuItems();
+ }
+ }
+ });
+
+ // Style the URL field as invalid when there is an invalid_url notification.
+ updateNotifications = function() {
+ control.elements.url.element.toggleClass( 'invalid', control.setting.notifications.has( 'invalid_url' ) );
+ };
+ control.setting.notifications.bind( 'add', updateNotifications );
+ control.setting.notifications.bind( 'removed', updateNotifications );
+ },
+
+ /**
+ * Set up event handlers for menu item deletion.
+ */
+ _setupRemoveUI: function() {
+ var control = this, $removeBtn;
+
+ // Configure delete button.
+ $removeBtn = control.container.find( '.item-delete' );
+
+ $removeBtn.on( 'click', function() {
+ // Find an adjacent element to add focus to when this menu item goes away
+ var addingItems = true, $adjacentFocusTarget, $next, $prev;
+
+ if ( ! $( 'body' ).hasClass( 'adding-menu-items' ) ) {
+ addingItems = false;
+ }
+
+ $next = control.container.nextAll( '.customize-control-nav_menu_item:visible' ).first();
+ $prev = control.container.prevAll( '.customize-control-nav_menu_item:visible' ).first();
+
+ if ( $next.length ) {
+ $adjacentFocusTarget = $next.find( false === addingItems ? '.item-edit' : '.item-delete' ).first();
+ } else if ( $prev.length ) {
+ $adjacentFocusTarget = $prev.find( false === addingItems ? '.item-edit' : '.item-delete' ).first();
+ } else {
+ $adjacentFocusTarget = control.container.nextAll( '.customize-control-nav_menu' ).find( '.add-new-menu-item' ).first();
+ }
+
+ control.container.slideUp( function() {
+ control.setting.set( false );
+ wp.a11y.speak( api.Menus.data.l10n.itemDeleted );
+ $adjacentFocusTarget.focus(); // keyboard accessibility
+ } );
+
+ control.setting.set( false );
+ } );
+ },
+
+ _setupLinksUI: function() {
+ var $origBtn;
+
+ // Configure original link.
+ $origBtn = this.container.find( 'a.original-link' );
+
+ $origBtn.on( 'click', function( e ) {
+ e.preventDefault();
+ api.previewer.previewUrl( e.target.toString() );
+ } );
+ },
+
+ /**
+ * Update item handle title when changed.
+ */
+ _setupTitleUI: function() {
+ var control = this, titleEl;
+
+ // Ensure that whitespace is trimmed on blur so placeholder can be shown.
+ control.container.find( '.edit-menu-item-title' ).on( 'blur', function() {
+ $( this ).val( $.trim( $( this ).val() ) );
+ } );
+
+ titleEl = control.container.find( '.menu-item-title' );
+ control.setting.bind( function( item ) {
+ var trimmedTitle, titleText;
+ if ( ! item ) {
+ return;
+ }
+ trimmedTitle = $.trim( item.title );
+
+ titleText = trimmedTitle || item.original_title || api.Menus.data.l10n.untitled;
+
+ if ( item._invalid ) {
+ titleText = api.Menus.data.l10n.invalidTitleTpl.replace( '%s', titleText );
+ }
+
+ // Don't update to an empty title.
+ if ( trimmedTitle || item.original_title ) {
+ titleEl
+ .text( titleText )
+ .removeClass( 'no-title' );
+ } else {
+ titleEl
+ .text( titleText )
+ .addClass( 'no-title' );
+ }
+ } );
+ },
+
+ /**
+ *
+ * @returns {number}
+ */
+ getDepth: function() {
+ var control = this, setting = control.setting(), depth = 0;
+ if ( ! setting ) {
+ return 0;
+ }
+ while ( setting && setting.menu_item_parent ) {
+ depth += 1;
+ control = api.control( 'nav_menu_item[' + setting.menu_item_parent + ']' );
+ if ( ! control ) {
+ break;
+ }
+ setting = control.setting();
+ }
+ return depth;
+ },
+
+ /**
+ * Amend the control's params with the data necessary for the JS template just in time.
+ */
+ renderContent: function() {
+ var control = this,
+ settingValue = control.setting(),
+ containerClasses;
+
+ control.params.title = settingValue.title || '';
+ control.params.depth = control.getDepth();
+ control.container.data( 'item-depth', control.params.depth );
+ containerClasses = [
+ 'menu-item',
+ 'menu-item-depth-' + String( control.params.depth ),
+ 'menu-item-' + settingValue.object,
+ 'menu-item-edit-inactive'
+ ];
+
+ if ( settingValue._invalid ) {
+ containerClasses.push( 'menu-item-invalid' );
+ control.params.title = api.Menus.data.l10n.invalidTitleTpl.replace( '%s', control.params.title );
+ } else if ( 'draft' === settingValue.status ) {
+ containerClasses.push( 'pending' );
+ control.params.title = api.Menus.data.pendingTitleTpl.replace( '%s', control.params.title );
+ }
+
+ control.params.el_classes = containerClasses.join( ' ' );
+ control.params.item_type_label = settingValue.type_label;
+ control.params.item_type = settingValue.type;
+ control.params.url = settingValue.url;
+ control.params.target = settingValue.target;
+ control.params.attr_title = settingValue.attr_title;
+ control.params.classes = _.isArray( settingValue.classes ) ? settingValue.classes.join( ' ' ) : settingValue.classes;
+ control.params.attr_title = settingValue.attr_title;
+ control.params.xfn = settingValue.xfn;
+ control.params.description = settingValue.description;
+ control.params.parent = settingValue.menu_item_parent;
+ control.params.original_title = settingValue.original_title || '';
+
+ control.container.addClass( control.params.el_classes );
+
+ api.Control.prototype.renderContent.call( control );
+ },
+
+ /***********************************************************************
+ * Begin public API methods
+ **********************************************************************/
+
+ /**
+ * @return {wp.customize.controlConstructor.nav_menu|null}
+ */
+ getMenuControl: function() {
+ var control = this, settingValue = control.setting();
+ if ( settingValue && settingValue.nav_menu_term_id ) {
+ return api.control( 'nav_menu[' + settingValue.nav_menu_term_id + ']' );
+ } else {
+ return null;
+ }
+ },
+
+ /**
+ * Expand the accordion section containing a control
+ */
+ expandControlSection: function() {
+ var $section = this.container.closest( '.accordion-section' );
+ if ( ! $section.hasClass( 'open' ) ) {
+ $section.find( '.accordion-section-title:first' ).trigger( 'click' );
+ }
+ },
+
+ /**
+ * @since 4.6.0
+ *
+ * @param {Boolean} expanded
+ * @param {Object} [params]
+ * @returns {Boolean} false if state already applied
+ */
+ _toggleExpanded: api.Section.prototype._toggleExpanded,
+
+ /**
+ * @since 4.6.0
+ *
+ * @param {Object} [params]
+ * @returns {Boolean} false if already expanded
+ */
+ expand: api.Section.prototype.expand,
+
+ /**
+ * Expand the menu item form control.
+ *
+ * @since 4.5.0 Added params.completeCallback.
+ *
+ * @param {Object} [params] - Optional params.
+ * @param {Function} [params.completeCallback] - Function to call when the form toggle has finished animating.
+ */
+ expandForm: function( params ) {
+ this.expand( params );
+ },
+
+ /**
+ * @since 4.6.0
+ *
+ * @param {Object} [params]
+ * @returns {Boolean} false if already collapsed
+ */
+ collapse: api.Section.prototype.collapse,
+
+ /**
+ * Collapse the menu item form control.
+ *
+ * @since 4.5.0 Added params.completeCallback.
+ *
+ * @param {Object} [params] - Optional params.
+ * @param {Function} [params.completeCallback] - Function to call when the form toggle has finished animating.
+ */
+ collapseForm: function( params ) {
+ this.collapse( params );
+ },
+
+ /**
+ * Expand or collapse the menu item control.
+ *
+ * @deprecated this is poor naming, and it is better to directly set control.expanded( showOrHide )
+ * @since 4.5.0 Added params.completeCallback.
+ *
+ * @param {boolean} [showOrHide] - If not supplied, will be inverse of current visibility
+ * @param {Object} [params] - Optional params.
+ * @param {Function} [params.completeCallback] - Function to call when the form toggle has finished animating.
+ */
+ toggleForm: function( showOrHide, params ) {
+ if ( typeof showOrHide === 'undefined' ) {
+ showOrHide = ! this.expanded();
+ }
+ if ( showOrHide ) {
+ this.expand( params );
+ } else {
+ this.collapse( params );
+ }
+ },
+
+ /**
+ * Expand or collapse the menu item control.
+ *
+ * @since 4.6.0
+ * @param {boolean} [showOrHide] - If not supplied, will be inverse of current visibility
+ * @param {Object} [params] - Optional params.
+ * @param {Function} [params.completeCallback] - Function to call when the form toggle has finished animating.
+ */
+ onChangeExpanded: function( showOrHide, params ) {
+ var self = this, $menuitem, $inside, complete;
+
+ $menuitem = this.container;
+ $inside = $menuitem.find( '.menu-item-settings:first' );
+ if ( 'undefined' === typeof showOrHide ) {
+ showOrHide = ! $inside.is( ':visible' );
+ }
+
+ // Already expanded or collapsed.
+ if ( $inside.is( ':visible' ) === showOrHide ) {
+ if ( params && params.completeCallback ) {
+ params.completeCallback();
+ }
+ return;
+ }
+
+ if ( showOrHide ) {
+ // Close all other menu item controls before expanding this one.
+ api.control.each( function( otherControl ) {
+ if ( self.params.type === otherControl.params.type && self !== otherControl ) {
+ otherControl.collapseForm();
+ }
+ } );
+
+ complete = function() {
+ $menuitem
+ .removeClass( 'menu-item-edit-inactive' )
+ .addClass( 'menu-item-edit-active' );
+ self.container.trigger( 'expanded' );
+
+ if ( params && params.completeCallback ) {
+ params.completeCallback();
+ }
+ };
+
+ $menuitem.find( '.item-edit' ).attr( 'aria-expanded', 'true' );
+ $inside.slideDown( 'fast', complete );
+
+ self.container.trigger( 'expand' );
+ } else {
+ complete = function() {
+ $menuitem
+ .addClass( 'menu-item-edit-inactive' )
+ .removeClass( 'menu-item-edit-active' );
+ self.container.trigger( 'collapsed' );
+
+ if ( params && params.completeCallback ) {
+ params.completeCallback();
+ }
+ };
+
+ self.container.trigger( 'collapse' );
+
+ $menuitem.find( '.item-edit' ).attr( 'aria-expanded', 'false' );
+ $inside.slideUp( 'fast', complete );
+ }
+ },
+
+ /**
+ * Expand the containing menu section, expand the form, and focus on
+ * the first input in the control.
+ *
+ * @since 4.5.0 Added params.completeCallback.
+ *
+ * @param {Object} [params] - Params object.
+ * @param {Function} [params.completeCallback] - Optional callback function when focus has completed.
+ */
+ focus: function( params ) {
+ params = params || {};
+ var control = this, originalCompleteCallback = params.completeCallback, focusControl;
+
+ focusControl = function() {
+ control.expandControlSection();
+
+ params.completeCallback = function() {
+ var focusable;
+
+ // Note that we can't use :focusable due to a jQuery UI issue. See: https://github.com/jquery/jquery-ui/pull/1583
+ focusable = control.container.find( '.menu-item-settings' ).find( 'input, select, textarea, button, object, a[href], [tabindex]' ).filter( ':visible' );
+ focusable.first().focus();
+
+ if ( originalCompleteCallback ) {
+ originalCompleteCallback();
+ }
+ };
+
+ control.expandForm( params );
+ };
+
+ if ( api.section.has( control.section() ) ) {
+ api.section( control.section() ).expand( {
+ completeCallback: focusControl
+ } );
+ } else {
+ focusControl();
+ }
+ },
+
+ /**
+ * Move menu item up one in the menu.
+ */
+ moveUp: function() {
+ this._changePosition( -1 );
+ wp.a11y.speak( api.Menus.data.l10n.movedUp );
+ },
+
+ /**
+ * Move menu item up one in the menu.
+ */
+ moveDown: function() {
+ this._changePosition( 1 );
+ wp.a11y.speak( api.Menus.data.l10n.movedDown );
+ },
+ /**
+ * Move menu item and all children up one level of depth.
+ */
+ moveLeft: function() {
+ this._changeDepth( -1 );
+ wp.a11y.speak( api.Menus.data.l10n.movedLeft );
+ },
+
+ /**
+ * Move menu item and children one level deeper, as a submenu of the previous item.
+ */
+ moveRight: function() {
+ this._changeDepth( 1 );
+ wp.a11y.speak( api.Menus.data.l10n.movedRight );
+ },
+
+ /**
+ * Note that this will trigger a UI update, causing child items to
+ * move as well and cardinal order class names to be updated.
+ *
+ * @private
+ *
+ * @param {Number} offset 1|-1
+ */
+ _changePosition: function( offset ) {
+ var control = this,
+ adjacentSetting,
+ settingValue = _.clone( control.setting() ),
+ siblingSettings = [],
+ realPosition;
+
+ if ( 1 !== offset && -1 !== offset ) {
+ throw new Error( 'Offset changes by 1 are only supported.' );
+ }
+
+ // Skip moving deleted items.
+ if ( ! control.setting() ) {
+ return;
+ }
+
+ // Locate the other items under the same parent (siblings).
+ _( control.getMenuControl().getMenuItemControls() ).each(function( otherControl ) {
+ if ( otherControl.setting().menu_item_parent === settingValue.menu_item_parent ) {
+ siblingSettings.push( otherControl.setting );
+ }
+ });
+ siblingSettings.sort(function( a, b ) {
+ return a().position - b().position;
+ });
+
+ realPosition = _.indexOf( siblingSettings, control.setting );
+ if ( -1 === realPosition ) {
+ throw new Error( 'Expected setting to be among siblings.' );
+ }
+
+ // Skip doing anything if the item is already at the edge in the desired direction.
+ if ( ( realPosition === 0 && offset < 0 ) || ( realPosition === siblingSettings.length - 1 && offset > 0 ) ) {
+ // @todo Should we allow a menu item to be moved up to break it out of a parent? Adopt with previous or following parent?
+ return;
+ }
+
+ // Update any adjacent menu item setting to take on this item's position.
+ adjacentSetting = siblingSettings[ realPosition + offset ];
+ if ( adjacentSetting ) {
+ adjacentSetting.set( $.extend(
+ _.clone( adjacentSetting() ),
+ {
+ position: settingValue.position
+ }
+ ) );
+ }
+
+ settingValue.position += offset;
+ control.setting.set( settingValue );
+ },
+
+ /**
+ * Note that this will trigger a UI update, causing child items to
+ * move as well and cardinal order class names to be updated.
+ *
+ * @private
+ *
+ * @param {Number} offset 1|-1
+ */
+ _changeDepth: function( offset ) {
+ if ( 1 !== offset && -1 !== offset ) {
+ throw new Error( 'Offset changes by 1 are only supported.' );
+ }
+ var control = this,
+ settingValue = _.clone( control.setting() ),
+ siblingControls = [],
+ realPosition,
+ siblingControl,
+ parentControl;
+
+ // Locate the other items under the same parent (siblings).
+ _( control.getMenuControl().getMenuItemControls() ).each(function( otherControl ) {
+ if ( otherControl.setting().menu_item_parent === settingValue.menu_item_parent ) {
+ siblingControls.push( otherControl );
+ }
+ });
+ siblingControls.sort(function( a, b ) {
+ return a.setting().position - b.setting().position;
+ });
+
+ realPosition = _.indexOf( siblingControls, control );
+ if ( -1 === realPosition ) {
+ throw new Error( 'Expected control to be among siblings.' );
+ }
+
+ if ( -1 === offset ) {
+ // Skip moving left an item that is already at the top level.
+ if ( ! settingValue.menu_item_parent ) {
+ return;
+ }
+
+ parentControl = api.control( 'nav_menu_item[' + settingValue.menu_item_parent + ']' );
+
+ // Make this control the parent of all the following siblings.
+ _( siblingControls ).chain().slice( realPosition ).each(function( siblingControl, i ) {
+ siblingControl.setting.set(
+ $.extend(
+ {},
+ siblingControl.setting(),
+ {
+ menu_item_parent: control.params.menu_item_id,
+ position: i
+ }
+ )
+ );
+ });
+
+ // Increase the positions of the parent item's subsequent children to make room for this one.
+ _( control.getMenuControl().getMenuItemControls() ).each(function( otherControl ) {
+ var otherControlSettingValue, isControlToBeShifted;
+ isControlToBeShifted = (
+ otherControl.setting().menu_item_parent === parentControl.setting().menu_item_parent &&
+ otherControl.setting().position > parentControl.setting().position
+ );
+ if ( isControlToBeShifted ) {
+ otherControlSettingValue = _.clone( otherControl.setting() );
+ otherControl.setting.set(
+ $.extend(
+ otherControlSettingValue,
+ { position: otherControlSettingValue.position + 1 }
+ )
+ );
+ }
+ });
+
+ // Make this control the following sibling of its parent item.
+ settingValue.position = parentControl.setting().position + 1;
+ settingValue.menu_item_parent = parentControl.setting().menu_item_parent;
+ control.setting.set( settingValue );
+
+ } else if ( 1 === offset ) {
+ // Skip moving right an item that doesn't have a previous sibling.
+ if ( realPosition === 0 ) {
+ return;
+ }
+
+ // Make the control the last child of the previous sibling.
+ siblingControl = siblingControls[ realPosition - 1 ];
+ settingValue.menu_item_parent = siblingControl.params.menu_item_id;
+ settingValue.position = 0;
+ _( control.getMenuControl().getMenuItemControls() ).each(function( otherControl ) {
+ if ( otherControl.setting().menu_item_parent === settingValue.menu_item_parent ) {
+ settingValue.position = Math.max( settingValue.position, otherControl.setting().position );
+ }
+ });
+ settingValue.position += 1;
+ control.setting.set( settingValue );
+ }
+ }
+ } );
+
+ /**
+ * wp.customize.Menus.MenuNameControl
+ *
+ * Customizer control for a nav menu's name.
+ *
+ * @class wp.customize.Menus.MenuNameControl
+ * @augments wp.customize.Control
+ */
+ api.Menus.MenuNameControl = api.Control.extend(/** @lends wp.customize.Menus.MenuNameControl.prototype */{
+
+ ready: function() {
+ var control = this;
+
+ if ( control.setting ) {
+ var settingValue = control.setting();
+
+ control.nameElement = new api.Element( control.container.find( '.menu-name-field' ) );
+
+ control.nameElement.bind(function( value ) {
+ var settingValue = control.setting();
+ if ( settingValue && settingValue.name !== value ) {
+ settingValue = _.clone( settingValue );
+ settingValue.name = value;
+ control.setting.set( settingValue );
+ }
+ });
+ if ( settingValue ) {
+ control.nameElement.set( settingValue.name );
+ }
+
+ control.setting.bind(function( object ) {
+ if ( object ) {
+ control.nameElement.set( object.name );
+ }
+ });
+ }
+ }
+ });
+
+ /**
+ * wp.customize.Menus.MenuLocationsControl
+ *
+ * Customizer control for a nav menu's locations.
+ *
+ * @since 4.9.0
+ * @class wp.customize.Menus.MenuLocationsControl
+ * @augments wp.customize.Control
+ */
+ api.Menus.MenuLocationsControl = api.Control.extend(/** @lends wp.customize.Menus.MenuLocationsControl.prototype */{
+
+ /**
+ * Set up the control.
+ *
+ * @since 4.9.0
+ */
+ ready: function () {
+ var control = this;
+
+ control.container.find( '.assigned-menu-location' ).each(function() {
+ var container = $( this ),
+ checkbox = container.find( 'input[type=checkbox]' ),
+ element = new api.Element( checkbox ),
+ navMenuLocationSetting = api( 'nav_menu_locations[' + checkbox.data( 'location-id' ) + ']' ),
+ isNewMenu = control.params.menu_id === '',
+ updateCheckbox = isNewMenu ? _.noop : function( checked ) {
+ element.set( checked );
+ },
+ updateSetting = isNewMenu ? _.noop : function( checked ) {
+ navMenuLocationSetting.set( checked ? control.params.menu_id : 0 );
+ },
+ updateSelectedMenuLabel = function( selectedMenuId ) {
+ var menuSetting = api( 'nav_menu[' + String( selectedMenuId ) + ']' );
+ if ( ! selectedMenuId || ! menuSetting || ! menuSetting() ) {
+ container.find( '.theme-location-set' ).hide();
+ } else {
+ container.find( '.theme-location-set' ).show().find( 'span' ).text( displayNavMenuName( menuSetting().name ) );
+ }
+ };
+
+ updateCheckbox( navMenuLocationSetting.get() === control.params.menu_id );
+
+ checkbox.on( 'change', function() {
+ // Note: We can't use element.bind( function( checked ){ ... } ) here because it will trigger a change as well.
+ updateSetting( this.checked );
+ } );
+
+ navMenuLocationSetting.bind( function( selectedMenuId ) {
+ updateCheckbox( selectedMenuId === control.params.menu_id );
+ updateSelectedMenuLabel( selectedMenuId );
+ } );
+ updateSelectedMenuLabel( navMenuLocationSetting.get() );
+ });
+ },
+
+ /**
+ * Set the selected locations.
+ *
+ * This method sets the selected locations and allows us to do things like
+ * set the default location for a new menu.
+ *
+ * @since 4.9.0
+ *
+ * @param {Object.<string,boolean>} selections - A map of location selections.
+ * @returns {void}
+ */
+ setSelections: function( selections ) {
+ this.container.find( '.menu-location' ).each( function( i, checkboxNode ) {
+ var locationId = checkboxNode.dataset.locationId;
+ checkboxNode.checked = locationId in selections ? selections[ locationId ] : false;
+ } );
+ }
+ });
+
+ /**
+ * wp.customize.Menus.MenuAutoAddControl
+ *
+ * Customizer control for a nav menu's auto add.
+ *
+ * @class wp.customize.Menus.MenuAutoAddControl
+ * @augments wp.customize.Control
+ */
+ api.Menus.MenuAutoAddControl = api.Control.extend(/** @lends wp.customize.Menus.MenuAutoAddControl.prototype */{
+
+ ready: function() {
+ var control = this,
+ settingValue = control.setting();
+
+ /*
+ * Since the control is not registered in PHP, we need to prevent the
+ * preview's sending of the activeControls to result in this control
+ * being deactivated.
+ */
+ control.active.validate = function() {
+ var value, section = api.section( control.section() );
+ if ( section ) {
+ value = section.active();
+ } else {
+ value = false;
+ }
+ return value;
+ };
+
+ control.autoAddElement = new api.Element( control.container.find( 'input[type=checkbox].auto_add' ) );
+
+ control.autoAddElement.bind(function( value ) {
+ var settingValue = control.setting();
+ if ( settingValue && settingValue.name !== value ) {
+ settingValue = _.clone( settingValue );
+ settingValue.auto_add = value;
+ control.setting.set( settingValue );
+ }
+ });
+ if ( settingValue ) {
+ control.autoAddElement.set( settingValue.auto_add );
+ }
+
+ control.setting.bind(function( object ) {
+ if ( object ) {
+ control.autoAddElement.set( object.auto_add );
+ }
+ });
+ }
+
+ });
+
+ /**
+ * wp.customize.Menus.MenuControl
+ *
+ * Customizer control for menus.
+ * Note that 'nav_menu' must match the WP_Menu_Customize_Control::$type
+ *
+ * @class wp.customize.Menus.MenuControl
+ * @augments wp.customize.Control
+ */
+ api.Menus.MenuControl = api.Control.extend(/** @lends wp.customize.Menus.MenuControl.prototype */{
+ /**
+ * Set up the control.
+ */
+ ready: function() {
+ var control = this,
+ section = api.section( control.section() ),
+ menuId = control.params.menu_id,
+ menu = control.setting(),
+ name,
+ widgetTemplate,
+ select;
+
+ if ( 'undefined' === typeof this.params.menu_id ) {
+ throw new Error( 'params.menu_id was not defined' );
+ }
+
+ /*
+ * Since the control is not registered in PHP, we need to prevent the
+ * preview's sending of the activeControls to result in this control
+ * being deactivated.
+ */
+ control.active.validate = function() {
+ var value;
+ if ( section ) {
+ value = section.active();
+ } else {
+ value = false;
+ }
+ return value;
+ };
+
+ control.$controlSection = section.headContainer;
+ control.$sectionContent = control.container.closest( '.accordion-section-content' );
+
+ this._setupModel();
+
+ api.section( control.section(), function( section ) {
+ section.deferred.initSortables.done(function( menuList ) {
+ control._setupSortable( menuList );
+ });
+ } );
+
+ this._setupAddition();
+ this._setupTitle();
+
+ // Add menu to Navigation Menu widgets.
+ if ( menu ) {
+ name = displayNavMenuName( menu.name );
+
+ // Add the menu to the existing controls.
+ api.control.each( function( widgetControl ) {
+ if ( ! widgetControl.extended( api.controlConstructor.widget_form ) || 'nav_menu' !== widgetControl.params.widget_id_base ) {
+ return;
+ }
+ widgetControl.container.find( '.nav-menu-widget-form-controls:first' ).show();
+ widgetControl.container.find( '.nav-menu-widget-no-menus-message:first' ).hide();
+
+ select = widgetControl.container.find( 'select' );
+ if ( 0 === select.find( 'option[value=' + String( menuId ) + ']' ).length ) {
+ select.append( new Option( name, menuId ) );
+ }
+ } );
+
+ // Add the menu to the widget template.
+ widgetTemplate = $( '#available-widgets-list .widget-tpl:has( input.id_base[ value=nav_menu ] )' );
+ widgetTemplate.find( '.nav-menu-widget-form-controls:first' ).show();
+ widgetTemplate.find( '.nav-menu-widget-no-menus-message:first' ).hide();
+ select = widgetTemplate.find( '.widget-inside select:first' );
+ if ( 0 === select.find( 'option[value=' + String( menuId ) + ']' ).length ) {
+ select.append( new Option( name, menuId ) );
+ }
+ }
+
+ /*
+ * Wait for menu items to be added.
+ * Ideally, we'd bind to an event indicating construction is complete,
+ * but deferring appears to be the best option today.
+ */
+ _.defer( function () {
+ control.updateInvitationVisibility();
+ } );
+ },
+
+ /**
+ * Update ordering of menu item controls when the setting is updated.
+ */
+ _setupModel: function() {
+ var control = this,
+ menuId = control.params.menu_id;
+
+ control.setting.bind( function( to ) {
+ var name;
+ if ( false === to ) {
+ control._handleDeletion();
+ } else {
+ // Update names in the Navigation Menu widgets.
+ name = displayNavMenuName( to.name );
+ api.control.each( function( widgetControl ) {
+ if ( ! widgetControl.extended( api.controlConstructor.widget_form ) || 'nav_menu' !== widgetControl.params.widget_id_base ) {
+ return;
+ }
+ var select = widgetControl.container.find( 'select' );
+ select.find( 'option[value=' + String( menuId ) + ']' ).text( name );
+ });
+ }
+ } );
+ },
+
+ /**
+ * Allow items in each menu to be re-ordered, and for the order to be previewed.
+ *
+ * Notice that the UI aspects here are handled by wpNavMenu.initSortables()
+ * which is called in MenuSection.onChangeExpanded()
+ *
+ * @param {object} menuList - The element that has sortable().
+ */
+ _setupSortable: function( menuList ) {
+ var control = this;
+
+ if ( ! menuList.is( control.$sectionContent ) ) {
+ throw new Error( 'Unexpected menuList.' );
+ }
+
+ menuList.on( 'sortstart', function() {
+ control.isSorting = true;
+ });
+
+ menuList.on( 'sortstop', function() {
+ setTimeout( function() { // Next tick.
+ var menuItemContainerIds = control.$sectionContent.sortable( 'toArray' ),
+ menuItemControls = [],
+ position = 0,
+ priority = 10;
+
+ control.isSorting = false;
+
+ // Reset horizontal scroll position when done dragging.
+ control.$sectionContent.scrollLeft( 0 );
+
+ _.each( menuItemContainerIds, function( menuItemContainerId ) {
+ var menuItemId, menuItemControl, matches;
+ matches = menuItemContainerId.match( /^customize-control-nav_menu_item-(-?\d+)$/, '' );
+ if ( ! matches ) {
+ return;
+ }
+ menuItemId = parseInt( matches[1], 10 );
+ menuItemControl = api.control( 'nav_menu_item[' + String( menuItemId ) + ']' );
+ if ( menuItemControl ) {
+ menuItemControls.push( menuItemControl );
+ }
+ } );
+
+ _.each( menuItemControls, function( menuItemControl ) {
+ if ( false === menuItemControl.setting() ) {
+ // Skip deleted items.
+ return;
+ }
+ var setting = _.clone( menuItemControl.setting() );
+ position += 1;
+ priority += 1;
+ setting.position = position;
+ menuItemControl.priority( priority );
+
+ // Note that wpNavMenu will be setting this .menu-item-data-parent-id input's value.
+ setting.menu_item_parent = parseInt( menuItemControl.container.find( '.menu-item-data-parent-id' ).val(), 10 );
+ if ( ! setting.menu_item_parent ) {
+ setting.menu_item_parent = 0;
+ }
+
+ menuItemControl.setting.set( setting );
+ });
+ });
+
+ });
+ control.isReordering = false;
+
+ /**
+ * Keyboard-accessible reordering.
+ */
+ this.container.find( '.reorder-toggle' ).on( 'click', function() {
+ control.toggleReordering( ! control.isReordering );
+ } );
+ },
+
+ /**
+ * Set up UI for adding a new menu item.
+ */
+ _setupAddition: function() {
+ var self = this;
+
+ this.container.find( '.add-new-menu-item' ).on( 'click', function( event ) {
+ if ( self.$sectionContent.hasClass( 'reordering' ) ) {
+ return;
+ }
+
+ if ( ! $( 'body' ).hasClass( 'adding-menu-items' ) ) {
+ $( this ).attr( 'aria-expanded', 'true' );
+ api.Menus.availableMenuItemsPanel.open( self );
+ } else {
+ $( this ).attr( 'aria-expanded', 'false' );
+ api.Menus.availableMenuItemsPanel.close();
+ event.stopPropagation();
+ }
+ } );
+ },
+
+ _handleDeletion: function() {
+ var control = this,
+ section,
+ menuId = control.params.menu_id,
+ removeSection,
+ widgetTemplate,
+ navMenuCount = 0;
+ section = api.section( control.section() );
+ removeSection = function() {
+ section.container.remove();
+ api.section.remove( section.id );
+ };
+
+ if ( section && section.expanded() ) {
+ section.collapse({
+ completeCallback: function() {
+ removeSection();
+ wp.a11y.speak( api.Menus.data.l10n.menuDeleted );
+ api.panel( 'nav_menus' ).focus();
+ }
+ });
+ } else {
+ removeSection();
+ }
+
+ api.each(function( setting ) {
+ if ( /^nav_menu\[/.test( setting.id ) && false !== setting() ) {
+ navMenuCount += 1;
+ }
+ });
+
+ // Remove the menu from any Navigation Menu widgets.
+ api.control.each(function( widgetControl ) {
+ if ( ! widgetControl.extended( api.controlConstructor.widget_form ) || 'nav_menu' !== widgetControl.params.widget_id_base ) {
+ return;
+ }
+ var select = widgetControl.container.find( 'select' );
+ if ( select.val() === String( menuId ) ) {
+ select.prop( 'selectedIndex', 0 ).trigger( 'change' );
+ }
+
+ widgetControl.container.find( '.nav-menu-widget-form-controls:first' ).toggle( 0 !== navMenuCount );
+ widgetControl.container.find( '.nav-menu-widget-no-menus-message:first' ).toggle( 0 === navMenuCount );
+ widgetControl.container.find( 'option[value=' + String( menuId ) + ']' ).remove();
+ });
+
+ // Remove the menu to the nav menu widget template.
+ widgetTemplate = $( '#available-widgets-list .widget-tpl:has( input.id_base[ value=nav_menu ] )' );
+ widgetTemplate.find( '.nav-menu-widget-form-controls:first' ).toggle( 0 !== navMenuCount );
+ widgetTemplate.find( '.nav-menu-widget-no-menus-message:first' ).toggle( 0 === navMenuCount );
+ widgetTemplate.find( 'option[value=' + String( menuId ) + ']' ).remove();
+ },
+
+ /**
+ * Update Section Title as menu name is changed.
+ */
+ _setupTitle: function() {
+ var control = this;
+
+ control.setting.bind( function( menu ) {
+ if ( ! menu ) {
+ return;
+ }
+
+ var section = api.section( control.section() ),
+ menuId = control.params.menu_id,
+ controlTitle = section.headContainer.find( '.accordion-section-title' ),
+ sectionTitle = section.contentContainer.find( '.customize-section-title h3' ),
+ location = section.headContainer.find( '.menu-in-location' ),
+ action = sectionTitle.find( '.customize-action' ),
+ name = displayNavMenuName( menu.name );
+
+ // Update the control title
+ controlTitle.text( name );
+ if ( location.length ) {
+ location.appendTo( controlTitle );
+ }
+
+ // Update the section title
+ sectionTitle.text( name );
+ if ( action.length ) {
+ action.prependTo( sectionTitle );
+ }
+
+ // Update the nav menu name in location selects.
+ api.control.each( function( control ) {
+ if ( /^nav_menu_locations\[/.test( control.id ) ) {
+ control.container.find( 'option[value=' + menuId + ']' ).text( name );
+ }
+ } );
+
+ // Update the nav menu name in all location checkboxes.
+ section.contentContainer.find( '.customize-control-checkbox input' ).each( function() {
+ if ( $( this ).prop( 'checked' ) ) {
+ $( '.current-menu-location-name-' + $( this ).data( 'location-id' ) ).text( name );
+ }
+ } );
+ } );
+ },
+
+ /***********************************************************************
+ * Begin public API methods
+ **********************************************************************/
+
+ /**
+ * Enable/disable the reordering UI
+ *
+ * @param {Boolean} showOrHide to enable/disable reordering
+ */
+ toggleReordering: function( showOrHide ) {
+ var addNewItemBtn = this.container.find( '.add-new-menu-item' ),
+ reorderBtn = this.container.find( '.reorder-toggle' ),
+ itemsTitle = this.$sectionContent.find( '.item-title' );
+
+ showOrHide = Boolean( showOrHide );
+
+ if ( showOrHide === this.$sectionContent.hasClass( 'reordering' ) ) {
+ return;
+ }
+
+ this.isReordering = showOrHide;
+ this.$sectionContent.toggleClass( 'reordering', showOrHide );
+ this.$sectionContent.sortable( this.isReordering ? 'disable' : 'enable' );
+ if ( this.isReordering ) {
+ addNewItemBtn.attr({ 'tabindex': '-1', 'aria-hidden': 'true' });
+ reorderBtn.attr( 'aria-label', api.Menus.data.l10n.reorderLabelOff );
+ wp.a11y.speak( api.Menus.data.l10n.reorderModeOn );
+ itemsTitle.attr( 'aria-hidden', 'false' );
+ } else {
+ addNewItemBtn.removeAttr( 'tabindex aria-hidden' );
+ reorderBtn.attr( 'aria-label', api.Menus.data.l10n.reorderLabelOn );
+ wp.a11y.speak( api.Menus.data.l10n.reorderModeOff );
+ itemsTitle.attr( 'aria-hidden', 'true' );
+ }
+
+ if ( showOrHide ) {
+ _( this.getMenuItemControls() ).each( function( formControl ) {
+ formControl.collapseForm();
+ } );
+ }
+ },
+
+ /**
+ * @return {wp.customize.controlConstructor.nav_menu_item[]}
+ */
+ getMenuItemControls: function() {
+ var menuControl = this,
+ menuItemControls = [],
+ menuTermId = menuControl.params.menu_id;
+
+ api.control.each(function( control ) {
+ if ( 'nav_menu_item' === control.params.type && control.setting() && menuTermId === control.setting().nav_menu_term_id ) {
+ menuItemControls.push( control );
+ }
+ });
+
+ return menuItemControls;
+ },
+
+ /**
+ * Make sure that each menu item control has the proper depth.
+ */
+ reflowMenuItems: function() {
+ var menuControl = this,
+ menuItemControls = menuControl.getMenuItemControls(),
+ reflowRecursively;
+
+ reflowRecursively = function( context ) {
+ var currentMenuItemControls = [],
+ thisParent = context.currentParent;
+ _.each( context.menuItemControls, function( menuItemControl ) {
+ if ( thisParent === menuItemControl.setting().menu_item_parent ) {
+ currentMenuItemControls.push( menuItemControl );
+ // @todo We could remove this item from menuItemControls now, for efficiency.
+ }
+ });
+ currentMenuItemControls.sort( function( a, b ) {
+ return a.setting().position - b.setting().position;
+ });
+
+ _.each( currentMenuItemControls, function( menuItemControl ) {
+ // Update position.
+ context.currentAbsolutePosition += 1;
+ menuItemControl.priority.set( context.currentAbsolutePosition ); // This will change the sort order.
+
+ // Update depth.
+ if ( ! menuItemControl.container.hasClass( 'menu-item-depth-' + String( context.currentDepth ) ) ) {
+ _.each( menuItemControl.container.prop( 'className' ).match( /menu-item-depth-\d+/g ), function( className ) {
+ menuItemControl.container.removeClass( className );
+ });
+ menuItemControl.container.addClass( 'menu-item-depth-' + String( context.currentDepth ) );
+ }
+ menuItemControl.container.data( 'item-depth', context.currentDepth );
+
+ // Process any children items.
+ context.currentDepth += 1;
+ context.currentParent = menuItemControl.params.menu_item_id;
+ reflowRecursively( context );
+ context.currentDepth -= 1;
+ context.currentParent = thisParent;
+ });
+
+ // Update class names for reordering controls.
+ if ( currentMenuItemControls.length ) {
+ _( currentMenuItemControls ).each(function( menuItemControl ) {
+ menuItemControl.container.removeClass( 'move-up-disabled move-down-disabled move-left-disabled move-right-disabled' );
+ if ( 0 === context.currentDepth ) {
+ menuItemControl.container.addClass( 'move-left-disabled' );
+ } else if ( 10 === context.currentDepth ) {
+ menuItemControl.container.addClass( 'move-right-disabled' );
+ }
+ });
+
+ currentMenuItemControls[0].container
+ .addClass( 'move-up-disabled' )
+ .addClass( 'move-right-disabled' )
+ .toggleClass( 'move-down-disabled', 1 === currentMenuItemControls.length );
+ currentMenuItemControls[ currentMenuItemControls.length - 1 ].container
+ .addClass( 'move-down-disabled' )
+ .toggleClass( 'move-up-disabled', 1 === currentMenuItemControls.length );
+ }
+ };
+
+ reflowRecursively( {
+ menuItemControls: menuItemControls,
+ currentParent: 0,
+ currentDepth: 0,
+ currentAbsolutePosition: 0
+ } );
+
+ menuControl.updateInvitationVisibility( menuItemControls );
+ menuControl.container.find( '.reorder-toggle' ).toggle( menuItemControls.length > 1 );
+ },
+
+ /**
+ * Note that this function gets debounced so that when a lot of setting
+ * changes are made at once, for instance when moving a menu item that
+ * has child items, this function will only be called once all of the
+ * settings have been updated.
+ */
+ debouncedReflowMenuItems: _.debounce( function() {
+ this.reflowMenuItems.apply( this, arguments );
+ }, 0 ),
+
+ /**
+ * Add a new item to this menu.
+ *
+ * @param {object} item - Value for the nav_menu_item setting to be created.
+ * @returns {wp.customize.Menus.controlConstructor.nav_menu_item} The newly-created nav_menu_item control instance.
+ */
+ addItemToMenu: function( item ) {
+ var menuControl = this, customizeId, settingArgs, setting, menuItemControl, placeholderId, position = 0, priority = 10;
+
+ _.each( menuControl.getMenuItemControls(), function( control ) {
+ if ( false === control.setting() ) {
+ return;
+ }
+ priority = Math.max( priority, control.priority() );
+ if ( 0 === control.setting().menu_item_parent ) {
+ position = Math.max( position, control.setting().position );
+ }
+ });
+ position += 1;
+ priority += 1;
+
+ item = $.extend(
+ {},
+ api.Menus.data.defaultSettingValues.nav_menu_item,
+ item,
+ {
+ nav_menu_term_id: menuControl.params.menu_id,
+ original_title: item.title,
+ position: position
+ }
+ );
+ delete item.id; // only used by Backbone
+
+ placeholderId = api.Menus.generatePlaceholderAutoIncrementId();
+ customizeId = 'nav_menu_item[' + String( placeholderId ) + ']';
+ settingArgs = {
+ type: 'nav_menu_item',
+ transport: api.Menus.data.settingTransport,
+ previewer: api.previewer
+ };
+ setting = api.create( customizeId, customizeId, {}, settingArgs );
+ setting.set( item ); // Change from initial empty object to actual item to mark as dirty.
+
+ // Add the menu item control.
+ menuItemControl = new api.controlConstructor.nav_menu_item( customizeId, {
+ type: 'nav_menu_item',
+ section: menuControl.id,
+ priority: priority,
+ settings: {
+ 'default': customizeId
+ },
+ menu_item_id: placeholderId
+ } );
+
+ api.control.add( menuItemControl );
+ setting.preview();
+ menuControl.debouncedReflowMenuItems();
+
+ wp.a11y.speak( api.Menus.data.l10n.itemAdded );
+
+ return menuItemControl;
+ },
+
+ /**
+ * Show an invitation to add new menu items when there are no menu items.
+ *
+ * @since 4.9.0
+ *
+ * @param {wp.customize.controlConstructor.nav_menu_item[]} optionalMenuItemControls
+ */
+ updateInvitationVisibility: function ( optionalMenuItemControls ) {
+ var menuItemControls = optionalMenuItemControls || this.getMenuItemControls();
+
+ this.container.find( '.new-menu-item-invitation' ).toggle( menuItemControls.length === 0 );
+ }
+ } );
+
+ api.Menus.NewMenuControl = api.Control.extend(/** @lends wp.customize.Menus.NewMenuControl.prototype */{
+
+ /**
+ * wp.customize.Menus.NewMenuControl
+ *
+ * Customizer control for creating new menus and handling deletion of existing menus.
+ * Note that 'new_menu' must match the WP_Customize_New_Menu_Control::$type.
+ *
+ * @constructs wp.customize.Menus.NewMenuControl
+ * @augments wp.customize.Control
+ *
+ * @deprecated 4.9.0 This class is no longer used due to new menu creation UX.
+ */
+ initialize: function() {
+ if ( 'undefined' !== typeof console && console.warn ) {
+ console.warn( '[DEPRECATED] wp.customize.NewMenuControl will be removed. Please use wp.customize.Menus.createNavMenu() instead.' );
+ }
+ api.Control.prototype.initialize.apply( this, arguments );
+ },
+
+ /**
+ * Set up the control.
+ *
+ * @deprecated 4.9.0
+ */
+ ready: function() {
+ this._bindHandlers();
+ },
+
+ _bindHandlers: function() {
+ var self = this,
+ name = $( '#customize-control-new_menu_name input' ),
+ submit = $( '#create-new-menu-submit' );
+ name.on( 'keydown', function( event ) {
+ if ( 13 === event.which ) { // Enter.
+ self.submit();
+ }
+ } );
+ submit.on( 'click', function( event ) {
+ self.submit();
+ event.stopPropagation();
+ event.preventDefault();
+ } );
+ },
+
+ /**
+ * Create the new menu with the name supplied.
+ *
+ * @deprecated 4.9.0
+ */
+ submit: function() {
+
+ var control = this,
+ container = control.container.closest( '.accordion-section-new-menu' ),
+ nameInput = container.find( '.menu-name-field' ).first(),
+ name = nameInput.val(),
+ menuSection;
+
+ if ( ! name ) {
+ nameInput.addClass( 'invalid' );
+ nameInput.focus();
+ return;
+ }
+
+ menuSection = api.Menus.createNavMenu( name );
+
+ // Clear name field.
+ nameInput.val( '' );
+ nameInput.removeClass( 'invalid' );
+
+ wp.a11y.speak( api.Menus.data.l10n.menuAdded );
+
+ // Focus on the new menu section.
+ menuSection.focus();
+ }
+ });
+
+ /**
+ * Extends wp.customize.controlConstructor with control constructor for
+ * menu_location, menu_item, nav_menu, and new_menu.
+ */
+ $.extend( api.controlConstructor, {
+ nav_menu_location: api.Menus.MenuLocationControl,
+ nav_menu_item: api.Menus.MenuItemControl,
+ nav_menu: api.Menus.MenuControl,
+ nav_menu_name: api.Menus.MenuNameControl,
+ new_menu: api.Menus.NewMenuControl, // @todo Remove in a future release. See #42364.
+ nav_menu_locations: api.Menus.MenuLocationsControl,
+ nav_menu_auto_add: api.Menus.MenuAutoAddControl
+ });
+
+ /**
+ * Extends wp.customize.panelConstructor with section constructor for menus.
+ */
+ $.extend( api.panelConstructor, {
+ nav_menus: api.Menus.MenusPanel
+ });
+
+ /**
+ * Extends wp.customize.sectionConstructor with section constructor for menu.
+ */
+ $.extend( api.sectionConstructor, {
+ nav_menu: api.Menus.MenuSection,
+ new_menu: api.Menus.NewMenuSection
+ });
+
+ /**
+ * Init Customizer for menus.
+ */
+ api.bind( 'ready', function() {
+
+ // Set up the menu items panel.
+ api.Menus.availableMenuItemsPanel = new api.Menus.AvailableMenuItemsPanelView({
+ collection: api.Menus.availableMenuItems
+ });
+
+ api.bind( 'saved', function( data ) {
+ if ( data.nav_menu_updates || data.nav_menu_item_updates ) {
+ api.Menus.applySavedData( data );
+ }
+ } );
+
+ /*
+ * Reset the list of posts created in the customizer once published.
+ * The setting is updated quietly (bypassing events being triggered)
+ * so that the customized state doesn't become immediately dirty.
+ */
+ api.state( 'changesetStatus' ).bind( function( status ) {
+ if ( 'publish' === status ) {
+ api( 'nav_menus_created_posts' )._value = [];
+ }
+ } );
+
+ // Open and focus menu control.
+ api.previewer.bind( 'focus-nav-menu-item-control', api.Menus.focusMenuItemControl );
+ } );
+
+ /**
+ * When customize_save comes back with a success, make sure any inserted
+ * nav menus and items are properly re-added with their newly-assigned IDs.
+ *
+ * @alias wp.customize.Menus.applySavedData
+ *
+ * @param {object} data
+ * @param {array} data.nav_menu_updates
+ * @param {array} data.nav_menu_item_updates
+ */
+ api.Menus.applySavedData = function( data ) {
+
+ var insertedMenuIdMapping = {}, insertedMenuItemIdMapping = {};
+
+ _( data.nav_menu_updates ).each(function( update ) {
+ var oldCustomizeId, newCustomizeId, customizeId, oldSetting, newSetting, setting, settingValue, oldSection, newSection, wasSaved, widgetTemplate, navMenuCount, shouldExpandNewSection;
+ if ( 'inserted' === update.status ) {
+ if ( ! update.previous_term_id ) {
+ throw new Error( 'Expected previous_term_id' );
+ }
+ if ( ! update.term_id ) {
+ throw new Error( 'Expected term_id' );
+ }
+ oldCustomizeId = 'nav_menu[' + String( update.previous_term_id ) + ']';
+ if ( ! api.has( oldCustomizeId ) ) {
+ throw new Error( 'Expected setting to exist: ' + oldCustomizeId );
+ }
+ oldSetting = api( oldCustomizeId );
+ if ( ! api.section.has( oldCustomizeId ) ) {
+ throw new Error( 'Expected control to exist: ' + oldCustomizeId );
+ }
+ oldSection = api.section( oldCustomizeId );
+
+ settingValue = oldSetting.get();
+ if ( ! settingValue ) {
+ throw new Error( 'Did not expect setting to be empty (deleted).' );
+ }
+ settingValue = $.extend( _.clone( settingValue ), update.saved_value );
+
+ insertedMenuIdMapping[ update.previous_term_id ] = update.term_id;
+ newCustomizeId = 'nav_menu[' + String( update.term_id ) + ']';
+ newSetting = api.create( newCustomizeId, newCustomizeId, settingValue, {
+ type: 'nav_menu',
+ transport: api.Menus.data.settingTransport,
+ previewer: api.previewer
+ } );
+
+ shouldExpandNewSection = oldSection.expanded();
+ if ( shouldExpandNewSection ) {
+ oldSection.collapse();
+ }
+
+ // Add the menu section.
+ newSection = new api.Menus.MenuSection( newCustomizeId, {
+ panel: 'nav_menus',
+ title: settingValue.name,
+ customizeAction: api.Menus.data.l10n.customizingMenus,
+ type: 'nav_menu',
+ priority: oldSection.priority.get(),
+ menu_id: update.term_id
+ } );
+
+ // Add new control for the new menu.
+ api.section.add( newSection );
+
+ // Update the values for nav menus in Navigation Menu controls.
+ api.control.each( function( setting ) {
+ if ( ! setting.extended( api.controlConstructor.widget_form ) || 'nav_menu' !== setting.params.widget_id_base ) {
+ return;
+ }
+ var select, oldMenuOption, newMenuOption;
+ select = setting.container.find( 'select' );
+ oldMenuOption = select.find( 'option[value=' + String( update.previous_term_id ) + ']' );
+ newMenuOption = select.find( 'option[value=' + String( update.term_id ) + ']' );
+ newMenuOption.prop( 'selected', oldMenuOption.prop( 'selected' ) );
+ oldMenuOption.remove();
+ } );
+
+ // Delete the old placeholder nav_menu.
+ oldSetting.callbacks.disable(); // Prevent setting triggering Customizer dirty state when set.
+ oldSetting.set( false );
+ oldSetting.preview();
+ newSetting.preview();
+ oldSetting._dirty = false;
+
+ // Remove nav_menu section.
+ oldSection.container.remove();
+ api.section.remove( oldCustomizeId );
+
+ // Update the nav_menu widget to reflect removed placeholder menu.
+ navMenuCount = 0;
+ api.each(function( setting ) {
+ if ( /^nav_menu\[/.test( setting.id ) && false !== setting() ) {
+ navMenuCount += 1;
+ }
+ });
+ widgetTemplate = $( '#available-widgets-list .widget-tpl:has( input.id_base[ value=nav_menu ] )' );
+ widgetTemplate.find( '.nav-menu-widget-form-controls:first' ).toggle( 0 !== navMenuCount );
+ widgetTemplate.find( '.nav-menu-widget-no-menus-message:first' ).toggle( 0 === navMenuCount );
+ widgetTemplate.find( 'option[value=' + String( update.previous_term_id ) + ']' ).remove();
+
+ // Update the nav_menu_locations[...] controls to remove the placeholder menus from the dropdown options.
+ wp.customize.control.each(function( control ){
+ if ( /^nav_menu_locations\[/.test( control.id ) ) {
+ control.container.find( 'option[value=' + String( update.previous_term_id ) + ']' ).remove();
+ }
+ });
+
+ // Update nav_menu_locations to reference the new ID.
+ api.each( function( setting ) {
+ var wasSaved = api.state( 'saved' ).get();
+ if ( /^nav_menu_locations\[/.test( setting.id ) && setting.get() === update.previous_term_id ) {
+ setting.set( update.term_id );
+ setting._dirty = false; // Not dirty because this is has also just been done on server in WP_Customize_Nav_Menu_Setting::update().
+ api.state( 'saved' ).set( wasSaved );
+ setting.preview();
+ }
+ } );
+
+ if ( shouldExpandNewSection ) {
+ newSection.expand();
+ }
+ } else if ( 'updated' === update.status ) {
+ customizeId = 'nav_menu[' + String( update.term_id ) + ']';
+ if ( ! api.has( customizeId ) ) {
+ throw new Error( 'Expected setting to exist: ' + customizeId );
+ }
+
+ // Make sure the setting gets updated with its sanitized server value (specifically the conflict-resolved name).
+ setting = api( customizeId );
+ if ( ! _.isEqual( update.saved_value, setting.get() ) ) {
+ wasSaved = api.state( 'saved' ).get();
+ setting.set( update.saved_value );
+ setting._dirty = false;
+ api.state( 'saved' ).set( wasSaved );
+ }
+ }
+ } );
+
+ // Build up mapping of nav_menu_item placeholder IDs to inserted IDs.
+ _( data.nav_menu_item_updates ).each(function( update ) {
+ if ( update.previous_post_id ) {
+ insertedMenuItemIdMapping[ update.previous_post_id ] = update.post_id;
+ }
+ });
+
+ _( data.nav_menu_item_updates ).each(function( update ) {
+ var oldCustomizeId, newCustomizeId, oldSetting, newSetting, settingValue, oldControl, newControl;
+ if ( 'inserted' === update.status ) {
+ if ( ! update.previous_post_id ) {
+ throw new Error( 'Expected previous_post_id' );
+ }
+ if ( ! update.post_id ) {
+ throw new Error( 'Expected post_id' );
+ }
+ oldCustomizeId = 'nav_menu_item[' + String( update.previous_post_id ) + ']';
+ if ( ! api.has( oldCustomizeId ) ) {
+ throw new Error( 'Expected setting to exist: ' + oldCustomizeId );
+ }
+ oldSetting = api( oldCustomizeId );
+ if ( ! api.control.has( oldCustomizeId ) ) {
+ throw new Error( 'Expected control to exist: ' + oldCustomizeId );
+ }
+ oldControl = api.control( oldCustomizeId );
+
+ settingValue = oldSetting.get();
+ if ( ! settingValue ) {
+ throw new Error( 'Did not expect setting to be empty (deleted).' );
+ }
+ settingValue = _.clone( settingValue );
+
+ // If the parent menu item was also inserted, update the menu_item_parent to the new ID.
+ if ( settingValue.menu_item_parent < 0 ) {
+ if ( ! insertedMenuItemIdMapping[ settingValue.menu_item_parent ] ) {
+ throw new Error( 'inserted ID for menu_item_parent not available' );
+ }
+ settingValue.menu_item_parent = insertedMenuItemIdMapping[ settingValue.menu_item_parent ];
+ }
+
+ // If the menu was also inserted, then make sure it uses the new menu ID for nav_menu_term_id.
+ if ( insertedMenuIdMapping[ settingValue.nav_menu_term_id ] ) {
+ settingValue.nav_menu_term_id = insertedMenuIdMapping[ settingValue.nav_menu_term_id ];
+ }
+
+ newCustomizeId = 'nav_menu_item[' + String( update.post_id ) + ']';
+ newSetting = api.create( newCustomizeId, newCustomizeId, settingValue, {
+ type: 'nav_menu_item',
+ transport: api.Menus.data.settingTransport,
+ previewer: api.previewer
+ } );
+
+ // Add the menu control.
+ newControl = new api.controlConstructor.nav_menu_item( newCustomizeId, {
+ type: 'nav_menu_item',
+ menu_id: update.post_id,
+ section: 'nav_menu[' + String( settingValue.nav_menu_term_id ) + ']',
+ priority: oldControl.priority.get(),
+ settings: {
+ 'default': newCustomizeId
+ },
+ menu_item_id: update.post_id
+ } );
+
+ // Remove old control.
+ oldControl.container.remove();
+ api.control.remove( oldCustomizeId );
+
+ // Add new control to take its place.
+ api.control.add( newControl );
+
+ // Delete the placeholder and preview the new setting.
+ oldSetting.callbacks.disable(); // Prevent setting triggering Customizer dirty state when set.
+ oldSetting.set( false );
+ oldSetting.preview();
+ newSetting.preview();
+ oldSetting._dirty = false;
+
+ newControl.container.toggleClass( 'menu-item-edit-inactive', oldControl.container.hasClass( 'menu-item-edit-inactive' ) );
+ }
+ });
+
+ /*
+ * Update the settings for any nav_menu widgets that had selected a placeholder ID.
+ */
+ _.each( data.widget_nav_menu_updates, function( widgetSettingValue, widgetSettingId ) {
+ var setting = api( widgetSettingId );
+ if ( setting ) {
+ setting._value = widgetSettingValue;
+ setting.preview(); // Send to the preview now so that menu refresh will use the inserted menu.
+ }
+ });
+ };
+
+ /**
+ * Focus a menu item control.
+ *
+ * @alias wp.customize.Menus.focusMenuItemControl
+ *
+ * @param {string} menuItemId
+ */
+ api.Menus.focusMenuItemControl = function( menuItemId ) {
+ var control = api.Menus.getMenuItemControl( menuItemId );
+ if ( control ) {
+ control.focus();
+ }
+ };
+
+ /**
+ * Get the control for a given menu.
+ *
+ * @alias wp.customize.Menus.getMenuControl
+ *
+ * @param menuId
+ * @return {wp.customize.controlConstructor.menus[]}
+ */
+ api.Menus.getMenuControl = function( menuId ) {
+ return api.control( 'nav_menu[' + menuId + ']' );
+ };
+
+ /**
+ * Given a menu item ID, get the control associated with it.
+ *
+ * @alias wp.customize.Menus.getMenuItemControl
+ *
+ * @param {string} menuItemId
+ * @return {object|null}
+ */
+ api.Menus.getMenuItemControl = function( menuItemId ) {
+ return api.control( menuItemIdToSettingId( menuItemId ) );
+ };
+
+ /**
+ * @alias wp.customize.Menus~menuItemIdToSettingId
+ *
+ * @param {String} menuItemId
+ */
+ function menuItemIdToSettingId( menuItemId ) {
+ return 'nav_menu_item[' + menuItemId + ']';
+ }
+
+ /**
+ * Apply sanitize_text_field()-like logic to the supplied name, returning a
+ * "unnammed" fallback string if the name is then empty.
+ *
+ * @alias wp.customize.Menus~displayNavMenuName
+ *
+ * @param {string} name
+ * @returns {string}
+ */
+ function displayNavMenuName( name ) {
+ name = name || '';
+ name = wp.sanitize.stripTagsAndEncodeText( name ); // Remove any potential tags from name.
+ name = $.trim( name );
+ return name || api.Menus.data.l10n.unnamed;
+ }
+
+})( wp.customize, wp, jQuery );
diff --git a/www/crm/wp-admin/js/customize-nav-menus.min.js b/www/crm/wp-admin/js/customize-nav-menus.min.js
new file mode 100644
index 00000000..4bc3d158
--- /dev/null
+++ b/www/crm/wp-admin/js/customize-nav-menus.min.js
@@ -0,0 +1,2 @@
+!function(a,b,c){"use strict";function d(a){return"nav_menu_item["+a+"]"}function e(d){return d=d||"",d=b.sanitize.stripTagsAndEncodeText(d),d=c.trim(d),d||a.Menus.data.l10n.unnamed}wpNavMenu.originalInit=wpNavMenu.init,wpNavMenu.options.menuItemDepthPerLevel=20,wpNavMenu.options.sortableItems="> .customize-control-nav_menu_item",wpNavMenu.options.targetTolerance=10,wpNavMenu.init=function(){this.jQueryExtensions()},a.Menus=a.Menus||{},a.Menus.data={itemTypes:[],l10n:{},settingTransport:"refresh",phpIntMax:0,defaultSettingValues:{nav_menu:{},nav_menu_item:{}},locationSlugMappedToName:{}},"undefined"!=typeof _wpCustomizeNavMenusSettings&&c.extend(a.Menus.data,_wpCustomizeNavMenusSettings),a.Menus.generatePlaceholderAutoIncrementId=function(){return-Math.ceil(a.Menus.data.phpIntMax*Math.random())},a.Menus.AvailableItemModel=Backbone.Model.extend(c.extend({id:null},a.Menus.data.defaultSettingValues.nav_menu_item)),a.Menus.AvailableItemCollection=Backbone.Collection.extend({model:a.Menus.AvailableItemModel,sort_key:"order",comparator:function(a){return-a.get(this.sort_key)},sortByField:function(a){this.sort_key=a,this.sort()}}),a.Menus.availableMenuItems=new a.Menus.AvailableItemCollection(a.Menus.data.availableMenuItems),a.Menus.insertAutoDraftPost=function(d){var e,f=c.Deferred();return e=b.ajax.post("customize-nav-menus-insert-auto-draft",{"customize-menus-nonce":a.settings.nonce["customize-menus"],wp_customize:"on",customize_changeset_uuid:a.settings.changeset.uuid,params:d}),e.done(function(b){b.post_id&&(a("nav_menus_created_posts").set(a("nav_menus_created_posts").get().concat([b.post_id])),"page"===d.post_type&&(a.section.has("static_front_page")&&a.section("static_front_page").activate(),a.control.each(function(a){var c;"dropdown-pages"===a.params.type&&(c=a.container.find('select[name^="_customize-dropdown-pages-"]'),c.append(new Option(d.post_title,b.post_id)))})),f.resolve(b))}),e.fail(function(a){var b=a||"";"undefined"!=typeof a.message&&(b=a.message),console.error(b),f.rejectWith(b)}),f.promise()},a.Menus.AvailableMenuItemsPanelView=b.Backbone.View.extend({el:"#available-menu-items",events:{"input #menu-items-search":"debounceSearch","focus .menu-item-tpl":"focus","click .menu-item-tpl":"_submit","click #custom-menu-item-submit":"_submitLink","keypress #custom-menu-item-name":"_submitLink","click .new-content-item .add-content":"_submitNew","keypress .create-item-input":"_submitNew",keydown:"keyboardAccessible"},selected:null,currentMenuControl:null,debounceSearch:null,$search:null,$clearResults:null,searchTerm:"",rendered:!1,pages:{},sectionContent:"",loading:!1,addingNew:!1,initialize:function(){var b=this;a.panel.has("nav_menus")&&(this.$search=c("#menu-items-search"),this.$clearResults=this.$el.find(".clear-results"),this.sectionContent=this.$el.find(".available-menu-items-list"),this.debounceSearch=_.debounce(b.search,500),_.bindAll(this,"close"),c("#customize-controls, .customize-section-back").on("click keydown",function(a){var d=c(a.target).is(".item-delete, .item-delete *"),e=c(a.target).is(".add-new-menu-item, .add-new-menu-item *");!c("body").hasClass("adding-menu-items")||d||e||b.close()}),this.$clearResults.on("click",function(){b.$search.val("").focus().trigger("keyup")}),this.$el.on("input","#custom-menu-item-name.invalid, #custom-menu-item-url.invalid",function(){c(this).removeClass("invalid")}),a.panel("nav_menus").container.bind("expanded",function(){b.rendered||(b.initList(),b.rendered=!0)}),this.sectionContent.scroll(function(){var a=b.$el.find(".accordion-section.open .available-menu-items-list").prop("scrollHeight"),d=b.$el.find(".accordion-section.open").height();if(!b.loading&&c(this).scrollTop()>.75*a-d){var e=c(this).data("type"),f=c(this).data("object");"search"===e?b.searchTerm&&b.doSearch(b.pages.search):b.loadItems([{type:e,object:f}])}}),a.previewer.bind("url",this.close),b.delegateEvents())},search:function(a){var b=c("#available-menu-items-search"),d=c("#available-menu-items .accordion-section").not(b);a&&this.searchTerm!==a.target.value&&(""===a.target.value||b.hasClass("open")?""===a.target.value&&(b.removeClass("open"),d.show(),this.$clearResults.removeClass("is-visible")):(d.fadeOut(100),b.find(".accordion-section-content").slideDown("fast"),b.addClass("open"),this.$clearResults.addClass("is-visible")),this.searchTerm=a.target.value,this.pages.search=1,this.doSearch(1))},doSearch:function(d){var e,f=this,g=c("#available-menu-items-search"),h=g.find(".accordion-section-content"),i=b.template("available-menu-item");if(f.currentRequest&&f.currentRequest.abort(),!(d<0)){if(d>1)g.addClass("loading-more"),h.attr("aria-busy","true"),b.a11y.speak(a.Menus.data.l10n.itemsLoadingMore);else if(""===f.searchTerm)return h.html(""),void b.a11y.speak("");g.addClass("loading"),f.loading=!0,e=a.previewer.query({excludeCustomizedSaved:!0}),_.extend(e,{"customize-menus-nonce":a.settings.nonce["customize-menus"],wp_customize:"on",search:f.searchTerm,page:d}),f.currentRequest=b.ajax.post("search-available-menu-items-customizer",e),f.currentRequest.done(function(c){var e;1===d&&h.empty(),g.removeClass("loading loading-more"),h.attr("aria-busy","false"),g.addClass("open"),f.loading=!1,e=new a.Menus.AvailableItemCollection(c.items),f.collection.add(e.models),e.each(function(a){h.append(i(a.attributes))}),20>e.length?f.pages.search=-1:f.pages.search=f.pages.search+1,e&&d>1?b.a11y.speak(a.Menus.data.l10n.itemsFoundMore.replace("%d",e.length)):e&&1===d&&b.a11y.speak(a.Menus.data.l10n.itemsFound.replace("%d",e.length))}),f.currentRequest.fail(function(a){a.message&&(h.empty().append(c('<li class="nothing-found"></li>').text(a.message)),b.a11y.speak(a.message)),f.pages.search=-1}),f.currentRequest.always(function(){g.removeClass("loading loading-more"),h.attr("aria-busy","false"),f.loading=!1,f.currentRequest=null})}},initList:function(){var b=this;_.each(a.Menus.data.itemTypes,function(a){b.pages[a.type+":"+a.object]=0}),b.loadItems(a.Menus.data.itemTypes)},loadItems:function(d,e){var f,g,h,i,j=this,k=[],l={};i=b.template("available-menu-item"),f=_.isString(d)&&_.isString(e)?[{type:d,object:e}]:d,_.each(f,function(a){var b,d=a.type+":"+a.object;-1!==j.pages[d]&&(b=c("#available-menu-items-"+a.type+"-"+a.object),b.find(".accordion-section-title").addClass("loading"),l[d]=b,k.push({object:a.object,type:a.type,page:j.pages[d]}))}),0!==k.length&&(j.loading=!0,g=a.previewer.query({excludeCustomizedSaved:!0}),_.extend(g,{"customize-menus-nonce":a.settings.nonce["customize-menus"],wp_customize:"on",item_types:k}),h=b.ajax.post("load-available-menu-items-customizer",g),h.done(function(b){var c;_.each(b.items,function(b,d){return 0===b.length?(0===j.pages[d]&&l[d].find(".accordion-section-title").addClass("cannot-expand").removeClass("loading").find(".accordion-section-title > button").prop("tabIndex",-1),void(j.pages[d]=-1)):("post_type:page"!==d||l[d].hasClass("open")||l[d].find(".accordion-section-title > button").click(),b=new a.Menus.AvailableItemCollection(b),j.collection.add(b.models),c=l[d].find(".available-menu-items-list"),b.each(function(a){c.append(i(a.attributes))}),void(j.pages[d]+=1))})}),h.fail(function(a){"undefined"!=typeof console&&console.error&&console.error(a)}),h.always(function(){_.each(l,function(a){a.find(".accordion-section-title").removeClass("loading")}),j.loading=!1}))},itemSectionHeight:function(){var a,b,c,d,e;c=window.innerHeight,a=this.$el.find(".accordion-section:not( #available-menu-items-search ) .accordion-section-content"),b=this.$el.find('.accordion-section:not( #available-menu-items-search ) .available-menu-items-list:not(":only-child")'),d=46*(1+a.length)+14,e=c-d,120<e&&290>e&&(a.css("max-height",e),b.css("max-height",e-60))},select:function(a){this.selected=c(a),this.selected.siblings(".menu-item-tpl").removeClass("selected"),this.selected.addClass("selected")},focus:function(a){this.select(c(a.currentTarget))},_submit:function(a){"keypress"===a.type&&13!==a.which&&32!==a.which||this.submit(c(a.currentTarget))},submit:function(a){var b,d;a||(a=this.selected),a&&this.currentMenuControl&&(this.select(a),b=c(this.selected).data("menu-item-id"),d=this.collection.findWhere({id:b}),d&&(this.currentMenuControl.addItemToMenu(d.attributes),c(a).find(".menu-item-handle").addClass("item-added")))},_submitLink:function(a){"keypress"===a.type&&13!==a.which||this.submitLink()},submitLink:function(){var b,d,e=c("#custom-menu-item-name"),f=c("#custom-menu-item-url"),g=f.val().trim();if(this.currentMenuControl){if(d=/^((\w+:)?\/\/\w.*|\w+:(?!\/\/$)|\/|\?|#)/,""===e.val())return void e.addClass("invalid");if(!d.test(g))return void f.addClass("invalid");b={title:e.val(),url:g,type:"custom",type_label:a.Menus.data.l10n.custom_label,object:"custom"},this.currentMenuControl.addItemToMenu(b),f.val("http://"),e.val("")}},_submitNew:function(a){var b;"keypress"===a.type&&13!==a.which||this.addingNew||(b=c(a.target).closest(".accordion-section"),this.submitNew(b))},submitNew:function(d){var e,f=this,g=d.find(".create-item-input"),h=g.val(),i=d.find(".available-menu-items-list"),j=i.data("type"),k=i.data("object"),l=i.data("type_label");if(this.currentMenuControl&&"post_type"===j){if(""===c.trim(g.val()))return g.addClass("invalid"),void g.focus();g.removeClass("invalid"),d.find(".accordion-section-title").addClass("loading"),f.addingNew=!0,g.attr("disabled","disabled"),e=a.Menus.insertAutoDraftPost({post_title:h,post_type:k}),e.done(function(e){var h,i,m;h=new a.Menus.AvailableItemModel({id:"post-"+e.post_id,title:g.val(),type:j,type_label:l,object:k,object_id:e.post_id,url:e.url}),f.currentMenuControl.addItemToMenu(h.attributes),a.Menus.availableMenuItemsPanel.collection.add(h),i=d.find(".available-menu-items-list"),m=c(b.template("available-menu-item")(h.attributes)),m.find(".menu-item-handle:first").addClass("item-added"),i.prepend(m),i.scrollTop(),g.val("").removeAttr("disabled"),f.addingNew=!1,d.find(".accordion-section-title").removeClass("loading")})}},open:function(b){var d,e=this;this.currentMenuControl=b,this.itemSectionHeight(),a.section.has("publish_settings")&&a.section("publish_settings").collapse(),c("body").addClass("adding-menu-items"),d=function(){e.close(),c(this).off("click",d)},c("#customize-preview").on("click",d),_(this.currentMenuControl.getMenuItemControls()).each(function(a){a.collapseForm()}),this.$el.find(".selected").removeClass("selected"),this.$search.focus()},close:function(a){a=a||{},a.returnFocus&&this.currentMenuControl&&this.currentMenuControl.container.find(".add-new-menu-item").focus(),this.currentMenuControl=null,this.selected=null,c("body").removeClass("adding-menu-items"),c("#available-menu-items .menu-item-handle.item-added").removeClass("item-added"),this.$search.val("").trigger("keyup")},keyboardAccessible:function(a){var b=13===a.which,d=27===a.which,e=9===a.which&&a.shiftKey,f=c(a.target).is(this.$search);b&&!this.$search.val()||(f&&e?(this.currentMenuControl.container.find(".add-new-menu-item").focus(),a.preventDefault()):d&&this.close({returnFocus:!0}))}}),a.Menus.MenusPanel=a.Panel.extend({attachEvents:function(){a.Panel.prototype.attachEvents.call(this);var b=this,d=b.container.find(".panel-meta"),e=d.find(".customize-help-toggle"),f=d.find(".customize-panel-description"),g=c("#screen-options-wrap"),h=d.find(".customize-screen-options-toggle");h.on("click keydown",function(b){if(!a.utils.isKeydownButNotEnterEvent(b))return b.preventDefault(),f.not(":hidden")&&(f.slideUp("fast"),e.attr("aria-expanded","false")),"true"===h.attr("aria-expanded")?(h.attr("aria-expanded","false"),d.removeClass("open"),d.removeClass("active-menu-screen-options"),g.slideUp("fast")):(h.attr("aria-expanded","true"),d.addClass("open"),d.addClass("active-menu-screen-options"),g.slideDown("fast")),!1}),e.on("click keydown",function(b){a.utils.isKeydownButNotEnterEvent(b)||(b.preventDefault(),"true"===h.attr("aria-expanded")&&(h.attr("aria-expanded","false"),e.attr("aria-expanded","true"),d.addClass("open"),d.removeClass("active-menu-screen-options"),g.slideUp("fast"),f.slideDown("fast")))})},ready:function(){var c=this;c.container.find(".hide-column-tog").click(function(){c.saveManageColumnsState()}),a.section("menu_locations",function(c){c.headContainer.prepend(b.template("nav-menu-locations-header")(a.Menus.data))})},saveManageColumnsState:_.debounce(function(){var a=this;a._updateHiddenColumnsRequest&&a._updateHiddenColumnsRequest.abort(),a._updateHiddenColumnsRequest=b.ajax.post("hidden-columns",{hidden:a.hidden(),screenoptionnonce:c("#screenoptionnonce").val(),page:"nav-menus"}),a._updateHiddenColumnsRequest.always(function(){a._updateHiddenColumnsRequest=null})},2e3),checked:function(){},unchecked:function(){},hidden:function(){return c(".hide-column-tog").not(":checked").map(function(){var a=this.id;return a.substring(0,a.length-5)}).get().join(",")}}),a.Menus.MenuSection=a.Section.extend({initialize:function(b,d){var e=this;a.Section.prototype.initialize.call(e,b,d),e.deferred.initSortables=c.Deferred()},ready:function(){var b,d,e=this;if("undefined"==typeof e.params.menu_id)throw new Error("params.menu_id was not defined");e.active.validate=function(){return!!a.has(e.id)&&!!a(e.id).get()},e.populateControls(),e.navMenuLocationSettings={},e.assignedLocations=new a.Value([]),a.each(function(a,b){var c=b.match(/^nav_menu_locations\[(.+?)]/);c&&(e.navMenuLocationSettings[c[1]]=a,a.bind(function(){e.refreshAssignedLocations()}))}),e.assignedLocations.bind(function(a){e.updateAssignedLocationsInSectionTitle(a)}),e.refreshAssignedLocations(),a.bind("pane-contents-reflowed",function(){e.contentContainer.parent().length&&(e.container.find(".menu-item .menu-item-reorder-nav button").attr({tabindex:"0","aria-hidden":"false"}),e.container.find(".menu-item.move-up-disabled .menus-move-up").attr({tabindex:"-1","aria-hidden":"true"}),e.container.find(".menu-item.move-down-disabled .menus-move-down").attr({tabindex:"-1","aria-hidden":"true"}),e.container.find(".menu-item.move-left-disabled .menus-move-left").attr({tabindex:"-1","aria-hidden":"true"}),e.container.find(".menu-item.move-right-disabled .menus-move-right").attr({tabindex:"-1","aria-hidden":"true"}))}),d=function(){var a="field-"+c(this).val()+"-active";e.contentContainer.toggleClass(a,c(this).prop("checked"))},b=a.panel("nav_menus").contentContainer.find(".metabox-prefs:first").find(".hide-column-tog"),b.each(d),b.on("click",d)},populateControls:function(){var b,c,d,e,f,g,h,i,j,k=this;b=k.id+"[name]",g=a.control(b),g||(g=new a.controlConstructor.nav_menu_name(b,{type:"nav_menu_name",label:a.Menus.data.l10n.menuNameLabel,section:k.id,priority:0,settings:{"default":k.id}}),a.control.add(g),g.active.set(!0)),f=a.control(k.id),f||(f=new a.controlConstructor.nav_menu(k.id,{type:"nav_menu",section:k.id,priority:998,settings:{"default":k.id},menu_id:k.params.menu_id}),a.control.add(f),f.active.set(!0)),c=k.id+"[locations]",h=a.control(c),h||(h=new a.controlConstructor.nav_menu_locations(c,{section:k.id,priority:999,settings:{"default":k.id},menu_id:k.params.menu_id}),a.control.add(h.id,h),f.active.set(!0)),d=k.id+"[auto_add]",i=a.control(d),i||(i=new a.controlConstructor.nav_menu_auto_add(d,{type:"nav_menu_auto_add",label:"",section:k.id,priority:1e3,settings:{"default":k.id}}),a.control.add(i),i.active.set(!0)),e=k.id+"[delete]",j=a.control(e),j||(j=new a.Control(e,{section:k.id,priority:1001,templateId:"nav-menu-delete-button"}),a.control.add(j.id,j),j.active.set(!0),j.deferred.embedded.done(function(){j.container.find("button").on("click",function(){var b=k.params.menu_id,c=a.Menus.getMenuControl(b);c.setting.set(!1)})}))},refreshAssignedLocations:function(){var a=this,b=a.params.menu_id,c=[];_.each(a.navMenuLocationSettings,function(a,d){a()===b&&c.push(d)}),a.assignedLocations.set(c)},updateAssignedLocationsInSectionTitle:function(b){var d,e=this;d=e.container.find(".accordion-section-title:first"),d.find(".menu-in-location").remove(),_.each(b,function(b){var e,f;e=c('<span class="menu-in-location"></span>'),f=a.Menus.data.locationSlugMappedToName[b],e.text(a.Menus.data.l10n.menuLocation.replace("%s",f)),d.append(e)}),e.container.toggleClass("assigned-to-menu-location",0!==b.length)},onChangeExpanded:function(b,d){var e,f=this;b&&(wpNavMenu.menuList=f.contentContainer,wpNavMenu.targetList=wpNavMenu.menuList,c("#menu-to-edit").removeAttr("id"),wpNavMenu.menuList.attr("id","menu-to-edit").addClass("menu"),_.each(a.section(f.id).controls(),function(a){"nav_menu_item"===a.params.type&&a.actuallyEmbed()}),d.completeCallback&&(e=d.completeCallback),d.completeCallback=function(){"resolved"!==f.deferred.initSortables.state()&&(wpNavMenu.initSortables(),f.deferred.initSortables.resolve(wpNavMenu.menuList),a.control("nav_menu["+String(f.params.menu_id)+"]").reflowMenuItems()),_.isFunction(e)&&e()}),a.Section.prototype.onChangeExpanded.call(f,b,d)},highlightNewItemButton:function(){a.utils.highlightButton(this.contentContainer.find(".add-new-menu-item"),{delay:2e3})}}),a.Menus.createNavMenu=function(b){var d,f,g;return f=a.Menus.generatePlaceholderAutoIncrementId(),d="nav_menu["+String(f)+"]",g=a.create(d,d,{},{type:"nav_menu",transport:a.Menus.data.settingTransport,previewer:a.previewer}),g.set(c.extend({},a.Menus.data.defaultSettingValues.nav_menu,{name:b||""})),a.section.add(new a.Menus.MenuSection(d,{panel:"nav_menus",title:e(b),customizeAction:a.Menus.data.l10n.customizingMenus,priority:10,menu_id:f}))},a.Menus.NewMenuSection=a.Section.extend({attachEvents:function(){function c(){var b=0;return a.each(function(a){j.test(a.id)&&!1!==a.get()&&(b+=1)}),b}function d(){h.find(".add-new-menu-notice").prop("hidden",c()>0)}function e(a){j.test(a.id)&&(a.bind(d),d())}function f(a){j.test(a.id)&&(a.unbind(d),d())}var g=this,h=g.container,i=g.contentContainer,j=/^nav_menu\[/;g.headContainer.find(".accordion-section-title").replaceWith(b.template("nav-menu-create-menu-section-title")),h.on("click",".customize-add-menu-button",function(){g.expand()}),i.on("keydown",".menu-name-field",function(a){13===a.which&&g.submit()}),i.on("click","#customize-new-menu-submit",function(a){g.submit(),a.stopPropagation(),a.preventDefault()}),a.each(e),a.bind("add",e),a.bind("removed",f),d(),a.Section.prototype.attachEvents.apply(g,arguments)},ready:function(){this.populateControls()},populateControls:function(){var b,c,d,e,f,g,h=this;b=h.id+"[name]",e=a.control(b),e||(e=new a.controlConstructor.nav_menu_name(b,{label:a.Menus.data.l10n.menuNameLabel,description:a.Menus.data.l10n.newMenuNameDescription,section:h.id,priority:0}),a.control.add(e.id,e),e.active.set(!0)),c=h.id+"[locations]",f=a.control(c),f||(f=new a.controlConstructor.nav_menu_locations(c,{section:h.id,priority:1,menu_id:"",isCreating:!0}),a.control.add(c,f),f.active.set(!0)),d=h.id+"[submit]",g=a.control(d),g||(g=new a.Control(d,{section:h.id,priority:1,templateId:"nav-menu-submit-new-button"}),a.control.add(d,g),g.active.set(!0))},submit:function(){var d,e=this,f=e.contentContainer,g=f.find(".menu-name-field").first(),h=g.val();return h?(d=a.Menus.createNavMenu(h),g.val(""),g.removeClass("invalid"),f.find(".assigned-menu-location input[type=checkbox]").each(function(){var b,e=c(this);e.prop("checked")&&(b=a("nav_menu_locations["+e.data("location-id")+"]"),b.set(d.params.menu_id),e.prop("checked",!1))}),b.a11y.speak(a.Menus.data.l10n.menuAdded),void d.focus({completeCallback:function(){d.highlightNewItemButton()}})):(g.addClass("invalid"),void g.focus())},selectDefaultLocation:function(b){var c=a.control(this.id+"[locations]"),d={};null!==b&&(d[b]=!0),c.setSelections(d)}}),a.Menus.MenuLocationControl=a.Control.extend({initialize:function(b,c){var d=this,e=b.match(/^nav_menu_locations\[(.+?)]/);d.themeLocation=e[1],a.Control.prototype.initialize.call(d,b,c)},ready:function(){var b=this,c=/^nav_menu\[(-?\d+)]/;b.setting.validate=function(a){return""===a?0:parseInt(a,10)},b.container.find(".create-menu").on("click",function(){var b=a.section("add_menu");b.selectDefaultLocation(this.dataset.locationId),b.focus()}),b.container.find(".edit-menu").on("click",function(){var c=b.setting();a.section("nav_menu["+c+"]").focus()}),b.setting.bind("change",function(){var a=0!==b.setting();b.container.find(".create-menu").toggleClass("hidden",a),b.container.find(".edit-menu").toggleClass("hidden",!a)}),a.bind("add",function(a){var d,f,g=a.id.match(c);g&&!1!==a()&&(f=g[1],d=new Option(e(a().name),f),b.container.find("select").append(d))}),a.bind("remove",function(a){var d,e=a.id.match(c);e&&(d=parseInt(e[1],10),b.setting()===d&&b.setting.set(""),b.container.find("option[value="+d+"]").remove())}),a.bind("change",function(a){var d,f=a.id.match(c);f&&(d=parseInt(f[1],10),!1===a()?(b.setting()===d&&b.setting.set(""),b.container.find("option[value="+d+"]").remove()):b.container.find("option[value="+d+"]").text(e(a().name)))})}}),a.Menus.MenuItemControl=a.Control.extend({initialize:function(b,d){var e=this;e.expanded=new a.Value(!1),e.expandedArgumentsQueue=[],e.expanded.bind(function(a){var b=e.expandedArgumentsQueue.shift();b=c.extend({},e.defaultExpandedArguments,b),e.onChangeExpanded(a,b)}),a.Control.prototype.initialize.call(e,b,d),e.active.validate=function(){var b,c=a.section(e.section());return b=!!c&&c.active()}},embed:function(){var b,c=this,d=c.section();d&&(b=a.section(d),(b&&b.expanded()||a.settings.autofocus.control===c.id)&&c.actuallyEmbed())},actuallyEmbed:function(){var a=this;"resolved"!==a.deferred.embedded.state()&&(a.renderContent(),a.deferred.embedded.resolve())},ready:function(){if("undefined"==typeof this.params.menu_item_id)throw new Error("params.menu_item_id was not defined");this._setupControlToggle(),this._setupReorderUI(),this._setupUpdateUI(),this._setupRemoveUI(),this._setupLinksUI(),this._setupTitleUI()},_setupControlToggle:function(){var b=this;this.container.find(".menu-item-handle").on("click",function(d){d.preventDefault(),d.stopPropagation();var e=b.getMenuControl(),f=c(d.target).is(".item-delete, .item-delete *"),g=c(d.target).is(".add-new-menu-item, .add-new-menu-item *");!c("body").hasClass("adding-menu-items")||f||g||a.Menus.availableMenuItemsPanel.close(),e.isReordering||e.isSorting||b.toggleForm()})},_setupReorderUI:function(){var a,d,e=this;a=b.template("menu-item-reorder-nav"),e.container.find(".item-controls").after(a),d=e.container.find(".menu-item-reorder-nav"),d.find(".menus-move-up, .menus-move-down, .menus-move-left, .menus-move-right").on("click",function(){var a=c(this);a.focus();var b=a.is(".menus-move-up"),d=a.is(".menus-move-down"),f=a.is(".menus-move-left"),g=a.is(".menus-move-right");b?e.moveUp():d?e.moveDown():f?e.moveLeft():g&&e.moveRight(),a.focus()})},_setupUpdateUI:function(){var b,c=this,d=c.setting();c.elements={},c.elements.url=new a.Element(c.container.find(".edit-menu-item-url")),c.elements.title=new a.Element(c.container.find(".edit-menu-item-title")),c.elements.attr_title=new a.Element(c.container.find(".edit-menu-item-attr-title")),c.elements.target=new a.Element(c.container.find(".edit-menu-item-target")),c.elements.classes=new a.Element(c.container.find(".edit-menu-item-classes")),c.elements.xfn=new a.Element(c.container.find(".edit-menu-item-xfn")),c.elements.description=new a.Element(c.container.find(".edit-menu-item-description")),_.each(c.elements,function(a,b){a.bind(function(d){a.element.is("input[type=checkbox]")&&(d=d?a.element.val():"");var e=c.setting();e&&e[b]!==d&&(e=_.clone(e),e[b]=d,c.setting.set(e))}),d&&("classes"!==b&&"xfn"!==b||!_.isArray(d[b])?a.set(d[b]):a.set(d[b].join(" ")))}),c.setting.bind(function(b,d){var e,f=c.params.menu_item_id,g=[],h=[];!1===b?(e=a.control("nav_menu["+String(d.nav_menu_term_id)+"]"),c.container.remove(),_.each(e.getMenuItemControls(),function(a){d.menu_item_parent===a.setting().menu_item_parent&&a.setting().position>d.position?g.push(a):a.setting().menu_item_parent===f&&h.push(a)}),_.each(g,function(a){var b=_.clone(a.setting());b.position+=h.length,a.setting.set(b)}),_.each(h,function(a,b){var c=_.clone(a.setting());c.position=d.position+b,c.menu_item_parent=d.menu_item_parent,a.setting.set(c)}),e.debouncedReflowMenuItems()):(_.each(b,function(a,d){c.elements[d]&&c.elements[d].set(b[d])}),c.container.find(".menu-item-data-parent-id").val(b.menu_item_parent),b.position===d.position&&b.menu_item_parent===d.menu_item_parent||c.getMenuControl().debouncedReflowMenuItems())}),b=function(){c.elements.url.element.toggleClass("invalid",c.setting.notifications.has("invalid_url"))},c.setting.notifications.bind("add",b),c.setting.notifications.bind("removed",b)},_setupRemoveUI:function(){var d,e=this;d=e.container.find(".item-delete"),d.on("click",function(){var d,f,g,h=!0;c("body").hasClass("adding-menu-items")||(h=!1),f=e.container.nextAll(".customize-control-nav_menu_item:visible").first(),g=e.container.prevAll(".customize-control-nav_menu_item:visible").first(),d=f.length?f.find(!1===h?".item-edit":".item-delete").first():g.length?g.find(!1===h?".item-edit":".item-delete").first():e.container.nextAll(".customize-control-nav_menu").find(".add-new-menu-item").first(),e.container.slideUp(function(){e.setting.set(!1),b.a11y.speak(a.Menus.data.l10n.itemDeleted),d.focus()}),e.setting.set(!1)})},_setupLinksUI:function(){var b;b=this.container.find("a.original-link"),b.on("click",function(b){b.preventDefault(),a.previewer.previewUrl(b.target.toString())})},_setupTitleUI:function(){var b,d=this;d.container.find(".edit-menu-item-title").on("blur",function(){c(this).val(c.trim(c(this).val()))}),b=d.container.find(".menu-item-title"),d.setting.bind(function(d){var e,f;d&&(e=c.trim(d.title),f=e||d.original_title||a.Menus.data.l10n.untitled,d._invalid&&(f=a.Menus.data.l10n.invalidTitleTpl.replace("%s",f)),e||d.original_title?b.text(f).removeClass("no-title"):b.text(f).addClass("no-title"))})},getDepth:function(){var b=this,c=b.setting(),d=0;if(!c)return 0;for(;c&&c.menu_item_parent&&(d+=1,b=a.control("nav_menu_item["+c.menu_item_parent+"]"));)c=b.setting();return d},renderContent:function(){var b,c=this,d=c.setting();c.params.title=d.title||"",c.params.depth=c.getDepth(),c.container.data("item-depth",c.params.depth),b=["menu-item","menu-item-depth-"+String(c.params.depth),"menu-item-"+d.object,"menu-item-edit-inactive"],d._invalid?(b.push("menu-item-invalid"),c.params.title=a.Menus.data.l10n.invalidTitleTpl.replace("%s",c.params.title)):"draft"===d.status&&(b.push("pending"),c.params.title=a.Menus.data.pendingTitleTpl.replace("%s",c.params.title)),c.params.el_classes=b.join(" "),c.params.item_type_label=d.type_label,c.params.item_type=d.type,c.params.url=d.url,c.params.target=d.target,c.params.attr_title=d.attr_title,c.params.classes=_.isArray(d.classes)?d.classes.join(" "):d.classes,c.params.attr_title=d.attr_title,c.params.xfn=d.xfn,c.params.description=d.description,c.params.parent=d.menu_item_parent,c.params.original_title=d.original_title||"",c.container.addClass(c.params.el_classes),a.Control.prototype.renderContent.call(c)},getMenuControl:function(){var b=this,c=b.setting();return c&&c.nav_menu_term_id?a.control("nav_menu["+c.nav_menu_term_id+"]"):null},expandControlSection:function(){var a=this.container.closest(".accordion-section");a.hasClass("open")||a.find(".accordion-section-title:first").trigger("click")},_toggleExpanded:a.Section.prototype._toggleExpanded,expand:a.Section.prototype.expand,expandForm:function(a){this.expand(a)},collapse:a.Section.prototype.collapse,collapseForm:function(a){this.collapse(a)},toggleForm:function(a,b){"undefined"==typeof a&&(a=!this.expanded()),a?this.expand(b):this.collapse(b)},onChangeExpanded:function(b,c){var d,e,f,g=this;return d=this.container,e=d.find(".menu-item-settings:first"),"undefined"==typeof b&&(b=!e.is(":visible")),e.is(":visible")===b?void(c&&c.completeCallback&&c.completeCallback()):void(b?(a.control.each(function(a){g.params.type===a.params.type&&g!==a&&a.collapseForm()}),f=function(){d.removeClass("menu-item-edit-inactive").addClass("menu-item-edit-active"),g.container.trigger("expanded"),c&&c.completeCallback&&c.completeCallback()},d.find(".item-edit").attr("aria-expanded","true"),e.slideDown("fast",f),g.container.trigger("expand")):(f=function(){d.addClass("menu-item-edit-inactive").removeClass("menu-item-edit-active"),g.container.trigger("collapsed"),c&&c.completeCallback&&c.completeCallback()},g.container.trigger("collapse"),d.find(".item-edit").attr("aria-expanded","false"),e.slideUp("fast",f)))},focus:function(b){b=b||{};var c,d=this,e=b.completeCallback;c=function(){d.expandControlSection(),b.completeCallback=function(){var a;a=d.container.find(".menu-item-settings").find("input, select, textarea, button, object, a[href], [tabindex]").filter(":visible"),a.first().focus(),e&&e()},d.expandForm(b)},a.section.has(d.section())?a.section(d.section()).expand({completeCallback:c}):c()},moveUp:function(){this._changePosition(-1),b.a11y.speak(a.Menus.data.l10n.movedUp)},moveDown:function(){this._changePosition(1),b.a11y.speak(a.Menus.data.l10n.movedDown)},moveLeft:function(){this._changeDepth(-1),b.a11y.speak(a.Menus.data.l10n.movedLeft)},moveRight:function(){this._changeDepth(1),b.a11y.speak(a.Menus.data.l10n.movedRight)},_changePosition:function(a){var b,d,e=this,f=_.clone(e.setting()),g=[];if(1!==a&&-1!==a)throw new Error("Offset changes by 1 are only supported.");if(e.setting()){if(_(e.getMenuControl().getMenuItemControls()).each(function(a){a.setting().menu_item_parent===f.menu_item_parent&&g.push(a.setting)}),g.sort(function(a,b){return a().position-b().position}),d=_.indexOf(g,e.setting),-1===d)throw new Error("Expected setting to be among siblings.");0===d&&a<0||d===g.length-1&&a>0||(b=g[d+a],b&&b.set(c.extend(_.clone(b()),{position:f.position})),f.position+=a,e.setting.set(f))}},_changeDepth:function(b){if(1!==b&&-1!==b)throw new Error("Offset changes by 1 are only supported.");var d,e,f,g=this,h=_.clone(g.setting()),i=[];if(_(g.getMenuControl().getMenuItemControls()).each(function(a){a.setting().menu_item_parent===h.menu_item_parent&&i.push(a)}),i.sort(function(a,b){return a.setting().position-b.setting().position}),d=_.indexOf(i,g),-1===d)throw new Error("Expected control to be among siblings.");if(-1===b){if(!h.menu_item_parent)return;f=a.control("nav_menu_item["+h.menu_item_parent+"]"),_(i).chain().slice(d).each(function(a,b){a.setting.set(c.extend({},a.setting(),{menu_item_parent:g.params.menu_item_id,position:b}))}),_(g.getMenuControl().getMenuItemControls()).each(function(a){var b,d;d=a.setting().menu_item_parent===f.setting().menu_item_parent&&a.setting().position>f.setting().position,d&&(b=_.clone(a.setting()),a.setting.set(c.extend(b,{position:b.position+1})))}),h.position=f.setting().position+1,h.menu_item_parent=f.setting().menu_item_parent,g.setting.set(h)}else if(1===b){if(0===d)return;e=i[d-1],h.menu_item_parent=e.params.menu_item_id,h.position=0,_(g.getMenuControl().getMenuItemControls()).each(function(a){a.setting().menu_item_parent===h.menu_item_parent&&(h.position=Math.max(h.position,a.setting().position))}),h.position+=1,g.setting.set(h)}}}),a.Menus.MenuNameControl=a.Control.extend({ready:function(){var b=this;if(b.setting){var c=b.setting();b.nameElement=new a.Element(b.container.find(".menu-name-field")),b.nameElement.bind(function(a){var c=b.setting();c&&c.name!==a&&(c=_.clone(c),c.name=a,b.setting.set(c))}),c&&b.nameElement.set(c.name),b.setting.bind(function(a){a&&b.nameElement.set(a.name)})}}}),a.Menus.MenuLocationsControl=a.Control.extend({ready:function(){var b=this;b.container.find(".assigned-menu-location").each(function(){var d=c(this),f=d.find("input[type=checkbox]"),g=new a.Element(f),h=a("nav_menu_locations["+f.data("location-id")+"]"),i=""===b.params.menu_id,j=i?_.noop:function(a){g.set(a)},k=i?_.noop:function(a){h.set(a?b.params.menu_id:0)},l=function(b){var c=a("nav_menu["+String(b)+"]");b&&c&&c()?d.find(".theme-location-set").show().find("span").text(e(c().name)):d.find(".theme-location-set").hide()};j(h.get()===b.params.menu_id),f.on("change",function(){k(this.checked);
+}),h.bind(function(a){j(a===b.params.menu_id),l(a)}),l(h.get())})},setSelections:function(a){this.container.find(".menu-location").each(function(b,c){var d=c.dataset.locationId;c.checked=d in a&&a[d]})}}),a.Menus.MenuAutoAddControl=a.Control.extend({ready:function(){var b=this,c=b.setting();b.active.validate=function(){var c,d=a.section(b.section());return c=!!d&&d.active()},b.autoAddElement=new a.Element(b.container.find("input[type=checkbox].auto_add")),b.autoAddElement.bind(function(a){var c=b.setting();c&&c.name!==a&&(c=_.clone(c),c.auto_add=a,b.setting.set(c))}),c&&b.autoAddElement.set(c.auto_add),b.setting.bind(function(a){a&&b.autoAddElement.set(a.auto_add)})}}),a.Menus.MenuControl=a.Control.extend({ready:function(){var b,d,f,g=this,h=a.section(g.section()),i=g.params.menu_id,j=g.setting();if("undefined"==typeof this.params.menu_id)throw new Error("params.menu_id was not defined");g.active.validate=function(){var a;return a=!!h&&h.active()},g.$controlSection=h.headContainer,g.$sectionContent=g.container.closest(".accordion-section-content"),this._setupModel(),a.section(g.section(),function(a){a.deferred.initSortables.done(function(a){g._setupSortable(a)})}),this._setupAddition(),this._setupTitle(),j&&(b=e(j.name),a.control.each(function(c){c.extended(a.controlConstructor.widget_form)&&"nav_menu"===c.params.widget_id_base&&(c.container.find(".nav-menu-widget-form-controls:first").show(),c.container.find(".nav-menu-widget-no-menus-message:first").hide(),f=c.container.find("select"),0===f.find("option[value="+String(i)+"]").length&&f.append(new Option(b,i)))}),d=c("#available-widgets-list .widget-tpl:has( input.id_base[ value=nav_menu ] )"),d.find(".nav-menu-widget-form-controls:first").show(),d.find(".nav-menu-widget-no-menus-message:first").hide(),f=d.find(".widget-inside select:first"),0===f.find("option[value="+String(i)+"]").length&&f.append(new Option(b,i))),_.defer(function(){g.updateInvitationVisibility()})},_setupModel:function(){var b=this,c=b.params.menu_id;b.setting.bind(function(d){var f;!1===d?b._handleDeletion():(f=e(d.name),a.control.each(function(b){if(b.extended(a.controlConstructor.widget_form)&&"nav_menu"===b.params.widget_id_base){var d=b.container.find("select");d.find("option[value="+String(c)+"]").text(f)}}))})},_setupSortable:function(b){var c=this;if(!b.is(c.$sectionContent))throw new Error("Unexpected menuList.");b.on("sortstart",function(){c.isSorting=!0}),b.on("sortstop",function(){setTimeout(function(){var b=c.$sectionContent.sortable("toArray"),d=[],e=0,f=10;c.isSorting=!1,c.$sectionContent.scrollLeft(0),_.each(b,function(b){var c,e,f;f=b.match(/^customize-control-nav_menu_item-(-?\d+)$/,""),f&&(c=parseInt(f[1],10),e=a.control("nav_menu_item["+String(c)+"]"),e&&d.push(e))}),_.each(d,function(a){if(!1!==a.setting()){var b=_.clone(a.setting());e+=1,f+=1,b.position=e,a.priority(f),b.menu_item_parent=parseInt(a.container.find(".menu-item-data-parent-id").val(),10),b.menu_item_parent||(b.menu_item_parent=0),a.setting.set(b)}})})}),c.isReordering=!1,this.container.find(".reorder-toggle").on("click",function(){c.toggleReordering(!c.isReordering)})},_setupAddition:function(){var b=this;this.container.find(".add-new-menu-item").on("click",function(d){b.$sectionContent.hasClass("reordering")||(c("body").hasClass("adding-menu-items")?(c(this).attr("aria-expanded","false"),a.Menus.availableMenuItemsPanel.close(),d.stopPropagation()):(c(this).attr("aria-expanded","true"),a.Menus.availableMenuItemsPanel.open(b)))})},_handleDeletion:function(){var d,e,f,g=this,h=g.params.menu_id,i=0;d=a.section(g.section()),e=function(){d.container.remove(),a.section.remove(d.id)},d&&d.expanded()?d.collapse({completeCallback:function(){e(),b.a11y.speak(a.Menus.data.l10n.menuDeleted),a.panel("nav_menus").focus()}}):e(),a.each(function(a){/^nav_menu\[/.test(a.id)&&!1!==a()&&(i+=1)}),a.control.each(function(b){if(b.extended(a.controlConstructor.widget_form)&&"nav_menu"===b.params.widget_id_base){var c=b.container.find("select");c.val()===String(h)&&c.prop("selectedIndex",0).trigger("change"),b.container.find(".nav-menu-widget-form-controls:first").toggle(0!==i),b.container.find(".nav-menu-widget-no-menus-message:first").toggle(0===i),b.container.find("option[value="+String(h)+"]").remove()}}),f=c("#available-widgets-list .widget-tpl:has( input.id_base[ value=nav_menu ] )"),f.find(".nav-menu-widget-form-controls:first").toggle(0!==i),f.find(".nav-menu-widget-no-menus-message:first").toggle(0===i),f.find("option[value="+String(h)+"]").remove()},_setupTitle:function(){var b=this;b.setting.bind(function(d){if(d){var f=a.section(b.section()),g=b.params.menu_id,h=f.headContainer.find(".accordion-section-title"),i=f.contentContainer.find(".customize-section-title h3"),j=f.headContainer.find(".menu-in-location"),k=i.find(".customize-action"),l=e(d.name);h.text(l),j.length&&j.appendTo(h),i.text(l),k.length&&k.prependTo(i),a.control.each(function(a){/^nav_menu_locations\[/.test(a.id)&&a.container.find("option[value="+g+"]").text(l)}),f.contentContainer.find(".customize-control-checkbox input").each(function(){c(this).prop("checked")&&c(".current-menu-location-name-"+c(this).data("location-id")).text(l)})}})},toggleReordering:function(c){var d=this.container.find(".add-new-menu-item"),e=this.container.find(".reorder-toggle"),f=this.$sectionContent.find(".item-title");c=Boolean(c),c!==this.$sectionContent.hasClass("reordering")&&(this.isReordering=c,this.$sectionContent.toggleClass("reordering",c),this.$sectionContent.sortable(this.isReordering?"disable":"enable"),this.isReordering?(d.attr({tabindex:"-1","aria-hidden":"true"}),e.attr("aria-label",a.Menus.data.l10n.reorderLabelOff),b.a11y.speak(a.Menus.data.l10n.reorderModeOn),f.attr("aria-hidden","false")):(d.removeAttr("tabindex aria-hidden"),e.attr("aria-label",a.Menus.data.l10n.reorderLabelOn),b.a11y.speak(a.Menus.data.l10n.reorderModeOff),f.attr("aria-hidden","true")),c&&_(this.getMenuItemControls()).each(function(a){a.collapseForm()}))},getMenuItemControls:function(){var b=this,c=[],d=b.params.menu_id;return a.control.each(function(a){"nav_menu_item"===a.params.type&&a.setting()&&d===a.setting().nav_menu_term_id&&c.push(a)}),c},reflowMenuItems:function(){var a,b=this,c=b.getMenuItemControls();a=function(b){var c=[],d=b.currentParent;_.each(b.menuItemControls,function(a){d===a.setting().menu_item_parent&&c.push(a)}),c.sort(function(a,b){return a.setting().position-b.setting().position}),_.each(c,function(c){b.currentAbsolutePosition+=1,c.priority.set(b.currentAbsolutePosition),c.container.hasClass("menu-item-depth-"+String(b.currentDepth))||(_.each(c.container.prop("className").match(/menu-item-depth-\d+/g),function(a){c.container.removeClass(a)}),c.container.addClass("menu-item-depth-"+String(b.currentDepth))),c.container.data("item-depth",b.currentDepth),b.currentDepth+=1,b.currentParent=c.params.menu_item_id,a(b),b.currentDepth-=1,b.currentParent=d}),c.length&&(_(c).each(function(a){a.container.removeClass("move-up-disabled move-down-disabled move-left-disabled move-right-disabled"),0===b.currentDepth?a.container.addClass("move-left-disabled"):10===b.currentDepth&&a.container.addClass("move-right-disabled")}),c[0].container.addClass("move-up-disabled").addClass("move-right-disabled").toggleClass("move-down-disabled",1===c.length),c[c.length-1].container.addClass("move-down-disabled").toggleClass("move-up-disabled",1===c.length))},a({menuItemControls:c,currentParent:0,currentDepth:0,currentAbsolutePosition:0}),b.updateInvitationVisibility(c),b.container.find(".reorder-toggle").toggle(c.length>1)},debouncedReflowMenuItems:_.debounce(function(){this.reflowMenuItems.apply(this,arguments)},0),addItemToMenu:function(d){var e,f,g,h,i,j=this,k=0,l=10;return _.each(j.getMenuItemControls(),function(a){!1!==a.setting()&&(l=Math.max(l,a.priority()),0===a.setting().menu_item_parent&&(k=Math.max(k,a.setting().position)))}),k+=1,l+=1,d=c.extend({},a.Menus.data.defaultSettingValues.nav_menu_item,d,{nav_menu_term_id:j.params.menu_id,original_title:d.title,position:k}),delete d.id,i=a.Menus.generatePlaceholderAutoIncrementId(),e="nav_menu_item["+String(i)+"]",f={type:"nav_menu_item",transport:a.Menus.data.settingTransport,previewer:a.previewer},g=a.create(e,e,{},f),g.set(d),h=new a.controlConstructor.nav_menu_item(e,{type:"nav_menu_item",section:j.id,priority:l,settings:{"default":e},menu_item_id:i}),a.control.add(h),g.preview(),j.debouncedReflowMenuItems(),b.a11y.speak(a.Menus.data.l10n.itemAdded),h},updateInvitationVisibility:function(a){var b=a||this.getMenuItemControls();this.container.find(".new-menu-item-invitation").toggle(0===b.length)}}),a.Menus.NewMenuControl=a.Control.extend({initialize:function(){"undefined"!=typeof console&&console.warn&&console.warn("[DEPRECATED] wp.customize.NewMenuControl will be removed. Please use wp.customize.Menus.createNavMenu() instead."),a.Control.prototype.initialize.apply(this,arguments)},ready:function(){this._bindHandlers()},_bindHandlers:function(){var a=this,b=c("#customize-control-new_menu_name input"),d=c("#create-new-menu-submit");b.on("keydown",function(b){13===b.which&&a.submit()}),d.on("click",function(b){a.submit(),b.stopPropagation(),b.preventDefault()})},submit:function(){var c,d=this,e=d.container.closest(".accordion-section-new-menu"),f=e.find(".menu-name-field").first(),g=f.val();return g?(c=a.Menus.createNavMenu(g),f.val(""),f.removeClass("invalid"),b.a11y.speak(a.Menus.data.l10n.menuAdded),void c.focus()):(f.addClass("invalid"),void f.focus())}}),c.extend(a.controlConstructor,{nav_menu_location:a.Menus.MenuLocationControl,nav_menu_item:a.Menus.MenuItemControl,nav_menu:a.Menus.MenuControl,nav_menu_name:a.Menus.MenuNameControl,new_menu:a.Menus.NewMenuControl,nav_menu_locations:a.Menus.MenuLocationsControl,nav_menu_auto_add:a.Menus.MenuAutoAddControl}),c.extend(a.panelConstructor,{nav_menus:a.Menus.MenusPanel}),c.extend(a.sectionConstructor,{nav_menu:a.Menus.MenuSection,new_menu:a.Menus.NewMenuSection}),a.bind("ready",function(){a.Menus.availableMenuItemsPanel=new a.Menus.AvailableMenuItemsPanelView({collection:a.Menus.availableMenuItems}),a.bind("saved",function(b){(b.nav_menu_updates||b.nav_menu_item_updates)&&a.Menus.applySavedData(b)}),a.state("changesetStatus").bind(function(b){"publish"===b&&(a("nav_menus_created_posts")._value=[])}),a.previewer.bind("focus-nav-menu-item-control",a.Menus.focusMenuItemControl)}),a.Menus.applySavedData=function(d){var e={},f={};_(d.nav_menu_updates).each(function(d){var f,g,h,i,j,k,l,m,n,o,p,q,r;if("inserted"===d.status){if(!d.previous_term_id)throw new Error("Expected previous_term_id");if(!d.term_id)throw new Error("Expected term_id");if(f="nav_menu["+String(d.previous_term_id)+"]",!a.has(f))throw new Error("Expected setting to exist: "+f);if(i=a(f),!a.section.has(f))throw new Error("Expected control to exist: "+f);if(m=a.section(f),l=i.get(),!l)throw new Error("Did not expect setting to be empty (deleted).");l=c.extend(_.clone(l),d.saved_value),e[d.previous_term_id]=d.term_id,g="nav_menu["+String(d.term_id)+"]",j=a.create(g,g,l,{type:"nav_menu",transport:a.Menus.data.settingTransport,previewer:a.previewer}),r=m.expanded(),r&&m.collapse(),n=new a.Menus.MenuSection(g,{panel:"nav_menus",title:l.name,customizeAction:a.Menus.data.l10n.customizingMenus,type:"nav_menu",priority:m.priority.get(),menu_id:d.term_id}),a.section.add(n),a.control.each(function(b){if(b.extended(a.controlConstructor.widget_form)&&"nav_menu"===b.params.widget_id_base){var c,e,f;c=b.container.find("select"),e=c.find("option[value="+String(d.previous_term_id)+"]"),f=c.find("option[value="+String(d.term_id)+"]"),f.prop("selected",e.prop("selected")),e.remove()}}),i.callbacks.disable(),i.set(!1),i.preview(),j.preview(),i._dirty=!1,m.container.remove(),a.section.remove(f),q=0,a.each(function(a){/^nav_menu\[/.test(a.id)&&!1!==a()&&(q+=1)}),p=c("#available-widgets-list .widget-tpl:has( input.id_base[ value=nav_menu ] )"),p.find(".nav-menu-widget-form-controls:first").toggle(0!==q),p.find(".nav-menu-widget-no-menus-message:first").toggle(0===q),p.find("option[value="+String(d.previous_term_id)+"]").remove(),b.customize.control.each(function(a){/^nav_menu_locations\[/.test(a.id)&&a.container.find("option[value="+String(d.previous_term_id)+"]").remove()}),a.each(function(b){var c=a.state("saved").get();/^nav_menu_locations\[/.test(b.id)&&b.get()===d.previous_term_id&&(b.set(d.term_id),b._dirty=!1,a.state("saved").set(c),b.preview())}),r&&n.expand()}else if("updated"===d.status){if(h="nav_menu["+String(d.term_id)+"]",!a.has(h))throw new Error("Expected setting to exist: "+h);k=a(h),_.isEqual(d.saved_value,k.get())||(o=a.state("saved").get(),k.set(d.saved_value),k._dirty=!1,a.state("saved").set(o))}}),_(d.nav_menu_item_updates).each(function(a){a.previous_post_id&&(f[a.previous_post_id]=a.post_id)}),_(d.nav_menu_item_updates).each(function(b){var c,d,g,h,i,j,k;if("inserted"===b.status){if(!b.previous_post_id)throw new Error("Expected previous_post_id");if(!b.post_id)throw new Error("Expected post_id");if(c="nav_menu_item["+String(b.previous_post_id)+"]",!a.has(c))throw new Error("Expected setting to exist: "+c);if(g=a(c),!a.control.has(c))throw new Error("Expected control to exist: "+c);if(j=a.control(c),i=g.get(),!i)throw new Error("Did not expect setting to be empty (deleted).");if(i=_.clone(i),i.menu_item_parent<0){if(!f[i.menu_item_parent])throw new Error("inserted ID for menu_item_parent not available");i.menu_item_parent=f[i.menu_item_parent]}e[i.nav_menu_term_id]&&(i.nav_menu_term_id=e[i.nav_menu_term_id]),d="nav_menu_item["+String(b.post_id)+"]",h=a.create(d,d,i,{type:"nav_menu_item",transport:a.Menus.data.settingTransport,previewer:a.previewer}),k=new a.controlConstructor.nav_menu_item(d,{type:"nav_menu_item",menu_id:b.post_id,section:"nav_menu["+String(i.nav_menu_term_id)+"]",priority:j.priority.get(),settings:{"default":d},menu_item_id:b.post_id}),j.container.remove(),a.control.remove(c),a.control.add(k),g.callbacks.disable(),g.set(!1),g.preview(),h.preview(),g._dirty=!1,k.container.toggleClass("menu-item-edit-inactive",j.container.hasClass("menu-item-edit-inactive"))}}),_.each(d.widget_nav_menu_updates,function(b,c){var d=a(c);d&&(d._value=b,d.preview())})},a.Menus.focusMenuItemControl=function(b){var c=a.Menus.getMenuItemControl(b);c&&c.focus()},a.Menus.getMenuControl=function(b){return a.control("nav_menu["+b+"]")},a.Menus.getMenuItemControl=function(b){return a.control(d(b))}}(wp.customize,wp,jQuery); \ No newline at end of file
diff --git a/www/crm/wp-admin/js/customize-widgets.js b/www/crm/wp-admin/js/customize-widgets.js
new file mode 100644
index 00000000..77f44def
--- /dev/null
+++ b/www/crm/wp-admin/js/customize-widgets.js
@@ -0,0 +1,2367 @@
+/**
+ * @output wp-admin/js/customize-widgets.js
+ */
+
+/* global _wpCustomizeWidgetsSettings */
+(function( wp, $ ){
+
+ if ( ! wp || ! wp.customize ) { return; }
+
+ // Set up our namespace...
+ var api = wp.customize,
+ l10n;
+
+ /**
+ * @namespace wp.customize.Widgets
+ */
+ api.Widgets = api.Widgets || {};
+ api.Widgets.savedWidgetIds = {};
+
+ // Link settings
+ api.Widgets.data = _wpCustomizeWidgetsSettings || {};
+ l10n = api.Widgets.data.l10n;
+
+ /**
+ * wp.customize.Widgets.WidgetModel
+ *
+ * A single widget model.
+ *
+ * @class wp.customize.Widgets.WidgetModel
+ * @augments Backbone.Model
+ */
+ api.Widgets.WidgetModel = Backbone.Model.extend(/** @lends wp.customize.Widgets.WidgetModel.prototype */{
+ id: null,
+ temp_id: null,
+ classname: null,
+ control_tpl: null,
+ description: null,
+ is_disabled: null,
+ is_multi: null,
+ multi_number: null,
+ name: null,
+ id_base: null,
+ transport: null,
+ params: [],
+ width: null,
+ height: null,
+ search_matched: true
+ });
+
+ /**
+ * wp.customize.Widgets.WidgetCollection
+ *
+ * Collection for widget models.
+ *
+ * @class wp.customize.Widgets.WidgetCollection
+ * @augments Backbone.Collection
+ */
+ api.Widgets.WidgetCollection = Backbone.Collection.extend(/** @lends wp.customize.Widgets.WidgetCollection.prototype */{
+ model: api.Widgets.WidgetModel,
+
+ // Controls searching on the current widget collection
+ // and triggers an update event
+ doSearch: function( value ) {
+
+ // Don't do anything if we've already done this search
+ // Useful because the search handler fires multiple times per keystroke
+ if ( this.terms === value ) {
+ return;
+ }
+
+ // Updates terms with the value passed
+ this.terms = value;
+
+ // If we have terms, run a search...
+ if ( this.terms.length > 0 ) {
+ this.search( this.terms );
+ }
+
+ // If search is blank, set all the widgets as they matched the search to reset the views.
+ if ( this.terms === '' ) {
+ this.each( function ( widget ) {
+ widget.set( 'search_matched', true );
+ } );
+ }
+ },
+
+ // Performs a search within the collection
+ // @uses RegExp
+ search: function( term ) {
+ var match, haystack;
+
+ // Escape the term string for RegExp meta characters
+ term = term.replace( /[-\/\\^$*+?.()|[\]{}]/g, '\\$&' );
+
+ // Consider spaces as word delimiters and match the whole string
+ // so matching terms can be combined
+ term = term.replace( / /g, ')(?=.*' );
+ match = new RegExp( '^(?=.*' + term + ').+', 'i' );
+
+ this.each( function ( data ) {
+ haystack = [ data.get( 'name' ), data.get( 'id' ), data.get( 'description' ) ].join( ' ' );
+ data.set( 'search_matched', match.test( haystack ) );
+ } );
+ }
+ });
+ api.Widgets.availableWidgets = new api.Widgets.WidgetCollection( api.Widgets.data.availableWidgets );
+
+ /**
+ * wp.customize.Widgets.SidebarModel
+ *
+ * A single sidebar model.
+ *
+ * @class wp.customize.Widgets.SidebarModel
+ * @augments Backbone.Model
+ */
+ api.Widgets.SidebarModel = Backbone.Model.extend(/** @lends wp.customize.Widgets.SidebarModel.prototype */{
+ after_title: null,
+ after_widget: null,
+ before_title: null,
+ before_widget: null,
+ 'class': null,
+ description: null,
+ id: null,
+ name: null,
+ is_rendered: false
+ });
+
+ /**
+ * wp.customize.Widgets.SidebarCollection
+ *
+ * Collection for sidebar models.
+ *
+ * @class wp.customize.Widgets.SidebarCollection
+ * @augments Backbone.Collection
+ */
+ api.Widgets.SidebarCollection = Backbone.Collection.extend(/** @lends wp.customize.Widgets.SidebarCollection.prototype */{
+ model: api.Widgets.SidebarModel
+ });
+ api.Widgets.registeredSidebars = new api.Widgets.SidebarCollection( api.Widgets.data.registeredSidebars );
+
+ api.Widgets.AvailableWidgetsPanelView = wp.Backbone.View.extend(/** @lends wp.customize.Widgets.AvailableWidgetsPanelView.prototype */{
+
+ el: '#available-widgets',
+
+ events: {
+ 'input #widgets-search': 'search',
+ 'focus .widget-tpl' : 'focus',
+ 'click .widget-tpl' : '_submit',
+ 'keypress .widget-tpl' : '_submit',
+ 'keydown' : 'keyboardAccessible'
+ },
+
+ // Cache current selected widget
+ selected: null,
+
+ // Cache sidebar control which has opened panel
+ currentSidebarControl: null,
+ $search: null,
+ $clearResults: null,
+ searchMatchesCount: null,
+
+ /**
+ * View class for the available widgets panel.
+ *
+ * @constructs wp.customize.Widgets.AvailableWidgetsPanelView
+ * @augments wp.Backbone.View
+ */
+ initialize: function() {
+ var self = this;
+
+ this.$search = $( '#widgets-search' );
+
+ this.$clearResults = this.$el.find( '.clear-results' );
+
+ _.bindAll( this, 'close' );
+
+ this.listenTo( this.collection, 'change', this.updateList );
+
+ this.updateList();
+
+ // Set the initial search count to the number of available widgets.
+ this.searchMatchesCount = this.collection.length;
+
+ // If the available widgets panel is open and the customize controls are
+ // interacted with (i.e. available widgets panel is blurred) then close the
+ // available widgets panel. Also close on back button click.
+ $( '#customize-controls, #available-widgets .customize-section-title' ).on( 'click keydown', function( e ) {
+ var isAddNewBtn = $( e.target ).is( '.add-new-widget, .add-new-widget *' );
+ if ( $( 'body' ).hasClass( 'adding-widget' ) && ! isAddNewBtn ) {
+ self.close();
+ }
+ } );
+
+ // Clear the search results and trigger a `keyup` event to fire a new search.
+ this.$clearResults.on( 'click', function() {
+ self.$search.val( '' ).focus().trigger( 'keyup' );
+ } );
+
+ // Close the panel if the URL in the preview changes
+ api.previewer.bind( 'url', this.close );
+ },
+
+ /**
+ * Performs a search and handles selected widget.
+ */
+ search: _.debounce( function( event ) {
+ var firstVisible;
+
+ this.collection.doSearch( event.target.value );
+ // Update the search matches count.
+ this.updateSearchMatchesCount();
+ // Announce how many search results.
+ this.announceSearchMatches();
+
+ // Remove a widget from being selected if it is no longer visible
+ if ( this.selected && ! this.selected.is( ':visible' ) ) {
+ this.selected.removeClass( 'selected' );
+ this.selected = null;
+ }
+
+ // If a widget was selected but the filter value has been cleared out, clear selection
+ if ( this.selected && ! event.target.value ) {
+ this.selected.removeClass( 'selected' );
+ this.selected = null;
+ }
+
+ // If a filter has been entered and a widget hasn't been selected, select the first one shown
+ if ( ! this.selected && event.target.value ) {
+ firstVisible = this.$el.find( '> .widget-tpl:visible:first' );
+ if ( firstVisible.length ) {
+ this.select( firstVisible );
+ }
+ }
+
+ // Toggle the clear search results button.
+ if ( '' !== event.target.value ) {
+ this.$clearResults.addClass( 'is-visible' );
+ } else if ( '' === event.target.value ) {
+ this.$clearResults.removeClass( 'is-visible' );
+ }
+
+ // Set a CSS class on the search container when there are no search results.
+ if ( ! this.searchMatchesCount ) {
+ this.$el.addClass( 'no-widgets-found' );
+ } else {
+ this.$el.removeClass( 'no-widgets-found' );
+ }
+ }, 500 ),
+
+ /**
+ * Updates the count of the available widgets that have the `search_matched` attribute.
+ */
+ updateSearchMatchesCount: function() {
+ this.searchMatchesCount = this.collection.where({ search_matched: true }).length;
+ },
+
+ /**
+ * Sends a message to the aria-live region to announce how many search results.
+ */
+ announceSearchMatches: function() {
+ var message = l10n.widgetsFound.replace( '%d', this.searchMatchesCount ) ;
+
+ if ( ! this.searchMatchesCount ) {
+ message = l10n.noWidgetsFound;
+ }
+
+ wp.a11y.speak( message );
+ },
+
+ /**
+ * Changes visibility of available widgets.
+ */
+ updateList: function() {
+ this.collection.each( function( widget ) {
+ var widgetTpl = $( '#widget-tpl-' + widget.id );
+ widgetTpl.toggle( widget.get( 'search_matched' ) && ! widget.get( 'is_disabled' ) );
+ if ( widget.get( 'is_disabled' ) && widgetTpl.is( this.selected ) ) {
+ this.selected = null;
+ }
+ } );
+ },
+
+ /**
+ * Highlights a widget.
+ */
+ select: function( widgetTpl ) {
+ this.selected = $( widgetTpl );
+ this.selected.siblings( '.widget-tpl' ).removeClass( 'selected' );
+ this.selected.addClass( 'selected' );
+ },
+
+ /**
+ * Highlights a widget on focus.
+ */
+ focus: function( event ) {
+ this.select( $( event.currentTarget ) );
+ },
+
+ /**
+ * Handles submit for keypress and click on widget.
+ */
+ _submit: function( event ) {
+ // Only proceed with keypress if it is Enter or Spacebar
+ if ( event.type === 'keypress' && ( event.which !== 13 && event.which !== 32 ) ) {
+ return;
+ }
+
+ this.submit( $( event.currentTarget ) );
+ },
+
+ /**
+ * Adds a selected widget to the sidebar.
+ */
+ submit: function( widgetTpl ) {
+ var widgetId, widget, widgetFormControl;
+
+ if ( ! widgetTpl ) {
+ widgetTpl = this.selected;
+ }
+
+ if ( ! widgetTpl || ! this.currentSidebarControl ) {
+ return;
+ }
+
+ this.select( widgetTpl );
+
+ widgetId = $( this.selected ).data( 'widget-id' );
+ widget = this.collection.findWhere( { id: widgetId } );
+ if ( ! widget ) {
+ return;
+ }
+
+ widgetFormControl = this.currentSidebarControl.addWidget( widget.get( 'id_base' ) );
+ if ( widgetFormControl ) {
+ widgetFormControl.focus();
+ }
+
+ this.close();
+ },
+
+ /**
+ * Opens the panel.
+ */
+ open: function( sidebarControl ) {
+ this.currentSidebarControl = sidebarControl;
+
+ // Wide widget controls appear over the preview, and so they need to be collapsed when the panel opens
+ _( this.currentSidebarControl.getWidgetFormControls() ).each( function( control ) {
+ if ( control.params.is_wide ) {
+ control.collapseForm();
+ }
+ } );
+
+ if ( api.section.has( 'publish_settings' ) ) {
+ api.section( 'publish_settings' ).collapse();
+ }
+
+ $( 'body' ).addClass( 'adding-widget' );
+
+ this.$el.find( '.selected' ).removeClass( 'selected' );
+
+ // Reset search
+ this.collection.doSearch( '' );
+
+ if ( ! api.settings.browser.mobile ) {
+ this.$search.focus();
+ }
+ },
+
+ /**
+ * Closes the panel.
+ */
+ close: function( options ) {
+ options = options || {};
+
+ if ( options.returnFocus && this.currentSidebarControl ) {
+ this.currentSidebarControl.container.find( '.add-new-widget' ).focus();
+ }
+
+ this.currentSidebarControl = null;
+ this.selected = null;
+
+ $( 'body' ).removeClass( 'adding-widget' );
+
+ this.$search.val( '' );
+ },
+
+ /**
+ * Adds keyboard accessiblity to the panel.
+ */
+ keyboardAccessible: function( event ) {
+ var isEnter = ( event.which === 13 ),
+ isEsc = ( event.which === 27 ),
+ isDown = ( event.which === 40 ),
+ isUp = ( event.which === 38 ),
+ isTab = ( event.which === 9 ),
+ isShift = ( event.shiftKey ),
+ selected = null,
+ firstVisible = this.$el.find( '> .widget-tpl:visible:first' ),
+ lastVisible = this.$el.find( '> .widget-tpl:visible:last' ),
+ isSearchFocused = $( event.target ).is( this.$search ),
+ isLastWidgetFocused = $( event.target ).is( '.widget-tpl:visible:last' );
+
+ if ( isDown || isUp ) {
+ if ( isDown ) {
+ if ( isSearchFocused ) {
+ selected = firstVisible;
+ } else if ( this.selected && this.selected.nextAll( '.widget-tpl:visible' ).length !== 0 ) {
+ selected = this.selected.nextAll( '.widget-tpl:visible:first' );
+ }
+ } else if ( isUp ) {
+ if ( isSearchFocused ) {
+ selected = lastVisible;
+ } else if ( this.selected && this.selected.prevAll( '.widget-tpl:visible' ).length !== 0 ) {
+ selected = this.selected.prevAll( '.widget-tpl:visible:first' );
+ }
+ }
+
+ this.select( selected );
+
+ if ( selected ) {
+ selected.focus();
+ } else {
+ this.$search.focus();
+ }
+
+ return;
+ }
+
+ // If enter pressed but nothing entered, don't do anything
+ if ( isEnter && ! this.$search.val() ) {
+ return;
+ }
+
+ if ( isEnter ) {
+ this.submit();
+ } else if ( isEsc ) {
+ this.close( { returnFocus: true } );
+ }
+
+ if ( this.currentSidebarControl && isTab && ( isShift && isSearchFocused || ! isShift && isLastWidgetFocused ) ) {
+ this.currentSidebarControl.container.find( '.add-new-widget' ).focus();
+ event.preventDefault();
+ }
+ }
+ });
+
+ /**
+ * Handlers for the widget-synced event, organized by widget ID base.
+ * Other widgets may provide their own update handlers by adding
+ * listeners for the widget-synced event.
+ *
+ * @alias wp.customize.Widgets.formSyncHandlers
+ */
+ api.Widgets.formSyncHandlers = {
+
+ /**
+ * @param {jQuery.Event} e
+ * @param {jQuery} widget
+ * @param {String} newForm
+ */
+ rss: function( e, widget, newForm ) {
+ var oldWidgetError = widget.find( '.widget-error:first' ),
+ newWidgetError = $( '<div>' + newForm + '</div>' ).find( '.widget-error:first' );
+
+ if ( oldWidgetError.length && newWidgetError.length ) {
+ oldWidgetError.replaceWith( newWidgetError );
+ } else if ( oldWidgetError.length ) {
+ oldWidgetError.remove();
+ } else if ( newWidgetError.length ) {
+ widget.find( '.widget-content:first' ).prepend( newWidgetError );
+ }
+ }
+ };
+
+ api.Widgets.WidgetControl = api.Control.extend(/** @lends wp.customize.Widgets.WidgetControl.prototype */{
+ defaultExpandedArguments: {
+ duration: 'fast',
+ completeCallback: $.noop
+ },
+
+ /**
+ * wp.customize.Widgets.WidgetControl
+ *
+ * Customizer control for widgets.
+ * Note that 'widget_form' must match the WP_Widget_Form_Customize_Control::$type
+ *
+ * @since 4.1.0
+ *
+ * @constructs wp.customize.Widgets.WidgetControl
+ * @augments wp.customize.Control
+ */
+ initialize: function( id, options ) {
+ var control = this;
+
+ control.widgetControlEmbedded = false;
+ control.widgetContentEmbedded = false;
+ control.expanded = new api.Value( false );
+ control.expandedArgumentsQueue = [];
+ control.expanded.bind( function( expanded ) {
+ var args = control.expandedArgumentsQueue.shift();
+ args = $.extend( {}, control.defaultExpandedArguments, args );
+ control.onChangeExpanded( expanded, args );
+ });
+ control.altNotice = true;
+
+ api.Control.prototype.initialize.call( control, id, options );
+ },
+
+ /**
+ * Set up the control.
+ *
+ * @since 3.9.0
+ */
+ ready: function() {
+ var control = this;
+
+ /*
+ * Embed a placeholder once the section is expanded. The full widget
+ * form content will be embedded once the control itself is expanded,
+ * and at this point the widget-added event will be triggered.
+ */
+ if ( ! control.section() ) {
+ control.embedWidgetControl();
+ } else {
+ api.section( control.section(), function( section ) {
+ var onExpanded = function( isExpanded ) {
+ if ( isExpanded ) {
+ control.embedWidgetControl();
+ section.expanded.unbind( onExpanded );
+ }
+ };
+ if ( section.expanded() ) {
+ onExpanded( true );
+ } else {
+ section.expanded.bind( onExpanded );
+ }
+ } );
+ }
+ },
+
+ /**
+ * Embed the .widget element inside the li container.
+ *
+ * @since 4.4.0
+ */
+ embedWidgetControl: function() {
+ var control = this, widgetControl;
+
+ if ( control.widgetControlEmbedded ) {
+ return;
+ }
+ control.widgetControlEmbedded = true;
+
+ widgetControl = $( control.params.widget_control );
+ control.container.append( widgetControl );
+
+ control._setupModel();
+ control._setupWideWidget();
+ control._setupControlToggle();
+
+ control._setupWidgetTitle();
+ control._setupReorderUI();
+ control._setupHighlightEffects();
+ control._setupUpdateUI();
+ control._setupRemoveUI();
+ },
+
+ /**
+ * Embed the actual widget form inside of .widget-content and finally trigger the widget-added event.
+ *
+ * @since 4.4.0
+ */
+ embedWidgetContent: function() {
+ var control = this, widgetContent;
+
+ control.embedWidgetControl();
+ if ( control.widgetContentEmbedded ) {
+ return;
+ }
+ control.widgetContentEmbedded = true;
+
+ // Update the notification container element now that the widget content has been embedded.
+ control.notifications.container = control.getNotificationsContainerElement();
+ control.notifications.render();
+
+ widgetContent = $( control.params.widget_content );
+ control.container.find( '.widget-content:first' ).append( widgetContent );
+
+ /*
+ * Trigger widget-added event so that plugins can attach any event
+ * listeners and dynamic UI elements.
+ */
+ $( document ).trigger( 'widget-added', [ control.container.find( '.widget:first' ) ] );
+
+ },
+
+ /**
+ * Handle changes to the setting
+ */
+ _setupModel: function() {
+ var self = this, rememberSavedWidgetId;
+
+ // Remember saved widgets so we know which to trash (move to inactive widgets sidebar)
+ rememberSavedWidgetId = function() {
+ api.Widgets.savedWidgetIds[self.params.widget_id] = true;
+ };
+ api.bind( 'ready', rememberSavedWidgetId );
+ api.bind( 'saved', rememberSavedWidgetId );
+
+ this._updateCount = 0;
+ this.isWidgetUpdating = false;
+ this.liveUpdateMode = true;
+
+ // Update widget whenever model changes
+ this.setting.bind( function( to, from ) {
+ if ( ! _( from ).isEqual( to ) && ! self.isWidgetUpdating ) {
+ self.updateWidget( { instance: to } );
+ }
+ } );
+ },
+
+ /**
+ * Add special behaviors for wide widget controls
+ */
+ _setupWideWidget: function() {
+ var self = this, $widgetInside, $widgetForm, $customizeSidebar,
+ $themeControlsContainer, positionWidget;
+
+ if ( ! this.params.is_wide || $( window ).width() <= 640 /* max-width breakpoint in customize-controls.css */ ) {
+ return;
+ }
+
+ $widgetInside = this.container.find( '.widget-inside' );
+ $widgetForm = $widgetInside.find( '> .form' );
+ $customizeSidebar = $( '.wp-full-overlay-sidebar-content:first' );
+ this.container.addClass( 'wide-widget-control' );
+
+ this.container.find( '.form:first' ).css( {
+ 'max-width': this.params.width,
+ 'min-height': this.params.height
+ } );
+
+ /**
+ * Keep the widget-inside positioned so the top of fixed-positioned
+ * element is at the same top position as the widget-top. When the
+ * widget-top is scrolled out of view, keep the widget-top in view;
+ * likewise, don't allow the widget to drop off the bottom of the window.
+ * If a widget is too tall to fit in the window, don't let the height
+ * exceed the window height so that the contents of the widget control
+ * will become scrollable (overflow:auto).
+ */
+ positionWidget = function() {
+ var offsetTop = self.container.offset().top,
+ windowHeight = $( window ).height(),
+ formHeight = $widgetForm.outerHeight(),
+ top;
+ $widgetInside.css( 'max-height', windowHeight );
+ top = Math.max(
+ 0, // prevent top from going off screen
+ Math.min(
+ Math.max( offsetTop, 0 ), // distance widget in panel is from top of screen
+ windowHeight - formHeight // flush up against bottom of screen
+ )
+ );
+ $widgetInside.css( 'top', top );
+ };
+
+ $themeControlsContainer = $( '#customize-theme-controls' );
+ this.container.on( 'expand', function() {
+ positionWidget();
+ $customizeSidebar.on( 'scroll', positionWidget );
+ $( window ).on( 'resize', positionWidget );
+ $themeControlsContainer.on( 'expanded collapsed', positionWidget );
+ } );
+ this.container.on( 'collapsed', function() {
+ $customizeSidebar.off( 'scroll', positionWidget );
+ $( window ).off( 'resize', positionWidget );
+ $themeControlsContainer.off( 'expanded collapsed', positionWidget );
+ } );
+
+ // Reposition whenever a sidebar's widgets are changed
+ api.each( function( setting ) {
+ if ( 0 === setting.id.indexOf( 'sidebars_widgets[' ) ) {
+ setting.bind( function() {
+ if ( self.container.hasClass( 'expanded' ) ) {
+ positionWidget();
+ }
+ } );
+ }
+ } );
+ },
+
+ /**
+ * Show/hide the control when clicking on the form title, when clicking
+ * the close button
+ */
+ _setupControlToggle: function() {
+ var self = this, $closeBtn;
+
+ this.container.find( '.widget-top' ).on( 'click', function( e ) {
+ e.preventDefault();
+ var sidebarWidgetsControl = self.getSidebarWidgetsControl();
+ if ( sidebarWidgetsControl.isReordering ) {
+ return;
+ }
+ self.expanded( ! self.expanded() );
+ } );
+
+ $closeBtn = this.container.find( '.widget-control-close' );
+ $closeBtn.on( 'click', function() {
+ self.collapse();
+ self.container.find( '.widget-top .widget-action:first' ).focus(); // keyboard accessibility
+ } );
+ },
+
+ /**
+ * Update the title of the form if a title field is entered
+ */
+ _setupWidgetTitle: function() {
+ var self = this, updateTitle;
+
+ updateTitle = function() {
+ var title = self.setting().title,
+ inWidgetTitle = self.container.find( '.in-widget-title' );
+
+ if ( title ) {
+ inWidgetTitle.text( ': ' + title );
+ } else {
+ inWidgetTitle.text( '' );
+ }
+ };
+ this.setting.bind( updateTitle );
+ updateTitle();
+ },
+
+ /**
+ * Set up the widget-reorder-nav
+ */
+ _setupReorderUI: function() {
+ var self = this, selectSidebarItem, $moveWidgetArea,
+ $reorderNav, updateAvailableSidebars, template;
+
+ /**
+ * select the provided sidebar list item in the move widget area
+ *
+ * @param {jQuery} li
+ */
+ selectSidebarItem = function( li ) {
+ li.siblings( '.selected' ).removeClass( 'selected' );
+ li.addClass( 'selected' );
+ var isSelfSidebar = ( li.data( 'id' ) === self.params.sidebar_id );
+ self.container.find( '.move-widget-btn' ).prop( 'disabled', isSelfSidebar );
+ };
+
+ /**
+ * Add the widget reordering elements to the widget control
+ */
+ this.container.find( '.widget-title-action' ).after( $( api.Widgets.data.tpl.widgetReorderNav ) );
+
+
+ template = _.template( api.Widgets.data.tpl.moveWidgetArea );
+ $moveWidgetArea = $( template( {
+ sidebars: _( api.Widgets.registeredSidebars.toArray() ).pluck( 'attributes' )
+ } )
+ );
+ this.container.find( '.widget-top' ).after( $moveWidgetArea );
+
+ /**
+ * Update available sidebars when their rendered state changes
+ */
+ updateAvailableSidebars = function() {
+ var $sidebarItems = $moveWidgetArea.find( 'li' ), selfSidebarItem,
+ renderedSidebarCount = 0;
+
+ selfSidebarItem = $sidebarItems.filter( function(){
+ return $( this ).data( 'id' ) === self.params.sidebar_id;
+ } );
+
+ $sidebarItems.each( function() {
+ var li = $( this ),
+ sidebarId, sidebar, sidebarIsRendered;
+
+ sidebarId = li.data( 'id' );
+ sidebar = api.Widgets.registeredSidebars.get( sidebarId );
+ sidebarIsRendered = sidebar.get( 'is_rendered' );
+
+ li.toggle( sidebarIsRendered );
+
+ if ( sidebarIsRendered ) {
+ renderedSidebarCount += 1;
+ }
+
+ if ( li.hasClass( 'selected' ) && ! sidebarIsRendered ) {
+ selectSidebarItem( selfSidebarItem );
+ }
+ } );
+
+ if ( renderedSidebarCount > 1 ) {
+ self.container.find( '.move-widget' ).show();
+ } else {
+ self.container.find( '.move-widget' ).hide();
+ }
+ };
+
+ updateAvailableSidebars();
+ api.Widgets.registeredSidebars.on( 'change:is_rendered', updateAvailableSidebars );
+
+ /**
+ * Handle clicks for up/down/move on the reorder nav
+ */
+ $reorderNav = this.container.find( '.widget-reorder-nav' );
+ $reorderNav.find( '.move-widget, .move-widget-down, .move-widget-up' ).each( function() {
+ $( this ).prepend( self.container.find( '.widget-title' ).text() + ': ' );
+ } ).on( 'click keypress', function( event ) {
+ if ( event.type === 'keypress' && ( event.which !== 13 && event.which !== 32 ) ) {
+ return;
+ }
+ $( this ).focus();
+
+ if ( $( this ).is( '.move-widget' ) ) {
+ self.toggleWidgetMoveArea();
+ } else {
+ var isMoveDown = $( this ).is( '.move-widget-down' ),
+ isMoveUp = $( this ).is( '.move-widget-up' ),
+ i = self.getWidgetSidebarPosition();
+
+ if ( ( isMoveUp && i === 0 ) || ( isMoveDown && i === self.getSidebarWidgetsControl().setting().length - 1 ) ) {
+ return;
+ }
+
+ if ( isMoveUp ) {
+ self.moveUp();
+ wp.a11y.speak( l10n.widgetMovedUp );
+ } else {
+ self.moveDown();
+ wp.a11y.speak( l10n.widgetMovedDown );
+ }
+
+ $( this ).focus(); // re-focus after the container was moved
+ }
+ } );
+
+ /**
+ * Handle selecting a sidebar to move to
+ */
+ this.container.find( '.widget-area-select' ).on( 'click keypress', 'li', function( event ) {
+ if ( event.type === 'keypress' && ( event.which !== 13 && event.which !== 32 ) ) {
+ return;
+ }
+ event.preventDefault();
+ selectSidebarItem( $( this ) );
+ } );
+
+ /**
+ * Move widget to another sidebar
+ */
+ this.container.find( '.move-widget-btn' ).click( function() {
+ self.getSidebarWidgetsControl().toggleReordering( false );
+
+ var oldSidebarId = self.params.sidebar_id,
+ newSidebarId = self.container.find( '.widget-area-select li.selected' ).data( 'id' ),
+ oldSidebarWidgetsSetting, newSidebarWidgetsSetting,
+ oldSidebarWidgetIds, newSidebarWidgetIds, i;
+
+ oldSidebarWidgetsSetting = api( 'sidebars_widgets[' + oldSidebarId + ']' );
+ newSidebarWidgetsSetting = api( 'sidebars_widgets[' + newSidebarId + ']' );
+ oldSidebarWidgetIds = Array.prototype.slice.call( oldSidebarWidgetsSetting() );
+ newSidebarWidgetIds = Array.prototype.slice.call( newSidebarWidgetsSetting() );
+
+ i = self.getWidgetSidebarPosition();
+ oldSidebarWidgetIds.splice( i, 1 );
+ newSidebarWidgetIds.push( self.params.widget_id );
+
+ oldSidebarWidgetsSetting( oldSidebarWidgetIds );
+ newSidebarWidgetsSetting( newSidebarWidgetIds );
+
+ self.focus();
+ } );
+ },
+
+ /**
+ * Highlight widgets in preview when interacted with in the Customizer
+ */
+ _setupHighlightEffects: function() {
+ var self = this;
+
+ // Highlight whenever hovering or clicking over the form
+ this.container.on( 'mouseenter click', function() {
+ self.setting.previewer.send( 'highlight-widget', self.params.widget_id );
+ } );
+
+ // Highlight when the setting is updated
+ this.setting.bind( function() {
+ self.setting.previewer.send( 'highlight-widget', self.params.widget_id );
+ } );
+ },
+
+ /**
+ * Set up event handlers for widget updating
+ */
+ _setupUpdateUI: function() {
+ var self = this, $widgetRoot, $widgetContent,
+ $saveBtn, updateWidgetDebounced, formSyncHandler;
+
+ $widgetRoot = this.container.find( '.widget:first' );
+ $widgetContent = $widgetRoot.find( '.widget-content:first' );
+
+ // Configure update button
+ $saveBtn = this.container.find( '.widget-control-save' );
+ $saveBtn.val( l10n.saveBtnLabel );
+ $saveBtn.attr( 'title', l10n.saveBtnTooltip );
+ $saveBtn.removeClass( 'button-primary' );
+ $saveBtn.on( 'click', function( e ) {
+ e.preventDefault();
+ self.updateWidget( { disable_form: true } ); // @todo disable_form is unused?
+ } );
+
+ updateWidgetDebounced = _.debounce( function() {
+ self.updateWidget();
+ }, 250 );
+
+ // Trigger widget form update when hitting Enter within an input
+ $widgetContent.on( 'keydown', 'input', function( e ) {
+ if ( 13 === e.which ) { // Enter
+ e.preventDefault();
+ self.updateWidget( { ignoreActiveElement: true } );
+ }
+ } );
+
+ // Handle widgets that support live previews
+ $widgetContent.on( 'change input propertychange', ':input', function( e ) {
+ if ( ! self.liveUpdateMode ) {
+ return;
+ }
+ if ( e.type === 'change' || ( this.checkValidity && this.checkValidity() ) ) {
+ updateWidgetDebounced();
+ }
+ } );
+
+ // Remove loading indicators when the setting is saved and the preview updates
+ this.setting.previewer.channel.bind( 'synced', function() {
+ self.container.removeClass( 'previewer-loading' );
+ } );
+
+ api.previewer.bind( 'widget-updated', function( updatedWidgetId ) {
+ if ( updatedWidgetId === self.params.widget_id ) {
+ self.container.removeClass( 'previewer-loading' );
+ }
+ } );
+
+ formSyncHandler = api.Widgets.formSyncHandlers[ this.params.widget_id_base ];
+ if ( formSyncHandler ) {
+ $( document ).on( 'widget-synced', function( e, widget ) {
+ if ( $widgetRoot.is( widget ) ) {
+ formSyncHandler.apply( document, arguments );
+ }
+ } );
+ }
+ },
+
+ /**
+ * Update widget control to indicate whether it is currently rendered.
+ *
+ * Overrides api.Control.toggle()
+ *
+ * @since 4.1.0
+ *
+ * @param {Boolean} active
+ * @param {Object} args
+ * @param {function} args.completeCallback
+ */
+ onChangeActive: function ( active, args ) {
+ // Note: there is a second 'args' parameter being passed, merged on top of this.defaultActiveArguments
+ this.container.toggleClass( 'widget-rendered', active );
+ if ( args.completeCallback ) {
+ args.completeCallback();
+ }
+ },
+
+ /**
+ * Set up event handlers for widget removal
+ */
+ _setupRemoveUI: function() {
+ var self = this, $removeBtn, replaceDeleteWithRemove;
+
+ // Configure remove button
+ $removeBtn = this.container.find( '.widget-control-remove' );
+ $removeBtn.on( 'click', function() {
+ // Find an adjacent element to add focus to when this widget goes away
+ var $adjacentFocusTarget;
+ if ( self.container.next().is( '.customize-control-widget_form' ) ) {
+ $adjacentFocusTarget = self.container.next().find( '.widget-action:first' );
+ } else if ( self.container.prev().is( '.customize-control-widget_form' ) ) {
+ $adjacentFocusTarget = self.container.prev().find( '.widget-action:first' );
+ } else {
+ $adjacentFocusTarget = self.container.next( '.customize-control-sidebar_widgets' ).find( '.add-new-widget:first' );
+ }
+
+ self.container.slideUp( function() {
+ var sidebarsWidgetsControl = api.Widgets.getSidebarWidgetControlContainingWidget( self.params.widget_id ),
+ sidebarWidgetIds, i;
+
+ if ( ! sidebarsWidgetsControl ) {
+ return;
+ }
+
+ sidebarWidgetIds = sidebarsWidgetsControl.setting().slice();
+ i = _.indexOf( sidebarWidgetIds, self.params.widget_id );
+ if ( -1 === i ) {
+ return;
+ }
+
+ sidebarWidgetIds.splice( i, 1 );
+ sidebarsWidgetsControl.setting( sidebarWidgetIds );
+
+ $adjacentFocusTarget.focus(); // keyboard accessibility
+ } );
+ } );
+
+ replaceDeleteWithRemove = function() {
+ $removeBtn.text( l10n.removeBtnLabel ); // wp_widget_control() outputs the button as "Delete"
+ $removeBtn.attr( 'title', l10n.removeBtnTooltip );
+ };
+
+ if ( this.params.is_new ) {
+ api.bind( 'saved', replaceDeleteWithRemove );
+ } else {
+ replaceDeleteWithRemove();
+ }
+ },
+
+ /**
+ * Find all inputs in a widget container that should be considered when
+ * comparing the loaded form with the sanitized form, whose fields will
+ * be aligned to copy the sanitized over. The elements returned by this
+ * are passed into this._getInputsSignature(), and they are iterated
+ * over when copying sanitized values over to the form loaded.
+ *
+ * @param {jQuery} container element in which to look for inputs
+ * @returns {jQuery} inputs
+ * @private
+ */
+ _getInputs: function( container ) {
+ return $( container ).find( ':input[name]' );
+ },
+
+ /**
+ * Iterate over supplied inputs and create a signature string for all of them together.
+ * This string can be used to compare whether or not the form has all of the same fields.
+ *
+ * @param {jQuery} inputs
+ * @returns {string}
+ * @private
+ */
+ _getInputsSignature: function( inputs ) {
+ var inputsSignatures = _( inputs ).map( function( input ) {
+ var $input = $( input ), signatureParts;
+
+ if ( $input.is( ':checkbox, :radio' ) ) {
+ signatureParts = [ $input.attr( 'id' ), $input.attr( 'name' ), $input.prop( 'value' ) ];
+ } else {
+ signatureParts = [ $input.attr( 'id' ), $input.attr( 'name' ) ];
+ }
+
+ return signatureParts.join( ',' );
+ } );
+
+ return inputsSignatures.join( ';' );
+ },
+
+ /**
+ * Get the state for an input depending on its type.
+ *
+ * @param {jQuery|Element} input
+ * @returns {string|boolean|array|*}
+ * @private
+ */
+ _getInputState: function( input ) {
+ input = $( input );
+ if ( input.is( ':radio, :checkbox' ) ) {
+ return input.prop( 'checked' );
+ } else if ( input.is( 'select[multiple]' ) ) {
+ return input.find( 'option:selected' ).map( function () {
+ return $( this ).val();
+ } ).get();
+ } else {
+ return input.val();
+ }
+ },
+
+ /**
+ * Update an input's state based on its type.
+ *
+ * @param {jQuery|Element} input
+ * @param {string|boolean|array|*} state
+ * @private
+ */
+ _setInputState: function ( input, state ) {
+ input = $( input );
+ if ( input.is( ':radio, :checkbox' ) ) {
+ input.prop( 'checked', state );
+ } else if ( input.is( 'select[multiple]' ) ) {
+ if ( ! $.isArray( state ) ) {
+ state = [];
+ } else {
+ // Make sure all state items are strings since the DOM value is a string
+ state = _.map( state, function ( value ) {
+ return String( value );
+ } );
+ }
+ input.find( 'option' ).each( function () {
+ $( this ).prop( 'selected', -1 !== _.indexOf( state, String( this.value ) ) );
+ } );
+ } else {
+ input.val( state );
+ }
+ },
+
+ /***********************************************************************
+ * Begin public API methods
+ **********************************************************************/
+
+ /**
+ * @return {wp.customize.controlConstructor.sidebar_widgets[]}
+ */
+ getSidebarWidgetsControl: function() {
+ var settingId, sidebarWidgetsControl;
+
+ settingId = 'sidebars_widgets[' + this.params.sidebar_id + ']';
+ sidebarWidgetsControl = api.control( settingId );
+
+ if ( ! sidebarWidgetsControl ) {
+ return;
+ }
+
+ return sidebarWidgetsControl;
+ },
+
+ /**
+ * Submit the widget form via Ajax and get back the updated instance,
+ * along with the new widget control form to render.
+ *
+ * @param {object} [args]
+ * @param {Object|null} [args.instance=null] When the model changes, the instance is sent here; otherwise, the inputs from the form are used
+ * @param {Function|null} [args.complete=null] Function which is called when the request finishes. Context is bound to the control. First argument is any error. Following arguments are for success.
+ * @param {Boolean} [args.ignoreActiveElement=false] Whether or not updating a field will be deferred if focus is still on the element.
+ */
+ updateWidget: function( args ) {
+ var self = this, instanceOverride, completeCallback, $widgetRoot, $widgetContent,
+ updateNumber, params, data, $inputs, processing, jqxhr, isChanged;
+
+ // The updateWidget logic requires that the form fields to be fully present.
+ self.embedWidgetContent();
+
+ args = $.extend( {
+ instance: null,
+ complete: null,
+ ignoreActiveElement: false
+ }, args );
+
+ instanceOverride = args.instance;
+ completeCallback = args.complete;
+
+ this._updateCount += 1;
+ updateNumber = this._updateCount;
+
+ $widgetRoot = this.container.find( '.widget:first' );
+ $widgetContent = $widgetRoot.find( '.widget-content:first' );
+
+ // Remove a previous error message
+ $widgetContent.find( '.widget-error' ).remove();
+
+ this.container.addClass( 'widget-form-loading' );
+ this.container.addClass( 'previewer-loading' );
+ processing = api.state( 'processing' );
+ processing( processing() + 1 );
+
+ if ( ! this.liveUpdateMode ) {
+ this.container.addClass( 'widget-form-disabled' );
+ }
+
+ params = {};
+ params.action = 'update-widget';
+ params.wp_customize = 'on';
+ params.nonce = api.settings.nonce['update-widget'];
+ params.customize_theme = api.settings.theme.stylesheet;
+ params.customized = wp.customize.previewer.query().customized;
+
+ data = $.param( params );
+ $inputs = this._getInputs( $widgetContent );
+
+ // Store the value we're submitting in data so that when the response comes back,
+ // we know if it got sanitized; if there is no difference in the sanitized value,
+ // then we do not need to touch the UI and mess up the user's ongoing editing.
+ $inputs.each( function() {
+ $( this ).data( 'state' + updateNumber, self._getInputState( this ) );
+ } );
+
+ if ( instanceOverride ) {
+ data += '&' + $.param( { 'sanitized_widget_setting': JSON.stringify( instanceOverride ) } );
+ } else {
+ data += '&' + $inputs.serialize();
+ }
+ data += '&' + $widgetContent.find( '~ :input' ).serialize();
+
+ if ( this._previousUpdateRequest ) {
+ this._previousUpdateRequest.abort();
+ }
+ jqxhr = $.post( wp.ajax.settings.url, data );
+ this._previousUpdateRequest = jqxhr;
+
+ jqxhr.done( function( r ) {
+ var message, sanitizedForm, $sanitizedInputs, hasSameInputsInResponse,
+ isLiveUpdateAborted = false;
+
+ // Check if the user is logged out.
+ if ( '0' === r ) {
+ api.previewer.preview.iframe.hide();
+ api.previewer.login().done( function() {
+ self.updateWidget( args );
+ api.previewer.preview.iframe.show();
+ } );
+ return;
+ }
+
+ // Check for cheaters.
+ if ( '-1' === r ) {
+ api.previewer.cheatin();
+ return;
+ }
+
+ if ( r.success ) {
+ sanitizedForm = $( '<div>' + r.data.form + '</div>' );
+ $sanitizedInputs = self._getInputs( sanitizedForm );
+ hasSameInputsInResponse = self._getInputsSignature( $inputs ) === self._getInputsSignature( $sanitizedInputs );
+
+ // Restore live update mode if sanitized fields are now aligned with the existing fields
+ if ( hasSameInputsInResponse && ! self.liveUpdateMode ) {
+ self.liveUpdateMode = true;
+ self.container.removeClass( 'widget-form-disabled' );
+ self.container.find( 'input[name="savewidget"]' ).hide();
+ }
+
+ // Sync sanitized field states to existing fields if they are aligned
+ if ( hasSameInputsInResponse && self.liveUpdateMode ) {
+ $inputs.each( function( i ) {
+ var $input = $( this ),
+ $sanitizedInput = $( $sanitizedInputs[i] ),
+ submittedState, sanitizedState, canUpdateState;
+
+ submittedState = $input.data( 'state' + updateNumber );
+ sanitizedState = self._getInputState( $sanitizedInput );
+ $input.data( 'sanitized', sanitizedState );
+
+ canUpdateState = ( ! _.isEqual( submittedState, sanitizedState ) && ( args.ignoreActiveElement || ! $input.is( document.activeElement ) ) );
+ if ( canUpdateState ) {
+ self._setInputState( $input, sanitizedState );
+ }
+ } );
+
+ $( document ).trigger( 'widget-synced', [ $widgetRoot, r.data.form ] );
+
+ // Otherwise, if sanitized fields are not aligned with existing fields, disable live update mode if enabled
+ } else if ( self.liveUpdateMode ) {
+ self.liveUpdateMode = false;
+ self.container.find( 'input[name="savewidget"]' ).show();
+ isLiveUpdateAborted = true;
+
+ // Otherwise, replace existing form with the sanitized form
+ } else {
+ $widgetContent.html( r.data.form );
+
+ self.container.removeClass( 'widget-form-disabled' );
+
+ $( document ).trigger( 'widget-updated', [ $widgetRoot ] );
+ }
+
+ /**
+ * If the old instance is identical to the new one, there is nothing new
+ * needing to be rendered, and so we can preempt the event for the
+ * preview finishing loading.
+ */
+ isChanged = ! isLiveUpdateAborted && ! _( self.setting() ).isEqual( r.data.instance );
+ if ( isChanged ) {
+ self.isWidgetUpdating = true; // suppress triggering another updateWidget
+ self.setting( r.data.instance );
+ self.isWidgetUpdating = false;
+ } else {
+ // no change was made, so stop the spinner now instead of when the preview would updates
+ self.container.removeClass( 'previewer-loading' );
+ }
+
+ if ( completeCallback ) {
+ completeCallback.call( self, null, { noChange: ! isChanged, ajaxFinished: true } );
+ }
+ } else {
+ // General error message
+ message = l10n.error;
+
+ if ( r.data && r.data.message ) {
+ message = r.data.message;
+ }
+
+ if ( completeCallback ) {
+ completeCallback.call( self, message );
+ } else {
+ $widgetContent.prepend( '<p class="widget-error"><strong>' + message + '</strong></p>' );
+ }
+ }
+ } );
+
+ jqxhr.fail( function( jqXHR, textStatus ) {
+ if ( completeCallback ) {
+ completeCallback.call( self, textStatus );
+ }
+ } );
+
+ jqxhr.always( function() {
+ self.container.removeClass( 'widget-form-loading' );
+
+ $inputs.each( function() {
+ $( this ).removeData( 'state' + updateNumber );
+ } );
+
+ processing( processing() - 1 );
+ } );
+ },
+
+ /**
+ * Expand the accordion section containing a control
+ */
+ expandControlSection: function() {
+ api.Control.prototype.expand.call( this );
+ },
+
+ /**
+ * @since 4.1.0
+ *
+ * @param {Boolean} expanded
+ * @param {Object} [params]
+ * @returns {Boolean} false if state already applied
+ */
+ _toggleExpanded: api.Section.prototype._toggleExpanded,
+
+ /**
+ * @since 4.1.0
+ *
+ * @param {Object} [params]
+ * @returns {Boolean} false if already expanded
+ */
+ expand: api.Section.prototype.expand,
+
+ /**
+ * Expand the widget form control
+ *
+ * @deprecated 4.1.0 Use this.expand() instead.
+ */
+ expandForm: function() {
+ this.expand();
+ },
+
+ /**
+ * @since 4.1.0
+ *
+ * @param {Object} [params]
+ * @returns {Boolean} false if already collapsed
+ */
+ collapse: api.Section.prototype.collapse,
+
+ /**
+ * Collapse the widget form control
+ *
+ * @deprecated 4.1.0 Use this.collapse() instead.
+ */
+ collapseForm: function() {
+ this.collapse();
+ },
+
+ /**
+ * Expand or collapse the widget control
+ *
+ * @deprecated this is poor naming, and it is better to directly set control.expanded( showOrHide )
+ *
+ * @param {boolean|undefined} [showOrHide] If not supplied, will be inverse of current visibility
+ */
+ toggleForm: function( showOrHide ) {
+ if ( typeof showOrHide === 'undefined' ) {
+ showOrHide = ! this.expanded();
+ }
+ this.expanded( showOrHide );
+ },
+
+ /**
+ * Respond to change in the expanded state.
+ *
+ * @param {Boolean} expanded
+ * @param {Object} args merged on top of this.defaultActiveArguments
+ */
+ onChangeExpanded: function ( expanded, args ) {
+ var self = this, $widget, $inside, complete, prevComplete, expandControl, $toggleBtn;
+
+ self.embedWidgetControl(); // Make sure the outer form is embedded so that the expanded state can be set in the UI.
+ if ( expanded ) {
+ self.embedWidgetContent();
+ }
+
+ // If the expanded state is unchanged only manipulate container expanded states
+ if ( args.unchanged ) {
+ if ( expanded ) {
+ api.Control.prototype.expand.call( self, {
+ completeCallback: args.completeCallback
+ });
+ }
+ return;
+ }
+
+ $widget = this.container.find( 'div.widget:first' );
+ $inside = $widget.find( '.widget-inside:first' );
+ $toggleBtn = this.container.find( '.widget-top button.widget-action' );
+
+ expandControl = function() {
+
+ // Close all other widget controls before expanding this one
+ api.control.each( function( otherControl ) {
+ if ( self.params.type === otherControl.params.type && self !== otherControl ) {
+ otherControl.collapse();
+ }
+ } );
+
+ complete = function() {
+ self.container.removeClass( 'expanding' );
+ self.container.addClass( 'expanded' );
+ $widget.addClass( 'open' );
+ $toggleBtn.attr( 'aria-expanded', 'true' );
+ self.container.trigger( 'expanded' );
+ };
+ if ( args.completeCallback ) {
+ prevComplete = complete;
+ complete = function () {
+ prevComplete();
+ args.completeCallback();
+ };
+ }
+
+ if ( self.params.is_wide ) {
+ $inside.fadeIn( args.duration, complete );
+ } else {
+ $inside.slideDown( args.duration, complete );
+ }
+
+ self.container.trigger( 'expand' );
+ self.container.addClass( 'expanding' );
+ };
+
+ if ( expanded ) {
+ if ( api.section.has( self.section() ) ) {
+ api.section( self.section() ).expand( {
+ completeCallback: expandControl
+ } );
+ } else {
+ expandControl();
+ }
+ } else {
+ complete = function() {
+ self.container.removeClass( 'collapsing' );
+ self.container.removeClass( 'expanded' );
+ $widget.removeClass( 'open' );
+ $toggleBtn.attr( 'aria-expanded', 'false' );
+ self.container.trigger( 'collapsed' );
+ };
+ if ( args.completeCallback ) {
+ prevComplete = complete;
+ complete = function () {
+ prevComplete();
+ args.completeCallback();
+ };
+ }
+
+ self.container.trigger( 'collapse' );
+ self.container.addClass( 'collapsing' );
+
+ if ( self.params.is_wide ) {
+ $inside.fadeOut( args.duration, complete );
+ } else {
+ $inside.slideUp( args.duration, function() {
+ $widget.css( { width:'', margin:'' } );
+ complete();
+ } );
+ }
+ }
+ },
+
+ /**
+ * Get the position (index) of the widget in the containing sidebar
+ *
+ * @returns {Number}
+ */
+ getWidgetSidebarPosition: function() {
+ var sidebarWidgetIds, position;
+
+ sidebarWidgetIds = this.getSidebarWidgetsControl().setting();
+ position = _.indexOf( sidebarWidgetIds, this.params.widget_id );
+
+ if ( position === -1 ) {
+ return;
+ }
+
+ return position;
+ },
+
+ /**
+ * Move widget up one in the sidebar
+ */
+ moveUp: function() {
+ this._moveWidgetByOne( -1 );
+ },
+
+ /**
+ * Move widget up one in the sidebar
+ */
+ moveDown: function() {
+ this._moveWidgetByOne( 1 );
+ },
+
+ /**
+ * @private
+ *
+ * @param {Number} offset 1|-1
+ */
+ _moveWidgetByOne: function( offset ) {
+ var i, sidebarWidgetsSetting, sidebarWidgetIds, adjacentWidgetId;
+
+ i = this.getWidgetSidebarPosition();
+
+ sidebarWidgetsSetting = this.getSidebarWidgetsControl().setting;
+ sidebarWidgetIds = Array.prototype.slice.call( sidebarWidgetsSetting() ); // clone
+ adjacentWidgetId = sidebarWidgetIds[i + offset];
+ sidebarWidgetIds[i + offset] = this.params.widget_id;
+ sidebarWidgetIds[i] = adjacentWidgetId;
+
+ sidebarWidgetsSetting( sidebarWidgetIds );
+ },
+
+ /**
+ * Toggle visibility of the widget move area
+ *
+ * @param {Boolean} [showOrHide]
+ */
+ toggleWidgetMoveArea: function( showOrHide ) {
+ var self = this, $moveWidgetArea;
+
+ $moveWidgetArea = this.container.find( '.move-widget-area' );
+
+ if ( typeof showOrHide === 'undefined' ) {
+ showOrHide = ! $moveWidgetArea.hasClass( 'active' );
+ }
+
+ if ( showOrHide ) {
+ // reset the selected sidebar
+ $moveWidgetArea.find( '.selected' ).removeClass( 'selected' );
+
+ $moveWidgetArea.find( 'li' ).filter( function() {
+ return $( this ).data( 'id' ) === self.params.sidebar_id;
+ } ).addClass( 'selected' );
+
+ this.container.find( '.move-widget-btn' ).prop( 'disabled', true );
+ }
+
+ $moveWidgetArea.toggleClass( 'active', showOrHide );
+ },
+
+ /**
+ * Highlight the widget control and section
+ */
+ highlightSectionAndControl: function() {
+ var $target;
+
+ if ( this.container.is( ':hidden' ) ) {
+ $target = this.container.closest( '.control-section' );
+ } else {
+ $target = this.container;
+ }
+
+ $( '.highlighted' ).removeClass( 'highlighted' );
+ $target.addClass( 'highlighted' );
+
+ setTimeout( function() {
+ $target.removeClass( 'highlighted' );
+ }, 500 );
+ }
+ } );
+
+ /**
+ * wp.customize.Widgets.WidgetsPanel
+ *
+ * Customizer panel containing the widget area sections.
+ *
+ * @since 4.4.0
+ *
+ * @class wp.customize.Widgets.WidgetsPanel
+ * @augments wp.customize.Panel
+ */
+ api.Widgets.WidgetsPanel = api.Panel.extend(/** @lends wp.customize.Widgets.WigetsPanel.prototype */{
+
+ /**
+ * Add and manage the display of the no-rendered-areas notice.
+ *
+ * @since 4.4.0
+ */
+ ready: function () {
+ var panel = this;
+
+ api.Panel.prototype.ready.call( panel );
+
+ panel.deferred.embedded.done(function() {
+ var panelMetaContainer, noticeContainer, updateNotice, getActiveSectionCount, shouldShowNotice;
+ panelMetaContainer = panel.container.find( '.panel-meta' );
+
+ // @todo This should use the Notifications API introduced to panels. See <https://core.trac.wordpress.org/ticket/38794>.
+ noticeContainer = $( '<div></div>', {
+ 'class': 'no-widget-areas-rendered-notice'
+ });
+ panelMetaContainer.append( noticeContainer );
+
+ /**
+ * Get the number of active sections in the panel.
+ *
+ * @return {number} Number of active sidebar sections.
+ */
+ getActiveSectionCount = function() {
+ return _.filter( panel.sections(), function( section ) {
+ return 'sidebar' === section.params.type && section.active();
+ } ).length;
+ };
+
+ /**
+ * Determine whether or not the notice should be displayed.
+ *
+ * @return {boolean}
+ */
+ shouldShowNotice = function() {
+ var activeSectionCount = getActiveSectionCount();
+ if ( 0 === activeSectionCount ) {
+ return true;
+ } else {
+ return activeSectionCount !== api.Widgets.data.registeredSidebars.length;
+ }
+ };
+
+ /**
+ * Update the notice.
+ *
+ * @returns {void}
+ */
+ updateNotice = function() {
+ var activeSectionCount = getActiveSectionCount(), someRenderedMessage, nonRenderedAreaCount, registeredAreaCount;
+ noticeContainer.empty();
+
+ registeredAreaCount = api.Widgets.data.registeredSidebars.length;
+ if ( activeSectionCount !== registeredAreaCount ) {
+
+ if ( 0 !== activeSectionCount ) {
+ nonRenderedAreaCount = registeredAreaCount - activeSectionCount;
+ someRenderedMessage = l10n.someAreasShown[ nonRenderedAreaCount ];
+ } else {
+ someRenderedMessage = l10n.noAreasShown;
+ }
+ if ( someRenderedMessage ) {
+ noticeContainer.append( $( '<p></p>', {
+ text: someRenderedMessage
+ } ) );
+ }
+
+ noticeContainer.append( $( '<p></p>', {
+ text: l10n.navigatePreview
+ } ) );
+ }
+ };
+ updateNotice();
+
+ /*
+ * Set the initial visibility state for rendered notice.
+ * Update the visibility of the notice whenever a reflow happens.
+ */
+ noticeContainer.toggle( shouldShowNotice() );
+ api.previewer.deferred.active.done( function () {
+ noticeContainer.toggle( shouldShowNotice() );
+ });
+ api.bind( 'pane-contents-reflowed', function() {
+ var duration = ( 'resolved' === api.previewer.deferred.active.state() ) ? 'fast' : 0;
+ updateNotice();
+ if ( shouldShowNotice() ) {
+ noticeContainer.slideDown( duration );
+ } else {
+ noticeContainer.slideUp( duration );
+ }
+ });
+ });
+ },
+
+ /**
+ * Allow an active widgets panel to be contextually active even when it has no active sections (widget areas).
+ *
+ * This ensures that the widgets panel appears even when there are no
+ * sidebars displayed on the URL currently being previewed.
+ *
+ * @since 4.4.0
+ *
+ * @returns {boolean}
+ */
+ isContextuallyActive: function() {
+ var panel = this;
+ return panel.active();
+ }
+ });
+
+ /**
+ * wp.customize.Widgets.SidebarSection
+ *
+ * Customizer section representing a widget area widget
+ *
+ * @since 4.1.0
+ *
+ * @class wp.customize.Widgets.SidebarSection
+ * @augments wp.customize.Section
+ */
+ api.Widgets.SidebarSection = api.Section.extend(/** @lends wp.customize.Widgets.SidebarSection.prototype */{
+
+ /**
+ * Sync the section's active state back to the Backbone model's is_rendered attribute
+ *
+ * @since 4.1.0
+ */
+ ready: function () {
+ var section = this, registeredSidebar;
+ api.Section.prototype.ready.call( this );
+ registeredSidebar = api.Widgets.registeredSidebars.get( section.params.sidebarId );
+ section.active.bind( function ( active ) {
+ registeredSidebar.set( 'is_rendered', active );
+ });
+ registeredSidebar.set( 'is_rendered', section.active() );
+ }
+ });
+
+ /**
+ * wp.customize.Widgets.SidebarControl
+ *
+ * Customizer control for widgets.
+ * Note that 'sidebar_widgets' must match the WP_Widget_Area_Customize_Control::$type
+ *
+ * @since 3.9.0
+ *
+ * @class wp.customize.Widgets.SidebarControl
+ * @augments wp.customize.Control
+ */
+ api.Widgets.SidebarControl = api.Control.extend(/** @lends wp.customize.Widgets.SidebarControl.prototype */{
+
+ /**
+ * Set up the control
+ */
+ ready: function() {
+ this.$controlSection = this.container.closest( '.control-section' );
+ this.$sectionContent = this.container.closest( '.accordion-section-content' );
+
+ this._setupModel();
+ this._setupSortable();
+ this._setupAddition();
+ this._applyCardinalOrderClassNames();
+ },
+
+ /**
+ * Update ordering of widget control forms when the setting is updated
+ */
+ _setupModel: function() {
+ var self = this;
+
+ this.setting.bind( function( newWidgetIds, oldWidgetIds ) {
+ var widgetFormControls, removedWidgetIds, priority;
+
+ removedWidgetIds = _( oldWidgetIds ).difference( newWidgetIds );
+
+ // Filter out any persistent widget IDs for widgets which have been deactivated
+ newWidgetIds = _( newWidgetIds ).filter( function( newWidgetId ) {
+ var parsedWidgetId = parseWidgetId( newWidgetId );
+
+ return !! api.Widgets.availableWidgets.findWhere( { id_base: parsedWidgetId.id_base } );
+ } );
+
+ widgetFormControls = _( newWidgetIds ).map( function( widgetId ) {
+ var widgetFormControl = api.Widgets.getWidgetFormControlForWidget( widgetId );
+
+ if ( ! widgetFormControl ) {
+ widgetFormControl = self.addWidget( widgetId );
+ }
+
+ return widgetFormControl;
+ } );
+
+ // Sort widget controls to their new positions
+ widgetFormControls.sort( function( a, b ) {
+ var aIndex = _.indexOf( newWidgetIds, a.params.widget_id ),
+ bIndex = _.indexOf( newWidgetIds, b.params.widget_id );
+ return aIndex - bIndex;
+ });
+
+ priority = 0;
+ _( widgetFormControls ).each( function ( control ) {
+ control.priority( priority );
+ control.section( self.section() );
+ priority += 1;
+ });
+ self.priority( priority ); // Make sure sidebar control remains at end
+
+ // Re-sort widget form controls (including widgets form other sidebars newly moved here)
+ self._applyCardinalOrderClassNames();
+
+ // If the widget was dragged into the sidebar, make sure the sidebar_id param is updated
+ _( widgetFormControls ).each( function( widgetFormControl ) {
+ widgetFormControl.params.sidebar_id = self.params.sidebar_id;
+ } );
+
+ // Cleanup after widget removal
+ _( removedWidgetIds ).each( function( removedWidgetId ) {
+
+ // Using setTimeout so that when moving a widget to another sidebar, the other sidebars_widgets settings get a chance to update
+ setTimeout( function() {
+ var removedControl, wasDraggedToAnotherSidebar, inactiveWidgets, removedIdBase,
+ widget, isPresentInAnotherSidebar = false;
+
+ // Check if the widget is in another sidebar
+ api.each( function( otherSetting ) {
+ if ( otherSetting.id === self.setting.id || 0 !== otherSetting.id.indexOf( 'sidebars_widgets[' ) || otherSetting.id === 'sidebars_widgets[wp_inactive_widgets]' ) {
+ return;
+ }
+
+ var otherSidebarWidgets = otherSetting(), i;
+
+ i = _.indexOf( otherSidebarWidgets, removedWidgetId );
+ if ( -1 !== i ) {
+ isPresentInAnotherSidebar = true;
+ }
+ } );
+
+ // If the widget is present in another sidebar, abort!
+ if ( isPresentInAnotherSidebar ) {
+ return;
+ }
+
+ removedControl = api.Widgets.getWidgetFormControlForWidget( removedWidgetId );
+
+ // Detect if widget control was dragged to another sidebar
+ wasDraggedToAnotherSidebar = removedControl && $.contains( document, removedControl.container[0] ) && ! $.contains( self.$sectionContent[0], removedControl.container[0] );
+
+ // Delete any widget form controls for removed widgets
+ if ( removedControl && ! wasDraggedToAnotherSidebar ) {
+ api.control.remove( removedControl.id );
+ removedControl.container.remove();
+ }
+
+ // Move widget to inactive widgets sidebar (move it to trash) if has been previously saved
+ // This prevents the inactive widgets sidebar from overflowing with throwaway widgets
+ if ( api.Widgets.savedWidgetIds[removedWidgetId] ) {
+ inactiveWidgets = api.value( 'sidebars_widgets[wp_inactive_widgets]' )().slice();
+ inactiveWidgets.push( removedWidgetId );
+ api.value( 'sidebars_widgets[wp_inactive_widgets]' )( _( inactiveWidgets ).unique() );
+ }
+
+ // Make old single widget available for adding again
+ removedIdBase = parseWidgetId( removedWidgetId ).id_base;
+ widget = api.Widgets.availableWidgets.findWhere( { id_base: removedIdBase } );
+ if ( widget && ! widget.get( 'is_multi' ) ) {
+ widget.set( 'is_disabled', false );
+ }
+ } );
+
+ } );
+ } );
+ },
+
+ /**
+ * Allow widgets in sidebar to be re-ordered, and for the order to be previewed
+ */
+ _setupSortable: function() {
+ var self = this;
+
+ this.isReordering = false;
+
+ /**
+ * Update widget order setting when controls are re-ordered
+ */
+ this.$sectionContent.sortable( {
+ items: '> .customize-control-widget_form',
+ handle: '.widget-top',
+ axis: 'y',
+ tolerance: 'pointer',
+ connectWith: '.accordion-section-content:has(.customize-control-sidebar_widgets)',
+ update: function() {
+ var widgetContainerIds = self.$sectionContent.sortable( 'toArray' ), widgetIds;
+
+ widgetIds = $.map( widgetContainerIds, function( widgetContainerId ) {
+ return $( '#' + widgetContainerId ).find( ':input[name=widget-id]' ).val();
+ } );
+
+ self.setting( widgetIds );
+ }
+ } );
+
+ /**
+ * Expand other Customizer sidebar section when dragging a control widget over it,
+ * allowing the control to be dropped into another section
+ */
+ this.$controlSection.find( '.accordion-section-title' ).droppable({
+ accept: '.customize-control-widget_form',
+ over: function() {
+ var section = api.section( self.section.get() );
+ section.expand({
+ allowMultiple: true, // Prevent the section being dragged from to be collapsed
+ completeCallback: function () {
+ // @todo It is not clear when refreshPositions should be called on which sections, or if it is even needed
+ api.section.each( function ( otherSection ) {
+ if ( otherSection.container.find( '.customize-control-sidebar_widgets' ).length ) {
+ otherSection.container.find( '.accordion-section-content:first' ).sortable( 'refreshPositions' );
+ }
+ } );
+ }
+ });
+ }
+ });
+
+ /**
+ * Keyboard-accessible reordering
+ */
+ this.container.find( '.reorder-toggle' ).on( 'click', function() {
+ self.toggleReordering( ! self.isReordering );
+ } );
+ },
+
+ /**
+ * Set up UI for adding a new widget
+ */
+ _setupAddition: function() {
+ var self = this;
+
+ this.container.find( '.add-new-widget' ).on( 'click', function() {
+ var addNewWidgetBtn = $( this );
+
+ if ( self.$sectionContent.hasClass( 'reordering' ) ) {
+ return;
+ }
+
+ if ( ! $( 'body' ).hasClass( 'adding-widget' ) ) {
+ addNewWidgetBtn.attr( 'aria-expanded', 'true' );
+ api.Widgets.availableWidgetsPanel.open( self );
+ } else {
+ addNewWidgetBtn.attr( 'aria-expanded', 'false' );
+ api.Widgets.availableWidgetsPanel.close();
+ }
+ } );
+ },
+
+ /**
+ * Add classes to the widget_form controls to assist with styling
+ */
+ _applyCardinalOrderClassNames: function() {
+ var widgetControls = [];
+ _.each( this.setting(), function ( widgetId ) {
+ var widgetControl = api.Widgets.getWidgetFormControlForWidget( widgetId );
+ if ( widgetControl ) {
+ widgetControls.push( widgetControl );
+ }
+ });
+
+ if ( 0 === widgetControls.length || ( 1 === api.Widgets.registeredSidebars.length && widgetControls.length <= 1 ) ) {
+ this.container.find( '.reorder-toggle' ).hide();
+ return;
+ } else {
+ this.container.find( '.reorder-toggle' ).show();
+ }
+
+ $( widgetControls ).each( function () {
+ $( this.container )
+ .removeClass( 'first-widget' )
+ .removeClass( 'last-widget' )
+ .find( '.move-widget-down, .move-widget-up' ).prop( 'tabIndex', 0 );
+ });
+
+ _.first( widgetControls ).container
+ .addClass( 'first-widget' )
+ .find( '.move-widget-up' ).prop( 'tabIndex', -1 );
+
+ _.last( widgetControls ).container
+ .addClass( 'last-widget' )
+ .find( '.move-widget-down' ).prop( 'tabIndex', -1 );
+ },
+
+
+ /***********************************************************************
+ * Begin public API methods
+ **********************************************************************/
+
+ /**
+ * Enable/disable the reordering UI
+ *
+ * @param {Boolean} showOrHide to enable/disable reordering
+ *
+ * @todo We should have a reordering state instead and rename this to onChangeReordering
+ */
+ toggleReordering: function( showOrHide ) {
+ var addNewWidgetBtn = this.$sectionContent.find( '.add-new-widget' ),
+ reorderBtn = this.container.find( '.reorder-toggle' ),
+ widgetsTitle = this.$sectionContent.find( '.widget-title' );
+
+ showOrHide = Boolean( showOrHide );
+
+ if ( showOrHide === this.$sectionContent.hasClass( 'reordering' ) ) {
+ return;
+ }
+
+ this.isReordering = showOrHide;
+ this.$sectionContent.toggleClass( 'reordering', showOrHide );
+
+ if ( showOrHide ) {
+ _( this.getWidgetFormControls() ).each( function( formControl ) {
+ formControl.collapse();
+ } );
+
+ addNewWidgetBtn.attr({ 'tabindex': '-1', 'aria-hidden': 'true' });
+ reorderBtn.attr( 'aria-label', l10n.reorderLabelOff );
+ wp.a11y.speak( l10n.reorderModeOn );
+ // Hide widget titles while reordering: title is already in the reorder controls.
+ widgetsTitle.attr( 'aria-hidden', 'true' );
+ } else {
+ addNewWidgetBtn.removeAttr( 'tabindex aria-hidden' );
+ reorderBtn.attr( 'aria-label', l10n.reorderLabelOn );
+ wp.a11y.speak( l10n.reorderModeOff );
+ widgetsTitle.attr( 'aria-hidden', 'false' );
+ }
+ },
+
+ /**
+ * Get the widget_form Customize controls associated with the current sidebar.
+ *
+ * @since 3.9.0
+ * @return {wp.customize.controlConstructor.widget_form[]}
+ */
+ getWidgetFormControls: function() {
+ var formControls = [];
+
+ _( this.setting() ).each( function( widgetId ) {
+ var settingId = widgetIdToSettingId( widgetId ),
+ formControl = api.control( settingId );
+ if ( formControl ) {
+ formControls.push( formControl );
+ }
+ } );
+
+ return formControls;
+ },
+
+ /**
+ * @param {string} widgetId or an id_base for adding a previously non-existing widget
+ * @returns {object|false} widget_form control instance, or false on error
+ */
+ addWidget: function( widgetId ) {
+ var self = this, controlHtml, $widget, controlType = 'widget_form', controlContainer, controlConstructor,
+ parsedWidgetId = parseWidgetId( widgetId ),
+ widgetNumber = parsedWidgetId.number,
+ widgetIdBase = parsedWidgetId.id_base,
+ widget = api.Widgets.availableWidgets.findWhere( {id_base: widgetIdBase} ),
+ settingId, isExistingWidget, widgetFormControl, sidebarWidgets, settingArgs, setting;
+
+ if ( ! widget ) {
+ return false;
+ }
+
+ if ( widgetNumber && ! widget.get( 'is_multi' ) ) {
+ return false;
+ }
+
+ // Set up new multi widget
+ if ( widget.get( 'is_multi' ) && ! widgetNumber ) {
+ widget.set( 'multi_number', widget.get( 'multi_number' ) + 1 );
+ widgetNumber = widget.get( 'multi_number' );
+ }
+
+ controlHtml = $.trim( $( '#widget-tpl-' + widget.get( 'id' ) ).html() );
+ if ( widget.get( 'is_multi' ) ) {
+ controlHtml = controlHtml.replace( /<[^<>]+>/g, function( m ) {
+ return m.replace( /__i__|%i%/g, widgetNumber );
+ } );
+ } else {
+ widget.set( 'is_disabled', true ); // Prevent single widget from being added again now
+ }
+
+ $widget = $( controlHtml );
+
+ controlContainer = $( '<li/>' )
+ .addClass( 'customize-control' )
+ .addClass( 'customize-control-' + controlType )
+ .append( $widget );
+
+ // Remove icon which is visible inside the panel
+ controlContainer.find( '> .widget-icon' ).remove();
+
+ if ( widget.get( 'is_multi' ) ) {
+ controlContainer.find( 'input[name="widget_number"]' ).val( widgetNumber );
+ controlContainer.find( 'input[name="multi_number"]' ).val( widgetNumber );
+ }
+
+ widgetId = controlContainer.find( '[name="widget-id"]' ).val();
+
+ controlContainer.hide(); // to be slid-down below
+
+ settingId = 'widget_' + widget.get( 'id_base' );
+ if ( widget.get( 'is_multi' ) ) {
+ settingId += '[' + widgetNumber + ']';
+ }
+ controlContainer.attr( 'id', 'customize-control-' + settingId.replace( /\]/g, '' ).replace( /\[/g, '-' ) );
+
+ // Only create setting if it doesn't already exist (if we're adding a pre-existing inactive widget)
+ isExistingWidget = api.has( settingId );
+ if ( ! isExistingWidget ) {
+ settingArgs = {
+ transport: api.Widgets.data.selectiveRefreshableWidgets[ widget.get( 'id_base' ) ] ? 'postMessage' : 'refresh',
+ previewer: this.setting.previewer
+ };
+ setting = api.create( settingId, settingId, '', settingArgs );
+ setting.set( {} ); // mark dirty, changing from '' to {}
+ }
+
+ controlConstructor = api.controlConstructor[controlType];
+ widgetFormControl = new controlConstructor( settingId, {
+ settings: {
+ 'default': settingId
+ },
+ content: controlContainer,
+ sidebar_id: self.params.sidebar_id,
+ widget_id: widgetId,
+ widget_id_base: widget.get( 'id_base' ),
+ type: controlType,
+ is_new: ! isExistingWidget,
+ width: widget.get( 'width' ),
+ height: widget.get( 'height' ),
+ is_wide: widget.get( 'is_wide' )
+ } );
+ api.control.add( widgetFormControl );
+
+ // Make sure widget is removed from the other sidebars
+ api.each( function( otherSetting ) {
+ if ( otherSetting.id === self.setting.id ) {
+ return;
+ }
+
+ if ( 0 !== otherSetting.id.indexOf( 'sidebars_widgets[' ) ) {
+ return;
+ }
+
+ var otherSidebarWidgets = otherSetting().slice(),
+ i = _.indexOf( otherSidebarWidgets, widgetId );
+
+ if ( -1 !== i ) {
+ otherSidebarWidgets.splice( i );
+ otherSetting( otherSidebarWidgets );
+ }
+ } );
+
+ // Add widget to this sidebar
+ sidebarWidgets = this.setting().slice();
+ if ( -1 === _.indexOf( sidebarWidgets, widgetId ) ) {
+ sidebarWidgets.push( widgetId );
+ this.setting( sidebarWidgets );
+ }
+
+ controlContainer.slideDown( function() {
+ if ( isExistingWidget ) {
+ widgetFormControl.updateWidget( {
+ instance: widgetFormControl.setting()
+ } );
+ }
+ } );
+
+ return widgetFormControl;
+ }
+ } );
+
+ // Register models for custom panel, section, and control types
+ $.extend( api.panelConstructor, {
+ widgets: api.Widgets.WidgetsPanel
+ });
+ $.extend( api.sectionConstructor, {
+ sidebar: api.Widgets.SidebarSection
+ });
+ $.extend( api.controlConstructor, {
+ widget_form: api.Widgets.WidgetControl,
+ sidebar_widgets: api.Widgets.SidebarControl
+ });
+
+ /**
+ * Init Customizer for widgets.
+ */
+ api.bind( 'ready', function() {
+ // Set up the widgets panel
+ api.Widgets.availableWidgetsPanel = new api.Widgets.AvailableWidgetsPanelView({
+ collection: api.Widgets.availableWidgets
+ });
+
+ // Highlight widget control
+ api.previewer.bind( 'highlight-widget-control', api.Widgets.highlightWidgetFormControl );
+
+ // Open and focus widget control
+ api.previewer.bind( 'focus-widget-control', api.Widgets.focusWidgetFormControl );
+ } );
+
+ /**
+ * Highlight a widget control.
+ *
+ * @param {string} widgetId
+ */
+ api.Widgets.highlightWidgetFormControl = function( widgetId ) {
+ var control = api.Widgets.getWidgetFormControlForWidget( widgetId );
+
+ if ( control ) {
+ control.highlightSectionAndControl();
+ }
+ },
+
+ /**
+ * Focus a widget control.
+ *
+ * @param {string} widgetId
+ */
+ api.Widgets.focusWidgetFormControl = function( widgetId ) {
+ var control = api.Widgets.getWidgetFormControlForWidget( widgetId );
+
+ if ( control ) {
+ control.focus();
+ }
+ },
+
+ /**
+ * Given a widget control, find the sidebar widgets control that contains it.
+ * @param {string} widgetId
+ * @return {object|null}
+ */
+ api.Widgets.getSidebarWidgetControlContainingWidget = function( widgetId ) {
+ var foundControl = null;
+
+ // @todo this can use widgetIdToSettingId(), then pass into wp.customize.control( x ).getSidebarWidgetsControl()
+ api.control.each( function( control ) {
+ if ( control.params.type === 'sidebar_widgets' && -1 !== _.indexOf( control.setting(), widgetId ) ) {
+ foundControl = control;
+ }
+ } );
+
+ return foundControl;
+ };
+
+ /**
+ * Given a widget ID for a widget appearing in the preview, get the widget form control associated with it.
+ *
+ * @param {string} widgetId
+ * @return {object|null}
+ */
+ api.Widgets.getWidgetFormControlForWidget = function( widgetId ) {
+ var foundControl = null;
+
+ // @todo We can just use widgetIdToSettingId() here
+ api.control.each( function( control ) {
+ if ( control.params.type === 'widget_form' && control.params.widget_id === widgetId ) {
+ foundControl = control;
+ }
+ } );
+
+ return foundControl;
+ };
+
+ /**
+ * Initialize Edit Menu button in Nav Menu widget.
+ */
+ $( document ).on( 'widget-added', function( event, widgetContainer ) {
+ var parsedWidgetId, widgetControl, navMenuSelect, editMenuButton;
+ parsedWidgetId = parseWidgetId( widgetContainer.find( '> .widget-inside > .form > .widget-id' ).val() );
+ if ( 'nav_menu' !== parsedWidgetId.id_base ) {
+ return;
+ }
+ widgetControl = api.control( 'widget_nav_menu[' + String( parsedWidgetId.number ) + ']' );
+ if ( ! widgetControl ) {
+ return;
+ }
+ navMenuSelect = widgetContainer.find( 'select[name*="nav_menu"]' );
+ editMenuButton = widgetContainer.find( '.edit-selected-nav-menu > button' );
+ if ( 0 === navMenuSelect.length || 0 === editMenuButton.length ) {
+ return;
+ }
+ navMenuSelect.on( 'change', function() {
+ if ( api.section.has( 'nav_menu[' + navMenuSelect.val() + ']' ) ) {
+ editMenuButton.parent().show();
+ } else {
+ editMenuButton.parent().hide();
+ }
+ });
+ editMenuButton.on( 'click', function() {
+ var section = api.section( 'nav_menu[' + navMenuSelect.val() + ']' );
+ if ( section ) {
+ focusConstructWithBreadcrumb( section, widgetControl );
+ }
+ } );
+ } );
+
+ /**
+ * Focus (expand) one construct and then focus on another construct after the first is collapsed.
+ *
+ * This overrides the back button to serve the purpose of breadcrumb navigation.
+ *
+ * @param {wp.customize.Section|wp.customize.Panel|wp.customize.Control} focusConstruct - The object to initially focus.
+ * @param {wp.customize.Section|wp.customize.Panel|wp.customize.Control} returnConstruct - The object to return focus.
+ */
+ function focusConstructWithBreadcrumb( focusConstruct, returnConstruct ) {
+ focusConstruct.focus();
+ function onceCollapsed( isExpanded ) {
+ if ( ! isExpanded ) {
+ focusConstruct.expanded.unbind( onceCollapsed );
+ returnConstruct.focus();
+ }
+ }
+ focusConstruct.expanded.bind( onceCollapsed );
+ }
+
+ /**
+ * @param {String} widgetId
+ * @returns {Object}
+ */
+ function parseWidgetId( widgetId ) {
+ var matches, parsed = {
+ number: null,
+ id_base: null
+ };
+
+ matches = widgetId.match( /^(.+)-(\d+)$/ );
+ if ( matches ) {
+ parsed.id_base = matches[1];
+ parsed.number = parseInt( matches[2], 10 );
+ } else {
+ // likely an old single widget
+ parsed.id_base = widgetId;
+ }
+
+ return parsed;
+ }
+
+ /**
+ * @param {String} widgetId
+ * @returns {String} settingId
+ */
+ function widgetIdToSettingId( widgetId ) {
+ var parsed = parseWidgetId( widgetId ), settingId;
+
+ settingId = 'widget_' + parsed.id_base;
+ if ( parsed.number ) {
+ settingId += '[' + parsed.number + ']';
+ }
+
+ return settingId;
+ }
+
+})( window.wp, jQuery );
diff --git a/www/crm/wp-admin/js/customize-widgets.min.js b/www/crm/wp-admin/js/customize-widgets.min.js
new file mode 100644
index 00000000..7eb293ea
--- /dev/null
+++ b/www/crm/wp-admin/js/customize-widgets.min.js
@@ -0,0 +1 @@
+!function(a,b){function c(a,b){function c(d){d||(a.expanded.unbind(c),b.focus())}a.focus(),a.expanded.bind(c)}function d(a){var b,c={number:null,id_base:null};return b=a.match(/^(.+)-(\d+)$/),b?(c.id_base=b[1],c.number=parseInt(b[2],10)):c.id_base=a,c}function e(a){var b,c=d(a);return b="widget_"+c.id_base,c.number&&(b+="["+c.number+"]"),b}if(a&&a.customize){var f,g=a.customize;g.Widgets=g.Widgets||{},g.Widgets.savedWidgetIds={},g.Widgets.data=_wpCustomizeWidgetsSettings||{},f=g.Widgets.data.l10n,g.Widgets.WidgetModel=Backbone.Model.extend({id:null,temp_id:null,classname:null,control_tpl:null,description:null,is_disabled:null,is_multi:null,multi_number:null,name:null,id_base:null,transport:null,params:[],width:null,height:null,search_matched:!0}),g.Widgets.WidgetCollection=Backbone.Collection.extend({model:g.Widgets.WidgetModel,doSearch:function(a){this.terms!==a&&(this.terms=a,this.terms.length>0&&this.search(this.terms),""===this.terms&&this.each(function(a){a.set("search_matched",!0)}))},search:function(a){var b,c;a=a.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&"),a=a.replace(/ /g,")(?=.*"),b=new RegExp("^(?=.*"+a+").+","i"),this.each(function(a){c=[a.get("name"),a.get("id"),a.get("description")].join(" "),a.set("search_matched",b.test(c))})}}),g.Widgets.availableWidgets=new g.Widgets.WidgetCollection(g.Widgets.data.availableWidgets),g.Widgets.SidebarModel=Backbone.Model.extend({after_title:null,after_widget:null,before_title:null,before_widget:null,"class":null,description:null,id:null,name:null,is_rendered:!1}),g.Widgets.SidebarCollection=Backbone.Collection.extend({model:g.Widgets.SidebarModel}),g.Widgets.registeredSidebars=new g.Widgets.SidebarCollection(g.Widgets.data.registeredSidebars),g.Widgets.AvailableWidgetsPanelView=a.Backbone.View.extend({el:"#available-widgets",events:{"input #widgets-search":"search","focus .widget-tpl":"focus","click .widget-tpl":"_submit","keypress .widget-tpl":"_submit",keydown:"keyboardAccessible"},selected:null,currentSidebarControl:null,$search:null,$clearResults:null,searchMatchesCount:null,initialize:function(){var a=this;this.$search=b("#widgets-search"),this.$clearResults=this.$el.find(".clear-results"),_.bindAll(this,"close"),this.listenTo(this.collection,"change",this.updateList),this.updateList(),this.searchMatchesCount=this.collection.length,b("#customize-controls, #available-widgets .customize-section-title").on("click keydown",function(c){var d=b(c.target).is(".add-new-widget, .add-new-widget *");b("body").hasClass("adding-widget")&&!d&&a.close()}),this.$clearResults.on("click",function(){a.$search.val("").focus().trigger("keyup")}),g.previewer.bind("url",this.close)},search:_.debounce(function(a){var b;this.collection.doSearch(a.target.value),this.updateSearchMatchesCount(),this.announceSearchMatches(),this.selected&&!this.selected.is(":visible")&&(this.selected.removeClass("selected"),this.selected=null),this.selected&&!a.target.value&&(this.selected.removeClass("selected"),this.selected=null),!this.selected&&a.target.value&&(b=this.$el.find("> .widget-tpl:visible:first"),b.length&&this.select(b)),""!==a.target.value?this.$clearResults.addClass("is-visible"):""===a.target.value&&this.$clearResults.removeClass("is-visible"),this.searchMatchesCount?this.$el.removeClass("no-widgets-found"):this.$el.addClass("no-widgets-found")},500),updateSearchMatchesCount:function(){this.searchMatchesCount=this.collection.where({search_matched:!0}).length},announceSearchMatches:function(){var b=f.widgetsFound.replace("%d",this.searchMatchesCount);this.searchMatchesCount||(b=f.noWidgetsFound),a.a11y.speak(b)},updateList:function(){this.collection.each(function(a){var c=b("#widget-tpl-"+a.id);c.toggle(a.get("search_matched")&&!a.get("is_disabled")),a.get("is_disabled")&&c.is(this.selected)&&(this.selected=null)})},select:function(a){this.selected=b(a),this.selected.siblings(".widget-tpl").removeClass("selected"),this.selected.addClass("selected")},focus:function(a){this.select(b(a.currentTarget))},_submit:function(a){"keypress"===a.type&&13!==a.which&&32!==a.which||this.submit(b(a.currentTarget))},submit:function(a){var c,d,e;a||(a=this.selected),a&&this.currentSidebarControl&&(this.select(a),c=b(this.selected).data("widget-id"),d=this.collection.findWhere({id:c}),d&&(e=this.currentSidebarControl.addWidget(d.get("id_base")),e&&e.focus(),this.close()))},open:function(a){this.currentSidebarControl=a,_(this.currentSidebarControl.getWidgetFormControls()).each(function(a){a.params.is_wide&&a.collapseForm()}),g.section.has("publish_settings")&&g.section("publish_settings").collapse(),b("body").addClass("adding-widget"),this.$el.find(".selected").removeClass("selected"),this.collection.doSearch(""),g.settings.browser.mobile||this.$search.focus()},close:function(a){a=a||{},a.returnFocus&&this.currentSidebarControl&&this.currentSidebarControl.container.find(".add-new-widget").focus(),this.currentSidebarControl=null,this.selected=null,b("body").removeClass("adding-widget"),this.$search.val("")},keyboardAccessible:function(a){var c=13===a.which,d=27===a.which,e=40===a.which,f=38===a.which,g=9===a.which,h=a.shiftKey,i=null,j=this.$el.find("> .widget-tpl:visible:first"),k=this.$el.find("> .widget-tpl:visible:last"),l=b(a.target).is(this.$search),m=b(a.target).is(".widget-tpl:visible:last");return e||f?(e?l?i=j:this.selected&&0!==this.selected.nextAll(".widget-tpl:visible").length&&(i=this.selected.nextAll(".widget-tpl:visible:first")):f&&(l?i=k:this.selected&&0!==this.selected.prevAll(".widget-tpl:visible").length&&(i=this.selected.prevAll(".widget-tpl:visible:first"))),this.select(i),void(i?i.focus():this.$search.focus())):void(c&&!this.$search.val()||(c?this.submit():d&&this.close({returnFocus:!0}),this.currentSidebarControl&&g&&(h&&l||!h&&m)&&(this.currentSidebarControl.container.find(".add-new-widget").focus(),a.preventDefault())))}}),g.Widgets.formSyncHandlers={rss:function(a,c,d){var e=c.find(".widget-error:first"),f=b("<div>"+d+"</div>").find(".widget-error:first");e.length&&f.length?e.replaceWith(f):e.length?e.remove():f.length&&c.find(".widget-content:first").prepend(f)}},g.Widgets.WidgetControl=g.Control.extend({defaultExpandedArguments:{duration:"fast",completeCallback:b.noop},initialize:function(a,c){var d=this;d.widgetControlEmbedded=!1,d.widgetContentEmbedded=!1,d.expanded=new g.Value(!1),d.expandedArgumentsQueue=[],d.expanded.bind(function(a){var c=d.expandedArgumentsQueue.shift();c=b.extend({},d.defaultExpandedArguments,c),d.onChangeExpanded(a,c)}),d.altNotice=!0,g.Control.prototype.initialize.call(d,a,c)},ready:function(){var a=this;a.section()?g.section(a.section(),function(b){var c=function(d){d&&(a.embedWidgetControl(),b.expanded.unbind(c))};b.expanded()?c(!0):b.expanded.bind(c)}):a.embedWidgetControl()},embedWidgetControl:function(){var a,c=this;c.widgetControlEmbedded||(c.widgetControlEmbedded=!0,a=b(c.params.widget_control),c.container.append(a),c._setupModel(),c._setupWideWidget(),c._setupControlToggle(),c._setupWidgetTitle(),c._setupReorderUI(),c._setupHighlightEffects(),c._setupUpdateUI(),c._setupRemoveUI())},embedWidgetContent:function(){var a,c=this;c.embedWidgetControl(),c.widgetContentEmbedded||(c.widgetContentEmbedded=!0,c.notifications.container=c.getNotificationsContainerElement(),c.notifications.render(),a=b(c.params.widget_content),c.container.find(".widget-content:first").append(a),b(document).trigger("widget-added",[c.container.find(".widget:first")]))},_setupModel:function(){var a,b=this;a=function(){g.Widgets.savedWidgetIds[b.params.widget_id]=!0},g.bind("ready",a),g.bind("saved",a),this._updateCount=0,this.isWidgetUpdating=!1,this.liveUpdateMode=!0,this.setting.bind(function(a,c){_(c).isEqual(a)||b.isWidgetUpdating||b.updateWidget({instance:a})})},_setupWideWidget:function(){var a,c,d,e,f,h=this;!this.params.is_wide||b(window).width()<=640||(a=this.container.find(".widget-inside"),c=a.find("> .form"),d=b(".wp-full-overlay-sidebar-content:first"),this.container.addClass("wide-widget-control"),this.container.find(".form:first").css({"max-width":this.params.width,"min-height":this.params.height}),f=function(){var d,e=h.container.offset().top,f=b(window).height(),g=c.outerHeight();a.css("max-height",f),d=Math.max(0,Math.min(Math.max(e,0),f-g)),a.css("top",d)},e=b("#customize-theme-controls"),this.container.on("expand",function(){f(),d.on("scroll",f),b(window).on("resize",f),e.on("expanded collapsed",f)}),this.container.on("collapsed",function(){d.off("scroll",f),b(window).off("resize",f),e.off("expanded collapsed",f)}),g.each(function(a){0===a.id.indexOf("sidebars_widgets[")&&a.bind(function(){h.container.hasClass("expanded")&&f()})}))},_setupControlToggle:function(){var a,b=this;this.container.find(".widget-top").on("click",function(a){a.preventDefault();var c=b.getSidebarWidgetsControl();c.isReordering||b.expanded(!b.expanded())}),a=this.container.find(".widget-control-close"),a.on("click",function(){b.collapse(),b.container.find(".widget-top .widget-action:first").focus()})},_setupWidgetTitle:function(){var a,b=this;a=function(){var a=b.setting().title,c=b.container.find(".in-widget-title");a?c.text(": "+a):c.text("")},this.setting.bind(a),a()},_setupReorderUI:function(){var c,d,e,h,i,j=this;c=function(a){a.siblings(".selected").removeClass("selected"),a.addClass("selected");var b=a.data("id")===j.params.sidebar_id;j.container.find(".move-widget-btn").prop("disabled",b)},this.container.find(".widget-title-action").after(b(g.Widgets.data.tpl.widgetReorderNav)),i=_.template(g.Widgets.data.tpl.moveWidgetArea),d=b(i({sidebars:_(g.Widgets.registeredSidebars.toArray()).pluck("attributes")})),this.container.find(".widget-top").after(d),h=function(){var a,e=d.find("li"),f=0;a=e.filter(function(){return b(this).data("id")===j.params.sidebar_id}),e.each(function(){var d,e,h,i=b(this);d=i.data("id"),e=g.Widgets.registeredSidebars.get(d),h=e.get("is_rendered"),i.toggle(h),h&&(f+=1),i.hasClass("selected")&&!h&&c(a)}),f>1?j.container.find(".move-widget").show():j.container.find(".move-widget").hide()},h(),g.Widgets.registeredSidebars.on("change:is_rendered",h),e=this.container.find(".widget-reorder-nav"),e.find(".move-widget, .move-widget-down, .move-widget-up").each(function(){b(this).prepend(j.container.find(".widget-title").text()+": ")}).on("click keypress",function(c){if("keypress"!==c.type||13===c.which||32===c.which)if(b(this).focus(),b(this).is(".move-widget"))j.toggleWidgetMoveArea();else{var d=b(this).is(".move-widget-down"),e=b(this).is(".move-widget-up"),g=j.getWidgetSidebarPosition();if(e&&0===g||d&&g===j.getSidebarWidgetsControl().setting().length-1)return;e?(j.moveUp(),a.a11y.speak(f.widgetMovedUp)):(j.moveDown(),a.a11y.speak(f.widgetMovedDown)),b(this).focus()}}),this.container.find(".widget-area-select").on("click keypress","li",function(a){"keypress"===a.type&&13!==a.which&&32!==a.which||(a.preventDefault(),c(b(this)))}),this.container.find(".move-widget-btn").click(function(){j.getSidebarWidgetsControl().toggleReordering(!1);var a,b,c,d,e,f=j.params.sidebar_id,h=j.container.find(".widget-area-select li.selected").data("id");a=g("sidebars_widgets["+f+"]"),b=g("sidebars_widgets["+h+"]"),c=Array.prototype.slice.call(a()),d=Array.prototype.slice.call(b()),e=j.getWidgetSidebarPosition(),c.splice(e,1),d.push(j.params.widget_id),a(c),b(d),j.focus()})},_setupHighlightEffects:function(){var a=this;this.container.on("mouseenter click",function(){a.setting.previewer.send("highlight-widget",a.params.widget_id)}),this.setting.bind(function(){a.setting.previewer.send("highlight-widget",a.params.widget_id)})},_setupUpdateUI:function(){var a,c,d,e,h,i=this;a=this.container.find(".widget:first"),c=a.find(".widget-content:first"),d=this.container.find(".widget-control-save"),d.val(f.saveBtnLabel),d.attr("title",f.saveBtnTooltip),d.removeClass("button-primary"),d.on("click",function(a){a.preventDefault(),i.updateWidget({disable_form:!0})}),e=_.debounce(function(){i.updateWidget()},250),c.on("keydown","input",function(a){13===a.which&&(a.preventDefault(),i.updateWidget({ignoreActiveElement:!0}))}),c.on("change input propertychange",":input",function(a){i.liveUpdateMode&&("change"===a.type||this.checkValidity&&this.checkValidity())&&e()}),this.setting.previewer.channel.bind("synced",function(){i.container.removeClass("previewer-loading")}),g.previewer.bind("widget-updated",function(a){a===i.params.widget_id&&i.container.removeClass("previewer-loading")}),h=g.Widgets.formSyncHandlers[this.params.widget_id_base],h&&b(document).on("widget-synced",function(b,c){a.is(c)&&h.apply(document,arguments)})},onChangeActive:function(a,b){this.container.toggleClass("widget-rendered",a),b.completeCallback&&b.completeCallback()},_setupRemoveUI:function(){var a,b,c=this;a=this.container.find(".widget-control-remove"),a.on("click",function(){var a;a=c.container.next().is(".customize-control-widget_form")?c.container.next().find(".widget-action:first"):c.container.prev().is(".customize-control-widget_form")?c.container.prev().find(".widget-action:first"):c.container.next(".customize-control-sidebar_widgets").find(".add-new-widget:first"),c.container.slideUp(function(){var b,d,e=g.Widgets.getSidebarWidgetControlContainingWidget(c.params.widget_id);e&&(b=e.setting().slice(),d=_.indexOf(b,c.params.widget_id),-1!==d&&(b.splice(d,1),e.setting(b),a.focus()))})}),b=function(){a.text(f.removeBtnLabel),a.attr("title",f.removeBtnTooltip)},this.params.is_new?g.bind("saved",b):b()},_getInputs:function(a){return b(a).find(":input[name]")},_getInputsSignature:function(a){var c=_(a).map(function(a){var c,d=b(a);return c=d.is(":checkbox, :radio")?[d.attr("id"),d.attr("name"),d.prop("value")]:[d.attr("id"),d.attr("name")],c.join(",")});return c.join(";")},_getInputState:function(a){return a=b(a),a.is(":radio, :checkbox")?a.prop("checked"):a.is("select[multiple]")?a.find("option:selected").map(function(){return b(this).val()}).get():a.val()},_setInputState:function(a,c){a=b(a),a.is(":radio, :checkbox")?a.prop("checked",c):a.is("select[multiple]")?(c=b.isArray(c)?_.map(c,function(a){return String(a)}):[],a.find("option").each(function(){b(this).prop("selected",-1!==_.indexOf(c,String(this.value)))})):a.val(c)},getSidebarWidgetsControl:function(){var a,b;if(a="sidebars_widgets["+this.params.sidebar_id+"]",b=g.control(a))return b},updateWidget:function(c){var d,e,h,i,j,k,l,m,n,o,p,q=this;q.embedWidgetContent(),c=b.extend({instance:null,complete:null,ignoreActiveElement:!1},c),d=c.instance,e=c.complete,this._updateCount+=1,j=this._updateCount,h=this.container.find(".widget:first"),i=h.find(".widget-content:first"),i.find(".widget-error").remove(),this.container.addClass("widget-form-loading"),this.container.addClass("previewer-loading"),n=g.state("processing"),n(n()+1),this.liveUpdateMode||this.container.addClass("widget-form-disabled"),k={},k.action="update-widget",k.wp_customize="on",k.nonce=g.settings.nonce["update-widget"],k.customize_theme=g.settings.theme.stylesheet,k.customized=a.customize.previewer.query().customized,l=b.param(k),m=this._getInputs(i),m.each(function(){b(this).data("state"+j,q._getInputState(this))}),l+=d?"&"+b.param({sanitized_widget_setting:JSON.stringify(d)}):"&"+m.serialize(),l+="&"+i.find("~ :input").serialize(),this._previousUpdateRequest&&this._previousUpdateRequest.abort(),o=b.post(a.ajax.settings.url,l),this._previousUpdateRequest=o,o.done(function(a){var d,k,l,n,o=!1;return"0"===a?(g.previewer.preview.iframe.hide(),void g.previewer.login().done(function(){q.updateWidget(c),g.previewer.preview.iframe.show()})):"-1"===a?void g.previewer.cheatin():void(a.success?(k=b("<div>"+a.data.form+"</div>"),l=q._getInputs(k),n=q._getInputsSignature(m)===q._getInputsSignature(l),n&&!q.liveUpdateMode&&(q.liveUpdateMode=!0,q.container.removeClass("widget-form-disabled"),q.container.find('input[name="savewidget"]').hide()),n&&q.liveUpdateMode?(m.each(function(a){var d,e,f,g=b(this),h=b(l[a]);d=g.data("state"+j),e=q._getInputState(h),g.data("sanitized",e),f=!_.isEqual(d,e)&&(c.ignoreActiveElement||!g.is(document.activeElement)),f&&q._setInputState(g,e)}),b(document).trigger("widget-synced",[h,a.data.form])):q.liveUpdateMode?(q.liveUpdateMode=!1,q.container.find('input[name="savewidget"]').show(),o=!0):(i.html(a.data.form),q.container.removeClass("widget-form-disabled"),b(document).trigger("widget-updated",[h])),p=!o&&!_(q.setting()).isEqual(a.data.instance),p?(q.isWidgetUpdating=!0,q.setting(a.data.instance),q.isWidgetUpdating=!1):q.container.removeClass("previewer-loading"),e&&e.call(q,null,{noChange:!p,ajaxFinished:!0})):(d=f.error,a.data&&a.data.message&&(d=a.data.message),e?e.call(q,d):i.prepend('<p class="widget-error"><strong>'+d+"</strong></p>")))}),o.fail(function(a,b){e&&e.call(q,b)}),o.always(function(){q.container.removeClass("widget-form-loading"),m.each(function(){b(this).removeData("state"+j)}),n(n()-1)})},expandControlSection:function(){g.Control.prototype.expand.call(this)},_toggleExpanded:g.Section.prototype._toggleExpanded,expand:g.Section.prototype.expand,expandForm:function(){this.expand()},collapse:g.Section.prototype.collapse,collapseForm:function(){this.collapse()},toggleForm:function(a){"undefined"==typeof a&&(a=!this.expanded()),this.expanded(a)},onChangeExpanded:function(a,b){var c,d,e,f,h,i,j=this;return j.embedWidgetControl(),a&&j.embedWidgetContent(),b.unchanged?void(a&&g.Control.prototype.expand.call(j,{completeCallback:b.completeCallback})):(c=this.container.find("div.widget:first"),d=c.find(".widget-inside:first"),i=this.container.find(".widget-top button.widget-action"),h=function(){g.control.each(function(a){j.params.type===a.params.type&&j!==a&&a.collapse()}),e=function(){j.container.removeClass("expanding"),j.container.addClass("expanded"),c.addClass("open"),i.attr("aria-expanded","true"),j.container.trigger("expanded")},b.completeCallback&&(f=e,e=function(){f(),b.completeCallback()}),j.params.is_wide?d.fadeIn(b.duration,e):d.slideDown(b.duration,e),j.container.trigger("expand"),j.container.addClass("expanding")},void(a?g.section.has(j.section())?g.section(j.section()).expand({completeCallback:h}):h():(e=function(){j.container.removeClass("collapsing"),j.container.removeClass("expanded"),c.removeClass("open"),i.attr("aria-expanded","false"),j.container.trigger("collapsed")},b.completeCallback&&(f=e,e=function(){f(),b.completeCallback()}),j.container.trigger("collapse"),j.container.addClass("collapsing"),j.params.is_wide?d.fadeOut(b.duration,e):d.slideUp(b.duration,function(){c.css({width:"",margin:""}),e()}))))},getWidgetSidebarPosition:function(){var a,b;if(a=this.getSidebarWidgetsControl().setting(),b=_.indexOf(a,this.params.widget_id),b!==-1)return b},moveUp:function(){this._moveWidgetByOne(-1)},moveDown:function(){this._moveWidgetByOne(1)},_moveWidgetByOne:function(a){var b,c,d,e;b=this.getWidgetSidebarPosition(),c=this.getSidebarWidgetsControl().setting,d=Array.prototype.slice.call(c()),e=d[b+a],d[b+a]=this.params.widget_id,d[b]=e,c(d)},toggleWidgetMoveArea:function(a){var c,d=this;c=this.container.find(".move-widget-area"),"undefined"==typeof a&&(a=!c.hasClass("active")),a&&(c.find(".selected").removeClass("selected"),c.find("li").filter(function(){return b(this).data("id")===d.params.sidebar_id}).addClass("selected"),this.container.find(".move-widget-btn").prop("disabled",!0)),c.toggleClass("active",a)},highlightSectionAndControl:function(){var a;a=this.container.is(":hidden")?this.container.closest(".control-section"):this.container,b(".highlighted").removeClass("highlighted"),a.addClass("highlighted"),setTimeout(function(){a.removeClass("highlighted")},500)}}),g.Widgets.WidgetsPanel=g.Panel.extend({ready:function(){var a=this;g.Panel.prototype.ready.call(a),a.deferred.embedded.done(function(){var c,d,e,h,i;c=a.container.find(".panel-meta"),d=b("<div></div>",{"class":"no-widget-areas-rendered-notice"}),c.append(d),h=function(){return _.filter(a.sections(),function(a){return"sidebar"===a.params.type&&a.active()}).length},i=function(){var a=h();return 0===a||a!==g.Widgets.data.registeredSidebars.length},e=function(){var a,c,e,i=h();d.empty(),e=g.Widgets.data.registeredSidebars.length,i!==e&&(0!==i?(c=e-i,a=f.someAreasShown[c]):a=f.noAreasShown,a&&d.append(b("<p></p>",{text:a})),d.append(b("<p></p>",{text:f.navigatePreview})))},e(),d.toggle(i()),g.previewer.deferred.active.done(function(){d.toggle(i())}),g.bind("pane-contents-reflowed",function(){var a="resolved"===g.previewer.deferred.active.state()?"fast":0;e(),i()?d.slideDown(a):d.slideUp(a)})})},isContextuallyActive:function(){var a=this;return a.active()}}),g.Widgets.SidebarSection=g.Section.extend({ready:function(){var a,b=this;g.Section.prototype.ready.call(this),a=g.Widgets.registeredSidebars.get(b.params.sidebarId),b.active.bind(function(b){a.set("is_rendered",b)}),a.set("is_rendered",b.active())}}),g.Widgets.SidebarControl=g.Control.extend({ready:function(){this.$controlSection=this.container.closest(".control-section"),this.$sectionContent=this.container.closest(".accordion-section-content"),this._setupModel(),this._setupSortable(),this._setupAddition(),this._applyCardinalOrderClassNames()},_setupModel:function(){var a=this;this.setting.bind(function(c,e){var f,h,i;h=_(e).difference(c),c=_(c).filter(function(a){var b=d(a);return!!g.Widgets.availableWidgets.findWhere({id_base:b.id_base})}),f=_(c).map(function(b){var c=g.Widgets.getWidgetFormControlForWidget(b);return c||(c=a.addWidget(b)),c}),f.sort(function(a,b){var d=_.indexOf(c,a.params.widget_id),e=_.indexOf(c,b.params.widget_id);return d-e}),i=0,_(f).each(function(b){b.priority(i),b.section(a.section()),i+=1}),a.priority(i),a._applyCardinalOrderClassNames(),_(f).each(function(b){b.params.sidebar_id=a.params.sidebar_id}),_(h).each(function(c){setTimeout(function(){var e,f,h,i,j,k=!1;g.each(function(b){if(b.id!==a.setting.id&&0===b.id.indexOf("sidebars_widgets[")&&"sidebars_widgets[wp_inactive_widgets]"!==b.id){var d,e=b();d=_.indexOf(e,c),-1!==d&&(k=!0)}}),k||(e=g.Widgets.getWidgetFormControlForWidget(c),f=e&&b.contains(document,e.container[0])&&!b.contains(a.$sectionContent[0],e.container[0]),e&&!f&&(g.control.remove(e.id),e.container.remove()),g.Widgets.savedWidgetIds[c]&&(h=g.value("sidebars_widgets[wp_inactive_widgets]")().slice(),h.push(c),g.value("sidebars_widgets[wp_inactive_widgets]")(_(h).unique())),i=d(c).id_base,j=g.Widgets.availableWidgets.findWhere({id_base:i}),j&&!j.get("is_multi")&&j.set("is_disabled",!1))})})})},_setupSortable:function(){var a=this;this.isReordering=!1,this.$sectionContent.sortable({items:"> .customize-control-widget_form",handle:".widget-top",axis:"y",tolerance:"pointer",connectWith:".accordion-section-content:has(.customize-control-sidebar_widgets)",update:function(){var c,d=a.$sectionContent.sortable("toArray");c=b.map(d,function(a){return b("#"+a).find(":input[name=widget-id]").val()}),a.setting(c)}}),this.$controlSection.find(".accordion-section-title").droppable({accept:".customize-control-widget_form",over:function(){var b=g.section(a.section.get());b.expand({allowMultiple:!0,completeCallback:function(){g.section.each(function(a){a.container.find(".customize-control-sidebar_widgets").length&&a.container.find(".accordion-section-content:first").sortable("refreshPositions")})}})}}),this.container.find(".reorder-toggle").on("click",function(){a.toggleReordering(!a.isReordering)})},_setupAddition:function(){var a=this;this.container.find(".add-new-widget").on("click",function(){var c=b(this);a.$sectionContent.hasClass("reordering")||(b("body").hasClass("adding-widget")?(c.attr("aria-expanded","false"),g.Widgets.availableWidgetsPanel.close()):(c.attr("aria-expanded","true"),g.Widgets.availableWidgetsPanel.open(a)))})},_applyCardinalOrderClassNames:function(){var a=[];return _.each(this.setting(),function(b){var c=g.Widgets.getWidgetFormControlForWidget(b);c&&a.push(c)}),0===a.length||1===g.Widgets.registeredSidebars.length&&a.length<=1?void this.container.find(".reorder-toggle").hide():(this.container.find(".reorder-toggle").show(),b(a).each(function(){b(this.container).removeClass("first-widget").removeClass("last-widget").find(".move-widget-down, .move-widget-up").prop("tabIndex",0)}),_.first(a).container.addClass("first-widget").find(".move-widget-up").prop("tabIndex",-1),void _.last(a).container.addClass("last-widget").find(".move-widget-down").prop("tabIndex",-1))},toggleReordering:function(b){var c=this.$sectionContent.find(".add-new-widget"),d=this.container.find(".reorder-toggle"),e=this.$sectionContent.find(".widget-title");b=Boolean(b),b!==this.$sectionContent.hasClass("reordering")&&(this.isReordering=b,this.$sectionContent.toggleClass("reordering",b),b?(_(this.getWidgetFormControls()).each(function(a){a.collapse()}),c.attr({tabindex:"-1","aria-hidden":"true"}),d.attr("aria-label",f.reorderLabelOff),a.a11y.speak(f.reorderModeOn),e.attr("aria-hidden","true")):(c.removeAttr("tabindex aria-hidden"),d.attr("aria-label",f.reorderLabelOn),a.a11y.speak(f.reorderModeOff),e.attr("aria-hidden","false")))},getWidgetFormControls:function(){var a=[];return _(this.setting()).each(function(b){var c=e(b),d=g.control(c);d&&a.push(d)}),a},addWidget:function(a){var c,e,f,h,i,j,k,l,m,n,o=this,p="widget_form",q=d(a),r=q.number,s=q.id_base,t=g.Widgets.availableWidgets.findWhere({id_base:s});return!!t&&(!(r&&!t.get("is_multi"))&&(t.get("is_multi")&&!r&&(t.set("multi_number",t.get("multi_number")+1),r=t.get("multi_number")),c=b.trim(b("#widget-tpl-"+t.get("id")).html()),t.get("is_multi")?c=c.replace(/<[^<>]+>/g,function(a){return a.replace(/__i__|%i%/g,r)}):t.set("is_disabled",!0),e=b(c),f=b("<li/>").addClass("customize-control").addClass("customize-control-"+p).append(e),f.find("> .widget-icon").remove(),t.get("is_multi")&&(f.find('input[name="widget_number"]').val(r),f.find('input[name="multi_number"]').val(r)),a=f.find('[name="widget-id"]').val(),f.hide(),i="widget_"+t.get("id_base"),t.get("is_multi")&&(i+="["+r+"]"),f.attr("id","customize-control-"+i.replace(/\]/g,"").replace(/\[/g,"-")),j=g.has(i),j||(m={transport:g.Widgets.data.selectiveRefreshableWidgets[t.get("id_base")]?"postMessage":"refresh",previewer:this.setting.previewer},n=g.create(i,i,"",m),n.set({})),h=g.controlConstructor[p],k=new h(i,{settings:{"default":i},content:f,sidebar_id:o.params.sidebar_id,widget_id:a,widget_id_base:t.get("id_base"),type:p,is_new:!j,width:t.get("width"),height:t.get("height"),is_wide:t.get("is_wide")}),g.control.add(k),g.each(function(b){if(b.id!==o.setting.id&&0===b.id.indexOf("sidebars_widgets[")){var c=b().slice(),d=_.indexOf(c,a);-1!==d&&(c.splice(d),b(c))}}),l=this.setting().slice(),-1===_.indexOf(l,a)&&(l.push(a),this.setting(l)),f.slideDown(function(){j&&k.updateWidget({instance:k.setting()})}),k))}}),b.extend(g.panelConstructor,{widgets:g.Widgets.WidgetsPanel}),b.extend(g.sectionConstructor,{sidebar:g.Widgets.SidebarSection}),b.extend(g.controlConstructor,{widget_form:g.Widgets.WidgetControl,sidebar_widgets:g.Widgets.SidebarControl}),g.bind("ready",function(){g.Widgets.availableWidgetsPanel=new g.Widgets.AvailableWidgetsPanelView({collection:g.Widgets.availableWidgets}),g.previewer.bind("highlight-widget-control",g.Widgets.highlightWidgetFormControl),g.previewer.bind("focus-widget-control",g.Widgets.focusWidgetFormControl)}),g.Widgets.highlightWidgetFormControl=function(a){var b=g.Widgets.getWidgetFormControlForWidget(a);b&&b.highlightSectionAndControl()},g.Widgets.focusWidgetFormControl=function(a){var b=g.Widgets.getWidgetFormControlForWidget(a);b&&b.focus()},g.Widgets.getSidebarWidgetControlContainingWidget=function(a){var b=null;return g.control.each(function(c){"sidebar_widgets"===c.params.type&&-1!==_.indexOf(c.setting(),a)&&(b=c)}),b},g.Widgets.getWidgetFormControlForWidget=function(a){var b=null;return g.control.each(function(c){"widget_form"===c.params.type&&c.params.widget_id===a&&(b=c)}),b},b(document).on("widget-added",function(a,b){var e,f,h,i;e=d(b.find("> .widget-inside > .form > .widget-id").val()),"nav_menu"===e.id_base&&(f=g.control("widget_nav_menu["+String(e.number)+"]"),f&&(h=b.find('select[name*="nav_menu"]'),i=b.find(".edit-selected-nav-menu > button"),0!==h.length&&0!==i.length&&(h.on("change",function(){g.section.has("nav_menu["+h.val()+"]")?i.parent().show():i.parent().hide()}),i.on("click",function(){var a=g.section("nav_menu["+h.val()+"]");a&&c(a,f)}))))})}}(window.wp,jQuery); \ No newline at end of file
diff --git a/www/crm/wp-admin/js/dashboard.js b/www/crm/wp-admin/js/dashboard.js
new file mode 100644
index 00000000..aa9f9218
--- /dev/null
+++ b/www/crm/wp-admin/js/dashboard.js
@@ -0,0 +1,593 @@
+/**
+ * @output wp-admin/js/dashboard.js
+ */
+
+/* global pagenow, ajaxurl, postboxes, wpActiveEditor:true, ajaxWidgets */
+/* global ajaxPopulateWidgets, quickPressLoad, */
+window.wp = window.wp || {};
+
+/**
+ * Initializes the dashboard widget functionality.
+ *
+ * @since 2.7.0
+ */
+jQuery(document).ready( function($) {
+ var welcomePanel = $( '#welcome-panel' ),
+ welcomePanelHide = $('#wp_welcome_panel-hide'),
+ updateWelcomePanel;
+
+ /**
+ * Saves the visibility of the welcome panel.
+ *
+ * @since 3.3.0
+ *
+ * @param {boolean} visible Should it be visible or not.
+ *
+ * @returns {void}
+ */
+ updateWelcomePanel = function( visible ) {
+ $.post( ajaxurl, {
+ action: 'update-welcome-panel',
+ visible: visible,
+ welcomepanelnonce: $( '#welcomepanelnonce' ).val()
+ });
+ };
+
+ // Unhide the welcome panel if the Welcome Option checkbox is checked.
+ if ( welcomePanel.hasClass('hidden') && welcomePanelHide.prop('checked') ) {
+ welcomePanel.removeClass('hidden');
+ }
+
+ // Hide the welcome panel when the dismiss button or close button is clicked.
+ $('.welcome-panel-close, .welcome-panel-dismiss a', welcomePanel).click( function(e) {
+ e.preventDefault();
+ welcomePanel.addClass('hidden');
+ updateWelcomePanel( 0 );
+ $('#wp_welcome_panel-hide').prop('checked', false);
+ });
+
+ // Set welcome panel visibility based on Welcome Option checkbox value.
+ welcomePanelHide.click( function() {
+ welcomePanel.toggleClass('hidden', ! this.checked );
+ updateWelcomePanel( this.checked ? 1 : 0 );
+ });
+
+ /**
+ * These widgets can be populated via ajax.
+ *
+ * @since 2.7.0
+ *
+ * @type {string[]}
+ *
+ * @global
+ */
+ window.ajaxWidgets = ['dashboard_primary'];
+
+ /**
+ * Triggers widget updates via AJAX.
+ *
+ * @since 2.7.0
+ *
+ * @global
+ *
+ * @param {string} el Optional. Widget to fetch or none to update all.
+ *
+ * @returns {void}
+ */
+ window.ajaxPopulateWidgets = function(el) {
+ /**
+ * Fetch the latest representation of the widget via Ajax and show it.
+ *
+ * @param {number} i Number of half-seconds to use as the timeout.
+ * @param {string} id ID of the element which is going to be checked for changes.
+ *
+ * @returns {void}
+ */
+ function show(i, id) {
+ var p, e = $('#' + id + ' div.inside:visible').find('.widget-loading');
+ // If the element is found in the dom, queue to load latest representation.
+ if ( e.length ) {
+ p = e.parent();
+ setTimeout( function(){
+ // Request the widget content.
+ p.load( ajaxurl + '?action=dashboard-widgets&widget=' + id + '&pagenow=' + pagenow, '', function() {
+ // Hide the parent and slide it out for visual fancyness.
+ p.hide().slideDown('normal', function(){
+ $(this).css('display', '');
+ });
+ });
+ }, i * 500 );
+ }
+ }
+
+ // If we have received a specific element to fetch, check if it is valid.
+ if ( el ) {
+ el = el.toString();
+ // If the element is available as AJAX widget, show it.
+ if ( $.inArray(el, ajaxWidgets) !== -1 ) {
+ // Show element without any delay.
+ show(0, el);
+ }
+ } else {
+ // Walk through all ajaxWidgets, loading them after each other.
+ $.each( ajaxWidgets, show );
+ }
+ };
+
+ // Initially populate ajax widgets.
+ ajaxPopulateWidgets();
+
+ // Register ajax widgets as postbox toggles.
+ postboxes.add_postbox_toggles(pagenow, { pbshow: ajaxPopulateWidgets } );
+
+ /**
+ * Control the Quick Press (Quick Draft) widget.
+ *
+ * @since 2.7.0
+ *
+ * @global
+ *
+ * @returns {void}
+ */
+ window.quickPressLoad = function() {
+ var act = $('#quickpost-action'), t;
+
+ // Enable the submit buttons.
+ $( '#quick-press .submit input[type="submit"], #quick-press .submit input[type="reset"]' ).prop( 'disabled' , false );
+
+ t = $('#quick-press').submit( function( e ) {
+ e.preventDefault();
+
+ // Show a spinner.
+ $('#dashboard_quick_press #publishing-action .spinner').show();
+
+ // Disable the submit button to prevent duplicate submissions.
+ $('#quick-press .submit input[type="submit"], #quick-press .submit input[type="reset"]').prop('disabled', true);
+
+ // Post the entered data to save it.
+ $.post( t.attr( 'action' ), t.serializeArray(), function( data ) {
+ // Replace the form, and prepend the published post.
+ $('#dashboard_quick_press .inside').html( data );
+ $('#quick-press').removeClass('initial-form');
+ quickPressLoad();
+ highlightLatestPost();
+
+ // Focus the title to allow for quickly drafting another post.
+ $('#title').focus();
+ });
+
+ /**
+ * Highlights the latest post for one second.
+ *
+ * @returns {void}
+ */
+ function highlightLatestPost () {
+ var latestPost = $('.drafts ul li').first();
+ latestPost.css('background', '#fffbe5');
+ setTimeout(function () {
+ latestPost.css('background', 'none');
+ }, 1000);
+ }
+ } );
+
+ // Change the QuickPost action to the publish value.
+ $('#publish').click( function() { act.val( 'post-quickpress-publish' ); } );
+
+ $('#quick-press').on( 'click focusin', function() {
+ wpActiveEditor = 'content';
+ });
+
+ autoResizeTextarea();
+ };
+ window.quickPressLoad();
+
+ // Enable the dragging functionality of the widgets.
+ $( '.meta-box-sortables' ).sortable( 'option', 'containment', '#wpwrap' );
+
+ /**
+ * Adjust the height of the textarea based on the content.
+ *
+ * @since 3.6.0
+ *
+ * @returns {void}
+ */
+ function autoResizeTextarea() {
+ // When IE8 or older is used to render this document, exit.
+ if ( document.documentMode && document.documentMode < 9 ) {
+ return;
+ }
+
+ // Add a hidden div. We'll copy over the text from the textarea to measure its height.
+ $('body').append( '<div class="quick-draft-textarea-clone" style="display: none;"></div>' );
+
+ var clone = $('.quick-draft-textarea-clone'),
+ editor = $('#content'),
+ editorHeight = editor.height(),
+ /*
+ * 100px roughly accounts for browser chrome and allows the
+ * save draft button to show on-screen at the same time.
+ */
+ editorMaxHeight = $(window).height() - 100;
+
+ /*
+ * Match up textarea and clone div as much as possible.
+ * Padding cannot be reliably retrieved using shorthand in all browsers.
+ */
+ clone.css({
+ 'font-family': editor.css('font-family'),
+ 'font-size': editor.css('font-size'),
+ 'line-height': editor.css('line-height'),
+ 'padding-bottom': editor.css('paddingBottom'),
+ 'padding-left': editor.css('paddingLeft'),
+ 'padding-right': editor.css('paddingRight'),
+ 'padding-top': editor.css('paddingTop'),
+ 'white-space': 'pre-wrap',
+ 'word-wrap': 'break-word',
+ 'display': 'none'
+ });
+
+ // The 'propertychange' is used in IE < 9.
+ editor.on('focus input propertychange', function() {
+ var $this = $(this),
+ // Add a non-breaking space to ensure that the height of a trailing newline is
+ // included.
+ textareaContent = $this.val() + '&nbsp;',
+ // Add 2px to compensate for border-top & border-bottom.
+ cloneHeight = clone.css('width', $this.css('width')).text(textareaContent).outerHeight() + 2;
+
+ // Default to show a vertical scrollbar, if needed.
+ editor.css('overflow-y', 'auto');
+
+ // Only change the height if it has changed and both heights are below the max.
+ if ( cloneHeight === editorHeight || ( cloneHeight >= editorMaxHeight && editorHeight >= editorMaxHeight ) ) {
+ return;
+ }
+
+ /*
+ * Don't allow editor to exceed the height of the window.
+ * This is also bound in CSS to a max-height of 1300px to be extra safe.
+ */
+ if ( cloneHeight > editorMaxHeight ) {
+ editorHeight = editorMaxHeight;
+ } else {
+ editorHeight = cloneHeight;
+ }
+
+ // Disable scrollbars because we adjust the height to the content.
+ editor.css('overflow', 'hidden');
+
+ $this.css('height', editorHeight + 'px');
+ });
+ }
+
+} );
+
+jQuery( function( $ ) {
+ 'use strict';
+
+ var communityEventsData = window.communityEventsData || {},
+ app;
+
+ /**
+ * Global Community Events namespace.
+ *
+ * @since 4.8.0
+ *
+ * @memberOf wp
+ * @namespace wp.communityEvents
+ */
+ app = window.wp.communityEvents = /** @lends wp.communityEvents */{
+ initialized: false,
+ model: null,
+
+ /**
+ * Initializes the wp.communityEvents object.
+ *
+ * @since 4.8.0
+ *
+ * @returns {void}
+ */
+ init: function() {
+ if ( app.initialized ) {
+ return;
+ }
+
+ var $container = $( '#community-events' );
+
+ /*
+ * When JavaScript is disabled, the errors container is shown, so
+ * that "This widget requires JavaScript" message can be seen.
+ *
+ * When JS is enabled, the container is hidden at first, and then
+ * revealed during the template rendering, if there actually are
+ * errors to show.
+ *
+ * The display indicator switches from `hide-if-js` to `aria-hidden`
+ * here in order to maintain consistency with all the other fields
+ * that key off of `aria-hidden` to determine their visibility.
+ * `aria-hidden` can't be used initially, because there would be no
+ * way to set it to false when JavaScript is disabled, which would
+ * prevent people from seeing the "This widget requires JavaScript"
+ * message.
+ */
+ $( '.community-events-errors' )
+ .attr( 'aria-hidden', 'true' )
+ .removeClass( 'hide-if-js' );
+
+ $container.on( 'click', '.community-events-toggle-location, .community-events-cancel', app.toggleLocationForm );
+
+ /**
+ * Filters events based on entered location.
+ *
+ * @returns {void}
+ */
+ $container.on( 'submit', '.community-events-form', function( event ) {
+ var location = $.trim( $( '#community-events-location' ).val() );
+
+ event.preventDefault();
+
+ /*
+ * Don't trigger a search if the search field is empty or the
+ * search term was made of only spaces before being trimmed.
+ */
+ if ( ! location ) {
+ return;
+ }
+
+ app.getEvents({
+ location: location
+ });
+ });
+
+ if ( communityEventsData && communityEventsData.cache && communityEventsData.cache.location && communityEventsData.cache.events ) {
+ app.renderEventsTemplate( communityEventsData.cache, 'app' );
+ } else {
+ app.getEvents();
+ }
+
+ app.initialized = true;
+ },
+
+ /**
+ * Toggles the visibility of the Edit Location form.
+ *
+ * @since 4.8.0
+ *
+ * @param {event|string} action 'show' or 'hide' to specify a state;
+ * or an event object to flip between states.
+ *
+ * @returns {void}
+ */
+ toggleLocationForm: function( action ) {
+ var $toggleButton = $( '.community-events-toggle-location' ),
+ $cancelButton = $( '.community-events-cancel' ),
+ $form = $( '.community-events-form' ),
+ $target = $();
+
+ if ( 'object' === typeof action ) {
+ // The action is the event object: get the clicked element.
+ $target = $( action.target );
+ /*
+ * Strict comparison doesn't work in this case because sometimes
+ * we explicitly pass a string as value of aria-expanded and
+ * sometimes a boolean as the result of an evaluation.
+ */
+ action = 'true' == $toggleButton.attr( 'aria-expanded' ) ? 'hide' : 'show';
+ }
+
+ if ( 'hide' === action ) {
+ $toggleButton.attr( 'aria-expanded', 'false' );
+ $cancelButton.attr( 'aria-expanded', 'false' );
+ $form.attr( 'aria-hidden', 'true' );
+ /*
+ * If the Cancel button has been clicked, bring the focus back
+ * to the toggle button so users relying on screen readers don't
+ * lose their place.
+ */
+ if ( $target.hasClass( 'community-events-cancel' ) ) {
+ $toggleButton.focus();
+ }
+ } else {
+ $toggleButton.attr( 'aria-expanded', 'true' );
+ $cancelButton.attr( 'aria-expanded', 'true' );
+ $form.attr( 'aria-hidden', 'false' );
+ }
+ },
+
+ /**
+ * Sends REST API requests to fetch events for the widget.
+ *
+ * @since 4.8.0
+ *
+ * @param {Object} requestParams REST API Request parameters object.
+ *
+ * @returns {void}
+ */
+ getEvents: function( requestParams ) {
+ var initiatedBy,
+ app = this,
+ $spinner = $( '.community-events-form' ).children( '.spinner' );
+
+ requestParams = requestParams || {};
+ requestParams._wpnonce = communityEventsData.nonce;
+ requestParams.timezone = window.Intl ? window.Intl.DateTimeFormat().resolvedOptions().timeZone : '';
+
+ initiatedBy = requestParams.location ? 'user' : 'app';
+
+ $spinner.addClass( 'is-active' );
+
+ wp.ajax.post( 'get-community-events', requestParams )
+ .always( function() {
+ $spinner.removeClass( 'is-active' );
+ })
+
+ .done( function( response ) {
+ if ( 'no_location_available' === response.error ) {
+ if ( requestParams.location ) {
+ response.unknownCity = requestParams.location;
+ } else {
+ /*
+ * No location was passed, which means that this was an automatic query
+ * based on IP, locale, and timezone. Since the user didn't initiate it,
+ * it should fail silently. Otherwise, the error could confuse and/or
+ * annoy them.
+ */
+ delete response.error;
+ }
+ }
+ app.renderEventsTemplate( response, initiatedBy );
+ })
+
+ .fail( function() {
+ app.renderEventsTemplate({
+ 'location' : false,
+ 'error' : true
+ }, initiatedBy );
+ });
+ },
+
+ /**
+ * Renders the template for the Events section of the Events & News widget.
+ *
+ * @since 4.8.0
+ *
+ * @param {Object} templateParams The various parameters that will get passed to wp.template.
+ * @param {string} initiatedBy 'user' to indicate that this was triggered manually by the user;
+ * 'app' to indicate it was triggered automatically by the app itself.
+ *
+ * @returns {void}
+ */
+ renderEventsTemplate: function( templateParams, initiatedBy ) {
+ var template,
+ elementVisibility,
+ l10nPlaceholder = /%(?:\d\$)?s/g, // Match `%s`, `%1$s`, `%2$s`, etc.
+ $toggleButton = $( '.community-events-toggle-location' ),
+ $locationMessage = $( '#community-events-location-message' ),
+ $results = $( '.community-events-results' );
+
+ /*
+ * Hide all toggleable elements by default, to keep the logic simple.
+ * Otherwise, each block below would have to turn hide everything that
+ * could have been shown at an earlier point.
+ *
+ * The exception to that is that the .community-events container is hidden
+ * when the page is first loaded, because the content isn't ready yet,
+ * but once we've reached this point, it should always be shown.
+ */
+ elementVisibility = {
+ '.community-events' : true,
+ '.community-events-loading' : false,
+ '.community-events-errors' : false,
+ '.community-events-error-occurred' : false,
+ '.community-events-could-not-locate' : false,
+ '#community-events-location-message' : false,
+ '.community-events-toggle-location' : false,
+ '.community-events-results' : false
+ };
+
+ /*
+ * Determine which templates should be rendered and which elements
+ * should be displayed.
+ */
+ if ( templateParams.location.ip ) {
+ /*
+ * If the API determined the location by geolocating an IP, it will
+ * provide events, but not a specific location.
+ */
+ $locationMessage.text( communityEventsData.l10n.attend_event_near_generic );
+
+ if ( templateParams.events.length ) {
+ template = wp.template( 'community-events-event-list' );
+ $results.html( template( templateParams ) );
+ } else {
+ template = wp.template( 'community-events-no-upcoming-events' );
+ $results.html( template( templateParams ) );
+ }
+
+ elementVisibility['#community-events-location-message'] = true;
+ elementVisibility['.community-events-toggle-location'] = true;
+ elementVisibility['.community-events-results'] = true;
+
+ } else if ( templateParams.location.description ) {
+ template = wp.template( 'community-events-attend-event-near' );
+ $locationMessage.html( template( templateParams ) );
+
+ if ( templateParams.events.length ) {
+ template = wp.template( 'community-events-event-list' );
+ $results.html( template( templateParams ) );
+ } else {
+ template = wp.template( 'community-events-no-upcoming-events' );
+ $results.html( template( templateParams ) );
+ }
+
+ if ( 'user' === initiatedBy ) {
+ wp.a11y.speak( communityEventsData.l10n.city_updated.replace( l10nPlaceholder, templateParams.location.description ), 'assertive' );
+ }
+
+ elementVisibility['#community-events-location-message'] = true;
+ elementVisibility['.community-events-toggle-location'] = true;
+ elementVisibility['.community-events-results'] = true;
+
+ } else if ( templateParams.unknownCity ) {
+ template = wp.template( 'community-events-could-not-locate' );
+ $( '.community-events-could-not-locate' ).html( template( templateParams ) );
+ wp.a11y.speak( communityEventsData.l10n.could_not_locate_city.replace( l10nPlaceholder, templateParams.unknownCity ) );
+
+ elementVisibility['.community-events-errors'] = true;
+ elementVisibility['.community-events-could-not-locate'] = true;
+
+ } else if ( templateParams.error && 'user' === initiatedBy ) {
+ /*
+ * Errors messages are only shown for requests that were initiated
+ * by the user, not for ones that were initiated by the app itself.
+ * Showing error messages for an event that user isn't aware of
+ * could be confusing or unnecessarily distracting.
+ */
+ wp.a11y.speak( communityEventsData.l10n.error_occurred_please_try_again );
+
+ elementVisibility['.community-events-errors'] = true;
+ elementVisibility['.community-events-error-occurred'] = true;
+ } else {
+ $locationMessage.text( communityEventsData.l10n.enter_closest_city );
+
+ elementVisibility['#community-events-location-message'] = true;
+ elementVisibility['.community-events-toggle-location'] = true;
+ }
+
+ // Set the visibility of toggleable elements.
+ _.each( elementVisibility, function( isVisible, element ) {
+ $( element ).attr( 'aria-hidden', ! isVisible );
+ });
+
+ $toggleButton.attr( 'aria-expanded', elementVisibility['.community-events-toggle-location'] );
+
+ if ( templateParams.location && ( templateParams.location.ip || templateParams.location.latitude ) ) {
+ // Hide the form when there's a valid location.
+ app.toggleLocationForm( 'hide' );
+
+ if ( 'user' === initiatedBy ) {
+ /*
+ * When the form is programmatically hidden after a user search,
+ * bring the focus back to the toggle button so users relying
+ * on screen readers don't lose their place.
+ */
+ $toggleButton.focus();
+ }
+ } else {
+ app.toggleLocationForm( 'show' );
+ }
+ }
+ };
+
+ if ( $( '#dashboard_primary' ).is( ':visible' ) ) {
+ app.init();
+ } else {
+ $( document ).on( 'postbox-toggled', function( event, postbox ) {
+ var $postbox = $( postbox );
+
+ if ( 'dashboard_primary' === $postbox.attr( 'id' ) && $postbox.is( ':visible' ) ) {
+ app.init();
+ }
+ });
+ }
+});
diff --git a/www/crm/wp-admin/js/dashboard.min.js b/www/crm/wp-admin/js/dashboard.min.js
new file mode 100644
index 00000000..bf939f83
--- /dev/null
+++ b/www/crm/wp-admin/js/dashboard.min.js
@@ -0,0 +1 @@
+window.wp=window.wp||{},jQuery(document).ready(function(a){function b(){if(!(document.documentMode&&document.documentMode<9)){a("body").append('<div class="quick-draft-textarea-clone" style="display: none;"></div>');var b=a(".quick-draft-textarea-clone"),c=a("#content"),d=c.height(),e=a(window).height()-100;b.css({"font-family":c.css("font-family"),"font-size":c.css("font-size"),"line-height":c.css("line-height"),"padding-bottom":c.css("paddingBottom"),"padding-left":c.css("paddingLeft"),"padding-right":c.css("paddingRight"),"padding-top":c.css("paddingTop"),"white-space":"pre-wrap","word-wrap":"break-word",display:"none"}),c.on("focus input propertychange",function(){var f=a(this),g=f.val()+"&nbsp;",h=b.css("width",f.css("width")).text(g).outerHeight()+2;c.css("overflow-y","auto"),h===d||h>=e&&d>=e||(d=h>e?e:h,c.css("overflow","hidden"),f.css("height",d+"px"))})}}var c,d=a("#welcome-panel"),e=a("#wp_welcome_panel-hide");c=function(b){a.post(ajaxurl,{action:"update-welcome-panel",visible:b,welcomepanelnonce:a("#welcomepanelnonce").val()})},d.hasClass("hidden")&&e.prop("checked")&&d.removeClass("hidden"),a(".welcome-panel-close, .welcome-panel-dismiss a",d).click(function(b){b.preventDefault(),d.addClass("hidden"),c(0),a("#wp_welcome_panel-hide").prop("checked",!1)}),e.click(function(){d.toggleClass("hidden",!this.checked),c(this.checked?1:0)}),window.ajaxWidgets=["dashboard_primary"],window.ajaxPopulateWidgets=function(b){function c(b,c){var d,e=a("#"+c+" div.inside:visible").find(".widget-loading");e.length&&(d=e.parent(),setTimeout(function(){d.load(ajaxurl+"?action=dashboard-widgets&widget="+c+"&pagenow="+pagenow,"",function(){d.hide().slideDown("normal",function(){a(this).css("display","")})})},500*b))}b?(b=b.toString(),a.inArray(b,ajaxWidgets)!==-1&&c(0,b)):a.each(ajaxWidgets,c)},ajaxPopulateWidgets(),postboxes.add_postbox_toggles(pagenow,{pbshow:ajaxPopulateWidgets}),window.quickPressLoad=function(){var c,d=a("#quickpost-action");a('#quick-press .submit input[type="submit"], #quick-press .submit input[type="reset"]').prop("disabled",!1),c=a("#quick-press").submit(function(b){function d(){var b=a(".drafts ul li").first();b.css("background","#fffbe5"),setTimeout(function(){b.css("background","none")},1e3)}b.preventDefault(),a("#dashboard_quick_press #publishing-action .spinner").show(),a('#quick-press .submit input[type="submit"], #quick-press .submit input[type="reset"]').prop("disabled",!0),a.post(c.attr("action"),c.serializeArray(),function(b){a("#dashboard_quick_press .inside").html(b),a("#quick-press").removeClass("initial-form"),quickPressLoad(),d(),a("#title").focus()})}),a("#publish").click(function(){d.val("post-quickpress-publish")}),a("#quick-press").on("click focusin",function(){wpActiveEditor="content"}),b()},window.quickPressLoad(),a(".meta-box-sortables").sortable("option","containment","#wpwrap")}),jQuery(function(a){"use strict";var b,c=window.communityEventsData||{};b=window.wp.communityEvents={initialized:!1,model:null,init:function(){if(!b.initialized){var d=a("#community-events");a(".community-events-errors").attr("aria-hidden","true").removeClass("hide-if-js"),d.on("click",".community-events-toggle-location, .community-events-cancel",b.toggleLocationForm),d.on("submit",".community-events-form",function(c){var d=a.trim(a("#community-events-location").val());c.preventDefault(),d&&b.getEvents({location:d})}),c&&c.cache&&c.cache.location&&c.cache.events?b.renderEventsTemplate(c.cache,"app"):b.getEvents(),b.initialized=!0}},toggleLocationForm:function(b){var c=a(".community-events-toggle-location"),d=a(".community-events-cancel"),e=a(".community-events-form"),f=a();"object"==typeof b&&(f=a(b.target),b="true"==c.attr("aria-expanded")?"hide":"show"),"hide"===b?(c.attr("aria-expanded","false"),d.attr("aria-expanded","false"),e.attr("aria-hidden","true"),f.hasClass("community-events-cancel")&&c.focus()):(c.attr("aria-expanded","true"),d.attr("aria-expanded","true"),e.attr("aria-hidden","false"))},getEvents:function(b){var d,e=this,f=a(".community-events-form").children(".spinner");b=b||{},b._wpnonce=c.nonce,b.timezone=window.Intl?window.Intl.DateTimeFormat().resolvedOptions().timeZone:"",d=b.location?"user":"app",f.addClass("is-active"),wp.ajax.post("get-community-events",b).always(function(){f.removeClass("is-active")}).done(function(a){"no_location_available"===a.error&&(b.location?a.unknownCity=b.location:delete a.error),e.renderEventsTemplate(a,d)}).fail(function(){e.renderEventsTemplate({location:!1,error:!0},d)})},renderEventsTemplate:function(d,e){var f,g,h=/%(?:\d\$)?s/g,i=a(".community-events-toggle-location"),j=a("#community-events-location-message"),k=a(".community-events-results");g={".community-events":!0,".community-events-loading":!1,".community-events-errors":!1,".community-events-error-occurred":!1,".community-events-could-not-locate":!1,"#community-events-location-message":!1,".community-events-toggle-location":!1,".community-events-results":!1},d.location.ip?(j.text(c.l10n.attend_event_near_generic),d.events.length?(f=wp.template("community-events-event-list"),k.html(f(d))):(f=wp.template("community-events-no-upcoming-events"),k.html(f(d))),g["#community-events-location-message"]=!0,g[".community-events-toggle-location"]=!0,g[".community-events-results"]=!0):d.location.description?(f=wp.template("community-events-attend-event-near"),j.html(f(d)),d.events.length?(f=wp.template("community-events-event-list"),k.html(f(d))):(f=wp.template("community-events-no-upcoming-events"),k.html(f(d))),"user"===e&&wp.a11y.speak(c.l10n.city_updated.replace(h,d.location.description),"assertive"),g["#community-events-location-message"]=!0,g[".community-events-toggle-location"]=!0,g[".community-events-results"]=!0):d.unknownCity?(f=wp.template("community-events-could-not-locate"),a(".community-events-could-not-locate").html(f(d)),wp.a11y.speak(c.l10n.could_not_locate_city.replace(h,d.unknownCity)),g[".community-events-errors"]=!0,g[".community-events-could-not-locate"]=!0):d.error&&"user"===e?(wp.a11y.speak(c.l10n.error_occurred_please_try_again),g[".community-events-errors"]=!0,g[".community-events-error-occurred"]=!0):(j.text(c.l10n.enter_closest_city),g["#community-events-location-message"]=!0,g[".community-events-toggle-location"]=!0),_.each(g,function(b,c){a(c).attr("aria-hidden",!b)}),i.attr("aria-expanded",g[".community-events-toggle-location"]),d.location&&(d.location.ip||d.location.latitude)?(b.toggleLocationForm("hide"),"user"===e&&i.focus()):b.toggleLocationForm("show")}},a("#dashboard_primary").is(":visible")?b.init():a(document).on("postbox-toggled",function(c,d){var e=a(d);"dashboard_primary"===e.attr("id")&&e.is(":visible")&&b.init()})}); \ No newline at end of file
diff --git a/www/crm/wp-admin/js/edit-comments.js b/www/crm/wp-admin/js/edit-comments.js
new file mode 100644
index 00000000..62fd7fa7
--- /dev/null
+++ b/www/crm/wp-admin/js/edit-comments.js
@@ -0,0 +1,1016 @@
+/**
+ * @output wp-admin/js/edit-comments.js
+ */
+
+/* global adminCommentsL10n, thousandsSeparator, list_args, QTags, ajaxurl, wpAjax */
+/* global commentReply, theExtraList, theList, setCommentsList */
+
+(function($) {
+var getCount, updateCount, updateCountText, updatePending, updateApproved,
+ updateHtmlTitle, updateDashboardText, updateInModerationText, adminTitle = document.title,
+ isDashboard = $('#dashboard_right_now').length,
+ titleDiv, titleRegEx;
+
+ getCount = function(el) {
+ var n = parseInt( el.html().replace(/[^0-9]+/g, ''), 10 );
+ if ( isNaN(n) ) {
+ return 0;
+ }
+ return n;
+ };
+
+ updateCount = function(el, n) {
+ var n1 = '';
+ if ( isNaN(n) ) {
+ return;
+ }
+ n = n < 1 ? '0' : n.toString();
+ if ( n.length > 3 ) {
+ while ( n.length > 3 ) {
+ n1 = thousandsSeparator + n.substr(n.length - 3) + n1;
+ n = n.substr(0, n.length - 3);
+ }
+ n = n + n1;
+ }
+ el.html(n);
+ };
+
+ updateApproved = function( diff, commentPostId ) {
+ var postSelector = '.post-com-count-' + commentPostId,
+ noClass = 'comment-count-no-comments',
+ approvedClass = 'comment-count-approved',
+ approved,
+ noComments;
+
+ updateCountText( 'span.approved-count', diff );
+
+ if ( ! commentPostId ) {
+ return;
+ }
+
+ // cache selectors to not get dupes
+ approved = $( 'span.' + approvedClass, postSelector );
+ noComments = $( 'span.' + noClass, postSelector );
+
+ approved.each(function() {
+ var a = $(this), n = getCount(a) + diff;
+ if ( n < 1 )
+ n = 0;
+
+ if ( 0 === n ) {
+ a.removeClass( approvedClass ).addClass( noClass );
+ } else {
+ a.addClass( approvedClass ).removeClass( noClass );
+ }
+ updateCount( a, n );
+ });
+
+ noComments.each(function() {
+ var a = $(this);
+ if ( diff > 0 ) {
+ a.removeClass( noClass ).addClass( approvedClass );
+ } else {
+ a.addClass( noClass ).removeClass( approvedClass );
+ }
+ updateCount( a, diff );
+ });
+ };
+
+ updateCountText = function( selector, diff ) {
+ $( selector ).each(function() {
+ var a = $(this), n = getCount(a) + diff;
+ if ( n < 1 ) {
+ n = 0;
+ }
+ updateCount( a, n );
+ });
+ };
+
+ updateDashboardText = function( response ) {
+ if ( ! isDashboard || ! response || ! response.i18n_comments_text ) {
+ return;
+ }
+
+ $( '.comment-count a', '#dashboard_right_now' ).text( response.i18n_comments_text );
+ };
+
+ /**
+ * Updates the "comments in moderation" text across the UI.
+ *
+ * @since 5.2.0
+ *
+ * @param {object} response Ajax response from the server.
+ *
+ * @return {void}
+ */
+ updateInModerationText = function( response ) {
+ if ( ! response || ! response.i18n_moderation_text ) {
+ return;
+ }
+
+ // Update the "comment in moderation" text across the UI.
+ $( '.comments-in-moderation-text' ).text( response.i18n_moderation_text );
+ // Hide the "comment in moderation" text in the Dashboard "At a Glance" widget.
+ if ( isDashboard && response.in_moderation ) {
+ $( '.comment-mod-count', '#dashboard_right_now' )
+ [ response.in_moderation > 0 ? 'removeClass' : 'addClass' ]( 'hidden' );
+ }
+ };
+
+ updateHtmlTitle = function( diff ) {
+ var newTitle, regExMatch, titleCount, commentFrag;
+
+ titleRegEx = titleRegEx || new RegExp( adminCommentsL10n.docTitleCommentsCount.replace( '%s', '\\([0-9' + thousandsSeparator + ']+\\)' ) + '?' );
+ // count funcs operate on a $'d element
+ titleDiv = titleDiv || $( '<div />' );
+ newTitle = adminTitle;
+
+ commentFrag = titleRegEx.exec( document.title );
+ if ( commentFrag ) {
+ commentFrag = commentFrag[0];
+ titleDiv.html( commentFrag );
+ titleCount = getCount( titleDiv ) + diff;
+ } else {
+ titleDiv.html( 0 );
+ titleCount = diff;
+ }
+
+ if ( titleCount >= 1 ) {
+ updateCount( titleDiv, titleCount );
+ regExMatch = titleRegEx.exec( document.title );
+ if ( regExMatch ) {
+ newTitle = document.title.replace( regExMatch[0], adminCommentsL10n.docTitleCommentsCount.replace( '%s', titleDiv.text() ) + ' ' );
+ }
+ } else {
+ regExMatch = titleRegEx.exec( newTitle );
+ if ( regExMatch ) {
+ newTitle = newTitle.replace( regExMatch[0], adminCommentsL10n.docTitleComments );
+ }
+ }
+ document.title = newTitle;
+ };
+
+ updatePending = function( diff, commentPostId ) {
+ var postSelector = '.post-com-count-' + commentPostId,
+ noClass = 'comment-count-no-pending',
+ noParentClass = 'post-com-count-no-pending',
+ pendingClass = 'comment-count-pending',
+ pending,
+ noPending;
+
+ if ( ! isDashboard ) {
+ updateHtmlTitle( diff );
+ }
+
+ $( 'span.pending-count' ).each(function() {
+ var a = $(this), n = getCount(a) + diff;
+ if ( n < 1 )
+ n = 0;
+ a.closest('.awaiting-mod')[ 0 === n ? 'addClass' : 'removeClass' ]('count-0');
+ updateCount( a, n );
+ });
+
+ if ( ! commentPostId ) {
+ return;
+ }
+
+ // cache selectors to not get dupes
+ pending = $( 'span.' + pendingClass, postSelector );
+ noPending = $( 'span.' + noClass, postSelector );
+
+ pending.each(function() {
+ var a = $(this), n = getCount(a) + diff;
+ if ( n < 1 )
+ n = 0;
+
+ if ( 0 === n ) {
+ a.parent().addClass( noParentClass );
+ a.removeClass( pendingClass ).addClass( noClass );
+ } else {
+ a.parent().removeClass( noParentClass );
+ a.addClass( pendingClass ).removeClass( noClass );
+ }
+ updateCount( a, n );
+ });
+
+ noPending.each(function() {
+ var a = $(this);
+ if ( diff > 0 ) {
+ a.parent().removeClass( noParentClass );
+ a.removeClass( noClass ).addClass( pendingClass );
+ } else {
+ a.parent().addClass( noParentClass );
+ a.addClass( noClass ).removeClass( pendingClass );
+ }
+ updateCount( a, diff );
+ });
+ };
+
+window.setCommentsList = function() {
+ var totalInput, perPageInput, pageInput, dimAfter, delBefore, updateTotalCount, delAfter, refillTheExtraList, diff,
+ lastConfidentTime = 0;
+
+ totalInput = $('input[name="_total"]', '#comments-form');
+ perPageInput = $('input[name="_per_page"]', '#comments-form');
+ pageInput = $('input[name="_page"]', '#comments-form');
+
+ // Updates the current total (stored in the _total input)
+ updateTotalCount = function( total, time, setConfidentTime ) {
+ if ( time < lastConfidentTime )
+ return;
+
+ if ( setConfidentTime )
+ lastConfidentTime = time;
+
+ totalInput.val( total.toString() );
+ };
+
+ // this fires when viewing "All"
+ dimAfter = function( r, settings ) {
+ var editRow, replyID, replyButton, response,
+ c = $( '#' + settings.element );
+
+ if ( true !== settings.parsed ) {
+ response = settings.parsed.responses[0];
+ }
+
+ editRow = $('#replyrow');
+ replyID = $('#comment_ID', editRow).val();
+ replyButton = $('#replybtn', editRow);
+
+ if ( c.is('.unapproved') ) {
+ if ( settings.data.id == replyID )
+ replyButton.text(adminCommentsL10n.replyApprove);
+
+ c.find( '.row-actions span.view' ).addClass( 'hidden' ).end()
+ .find( 'div.comment_status' ).html( '0' );
+
+ } else {
+ if ( settings.data.id == replyID )
+ replyButton.text(adminCommentsL10n.reply);
+
+ c.find( '.row-actions span.view' ).removeClass( 'hidden' ).end()
+ .find( 'div.comment_status' ).html( '1' );
+ }
+
+ diff = $('#' + settings.element).is('.' + settings.dimClass) ? 1 : -1;
+ if ( response ) {
+ updateDashboardText( response.supplemental );
+ updateInModerationText( response.supplemental );
+ updatePending( diff, response.supplemental.postId );
+ updateApproved( -1 * diff, response.supplemental.postId );
+ } else {
+ updatePending( diff );
+ updateApproved( -1 * diff );
+ }
+ };
+
+ // Send current total, page, per_page and url
+ delBefore = function( settings, list ) {
+ var note, id, el, n, h, a, author,
+ action = false,
+ wpListsData = $( settings.target ).attr( 'data-wp-lists' );
+
+ settings.data._total = totalInput.val() || 0;
+ settings.data._per_page = perPageInput.val() || 0;
+ settings.data._page = pageInput.val() || 0;
+ settings.data._url = document.location.href;
+ settings.data.comment_status = $('input[name="comment_status"]', '#comments-form').val();
+
+ if ( wpListsData.indexOf(':trash=1') != -1 )
+ action = 'trash';
+ else if ( wpListsData.indexOf(':spam=1') != -1 )
+ action = 'spam';
+
+ if ( action ) {
+ id = wpListsData.replace(/.*?comment-([0-9]+).*/, '$1');
+ el = $('#comment-' + id);
+ note = $('#' + action + '-undo-holder').html();
+
+ el.find('.check-column :checkbox').prop('checked', false); // Uncheck the row so as not to be affected by Bulk Edits.
+
+ if ( el.siblings('#replyrow').length && commentReply.cid == id )
+ commentReply.close();
+
+ if ( el.is('tr') ) {
+ n = el.children(':visible').length;
+ author = $('.author strong', el).text();
+ h = $('<tr id="undo-' + id + '" class="undo un' + action + '" style="display:none;"><td colspan="' + n + '">' + note + '</td></tr>');
+ } else {
+ author = $('.comment-author', el).text();
+ h = $('<div id="undo-' + id + '" style="display:none;" class="undo un' + action + '">' + note + '</div>');
+ }
+
+ el.before(h);
+
+ $('strong', '#undo-' + id).text(author);
+ a = $('.undo a', '#undo-' + id);
+ a.attr('href', 'comment.php?action=un' + action + 'comment&c=' + id + '&_wpnonce=' + settings.data._ajax_nonce);
+ a.attr('data-wp-lists', 'delete:the-comment-list:comment-' + id + '::un' + action + '=1');
+ a.attr('class', 'vim-z vim-destructive aria-button-if-js');
+ $('.avatar', el).first().clone().prependTo('#undo-' + id + ' .' + action + '-undo-inside');
+
+ a.click(function( e ){
+ e.preventDefault();
+ e.stopPropagation(); // ticket #35904
+ list.wpList.del(this);
+ $('#undo-' + id).css( {backgroundColor:'#ceb'} ).fadeOut(350, function(){
+ $(this).remove();
+ $('#comment-' + id).css('backgroundColor', '').fadeIn(300, function(){ $(this).show(); });
+ });
+ });
+ }
+
+ return settings;
+ };
+
+ // In admin-ajax.php, we send back the unix time stamp instead of 1 on success
+ delAfter = function( r, settings ) {
+ var total_items_i18n, total, animated, animatedCallback,
+ response = true === settings.parsed ? {} : settings.parsed.responses[0],
+ commentStatus = true === settings.parsed ? '' : response.supplemental.status,
+ commentPostId = true === settings.parsed ? '' : response.supplemental.postId,
+ newTotal = true === settings.parsed ? '' : response.supplemental,
+
+ targetParent = $( settings.target ).parent(),
+ commentRow = $('#' + settings.element),
+
+ spamDiff, trashDiff, pendingDiff, approvedDiff,
+
+ /*
+ * As `wpList` toggles only the `unapproved` class, the approved comment
+ * rows can have both the `approved` and `unapproved` classes.
+ */
+ approved = commentRow.hasClass( 'approved' ) && ! commentRow.hasClass( 'unapproved' ),
+ unapproved = commentRow.hasClass( 'unapproved' ),
+ spammed = commentRow.hasClass( 'spam' ),
+ trashed = commentRow.hasClass( 'trash' ),
+ undoing = false; // ticket #35904
+
+ updateDashboardText( newTotal );
+ updateInModerationText( newTotal );
+
+ // the order of these checks is important
+ // .unspam can also have .approve or .unapprove
+ // .untrash can also have .approve or .unapprove
+
+ if ( targetParent.is( 'span.undo' ) ) {
+ // the comment was spammed
+ if ( targetParent.hasClass( 'unspam' ) ) {
+ spamDiff = -1;
+
+ if ( 'trash' === commentStatus ) {
+ trashDiff = 1;
+ } else if ( '1' === commentStatus ) {
+ approvedDiff = 1;
+ } else if ( '0' === commentStatus ) {
+ pendingDiff = 1;
+ }
+
+ // the comment was trashed
+ } else if ( targetParent.hasClass( 'untrash' ) ) {
+ trashDiff = -1;
+
+ if ( 'spam' === commentStatus ) {
+ spamDiff = 1;
+ } else if ( '1' === commentStatus ) {
+ approvedDiff = 1;
+ } else if ( '0' === commentStatus ) {
+ pendingDiff = 1;
+ }
+ }
+
+ undoing = true;
+
+ // user clicked "Spam"
+ } else if ( targetParent.is( 'span.spam' ) ) {
+ // the comment is currently approved
+ if ( approved ) {
+ approvedDiff = -1;
+ // the comment is currently pending
+ } else if ( unapproved ) {
+ pendingDiff = -1;
+ // the comment was in the trash
+ } else if ( trashed ) {
+ trashDiff = -1;
+ }
+ // you can't spam an item on the spam screen
+ spamDiff = 1;
+
+ // user clicked "Unspam"
+ } else if ( targetParent.is( 'span.unspam' ) ) {
+ if ( approved ) {
+ pendingDiff = 1;
+ } else if ( unapproved ) {
+ approvedDiff = 1;
+ } else if ( trashed ) {
+ // the comment was previously approved
+ if ( targetParent.hasClass( 'approve' ) ) {
+ approvedDiff = 1;
+ // the comment was previously pending
+ } else if ( targetParent.hasClass( 'unapprove' ) ) {
+ pendingDiff = 1;
+ }
+ } else if ( spammed ) {
+ if ( targetParent.hasClass( 'approve' ) ) {
+ approvedDiff = 1;
+
+ } else if ( targetParent.hasClass( 'unapprove' ) ) {
+ pendingDiff = 1;
+ }
+ }
+ // you can Unspam an item on the spam screen
+ spamDiff = -1;
+
+ // user clicked "Trash"
+ } else if ( targetParent.is( 'span.trash' ) ) {
+ if ( approved ) {
+ approvedDiff = -1;
+ } else if ( unapproved ) {
+ pendingDiff = -1;
+ // the comment was in the spam queue
+ } else if ( spammed ) {
+ spamDiff = -1;
+ }
+ // you can't trash an item on the trash screen
+ trashDiff = 1;
+
+ // user clicked "Restore"
+ } else if ( targetParent.is( 'span.untrash' ) ) {
+ if ( approved ) {
+ pendingDiff = 1;
+ } else if ( unapproved ) {
+ approvedDiff = 1;
+ } else if ( trashed ) {
+ if ( targetParent.hasClass( 'approve' ) ) {
+ approvedDiff = 1;
+ } else if ( targetParent.hasClass( 'unapprove' ) ) {
+ pendingDiff = 1;
+ }
+ }
+ // you can't go from trash to spam
+ // you can untrash on the trash screen
+ trashDiff = -1;
+
+ // User clicked "Approve"
+ } else if ( targetParent.is( 'span.approve:not(.unspam):not(.untrash)' ) ) {
+ approvedDiff = 1;
+ pendingDiff = -1;
+
+ // User clicked "Unapprove"
+ } else if ( targetParent.is( 'span.unapprove:not(.unspam):not(.untrash)' ) ) {
+ approvedDiff = -1;
+ pendingDiff = 1;
+
+ // User clicked "Delete Permanently"
+ } else if ( targetParent.is( 'span.delete' ) ) {
+ if ( spammed ) {
+ spamDiff = -1;
+ } else if ( trashed ) {
+ trashDiff = -1;
+ }
+ }
+
+ if ( pendingDiff ) {
+ updatePending( pendingDiff, commentPostId );
+ updateCountText( 'span.all-count', pendingDiff );
+ }
+
+ if ( approvedDiff ) {
+ updateApproved( approvedDiff, commentPostId );
+ updateCountText( 'span.all-count', approvedDiff );
+ }
+
+ if ( spamDiff ) {
+ updateCountText( 'span.spam-count', spamDiff );
+ }
+
+ if ( trashDiff ) {
+ updateCountText( 'span.trash-count', trashDiff );
+ }
+
+ if (
+ ( ( 'trash' === settings.data.comment_status ) && !getCount( $( 'span.trash-count' ) ) ) ||
+ ( ( 'spam' === settings.data.comment_status ) && !getCount( $( 'span.spam-count' ) ) )
+ ) {
+ $( '#delete_all' ).hide();
+ }
+
+ if ( ! isDashboard ) {
+ total = totalInput.val() ? parseInt( totalInput.val(), 10 ) : 0;
+ if ( $(settings.target).parent().is('span.undo') )
+ total++;
+ else
+ total--;
+
+ if ( total < 0 )
+ total = 0;
+
+ if ( 'object' === typeof r ) {
+ if ( response.supplemental.total_items_i18n && lastConfidentTime < response.supplemental.time ) {
+ total_items_i18n = response.supplemental.total_items_i18n || '';
+ if ( total_items_i18n ) {
+ $('.displaying-num').text( total_items_i18n );
+ $('.total-pages').text( response.supplemental.total_pages_i18n );
+ $('.tablenav-pages').find('.next-page, .last-page').toggleClass('disabled', response.supplemental.total_pages == $('.current-page').val());
+ }
+ updateTotalCount( total, response.supplemental.time, true );
+ } else if ( response.supplemental.time ) {
+ updateTotalCount( total, response.supplemental.time, false );
+ }
+ } else {
+ updateTotalCount( total, r, false );
+ }
+ }
+
+ if ( ! theExtraList || theExtraList.length === 0 || theExtraList.children().length === 0 || undoing ) {
+ return;
+ }
+
+ theList.get(0).wpList.add( theExtraList.children( ':eq(0):not(.no-items)' ).remove().clone() );
+
+ refillTheExtraList();
+
+ animated = $( ':animated', '#the-comment-list' );
+ animatedCallback = function() {
+ if ( ! $( '#the-comment-list tr:visible' ).length ) {
+ theList.get(0).wpList.add( theExtraList.find( '.no-items' ).clone() );
+ }
+ };
+
+ if ( animated.length ) {
+ animated.promise().done( animatedCallback );
+ } else {
+ animatedCallback();
+ }
+ };
+
+ refillTheExtraList = function(ev) {
+ var args = $.query.get(), total_pages = $('.total-pages').text(), per_page = $('input[name="_per_page"]', '#comments-form').val();
+
+ if (! args.paged)
+ args.paged = 1;
+
+ if (args.paged > total_pages) {
+ return;
+ }
+
+ if (ev) {
+ theExtraList.empty();
+ args.number = Math.min(8, per_page); // see WP_Comments_List_Table::prepare_items() @ class-wp-comments-list-table.php
+ } else {
+ args.number = 1;
+ args.offset = Math.min(8, per_page) - 1; // fetch only the next item on the extra list
+ }
+
+ args.no_placeholder = true;
+
+ args.paged ++;
+
+ // $.query.get() needs some correction to be sent into an ajax request
+ if ( true === args.comment_type )
+ args.comment_type = '';
+
+ args = $.extend(args, {
+ 'action': 'fetch-list',
+ 'list_args': list_args,
+ '_ajax_fetch_list_nonce': $('#_ajax_fetch_list_nonce').val()
+ });
+
+ $.ajax({
+ url: ajaxurl,
+ global: false,
+ dataType: 'json',
+ data: args,
+ success: function(response) {
+ theExtraList.get(0).wpList.add( response.rows );
+ }
+ });
+ };
+
+ window.theExtraList = $('#the-extra-comment-list').wpList( { alt: '', delColor: 'none', addColor: 'none' } );
+ window.theList = $('#the-comment-list').wpList( { alt: '', delBefore: delBefore, dimAfter: dimAfter, delAfter: delAfter, addColor: 'none' } )
+ .bind('wpListDelEnd', function(e, s){
+ var wpListsData = $(s.target).attr('data-wp-lists'), id = s.element.replace(/[^0-9]+/g, '');
+
+ if ( wpListsData.indexOf(':trash=1') != -1 || wpListsData.indexOf(':spam=1') != -1 )
+ $('#undo-' + id).fadeIn(300, function(){ $(this).show(); });
+ });
+};
+
+window.commentReply = {
+ cid : '',
+ act : '',
+ originalContent : '',
+
+ init : function() {
+ var row = $('#replyrow');
+
+ $( '.cancel', row ).click( function() { return commentReply.revert(); } );
+ $( '.save', row ).click( function() { return commentReply.send(); } );
+ $( 'input#author-name, input#author-email, input#author-url', row ).keypress( function( e ) {
+ if ( e.which == 13 ) {
+ commentReply.send();
+ e.preventDefault();
+ return false;
+ }
+ });
+
+ // add events
+ $('#the-comment-list .column-comment > p').dblclick(function(){
+ commentReply.toggle($(this).parent());
+ });
+
+ $('#doaction, #doaction2, #post-query-submit').click(function(){
+ if ( $('#the-comment-list #replyrow').length > 0 )
+ commentReply.close();
+ });
+
+ this.comments_listing = $('#comments-form > input[name="comment_status"]').val() || '';
+ },
+
+ addEvents : function(r) {
+ r.each(function() {
+ $(this).find('.column-comment > p').dblclick(function(){
+ commentReply.toggle($(this).parent());
+ });
+ });
+ },
+
+ toggle : function(el) {
+ if ( 'none' !== $( el ).css( 'display' ) && ( $( '#replyrow' ).parent().is('#com-reply') || window.confirm( adminCommentsL10n.warnQuickEdit ) ) ) {
+ $( el ).find( 'a.vim-q' ).click();
+ }
+ },
+
+ revert : function() {
+
+ if ( $('#the-comment-list #replyrow').length < 1 )
+ return false;
+
+ $('#replyrow').fadeOut('fast', function(){
+ commentReply.close();
+ });
+ },
+
+ close : function() {
+ var commentRow = $(),
+ replyRow = $( '#replyrow' );
+
+ // replyrow is not showing?
+ if ( replyRow.parent().is( '#com-reply' ) ) {
+ return;
+ }
+
+ if ( this.cid ) {
+ commentRow = $( '#comment-' + this.cid );
+ }
+
+ /*
+ * When closing the Quick Edit form, show the comment row and move focus
+ * back to the Quick Edit button.
+ */
+ if ( 'edit-comment' === this.act ) {
+ commentRow.fadeIn( 300, function() {
+ commentRow
+ .show()
+ .find( '.vim-q' )
+ .attr( 'aria-expanded', 'false' )
+ .focus();
+ } ).css( 'backgroundColor', '' );
+ }
+
+ // When closing the Reply form, move focus back to the Reply button.
+ if ( 'replyto-comment' === this.act ) {
+ commentRow.find( '.vim-r' )
+ .attr( 'aria-expanded', 'false' )
+ .focus();
+ }
+
+ // reset the Quicktags buttons
+ if ( typeof QTags != 'undefined' )
+ QTags.closeAllTags('replycontent');
+
+ $('#add-new-comment').css('display', '');
+
+ replyRow.hide();
+ $( '#com-reply' ).append( replyRow );
+ $('#replycontent').css('height', '').val('');
+ $('#edithead input').val('');
+ $( '.notice-error', replyRow )
+ .addClass( 'hidden' )
+ .find( '.error' ).empty();
+ $( '.spinner', replyRow ).removeClass( 'is-active' );
+
+ this.cid = '';
+ this.originalContent = '';
+ },
+
+ open : function(comment_id, post_id, action) {
+ var editRow, rowData, act, replyButton, editHeight,
+ t = this,
+ c = $('#comment-' + comment_id),
+ h = c.height(),
+ colspanVal = 0;
+
+ if ( ! this.discardCommentChanges() ) {
+ return false;
+ }
+
+ t.close();
+ t.cid = comment_id;
+
+ editRow = $('#replyrow');
+ rowData = $('#inline-'+comment_id);
+ action = action || 'replyto';
+ act = 'edit' == action ? 'edit' : 'replyto';
+ act = t.act = act + '-comment';
+ t.originalContent = $('textarea.comment', rowData).val();
+ colspanVal = $( '> th:visible, > td:visible', c ).length;
+
+ // Make sure it's actually a table and there's a `colspan` value to apply.
+ if ( editRow.hasClass( 'inline-edit-row' ) && 0 !== colspanVal ) {
+ $( 'td', editRow ).attr( 'colspan', colspanVal );
+ }
+
+ $('#action', editRow).val(act);
+ $('#comment_post_ID', editRow).val(post_id);
+ $('#comment_ID', editRow).val(comment_id);
+
+ if ( action == 'edit' ) {
+ $( '#author-name', editRow ).val( $( 'div.author', rowData ).text() );
+ $('#author-email', editRow).val( $('div.author-email', rowData).text() );
+ $('#author-url', editRow).val( $('div.author-url', rowData).text() );
+ $('#status', editRow).val( $('div.comment_status', rowData).text() );
+ $('#replycontent', editRow).val( $('textarea.comment', rowData).val() );
+ $( '#edithead, #editlegend, #savebtn', editRow ).show();
+ $('#replyhead, #replybtn, #addhead, #addbtn', editRow).hide();
+
+ if ( h > 120 ) {
+ // Limit the maximum height when editing very long comments to make it more manageable.
+ // The textarea is resizable in most browsers, so the user can adjust it if needed.
+ editHeight = h > 500 ? 500 : h;
+ $('#replycontent', editRow).css('height', editHeight + 'px');
+ }
+
+ c.after( editRow ).fadeOut('fast', function(){
+ $('#replyrow').fadeIn(300, function(){ $(this).show(); });
+ });
+ } else if ( action == 'add' ) {
+ $('#addhead, #addbtn', editRow).show();
+ $( '#replyhead, #replybtn, #edithead, #editlegend, #savebtn', editRow ) .hide();
+ $('#the-comment-list').prepend(editRow);
+ $('#replyrow').fadeIn(300);
+ } else {
+ replyButton = $('#replybtn', editRow);
+ $( '#edithead, #editlegend, #savebtn, #addhead, #addbtn', editRow ).hide();
+ $('#replyhead, #replybtn', editRow).show();
+ c.after(editRow);
+
+ if ( c.hasClass('unapproved') ) {
+ replyButton.text(adminCommentsL10n.replyApprove);
+ } else {
+ replyButton.text(adminCommentsL10n.reply);
+ }
+
+ $('#replyrow').fadeIn(300, function(){ $(this).show(); });
+ }
+
+ setTimeout(function() {
+ var rtop, rbottom, scrollTop, vp, scrollBottom;
+
+ rtop = $('#replyrow').offset().top;
+ rbottom = rtop + $('#replyrow').height();
+ scrollTop = window.pageYOffset || document.documentElement.scrollTop;
+ vp = document.documentElement.clientHeight || window.innerHeight || 0;
+ scrollBottom = scrollTop + vp;
+
+ if ( scrollBottom - 20 < rbottom )
+ window.scroll(0, rbottom - vp + 35);
+ else if ( rtop - 20 < scrollTop )
+ window.scroll(0, rtop - 35);
+
+ $('#replycontent').focus().keyup(function(e){
+ if ( e.which == 27 )
+ commentReply.revert(); // close on Escape
+ });
+ }, 600);
+
+ return false;
+ },
+
+ send : function() {
+ var post = {},
+ $errorNotice = $( '#replysubmit .error-notice' );
+
+ $errorNotice.addClass( 'hidden' );
+ $( '#replysubmit .spinner' ).addClass( 'is-active' );
+
+ $('#replyrow input').not(':button').each(function() {
+ var t = $(this);
+ post[ t.attr('name') ] = t.val();
+ });
+
+ post.content = $('#replycontent').val();
+ post.id = post.comment_post_ID;
+ post.comments_listing = this.comments_listing;
+ post.p = $('[name="p"]').val();
+
+ if ( $('#comment-' + $('#comment_ID').val()).hasClass('unapproved') )
+ post.approve_parent = 1;
+
+ $.ajax({
+ type : 'POST',
+ url : ajaxurl,
+ data : post,
+ success : function(x) { commentReply.show(x); },
+ error : function(r) { commentReply.error(r); }
+ });
+ },
+
+ show : function(xml) {
+ var t = this, r, c, id, bg, pid;
+
+ if ( typeof(xml) == 'string' ) {
+ t.error({'responseText': xml});
+ return false;
+ }
+
+ r = wpAjax.parseAjaxResponse(xml);
+ if ( r.errors ) {
+ t.error({'responseText': wpAjax.broken});
+ return false;
+ }
+
+ t.revert();
+
+ r = r.responses[0];
+ id = '#comment-' + r.id;
+
+ if ( 'edit-comment' == t.act )
+ $(id).remove();
+
+ if ( r.supplemental.parent_approved ) {
+ pid = $('#comment-' + r.supplemental.parent_approved);
+ updatePending( -1, r.supplemental.parent_post_id );
+
+ if ( this.comments_listing == 'moderated' ) {
+ pid.animate( { 'backgroundColor':'#CCEEBB' }, 400, function(){
+ pid.fadeOut();
+ });
+ return;
+ }
+ }
+
+ if ( r.supplemental.i18n_comments_text ) {
+ updateDashboardText( r.supplemental );
+ updateInModerationText( r.supplemental );
+ updateApproved( 1, r.supplemental.parent_post_id );
+ updateCountText( 'span.all-count', 1 );
+ }
+
+ c = $.trim(r.data); // Trim leading whitespaces
+ $(c).hide();
+ $('#replyrow').after(c);
+
+ id = $(id);
+ t.addEvents(id);
+ bg = id.hasClass('unapproved') ? '#FFFFE0' : id.closest('.widefat, .postbox').css('backgroundColor');
+
+ id.animate( { 'backgroundColor':'#CCEEBB' }, 300 )
+ .animate( { 'backgroundColor': bg }, 300, function() {
+ if ( pid && pid.length ) {
+ pid.animate( { 'backgroundColor':'#CCEEBB' }, 300 )
+ .animate( { 'backgroundColor': bg }, 300 )
+ .removeClass('unapproved').addClass('approved')
+ .find('div.comment_status').html('1');
+ }
+ });
+
+ },
+
+ error : function(r) {
+ var er = r.statusText,
+ $errorNotice = $( '#replysubmit .notice-error' ),
+ $error = $errorNotice.find( '.error' );
+
+ $( '#replysubmit .spinner' ).removeClass( 'is-active' );
+
+ if ( r.responseText )
+ er = r.responseText.replace( /<.[^<>]*?>/g, '' );
+
+ if ( er ) {
+ $errorNotice.removeClass( 'hidden' );
+ $error.html( er );
+ }
+ },
+
+ addcomment: function(post_id) {
+ var t = this;
+
+ $('#add-new-comment').fadeOut(200, function(){
+ t.open(0, post_id, 'add');
+ $('table.comments-box').css('display', '');
+ $('#no-comments').remove();
+ });
+ },
+
+ /**
+ * Alert the user if they have unsaved changes on a comment that will be
+ * lost if they proceed.
+ *
+ * @returns {boolean}
+ */
+ discardCommentChanges: function() {
+ var editRow = $( '#replyrow' );
+
+ if ( this.originalContent === $( '#replycontent', editRow ).val() ) {
+ return true;
+ }
+
+ return window.confirm( adminCommentsL10n.warnCommentChanges );
+ }
+};
+
+$(document).ready(function(){
+ var make_hotkeys_redirect, edit_comment, toggle_all, make_bulk;
+
+ setCommentsList();
+ commentReply.init();
+
+ $(document).on( 'click', 'span.delete a.delete', function( e ) {
+ e.preventDefault();
+ });
+
+ if ( typeof $.table_hotkeys != 'undefined' ) {
+ make_hotkeys_redirect = function(which) {
+ return function() {
+ var first_last, l;
+
+ first_last = 'next' == which? 'first' : 'last';
+ l = $('.tablenav-pages .'+which+'-page:not(.disabled)');
+ if (l.length)
+ window.location = l[0].href.replace(/\&hotkeys_highlight_(first|last)=1/g, '')+'&hotkeys_highlight_'+first_last+'=1';
+ };
+ };
+
+ edit_comment = function(event, current_row) {
+ window.location = $('span.edit a', current_row).attr('href');
+ };
+
+ toggle_all = function() {
+ $('#cb-select-all-1').data( 'wp-toggle', 1 ).trigger( 'click' ).removeData( 'wp-toggle' );
+ };
+
+ make_bulk = function(value) {
+ return function() {
+ var scope = $('select[name="action"]');
+ $('option[value="' + value + '"]', scope).prop('selected', true);
+ $('#doaction').click();
+ };
+ };
+
+ $.table_hotkeys(
+ $('table.widefat'),
+ [
+ 'a', 'u', 's', 'd', 'r', 'q', 'z',
+ ['e', edit_comment],
+ ['shift+x', toggle_all],
+ ['shift+a', make_bulk('approve')],
+ ['shift+s', make_bulk('spam')],
+ ['shift+d', make_bulk('delete')],
+ ['shift+t', make_bulk('trash')],
+ ['shift+z', make_bulk('untrash')],
+ ['shift+u', make_bulk('unapprove')]
+ ],
+ {
+ highlight_first: adminCommentsL10n.hotkeys_highlight_first,
+ highlight_last: adminCommentsL10n.hotkeys_highlight_last,
+ prev_page_link_cb: make_hotkeys_redirect('prev'),
+ next_page_link_cb: make_hotkeys_redirect('next'),
+ hotkeys_opts: {
+ disableInInput: true,
+ type: 'keypress',
+ noDisable: '.check-column input[type="checkbox"]'
+ },
+ cycle_expr: '#the-comment-list tr',
+ start_row_index: 0
+ }
+ );
+ }
+
+ // Quick Edit and Reply have an inline comment editor.
+ $( '#the-comment-list' ).on( 'click', '.comment-inline', function() {
+ var $el = $( this ),
+ action = 'replyto';
+
+ if ( 'undefined' !== typeof $el.data( 'action' ) ) {
+ action = $el.data( 'action' );
+ }
+
+ $( this ).attr( 'aria-expanded', 'true' );
+ commentReply.open( $el.data( 'commentId' ), $el.data( 'postId' ), action );
+ } );
+});
+
+})(jQuery);
diff --git a/www/crm/wp-admin/js/edit-comments.min.js b/www/crm/wp-admin/js/edit-comments.min.js
new file mode 100644
index 00000000..b4bade3b
--- /dev/null
+++ b/www/crm/wp-admin/js/edit-comments.min.js
@@ -0,0 +1 @@
+!function(a){var b,c,d,e,f,g,h,i,j,k,l=document.title,m=a("#dashboard_right_now").length;b=function(a){var b=parseInt(a.html().replace(/[^0-9]+/g,""),10);return isNaN(b)?0:b},c=function(a,b){var c="";if(!isNaN(b)){if(b=b<1?"0":b.toString(),b.length>3){for(;b.length>3;)c=thousandsSeparator+b.substr(b.length-3)+c,b=b.substr(0,b.length-3);b+=c}a.html(b)}},f=function(e,f){var g,h,i=".post-com-count-"+f,j="comment-count-no-comments",k="comment-count-approved";d("span.approved-count",e),f&&(g=a("span."+k,i),h=a("span."+j,i),g.each(function(){var d=a(this),f=b(d)+e;f<1&&(f=0),0===f?d.removeClass(k).addClass(j):d.addClass(k).removeClass(j),c(d,f)}),h.each(function(){var b=a(this);e>0?b.removeClass(j).addClass(k):b.addClass(j).removeClass(k),c(b,e)}))},d=function(d,e){a(d).each(function(){var d=a(this),f=b(d)+e;f<1&&(f=0),c(d,f)})},h=function(b){m&&b&&b.i18n_comments_text&&a(".comment-count a","#dashboard_right_now").text(b.i18n_comments_text)},i=function(b){b&&b.i18n_moderation_text&&(a(".comments-in-moderation-text").text(b.i18n_moderation_text),m&&b.in_moderation&&a(".comment-mod-count","#dashboard_right_now")[b.in_moderation>0?"removeClass":"addClass"]("hidden"))},g=function(d){var e,f,g,h;k=k||new RegExp(adminCommentsL10n.docTitleCommentsCount.replace("%s","\\([0-9"+thousandsSeparator+"]+\\)")+"?"),j=j||a("<div />"),e=l,h=k.exec(document.title),h?(h=h[0],j.html(h),g=b(j)+d):(j.html(0),g=d),g>=1?(c(j,g),f=k.exec(document.title),f&&(e=document.title.replace(f[0],adminCommentsL10n.docTitleCommentsCount.replace("%s",j.text())+" "))):(f=k.exec(e),f&&(e=e.replace(f[0],adminCommentsL10n.docTitleComments))),document.title=e},e=function(d,e){var f,h,i=".post-com-count-"+e,j="comment-count-no-pending",k="post-com-count-no-pending",l="comment-count-pending";m||g(d),a("span.pending-count").each(function(){var e=a(this),f=b(e)+d;f<1&&(f=0),e.closest(".awaiting-mod")[0===f?"addClass":"removeClass"]("count-0"),c(e,f)}),e&&(f=a("span."+l,i),h=a("span."+j,i),f.each(function(){var e=a(this),f=b(e)+d;f<1&&(f=0),0===f?(e.parent().addClass(k),e.removeClass(l).addClass(j)):(e.parent().removeClass(k),e.addClass(l).removeClass(j)),c(e,f)}),h.each(function(){var b=a(this);d>0?(b.parent().removeClass(k),b.removeClass(j).addClass(l)):(b.parent().addClass(k),b.addClass(j).removeClass(l)),c(b,d)}))},window.setCommentsList=function(){var c,g,j,k,l,n,o,p,q,r=0;c=a('input[name="_total"]',"#comments-form"),g=a('input[name="_per_page"]',"#comments-form"),j=a('input[name="_page"]',"#comments-form"),n=function(a,b,d){b<r||(d&&(r=b),c.val(a.toString()))},k=function(b,c){var d,g,j,k,l=a("#"+c.element);!0!==c.parsed&&(k=c.parsed.responses[0]),d=a("#replyrow"),g=a("#comment_ID",d).val(),j=a("#replybtn",d),l.is(".unapproved")?(c.data.id==g&&j.text(adminCommentsL10n.replyApprove),l.find(".row-actions span.view").addClass("hidden").end().find("div.comment_status").html("0")):(c.data.id==g&&j.text(adminCommentsL10n.reply),l.find(".row-actions span.view").removeClass("hidden").end().find("div.comment_status").html("1")),q=a("#"+c.element).is("."+c.dimClass)?1:-1,k?(h(k.supplemental),i(k.supplemental),e(q,k.supplemental.postId),f(-1*q,k.supplemental.postId)):(e(q),f(-1*q))},l=function(b,d){var e,f,h,i,k,l,m,n=!1,o=a(b.target).attr("data-wp-lists");return b.data._total=c.val()||0,b.data._per_page=g.val()||0,b.data._page=j.val()||0,b.data._url=document.location.href,b.data.comment_status=a('input[name="comment_status"]',"#comments-form").val(),o.indexOf(":trash=1")!=-1?n="trash":o.indexOf(":spam=1")!=-1&&(n="spam"),n&&(f=o.replace(/.*?comment-([0-9]+).*/,"$1"),h=a("#comment-"+f),e=a("#"+n+"-undo-holder").html(),h.find(".check-column :checkbox").prop("checked",!1),h.siblings("#replyrow").length&&commentReply.cid==f&&commentReply.close(),h.is("tr")?(i=h.children(":visible").length,m=a(".author strong",h).text(),k=a('<tr id="undo-'+f+'" class="undo un'+n+'" style="display:none;"><td colspan="'+i+'">'+e+"</td></tr>")):(m=a(".comment-author",h).text(),k=a('<div id="undo-'+f+'" style="display:none;" class="undo un'+n+'">'+e+"</div>")),h.before(k),a("strong","#undo-"+f).text(m),l=a(".undo a","#undo-"+f),l.attr("href","comment.php?action=un"+n+"comment&c="+f+"&_wpnonce="+b.data._ajax_nonce),l.attr("data-wp-lists","delete:the-comment-list:comment-"+f+"::un"+n+"=1"),l.attr("class","vim-z vim-destructive aria-button-if-js"),a(".avatar",h).first().clone().prependTo("#undo-"+f+" ."+n+"-undo-inside"),l.click(function(b){b.preventDefault(),b.stopPropagation(),d.wpList.del(this),a("#undo-"+f).css({backgroundColor:"#ceb"}).fadeOut(350,function(){a(this).remove(),a("#comment-"+f).css("backgroundColor","").fadeIn(300,function(){a(this).show()})})})),b},o=function(g,j){var k,l,o,q,s,t,u,v,w=!0===j.parsed?{}:j.parsed.responses[0],x=!0===j.parsed?"":w.supplemental.status,y=!0===j.parsed?"":w.supplemental.postId,z=!0===j.parsed?"":w.supplemental,A=a(j.target).parent(),B=a("#"+j.element),C=B.hasClass("approved")&&!B.hasClass("unapproved"),D=B.hasClass("unapproved"),E=B.hasClass("spam"),F=B.hasClass("trash"),G=!1;h(z),i(z),A.is("span.undo")?(A.hasClass("unspam")?(s=-1,"trash"===x?t=1:"1"===x?v=1:"0"===x&&(u=1)):A.hasClass("untrash")&&(t=-1,"spam"===x?s=1:"1"===x?v=1:"0"===x&&(u=1)),G=!0):A.is("span.spam")?(C?v=-1:D?u=-1:F&&(t=-1),s=1):A.is("span.unspam")?(C?u=1:D?v=1:F?A.hasClass("approve")?v=1:A.hasClass("unapprove")&&(u=1):E&&(A.hasClass("approve")?v=1:A.hasClass("unapprove")&&(u=1)),s=-1):A.is("span.trash")?(C?v=-1:D?u=-1:E&&(s=-1),t=1):A.is("span.untrash")?(C?u=1:D?v=1:F&&(A.hasClass("approve")?v=1:A.hasClass("unapprove")&&(u=1)),t=-1):A.is("span.approve:not(.unspam):not(.untrash)")?(v=1,u=-1):A.is("span.unapprove:not(.unspam):not(.untrash)")?(v=-1,u=1):A.is("span.delete")&&(E?s=-1:F&&(t=-1)),u&&(e(u,y),d("span.all-count",u)),v&&(f(v,y),d("span.all-count",v)),s&&d("span.spam-count",s),t&&d("span.trash-count",t),("trash"===j.data.comment_status&&!b(a("span.trash-count"))||"spam"===j.data.comment_status&&!b(a("span.spam-count")))&&a("#delete_all").hide(),m||(l=c.val()?parseInt(c.val(),10):0,a(j.target).parent().is("span.undo")?l++:l--,l<0&&(l=0),"object"==typeof g?w.supplemental.total_items_i18n&&r<w.supplemental.time?(k=w.supplemental.total_items_i18n||"",k&&(a(".displaying-num").text(k),a(".total-pages").text(w.supplemental.total_pages_i18n),a(".tablenav-pages").find(".next-page, .last-page").toggleClass("disabled",w.supplemental.total_pages==a(".current-page").val())),n(l,w.supplemental.time,!0)):w.supplemental.time&&n(l,w.supplemental.time,!1):n(l,g,!1)),theExtraList&&0!==theExtraList.length&&0!==theExtraList.children().length&&!G&&(theList.get(0).wpList.add(theExtraList.children(":eq(0):not(.no-items)").remove().clone()),p(),o=a(":animated","#the-comment-list"),q=function(){a("#the-comment-list tr:visible").length||theList.get(0).wpList.add(theExtraList.find(".no-items").clone())},o.length?o.promise().done(q):q())},p=function(b){var c=a.query.get(),d=a(".total-pages").text(),e=a('input[name="_per_page"]',"#comments-form").val();c.paged||(c.paged=1),c.paged>d||(b?(theExtraList.empty(),c.number=Math.min(8,e)):(c.number=1,c.offset=Math.min(8,e)-1),c.no_placeholder=!0,c.paged++,!0===c.comment_type&&(c.comment_type=""),c=a.extend(c,{action:"fetch-list",list_args:list_args,_ajax_fetch_list_nonce:a("#_ajax_fetch_list_nonce").val()}),a.ajax({url:ajaxurl,global:!1,dataType:"json",data:c,success:function(a){theExtraList.get(0).wpList.add(a.rows)}}))},window.theExtraList=a("#the-extra-comment-list").wpList({alt:"",delColor:"none",addColor:"none"}),window.theList=a("#the-comment-list").wpList({alt:"",delBefore:l,dimAfter:k,delAfter:o,addColor:"none"}).bind("wpListDelEnd",function(b,c){var d=a(c.target).attr("data-wp-lists"),e=c.element.replace(/[^0-9]+/g,"");d.indexOf(":trash=1")==-1&&d.indexOf(":spam=1")==-1||a("#undo-"+e).fadeIn(300,function(){a(this).show()})})},window.commentReply={cid:"",act:"",originalContent:"",init:function(){var b=a("#replyrow");a(".cancel",b).click(function(){return commentReply.revert()}),a(".save",b).click(function(){return commentReply.send()}),a("input#author-name, input#author-email, input#author-url",b).keypress(function(a){if(13==a.which)return commentReply.send(),a.preventDefault(),!1}),a("#the-comment-list .column-comment > p").dblclick(function(){commentReply.toggle(a(this).parent())}),a("#doaction, #doaction2, #post-query-submit").click(function(){a("#the-comment-list #replyrow").length>0&&commentReply.close()}),this.comments_listing=a('#comments-form > input[name="comment_status"]').val()||""},addEvents:function(b){b.each(function(){a(this).find(".column-comment > p").dblclick(function(){commentReply.toggle(a(this).parent())})})},toggle:function(b){"none"!==a(b).css("display")&&(a("#replyrow").parent().is("#com-reply")||window.confirm(adminCommentsL10n.warnQuickEdit))&&a(b).find("a.vim-q").click()},revert:function(){return!(a("#the-comment-list #replyrow").length<1)&&void a("#replyrow").fadeOut("fast",function(){commentReply.close()})},close:function(){var b=a(),c=a("#replyrow");c.parent().is("#com-reply")||(this.cid&&(b=a("#comment-"+this.cid)),"edit-comment"===this.act&&b.fadeIn(300,function(){b.show().find(".vim-q").attr("aria-expanded","false").focus()}).css("backgroundColor",""),"replyto-comment"===this.act&&b.find(".vim-r").attr("aria-expanded","false").focus(),"undefined"!=typeof QTags&&QTags.closeAllTags("replycontent"),a("#add-new-comment").css("display",""),c.hide(),a("#com-reply").append(c),a("#replycontent").css("height","").val(""),a("#edithead input").val(""),a(".notice-error",c).addClass("hidden").find(".error").empty(),a(".spinner",c).removeClass("is-active"),this.cid="",this.originalContent="")},open:function(b,c,d){var e,f,g,h,i,j=this,k=a("#comment-"+b),l=k.height(),m=0;return!!this.discardCommentChanges()&&(j.close(),j.cid=b,e=a("#replyrow"),f=a("#inline-"+b),d=d||"replyto",g="edit"==d?"edit":"replyto",g=j.act=g+"-comment",j.originalContent=a("textarea.comment",f).val(),m=a("> th:visible, > td:visible",k).length,e.hasClass("inline-edit-row")&&0!==m&&a("td",e).attr("colspan",m),a("#action",e).val(g),a("#comment_post_ID",e).val(c),a("#comment_ID",e).val(b),"edit"==d?(a("#author-name",e).val(a("div.author",f).text()),a("#author-email",e).val(a("div.author-email",f).text()),a("#author-url",e).val(a("div.author-url",f).text()),a("#status",e).val(a("div.comment_status",f).text()),a("#replycontent",e).val(a("textarea.comment",f).val()),a("#edithead, #editlegend, #savebtn",e).show(),a("#replyhead, #replybtn, #addhead, #addbtn",e).hide(),l>120&&(i=l>500?500:l,a("#replycontent",e).css("height",i+"px")),k.after(e).fadeOut("fast",function(){a("#replyrow").fadeIn(300,function(){a(this).show()})})):"add"==d?(a("#addhead, #addbtn",e).show(),a("#replyhead, #replybtn, #edithead, #editlegend, #savebtn",e).hide(),a("#the-comment-list").prepend(e),a("#replyrow").fadeIn(300)):(h=a("#replybtn",e),a("#edithead, #editlegend, #savebtn, #addhead, #addbtn",e).hide(),a("#replyhead, #replybtn",e).show(),k.after(e),k.hasClass("unapproved")?h.text(adminCommentsL10n.replyApprove):h.text(adminCommentsL10n.reply),a("#replyrow").fadeIn(300,function(){a(this).show()})),setTimeout(function(){var b,c,d,e,f;b=a("#replyrow").offset().top,c=b+a("#replyrow").height(),d=window.pageYOffset||document.documentElement.scrollTop,e=document.documentElement.clientHeight||window.innerHeight||0,f=d+e,f-20<c?window.scroll(0,c-e+35):b-20<d&&window.scroll(0,b-35),a("#replycontent").focus().keyup(function(a){27==a.which&&commentReply.revert()})},600),!1)},send:function(){var b={},c=a("#replysubmit .error-notice");c.addClass("hidden"),a("#replysubmit .spinner").addClass("is-active"),a("#replyrow input").not(":button").each(function(){var c=a(this);b[c.attr("name")]=c.val()}),b.content=a("#replycontent").val(),b.id=b.comment_post_ID,b.comments_listing=this.comments_listing,b.p=a('[name="p"]').val(),a("#comment-"+a("#comment_ID").val()).hasClass("unapproved")&&(b.approve_parent=1),a.ajax({type:"POST",url:ajaxurl,data:b,success:function(a){commentReply.show(a)},error:function(a){commentReply.error(a)}})},show:function(b){var c,g,j,k,l,m=this;return"string"==typeof b?(m.error({responseText:b}),!1):(c=wpAjax.parseAjaxResponse(b),c.errors?(m.error({responseText:wpAjax.broken}),!1):(m.revert(),c=c.responses[0],j="#comment-"+c.id,"edit-comment"==m.act&&a(j).remove(),c.supplemental.parent_approved&&(l=a("#comment-"+c.supplemental.parent_approved),e(-1,c.supplemental.parent_post_id),"moderated"==this.comments_listing)?void l.animate({backgroundColor:"#CCEEBB"},400,function(){l.fadeOut()}):(c.supplemental.i18n_comments_text&&(h(c.supplemental),i(c.supplemental),f(1,c.supplemental.parent_post_id),d("span.all-count",1)),g=a.trim(c.data),a(g).hide(),a("#replyrow").after(g),j=a(j),m.addEvents(j),k=j.hasClass("unapproved")?"#FFFFE0":j.closest(".widefat, .postbox").css("backgroundColor"),void j.animate({backgroundColor:"#CCEEBB"},300).animate({backgroundColor:k},300,function(){l&&l.length&&l.animate({backgroundColor:"#CCEEBB"},300).animate({backgroundColor:k},300).removeClass("unapproved").addClass("approved").find("div.comment_status").html("1")}))))},error:function(b){var c=b.statusText,d=a("#replysubmit .notice-error"),e=d.find(".error");a("#replysubmit .spinner").removeClass("is-active"),b.responseText&&(c=b.responseText.replace(/<.[^<>]*?>/g,"")),c&&(d.removeClass("hidden"),e.html(c))},addcomment:function(b){var c=this;a("#add-new-comment").fadeOut(200,function(){c.open(0,b,"add"),a("table.comments-box").css("display",""),a("#no-comments").remove()})},discardCommentChanges:function(){var b=a("#replyrow");return this.originalContent===a("#replycontent",b).val()||window.confirm(adminCommentsL10n.warnCommentChanges)}},a(document).ready(function(){var b,c,d,e;setCommentsList(),commentReply.init(),a(document).on("click","span.delete a.delete",function(a){a.preventDefault()}),"undefined"!=typeof a.table_hotkeys&&(b=function(b){return function(){var c,d;c="next"==b?"first":"last",d=a(".tablenav-pages ."+b+"-page:not(.disabled)"),d.length&&(window.location=d[0].href.replace(/\&hotkeys_highlight_(first|last)=1/g,"")+"&hotkeys_highlight_"+c+"=1")}},c=function(b,c){window.location=a("span.edit a",c).attr("href")},d=function(){a("#cb-select-all-1").data("wp-toggle",1).trigger("click").removeData("wp-toggle")},e=function(b){return function(){var c=a('select[name="action"]');a('option[value="'+b+'"]',c).prop("selected",!0),a("#doaction").click()}},a.table_hotkeys(a("table.widefat"),["a","u","s","d","r","q","z",["e",c],["shift+x",d],["shift+a",e("approve")],["shift+s",e("spam")],["shift+d",e("delete")],["shift+t",e("trash")],["shift+z",e("untrash")],["shift+u",e("unapprove")]],{highlight_first:adminCommentsL10n.hotkeys_highlight_first,highlight_last:adminCommentsL10n.hotkeys_highlight_last,prev_page_link_cb:b("prev"),next_page_link_cb:b("next"),hotkeys_opts:{disableInInput:!0,type:"keypress",noDisable:'.check-column input[type="checkbox"]'},cycle_expr:"#the-comment-list tr",start_row_index:0})),a("#the-comment-list").on("click",".comment-inline",function(){var b=a(this),c="replyto";"undefined"!=typeof b.data("action")&&(c=b.data("action")),a(this).attr("aria-expanded","true"),commentReply.open(b.data("commentId"),b.data("postId"),c)})})}(jQuery); \ No newline at end of file
diff --git a/www/crm/wp-admin/js/editor-expand.js b/www/crm/wp-admin/js/editor-expand.js
new file mode 100644
index 00000000..030193a3
--- /dev/null
+++ b/www/crm/wp-admin/js/editor-expand.js
@@ -0,0 +1,1616 @@
+/**
+ * @output wp-admin/js/editor-expand.js
+ */
+
+( function( window, $, undefined ) {
+ 'use strict';
+
+ var $window = $( window ),
+ $document = $( document ),
+ $adminBar = $( '#wpadminbar' ),
+ $footer = $( '#wpfooter' );
+
+ /**
+ * Handles the resizing of the editor.
+ *
+ * @since 4.0.0
+ *
+ * @returns {void}
+ */
+ $( function() {
+ var $wrap = $( '#postdivrich' ),
+ $contentWrap = $( '#wp-content-wrap' ),
+ $tools = $( '#wp-content-editor-tools' ),
+ $visualTop = $(),
+ $visualEditor = $(),
+ $textTop = $( '#ed_toolbar' ),
+ $textEditor = $( '#content' ),
+ textEditor = $textEditor[0],
+ oldTextLength = 0,
+ $bottom = $( '#post-status-info' ),
+ $menuBar = $(),
+ $statusBar = $(),
+ $sideSortables = $( '#side-sortables' ),
+ $postboxContainer = $( '#postbox-container-1' ),
+ $postBody = $('#post-body'),
+ fullscreen = window.wp.editor && window.wp.editor.fullscreen,
+ mceEditor,
+ mceBind = function(){},
+ mceUnbind = function(){},
+ fixedTop = false,
+ fixedBottom = false,
+ fixedSideTop = false,
+ fixedSideBottom = false,
+ scrollTimer,
+ lastScrollPosition = 0,
+ pageYOffsetAtTop = 130,
+ pinnedToolsTop = 56,
+ sidebarBottom = 20,
+ autoresizeMinHeight = 300,
+ initialMode = $contentWrap.hasClass( 'tmce-active' ) ? 'tinymce' : 'html',
+ advanced = !! parseInt( window.getUserSetting( 'hidetb' ), 10 ),
+ // These are corrected when adjust() runs, except on scrolling if already set.
+ heights = {
+ windowHeight: 0,
+ windowWidth: 0,
+ adminBarHeight: 0,
+ toolsHeight: 0,
+ menuBarHeight: 0,
+ visualTopHeight: 0,
+ textTopHeight: 0,
+ bottomHeight: 0,
+ statusBarHeight: 0,
+ sideSortablesHeight: 0
+ };
+
+ /**
+ * Resizes textarea based on scroll height and width.
+ *
+ * Doesn't shrink the editor size below the 300px auto resize minimum height.
+ *
+ * @since 4.6.1
+ *
+ * @returns {void}
+ */
+ var shrinkTextarea = window._.throttle( function() {
+ var x = window.scrollX || document.documentElement.scrollLeft;
+ var y = window.scrollY || document.documentElement.scrollTop;
+ var height = parseInt( textEditor.style.height, 10 );
+
+ textEditor.style.height = autoresizeMinHeight + 'px';
+
+ if ( textEditor.scrollHeight > autoresizeMinHeight ) {
+ textEditor.style.height = textEditor.scrollHeight + 'px';
+ }
+
+ if ( typeof x !== 'undefined' ) {
+ window.scrollTo( x, y );
+ }
+
+ if ( textEditor.scrollHeight < height ) {
+ adjust();
+ }
+ }, 300 );
+
+ /**
+ * Resizes the text editor depending on the old text length.
+ *
+ * If there is an mceEditor and it is hidden, it resizes the editor depending
+ * on the old text length. If the current length of the text is smaller than
+ * the old text length, it shrinks the text area. Otherwise it resizes the editor to
+ * the scroll height.
+ *
+ * @since 4.6.1
+ *
+ * @returns {void}
+ */
+ function textEditorResize() {
+ var length = textEditor.value.length;
+
+ if ( mceEditor && ! mceEditor.isHidden() ) {
+ return;
+ }
+
+ if ( ! mceEditor && initialMode === 'tinymce' ) {
+ return;
+ }
+
+ if ( length < oldTextLength ) {
+ shrinkTextarea();
+ } else if ( parseInt( textEditor.style.height, 10 ) < textEditor.scrollHeight ) {
+ textEditor.style.height = Math.ceil( textEditor.scrollHeight ) + 'px';
+ adjust();
+ }
+
+ oldTextLength = length;
+ }
+
+ /**
+ * Gets the height and widths of elements.
+ *
+ * Gets the heights of the window, the adminbar, the tools, the menu,
+ * the visualTop, the textTop, the bottom, the statusbar and sideSortables
+ * and stores these in the heights object. Defaults to 0.
+ * Gets the width of the window and stores this in the heights object.
+ *
+ * @since 4.0.0
+ *
+ * @returns {void}
+ */
+ function getHeights() {
+ var windowWidth = $window.width();
+
+ heights = {
+ windowHeight: $window.height(),
+ windowWidth: windowWidth,
+ adminBarHeight: ( windowWidth > 600 ? $adminBar.outerHeight() : 0 ),
+ toolsHeight: $tools.outerHeight() || 0,
+ menuBarHeight: $menuBar.outerHeight() || 0,
+ visualTopHeight: $visualTop.outerHeight() || 0,
+ textTopHeight: $textTop.outerHeight() || 0,
+ bottomHeight: $bottom.outerHeight() || 0,
+ statusBarHeight: $statusBar.outerHeight() || 0,
+ sideSortablesHeight: $sideSortables.height() || 0
+ };
+
+ // Adjust for hidden menubar.
+ if ( heights.menuBarHeight < 3 ) {
+ heights.menuBarHeight = 0;
+ }
+ }
+
+ // We need to wait for TinyMCE to initialize.
+ /**
+ * Binds all necessary functions for editor expand to the editor when the editor
+ * is initialized.
+ *
+ * @since 4.0.0
+ *
+ * @param {event} event The TinyMCE editor init event.
+ * @param {object} editor The editor to bind the vents on.
+ *
+ * @returns {void}
+ */
+ $document.on( 'tinymce-editor-init.editor-expand', function( event, editor ) {
+ // VK contains the type of key pressed. VK = virtual keyboard.
+ var VK = window.tinymce.util.VK,
+ /**
+ * Hides any float panel with a hover state. Additionally hides tooltips.
+ *
+ * @returns {void}
+ */
+ hideFloatPanels = _.debounce( function() {
+ ! $( '.mce-floatpanel:hover' ).length && window.tinymce.ui.FloatPanel.hideAll();
+ $( '.mce-tooltip' ).hide();
+ }, 1000, true );
+
+ // Make sure it's the main editor.
+ if ( editor.id !== 'content' ) {
+ return;
+ }
+
+ // Copy the editor instance.
+ mceEditor = editor;
+
+ // Set the minimum height to the initial viewport height.
+ editor.settings.autoresize_min_height = autoresizeMinHeight;
+
+ // Get the necessary UI elements.
+ $visualTop = $contentWrap.find( '.mce-toolbar-grp' );
+ $visualEditor = $contentWrap.find( '.mce-edit-area' );
+ $statusBar = $contentWrap.find( '.mce-statusbar' );
+ $menuBar = $contentWrap.find( '.mce-menubar' );
+
+ /**
+ * Gets the offset of the editor.
+ *
+ * @returns {Number|Boolean} Returns the offset of the editor
+ * or false if there is no offset height.
+ */
+ function mceGetCursorOffset() {
+ var node = editor.selection.getNode(),
+ range, view, offset;
+
+ /*
+ * If editor.wp.getView and the selection node from the editor selection
+ * are defined, use this as a view for the offset.
+ */
+ if ( editor.wp && editor.wp.getView && ( view = editor.wp.getView( node ) ) ) {
+ offset = view.getBoundingClientRect();
+ } else {
+ range = editor.selection.getRng();
+
+ // Try to get the offset from a range.
+ try {
+ offset = range.getClientRects()[0];
+ } catch( er ) {}
+
+ // Get the offset from the bounding client rectangle of the node.
+ if ( ! offset ) {
+ offset = node.getBoundingClientRect();
+ }
+ }
+
+ return offset.height ? offset : false;
+ }
+
+ /**
+ * Filters the special keys that should not be used for scrolling.
+ *
+ * @since 4.0.0
+ *
+ * @param {event} event The event to get the key code from.
+ *
+ * @returns {void}
+ */
+ function mceKeyup( event ) {
+ var key = event.keyCode;
+
+ // Bail on special keys. Key code 47 is a /
+ if ( key <= 47 && ! ( key === VK.SPACEBAR || key === VK.ENTER || key === VK.DELETE || key === VK.BACKSPACE || key === VK.UP || key === VK.LEFT || key === VK.DOWN || key === VK.UP ) ) {
+ return;
+ // OS keys, function keys, num lock, scroll lock. Key code 91-93 are OS keys. Key code 112-123 are F1 to F12. Key code 144 is num lock. Key code 145 is scroll lock.
+ } else if ( ( key >= 91 && key <= 93 ) || ( key >= 112 && key <= 123 ) || key === 144 || key === 145 ) {
+ return;
+ }
+
+ mceScroll( key );
+ }
+
+ /**
+ * Makes sure the cursor is always visible in the editor.
+ *
+ * Makes sure the cursor is kept between the toolbars of the editor and scrolls
+ * the window when the cursor moves out of the viewport to a wpview.
+ * Setting a buffer > 0 will prevent the browser default.
+ * Some browsers will scroll to the middle,
+ * others to the top/bottom of the *window* when moving the cursor out of the viewport.
+ *
+ * @since 4.1.0
+ *
+ * @param {string} key The key code of the pressed key.
+ *
+ * @returns {void}
+ */
+ function mceScroll( key ) {
+ var offset = mceGetCursorOffset(),
+ buffer = 50,
+ cursorTop, cursorBottom, editorTop, editorBottom;
+
+ // Don't scroll if there is no offset.
+ if ( ! offset ) {
+ return;
+ }
+
+ // Determine the cursorTop based on the offset and the top of the editor iframe.
+ cursorTop = offset.top + editor.iframeElement.getBoundingClientRect().top;
+
+ // Determine the cursorBottom based on the cursorTop and offset height.
+ cursorBottom = cursorTop + offset.height;
+
+ // Subtract the buffer from the cursorTop.
+ cursorTop = cursorTop - buffer;
+
+ // Add the buffer to the cursorBottom.
+ cursorBottom = cursorBottom + buffer;
+ editorTop = heights.adminBarHeight + heights.toolsHeight + heights.menuBarHeight + heights.visualTopHeight;
+
+ /*
+ * Set the editorBottom based on the window Height, and add the bottomHeight and statusBarHeight if the
+ * advanced editor is enabled.
+ */
+ editorBottom = heights.windowHeight - ( advanced ? heights.bottomHeight + heights.statusBarHeight : 0 );
+
+ // Don't scroll if the node is taller than the visible part of the editor.
+ if ( editorBottom - editorTop < offset.height ) {
+ return;
+ }
+
+ /*
+ * If the cursorTop is smaller than the editorTop and the up, left
+ * or backspace key is pressed, scroll the editor to the position defined
+ * by the cursorTop, pageYOffset and editorTop.
+ */
+ if ( cursorTop < editorTop && ( key === VK.UP || key === VK.LEFT || key === VK.BACKSPACE ) ) {
+ window.scrollTo( window.pageXOffset, cursorTop + window.pageYOffset - editorTop );
+
+ /*
+ * If any other key is pressed or the cursorTop is bigger than the editorTop,
+ * scroll the editor to the position defined by the cursorBottom,
+ * pageYOffset and editorBottom.
+ */
+ } else if ( cursorBottom > editorBottom ) {
+ window.scrollTo( window.pageXOffset, cursorBottom + window.pageYOffset - editorBottom );
+ }
+ }
+
+ /**
+ * If the editor is fullscreen, calls adjust.
+ *
+ * @since 4.1.0
+ *
+ * @param {event} event The FullscreenStateChanged event.
+ *
+ * @returns {void}
+ */
+ function mceFullscreenToggled( event ) {
+ // event.state is true if the editor is fullscreen.
+ if ( ! event.state ) {
+ adjust();
+ }
+ }
+
+ /**
+ * Shows the editor when scrolled.
+ *
+ * Binds the hideFloatPanels function on the window scroll.mce-float-panels event.
+ * Executes the wpAutoResize on the active editor.
+ *
+ * @since 4.0.0
+ *
+ * @returns {void}
+ */
+ function mceShow() {
+ $window.on( 'scroll.mce-float-panels', hideFloatPanels );
+
+ setTimeout( function() {
+ editor.execCommand( 'wpAutoResize' );
+ adjust();
+ }, 300 );
+ }
+
+ /**
+ * Resizes the editor.
+ *
+ * Removes all functions from the window scroll.mce-float-panels event.
+ * Resizes the text editor and scrolls to a position based on the pageXOffset and adminBarHeight.
+ *
+ * @since 4.0.0
+ *
+ * @returns {void}
+ */
+ function mceHide() {
+ $window.off( 'scroll.mce-float-panels' );
+
+ setTimeout( function() {
+ var top = $contentWrap.offset().top;
+
+ if ( window.pageYOffset > top ) {
+ window.scrollTo( window.pageXOffset, top - heights.adminBarHeight );
+ }
+
+ textEditorResize();
+ adjust();
+ }, 100 );
+
+ adjust();
+ }
+
+ /**
+ * Toggles advanced states.
+ *
+ * @since 4.1.0
+ *
+ * @returns {void}
+ */
+ function toggleAdvanced() {
+ advanced = ! advanced;
+ }
+
+ /**
+ * Binds events of the editor and window.
+ *
+ * @since 4.0.0
+ *
+ * @returns {void}
+ */
+ mceBind = function() {
+ editor.on( 'keyup', mceKeyup );
+ editor.on( 'show', mceShow );
+ editor.on( 'hide', mceHide );
+ editor.on( 'wp-toolbar-toggle', toggleAdvanced );
+
+ // Adjust when the editor resizes.
+ editor.on( 'setcontent wp-autoresize wp-toolbar-toggle', adjust );
+
+ // Don't hide the caret after undo/redo.
+ editor.on( 'undo redo', mceScroll );
+
+ // Adjust when exiting TinyMCE's fullscreen mode.
+ editor.on( 'FullscreenStateChanged', mceFullscreenToggled );
+
+ $window.off( 'scroll.mce-float-panels' ).on( 'scroll.mce-float-panels', hideFloatPanels );
+ };
+
+ /**
+ * Unbinds the events of the editor and window.
+ *
+ * @since 4.0.0
+ *
+ * @returns {void}
+ */
+ mceUnbind = function() {
+ editor.off( 'keyup', mceKeyup );
+ editor.off( 'show', mceShow );
+ editor.off( 'hide', mceHide );
+ editor.off( 'wp-toolbar-toggle', toggleAdvanced );
+ editor.off( 'setcontent wp-autoresize wp-toolbar-toggle', adjust );
+ editor.off( 'undo redo', mceScroll );
+ editor.off( 'FullscreenStateChanged', mceFullscreenToggled );
+
+ $window.off( 'scroll.mce-float-panels' );
+ };
+
+ if ( $wrap.hasClass( 'wp-editor-expand' ) ) {
+
+ // Adjust "immediately".
+ mceBind();
+ initialResize( adjust );
+ }
+ } );
+
+ /**
+ * Adjusts the toolbars heights and positions.
+ *
+ * Adjusts the toolbars heights and positions based on the scroll position on
+ * the page, the active editor mode and the heights of the editor, admin bar and
+ * side bar.
+ *
+ * @since 4.0.0
+ *
+ * @param {event} event The event that calls this function.
+ *
+ * @returns {void}
+ */
+ function adjust( event ) {
+
+ // Makes sure we're not in fullscreen mode.
+ if ( fullscreen && fullscreen.settings.visible ) {
+ return;
+ }
+
+ var windowPos = $window.scrollTop(),
+ type = event && event.type,
+ resize = type !== 'scroll',
+ visual = mceEditor && ! mceEditor.isHidden(),
+ buffer = autoresizeMinHeight,
+ postBodyTop = $postBody.offset().top,
+ borderWidth = 1,
+ contentWrapWidth = $contentWrap.width(),
+ $top, $editor, sidebarTop, footerTop, canPin,
+ topPos, topHeight, editorPos, editorHeight;
+
+ /*
+ * Refresh the heights if type isn't 'scroll'
+ * or heights.windowHeight isn't set.
+ */
+ if ( resize || ! heights.windowHeight ) {
+ getHeights();
+ }
+
+ // Resize on resize event when the editor is in text mode.
+ if ( ! visual && type === 'resize' ) {
+ textEditorResize();
+ }
+
+ if ( visual ) {
+ $top = $visualTop;
+ $editor = $visualEditor;
+ topHeight = heights.visualTopHeight;
+ } else {
+ $top = $textTop;
+ $editor = $textEditor;
+ topHeight = heights.textTopHeight;
+ }
+
+ // Return if TinyMCE is still initializing.
+ if ( ! visual && ! $top.length ) {
+ return;
+ }
+
+ topPos = $top.parent().offset().top;
+ editorPos = $editor.offset().top;
+ editorHeight = $editor.outerHeight();
+
+ /*
+ * If in visual mode, checks if the editorHeight is greater than the autoresizeMinHeight + topHeight.
+ * If not in visual mode, checks if the editorHeight is greater than the autoresizeMinHeight + 20.
+ */
+ canPin = visual ? autoresizeMinHeight + topHeight : autoresizeMinHeight + 20; // 20px from textarea padding
+ canPin = editorHeight > ( canPin + 5 );
+
+ if ( ! canPin ) {
+ if ( resize ) {
+ $tools.css( {
+ position: 'absolute',
+ top: 0,
+ width: contentWrapWidth
+ } );
+
+ if ( visual && $menuBar.length ) {
+ $menuBar.css( {
+ position: 'absolute',
+ top: 0,
+ width: contentWrapWidth - ( borderWidth * 2 )
+ } );
+ }
+
+ $top.css( {
+ position: 'absolute',
+ top: heights.menuBarHeight,
+ width: contentWrapWidth - ( borderWidth * 2 ) - ( visual ? 0 : ( $top.outerWidth() - $top.width() ) )
+ } );
+
+ $statusBar.attr( 'style', advanced ? '' : 'visibility: hidden;' );
+ $bottom.attr( 'style', '' );
+ }
+ } else {
+ // Check if the top is not already in a fixed position.
+ if ( ( ! fixedTop || resize ) &&
+ ( windowPos >= ( topPos - heights.toolsHeight - heights.adminBarHeight ) &&
+ windowPos <= ( topPos - heights.toolsHeight - heights.adminBarHeight + editorHeight - buffer ) ) ) {
+ fixedTop = true;
+
+ $tools.css( {
+ position: 'fixed',
+ top: heights.adminBarHeight,
+ width: contentWrapWidth
+ } );
+
+ if ( visual && $menuBar.length ) {
+ $menuBar.css( {
+ position: 'fixed',
+ top: heights.adminBarHeight + heights.toolsHeight,
+ width: contentWrapWidth - ( borderWidth * 2 ) - ( visual ? 0 : ( $top.outerWidth() - $top.width() ) )
+ } );
+ }
+
+ $top.css( {
+ position: 'fixed',
+ top: heights.adminBarHeight + heights.toolsHeight + heights.menuBarHeight,
+ width: contentWrapWidth - ( borderWidth * 2 ) - ( visual ? 0 : ( $top.outerWidth() - $top.width() ) )
+ } );
+ // Check if the top is already in a fixed position.
+ } else if ( fixedTop || resize ) {
+ if ( windowPos <= ( topPos - heights.toolsHeight - heights.adminBarHeight ) ) {
+ fixedTop = false;
+
+ $tools.css( {
+ position: 'absolute',
+ top: 0,
+ width: contentWrapWidth
+ } );
+
+ if ( visual && $menuBar.length ) {
+ $menuBar.css( {
+ position: 'absolute',
+ top: 0,
+ width: contentWrapWidth - ( borderWidth * 2 )
+ } );
+ }
+
+ $top.css( {
+ position: 'absolute',
+ top: heights.menuBarHeight,
+ width: contentWrapWidth - ( borderWidth * 2 ) - ( visual ? 0 : ( $top.outerWidth() - $top.width() ) )
+ } );
+ } else if ( windowPos >= ( topPos - heights.toolsHeight - heights.adminBarHeight + editorHeight - buffer ) ) {
+ fixedTop = false;
+
+ $tools.css( {
+ position: 'absolute',
+ top: editorHeight - buffer,
+ width: contentWrapWidth
+ } );
+
+ if ( visual && $menuBar.length ) {
+ $menuBar.css( {
+ position: 'absolute',
+ top: editorHeight - buffer,
+ width: contentWrapWidth - ( borderWidth * 2 )
+ } );
+ }
+
+ $top.css( {
+ position: 'absolute',
+ top: editorHeight - buffer + heights.menuBarHeight,
+ width: contentWrapWidth - ( borderWidth * 2 ) - ( visual ? 0 : ( $top.outerWidth() - $top.width() ) )
+ } );
+ }
+ }
+
+ // Check if the bottom is not already in a fixed position.
+ if ( ( ! fixedBottom || ( resize && advanced ) ) &&
+ // Add borderWidth for the border around the .wp-editor-container.
+ ( windowPos + heights.windowHeight ) <= ( editorPos + editorHeight + heights.bottomHeight + heights.statusBarHeight + borderWidth ) ) {
+
+ if ( event && event.deltaHeight > 0 && event.deltaHeight < 100 ) {
+ window.scrollBy( 0, event.deltaHeight );
+ } else if ( visual && advanced ) {
+ fixedBottom = true;
+
+ $statusBar.css( {
+ position: 'fixed',
+ bottom: heights.bottomHeight,
+ visibility: '',
+ width: contentWrapWidth - ( borderWidth * 2 )
+ } );
+
+ $bottom.css( {
+ position: 'fixed',
+ bottom: 0,
+ width: contentWrapWidth
+ } );
+ }
+ } else if ( ( ! advanced && fixedBottom ) ||
+ ( ( fixedBottom || resize ) &&
+ ( windowPos + heights.windowHeight ) > ( editorPos + editorHeight + heights.bottomHeight + heights.statusBarHeight - borderWidth ) ) ) {
+ fixedBottom = false;
+
+ $statusBar.attr( 'style', advanced ? '' : 'visibility: hidden;' );
+ $bottom.attr( 'style', '' );
+ }
+ }
+
+ // The postbox container is positioned with @media from CSS. Ensure it is pinned on the side.
+ if ( $postboxContainer.width() < 300 && heights.windowWidth > 600 &&
+
+ // Check if the sidebar is not taller than the document height.
+ $document.height() > ( $sideSortables.height() + postBodyTop + 120 ) &&
+
+ // Check if the editor is taller than the viewport.
+ heights.windowHeight < editorHeight ) {
+
+ if ( ( heights.sideSortablesHeight + pinnedToolsTop + sidebarBottom ) > heights.windowHeight || fixedSideTop || fixedSideBottom ) {
+
+ // Reset the sideSortables style when scrolling to the top.
+ if ( windowPos + pinnedToolsTop <= postBodyTop ) {
+ $sideSortables.attr( 'style', '' );
+ fixedSideTop = fixedSideBottom = false;
+ } else {
+
+ // When scrolling down.
+ if ( windowPos > lastScrollPosition ) {
+ if ( fixedSideTop ) {
+
+ // Let it scroll.
+ fixedSideTop = false;
+ sidebarTop = $sideSortables.offset().top - heights.adminBarHeight;
+ footerTop = $footer.offset().top;
+
+ // Don't get over the footer.
+ if ( footerTop < sidebarTop + heights.sideSortablesHeight + sidebarBottom ) {
+ sidebarTop = footerTop - heights.sideSortablesHeight - 12;
+ }
+
+ $sideSortables.css({
+ position: 'absolute',
+ top: sidebarTop,
+ bottom: ''
+ });
+ } else if ( ! fixedSideBottom && heights.sideSortablesHeight + $sideSortables.offset().top + sidebarBottom < windowPos + heights.windowHeight ) {
+ // Pin the bottom.
+ fixedSideBottom = true;
+
+ $sideSortables.css({
+ position: 'fixed',
+ top: 'auto',
+ bottom: sidebarBottom
+ });
+ }
+
+ // When scrolling up.
+ } else if ( windowPos < lastScrollPosition ) {
+ if ( fixedSideBottom ) {
+ // Let it scroll.
+ fixedSideBottom = false;
+ sidebarTop = $sideSortables.offset().top - sidebarBottom;
+ footerTop = $footer.offset().top;
+
+ // Don't get over the footer.
+ if ( footerTop < sidebarTop + heights.sideSortablesHeight + sidebarBottom ) {
+ sidebarTop = footerTop - heights.sideSortablesHeight - 12;
+ }
+
+ $sideSortables.css({
+ position: 'absolute',
+ top: sidebarTop,
+ bottom: ''
+ });
+ } else if ( ! fixedSideTop && $sideSortables.offset().top >= windowPos + pinnedToolsTop ) {
+ // Pin the top.
+ fixedSideTop = true;
+
+ $sideSortables.css({
+ position: 'fixed',
+ top: pinnedToolsTop,
+ bottom: ''
+ });
+ }
+ }
+ }
+ } else {
+ // If the sidebar container is smaller than the viewport, then pin/unpin the top when scrolling.
+ if ( windowPos >= ( postBodyTop - pinnedToolsTop ) ) {
+
+ $sideSortables.css( {
+ position: 'fixed',
+ top: pinnedToolsTop
+ } );
+ } else {
+ $sideSortables.attr( 'style', '' );
+ }
+
+ fixedSideTop = fixedSideBottom = false;
+ }
+
+ lastScrollPosition = windowPos;
+ } else {
+ $sideSortables.attr( 'style', '' );
+ fixedSideTop = fixedSideBottom = false;
+ }
+
+ if ( resize ) {
+ $contentWrap.css( {
+ paddingTop: heights.toolsHeight
+ } );
+
+ if ( visual ) {
+ $visualEditor.css( {
+ paddingTop: heights.visualTopHeight + heights.menuBarHeight
+ } );
+ } else {
+ $textEditor.css( {
+ marginTop: heights.textTopHeight
+ } );
+ }
+ }
+ }
+
+ /**
+ * Resizes the editor and adjusts the toolbars.
+ *
+ * @since 4.0.0
+ *
+ * @returns {void}
+ */
+ function fullscreenHide() {
+ textEditorResize();
+ adjust();
+ }
+
+ /**
+ * Runs the passed function with 500ms intervals.
+ *
+ * @since 4.0.0
+ *
+ * @param {function} callback The function to run in the timeout.
+ *
+ * @returns {void}
+ */
+ function initialResize( callback ) {
+ for ( var i = 1; i < 6; i++ ) {
+ setTimeout( callback, 500 * i );
+ }
+ }
+
+ /**
+ * Runs adjust after 100ms.
+ *
+ * @since 4.0.0
+ *
+ * @returns {void}
+ */
+ function afterScroll() {
+ clearTimeout( scrollTimer );
+ scrollTimer = setTimeout( adjust, 100 );
+ }
+
+ /**
+ * Binds editor expand events on elements.
+ *
+ * @since 4.0.0
+ *
+ * @returns {void}
+ */
+ function on() {
+ /*
+ * Scroll to the top when triggering this from JS.
+ * Ensure the toolbars are pinned properly.
+ */
+ if ( window.pageYOffset && window.pageYOffset > pageYOffsetAtTop ) {
+ window.scrollTo( window.pageXOffset, 0 );
+ }
+
+ $wrap.addClass( 'wp-editor-expand' );
+
+ // Adjust when the window is scrolled or resized.
+ $window.on( 'scroll.editor-expand resize.editor-expand', function( event ) {
+ adjust( event.type );
+ afterScroll();
+ } );
+
+ /*
+ * Adjust when collapsing the menu, changing the columns
+ * or changing the body class.
+ */
+ $document.on( 'wp-collapse-menu.editor-expand postboxes-columnchange.editor-expand editor-classchange.editor-expand', adjust )
+ .on( 'postbox-toggled.editor-expand postbox-moved.editor-expand', function() {
+ if ( ! fixedSideTop && ! fixedSideBottom && window.pageYOffset > pinnedToolsTop ) {
+ fixedSideBottom = true;
+ window.scrollBy( 0, -1 );
+ adjust();
+ window.scrollBy( 0, 1 );
+ }
+
+ adjust();
+ }).on( 'wp-window-resized.editor-expand', function() {
+ if ( mceEditor && ! mceEditor.isHidden() ) {
+ mceEditor.execCommand( 'wpAutoResize' );
+ } else {
+ textEditorResize();
+ }
+ });
+
+ $textEditor.on( 'focus.editor-expand input.editor-expand propertychange.editor-expand', textEditorResize );
+ mceBind();
+
+ // Adjust when entering or exiting fullscreen mode.
+ fullscreen && fullscreen.pubsub.subscribe( 'hidden', fullscreenHide );
+
+ if ( mceEditor ) {
+ mceEditor.settings.wp_autoresize_on = true;
+ mceEditor.execCommand( 'wpAutoResizeOn' );
+
+ if ( ! mceEditor.isHidden() ) {
+ mceEditor.execCommand( 'wpAutoResize' );
+ }
+ }
+
+ if ( ! mceEditor || mceEditor.isHidden() ) {
+ textEditorResize();
+ }
+
+ adjust();
+
+ $document.trigger( 'editor-expand-on' );
+ }
+
+ /**
+ * Unbinds editor expand events.
+ *
+ * @since 4.0.0
+ *
+ * @returns {void}
+ */
+ function off() {
+ var height = parseInt( window.getUserSetting( 'ed_size', 300 ), 10 );
+
+ if ( height < 50 ) {
+ height = 50;
+ } else if ( height > 5000 ) {
+ height = 5000;
+ }
+
+ /*
+ * Scroll to the top when triggering this from JS.
+ * Ensure the toolbars are reset properly.
+ */
+ if ( window.pageYOffset && window.pageYOffset > pageYOffsetAtTop ) {
+ window.scrollTo( window.pageXOffset, 0 );
+ }
+
+ $wrap.removeClass( 'wp-editor-expand' );
+
+ $window.off( '.editor-expand' );
+ $document.off( '.editor-expand' );
+ $textEditor.off( '.editor-expand' );
+ mceUnbind();
+
+ // Adjust when entering or exiting fullscreen mode.
+ fullscreen && fullscreen.pubsub.unsubscribe( 'hidden', fullscreenHide );
+
+ // Reset all css
+ $.each( [ $visualTop, $textTop, $tools, $menuBar, $bottom, $statusBar, $contentWrap, $visualEditor, $textEditor, $sideSortables ], function( i, element ) {
+ element && element.attr( 'style', '' );
+ });
+
+ fixedTop = fixedBottom = fixedSideTop = fixedSideBottom = false;
+
+ if ( mceEditor ) {
+ mceEditor.settings.wp_autoresize_on = false;
+ mceEditor.execCommand( 'wpAutoResizeOff' );
+
+ if ( ! mceEditor.isHidden() ) {
+ $textEditor.hide();
+
+ if ( height ) {
+ mceEditor.theme.resizeTo( null, height );
+ }
+ }
+ }
+
+ // If there is a height found in the user setting.
+ if ( height ) {
+ $textEditor.height( height );
+ }
+
+ $document.trigger( 'editor-expand-off' );
+ }
+
+ // Start on load.
+ if ( $wrap.hasClass( 'wp-editor-expand' ) ) {
+ on();
+
+ // Resize just after CSS has fully loaded and QuickTags is ready.
+ if ( $contentWrap.hasClass( 'html-active' ) ) {
+ initialResize( function() {
+ adjust();
+ textEditorResize();
+ } );
+ }
+ }
+
+ // Show the on/off checkbox.
+ $( '#adv-settings .editor-expand' ).show();
+ $( '#editor-expand-toggle' ).on( 'change.editor-expand', function() {
+ if ( $(this).prop( 'checked' ) ) {
+ on();
+ window.setUserSetting( 'editor_expand', 'on' );
+ } else {
+ off();
+ window.setUserSetting( 'editor_expand', 'off' );
+ }
+ });
+
+ // Expose on() and off().
+ window.editorExpand = {
+ on: on,
+ off: off
+ };
+ } );
+
+ /**
+ * Handles the distraction free writing of TinyMCE.
+ *
+ * @since 4.1.0
+ *
+ * @returns {void}
+ */
+ $( function() {
+ var $body = $( document.body ),
+ $wrap = $( '#wpcontent' ),
+ $editor = $( '#post-body-content' ),
+ $title = $( '#title' ),
+ $content = $( '#content' ),
+ $overlay = $( document.createElement( 'DIV' ) ),
+ $slug = $( '#edit-slug-box' ),
+ $slugFocusEl = $slug.find( 'a' )
+ .add( $slug.find( 'button' ) )
+ .add( $slug.find( 'input' ) ),
+ $menuWrap = $( '#adminmenuwrap' ),
+ $editorWindow = $(),
+ $editorIframe = $(),
+ _isActive = window.getUserSetting( 'editor_expand', 'on' ) === 'on',
+ _isOn = _isActive ? window.getUserSetting( 'post_dfw' ) === 'on' : false,
+ traveledX = 0,
+ traveledY = 0,
+ buffer = 20,
+ faded, fadedAdminBar, fadedSlug,
+ editorRect, x, y, mouseY, scrollY,
+ focusLostTimer, overlayTimer, editorHasFocus;
+
+ $body.append( $overlay );
+
+ $overlay.css( {
+ display: 'none',
+ position: 'fixed',
+ top: $adminBar.height(),
+ right: 0,
+ bottom: 0,
+ left: 0,
+ 'z-index': 9997
+ } );
+
+ $editor.css( {
+ position: 'relative'
+ } );
+
+ $window.on( 'mousemove.focus', function( event ) {
+ mouseY = event.pageY;
+ } );
+
+ /**
+ * Recalculates the bottom and right position of the editor in the DOM.
+ *
+ * @since 4.1.0
+ *
+ * @returns {void}
+ */
+ function recalcEditorRect() {
+ editorRect = $editor.offset();
+ editorRect.right = editorRect.left + $editor.outerWidth();
+ editorRect.bottom = editorRect.top + $editor.outerHeight();
+ }
+
+ /**
+ * Activates the distraction free writing mode.
+ *
+ * @since 4.1.0
+ *
+ * @returns {void}
+ */
+ function activate() {
+ if ( ! _isActive ) {
+ _isActive = true;
+
+ $document.trigger( 'dfw-activate' );
+ $content.on( 'keydown.focus-shortcut', toggleViaKeyboard );
+ }
+ }
+
+ /**
+ * Deactivates the distraction free writing mode.
+ *
+ * @since 4.1.0
+ *
+ * @returns {void}
+ */
+ function deactivate() {
+ if ( _isActive ) {
+ off();
+
+ _isActive = false;
+
+ $document.trigger( 'dfw-deactivate' );
+ $content.off( 'keydown.focus-shortcut' );
+ }
+ }
+
+ /**
+ * Returns _isActive.
+ *
+ * @since 4.1.0
+ *
+ * @returns {boolean} Returns true is _isActive is true.
+ */
+ function isActive() {
+ return _isActive;
+ }
+
+ /**
+ * Binds events on the editor for distraction free writing.
+ *
+ * @since 4.1.0
+ *
+ * @returns {void}
+ */
+ function on() {
+ if ( ! _isOn && _isActive ) {
+ _isOn = true;
+
+ $content.on( 'keydown.focus', fadeOut );
+
+ $title.add( $content ).on( 'blur.focus', maybeFadeIn );
+
+ fadeOut();
+
+ window.setUserSetting( 'post_dfw', 'on' );
+
+ $document.trigger( 'dfw-on' );
+ }
+ }
+
+ /**
+ * Unbinds events on the editor for distraction free writing.
+ *
+ * @since 4.1.0
+ *
+ * @returns {void}
+ */
+ function off() {
+ if ( _isOn ) {
+ _isOn = false;
+
+ $title.add( $content ).off( '.focus' );
+
+ fadeIn();
+
+ $editor.off( '.focus' );
+
+ window.setUserSetting( 'post_dfw', 'off' );
+
+ $document.trigger( 'dfw-off' );
+ }
+ }
+
+ /**
+ * Binds or unbinds the editor expand events.
+ *
+ * @since 4.1.0
+ *
+ * @returns {void}
+ */
+ function toggle() {
+ if ( _isOn ) {
+ off();
+ } else {
+ on();
+ }
+ }
+
+ /**
+ * Returns the value of _isOn.
+ *
+ * @since 4.1.0
+ *
+ * @returns {boolean} Returns true if _isOn is true.
+ */
+ function isOn() {
+ return _isOn;
+ }
+
+ /**
+ * Fades out all elements except for the editor.
+ *
+ * The fading is done based on key presses and mouse movements.
+ * Also calls the fadeIn on certain key presses
+ * or if the mouse leaves the editor.
+ *
+ * @since 4.1.0
+ *
+ * @param event The event that triggers this function.
+ *
+ * @returns {void}
+ */
+ function fadeOut( event ) {
+ var isMac,
+ key = event && event.keyCode;
+
+ if ( window.navigator.platform ) {
+ isMac = ( window.navigator.platform.indexOf( 'Mac' ) > -1 );
+ }
+
+ // Fade in and returns on Escape and keyboard shortcut Alt+Shift+W and Ctrl+Opt+W.
+ if ( key === 27 || ( key === 87 && event.altKey && ( ( ! isMac && event.shiftKey ) || ( isMac && event.ctrlKey ) ) ) ) {
+ fadeIn( event );
+ return;
+ }
+
+ // Return if any of the following keys or combinations of keys is pressed.
+ if ( event && ( event.metaKey || ( event.ctrlKey && ! event.altKey ) || ( event.altKey && event.shiftKey ) || ( key && (
+ // Special keys ( tab, ctrl, alt, esc, arrow keys... )
+ ( key <= 47 && key !== 8 && key !== 13 && key !== 32 && key !== 46 ) ||
+ // Windows keys
+ ( key >= 91 && key <= 93 ) ||
+ // F keys
+ ( key >= 112 && key <= 135 ) ||
+ // Num Lock, Scroll Lock, OEM
+ ( key >= 144 && key <= 150 ) ||
+ // OEM or non-printable
+ key >= 224
+ ) ) ) ) {
+ return;
+ }
+
+ if ( ! faded ) {
+ faded = true;
+
+ clearTimeout( overlayTimer );
+
+ overlayTimer = setTimeout( function() {
+ $overlay.show();
+ }, 600 );
+
+ $editor.css( 'z-index', 9998 );
+
+ $overlay
+ // Always recalculate the editor area when entering the overlay with the mouse.
+ .on( 'mouseenter.focus', function() {
+ recalcEditorRect();
+
+ $window.on( 'scroll.focus', function() {
+ var nScrollY = window.pageYOffset;
+
+ if ( (
+ scrollY && mouseY &&
+ scrollY !== nScrollY
+ ) && (
+ mouseY < editorRect.top - buffer ||
+ mouseY > editorRect.bottom + buffer
+ ) ) {
+ fadeIn();
+ }
+
+ scrollY = nScrollY;
+ } );
+ } )
+ .on( 'mouseleave.focus', function() {
+ x = y = null;
+ traveledX = traveledY = 0;
+
+ $window.off( 'scroll.focus' );
+ } )
+ // Fade in when the mouse moves away form the editor area.
+ .on( 'mousemove.focus', function( event ) {
+ var nx = event.clientX,
+ ny = event.clientY,
+ pageYOffset = window.pageYOffset,
+ pageXOffset = window.pageXOffset;
+
+ if ( x && y && ( nx !== x || ny !== y ) ) {
+ if (
+ ( ny <= y && ny < editorRect.top - pageYOffset ) ||
+ ( ny >= y && ny > editorRect.bottom - pageYOffset ) ||
+ ( nx <= x && nx < editorRect.left - pageXOffset ) ||
+ ( nx >= x && nx > editorRect.right - pageXOffset )
+ ) {
+ traveledX += Math.abs( x - nx );
+ traveledY += Math.abs( y - ny );
+
+ if ( (
+ ny <= editorRect.top - buffer - pageYOffset ||
+ ny >= editorRect.bottom + buffer - pageYOffset ||
+ nx <= editorRect.left - buffer - pageXOffset ||
+ nx >= editorRect.right + buffer - pageXOffset
+ ) && (
+ traveledX > 10 ||
+ traveledY > 10
+ ) ) {
+ fadeIn();
+
+ x = y = null;
+ traveledX = traveledY = 0;
+
+ return;
+ }
+ } else {
+ traveledX = traveledY = 0;
+ }
+ }
+
+ x = nx;
+ y = ny;
+ } )
+
+ // When the overlay is touched, fade in and cancel the event.
+ .on( 'touchstart.focus', function( event ) {
+ event.preventDefault();
+ fadeIn();
+ } );
+
+ $editor.off( 'mouseenter.focus' );
+
+ if ( focusLostTimer ) {
+ clearTimeout( focusLostTimer );
+ focusLostTimer = null;
+ }
+
+ $body.addClass( 'focus-on' ).removeClass( 'focus-off' );
+ }
+
+ fadeOutAdminBar();
+ fadeOutSlug();
+ }
+
+ /**
+ * Fades all elements back in.
+ *
+ * @since 4.1.0
+ *
+ * @param event The event that triggers this function.
+ *
+ * @returns {void}
+ */
+ function fadeIn( event ) {
+ if ( faded ) {
+ faded = false;
+
+ clearTimeout( overlayTimer );
+
+ overlayTimer = setTimeout( function() {
+ $overlay.hide();
+ }, 200 );
+
+ $editor.css( 'z-index', '' );
+
+ $overlay.off( 'mouseenter.focus mouseleave.focus mousemove.focus touchstart.focus' );
+
+ /*
+ * When fading in, temporarily watch for refocus and fade back out - helps
+ * with 'accidental' editor exits with the mouse. When fading in and the event
+ * is a key event (Escape or Alt+Shift+W) don't watch for refocus.
+ */
+ if ( 'undefined' === typeof event ) {
+ $editor.on( 'mouseenter.focus', function() {
+ if ( $.contains( $editor.get( 0 ), document.activeElement ) || editorHasFocus ) {
+ fadeOut();
+ }
+ } );
+ }
+
+ focusLostTimer = setTimeout( function() {
+ focusLostTimer = null;
+ $editor.off( 'mouseenter.focus' );
+ }, 1000 );
+
+ $body.addClass( 'focus-off' ).removeClass( 'focus-on' );
+ }
+
+ fadeInAdminBar();
+ fadeInSlug();
+ }
+
+ /**
+ * Fades in if the focused element based on it position.
+ *
+ * @since 4.1.0
+ *
+ * @returns {void}
+ */
+ function maybeFadeIn() {
+ setTimeout( function() {
+ var position = document.activeElement.compareDocumentPosition( $editor.get( 0 ) );
+
+ function hasFocus( $el ) {
+ return $.contains( $el.get( 0 ), document.activeElement );
+ }
+
+ // The focused node is before or behind the editor area, and not outside the wrap.
+ if ( ( position === 2 || position === 4 ) && ( hasFocus( $menuWrap ) || hasFocus( $wrap ) || hasFocus( $footer ) ) ) {
+ fadeIn();
+ }
+ }, 0 );
+ }
+
+ /**
+ * Fades out the admin bar based on focus on the admin bar.
+ *
+ * @since 4.1.0
+ *
+ * @returns {void}
+ */
+ function fadeOutAdminBar() {
+ if ( ! fadedAdminBar && faded ) {
+ fadedAdminBar = true;
+
+ $adminBar
+ .on( 'mouseenter.focus', function() {
+ $adminBar.addClass( 'focus-off' );
+ } )
+ .on( 'mouseleave.focus', function() {
+ $adminBar.removeClass( 'focus-off' );
+ } );
+ }
+ }
+
+ /**
+ * Fades in the admin bar.
+ *
+ * @since 4.1.0
+ *
+ * @returns {void}
+ */
+ function fadeInAdminBar() {
+ if ( fadedAdminBar ) {
+ fadedAdminBar = false;
+
+ $adminBar.off( '.focus' );
+ }
+ }
+
+ /**
+ * Fades out the edit slug box.
+ *
+ * @since 4.1.0
+ *
+ * @returns {void}
+ */
+ function fadeOutSlug() {
+ if ( ! fadedSlug && faded && ! $slug.find( ':focus').length ) {
+ fadedSlug = true;
+
+ $slug.stop().fadeTo( 'fast', 0.3 ).on( 'mouseenter.focus', fadeInSlug ).off( 'mouseleave.focus' );
+
+ $slugFocusEl.on( 'focus.focus', fadeInSlug ).off( 'blur.focus' );
+ }
+ }
+
+ /**
+ * Fades in the edit slug box.
+ *
+ * @since 4.1.0
+ *
+ * @returns {void}
+ */
+ function fadeInSlug() {
+ if ( fadedSlug ) {
+ fadedSlug = false;
+
+ $slug.stop().fadeTo( 'fast', 1 ).on( 'mouseleave.focus', fadeOutSlug ).off( 'mouseenter.focus' );
+
+ $slugFocusEl.on( 'blur.focus', fadeOutSlug ).off( 'focus.focus' );
+ }
+ }
+
+ /**
+ * Triggers the toggle on Alt + Shift + W.
+ *
+ * Keycode 87 = w.
+ *
+ * @since 4.1.0
+ *
+ * @param {event} event The event to trigger the toggle.
+ *
+ * @returns {void}
+ */
+ function toggleViaKeyboard( event ) {
+ if ( event.altKey && event.shiftKey && 87 === event.keyCode ) {
+ toggle();
+ }
+ }
+
+ if ( $( '#postdivrich' ).hasClass( 'wp-editor-expand' ) ) {
+ $content.on( 'keydown.focus-shortcut', toggleViaKeyboard );
+ }
+
+ /**
+ * Adds the distraction free writing button when setting up TinyMCE.
+ *
+ * @since 4.1.0
+ *
+ * @param {event} event The TinyMCE editor setup event.
+ * @param {object} editor The editor to add the button to.
+ *
+ * @returns {void}
+ */
+ $document.on( 'tinymce-editor-setup.focus', function( event, editor ) {
+ editor.addButton( 'dfw', {
+ active: _isOn,
+ classes: 'wp-dfw btn widget',
+ disabled: ! _isActive,
+ onclick: toggle,
+ onPostRender: function() {
+ var button = this;
+
+ editor.on( 'init', function() {
+ if ( button.disabled() ) {
+ button.hide();
+ }
+ } );
+
+ $document
+ .on( 'dfw-activate.focus', function() {
+ button.disabled( false );
+ button.show();
+ } )
+ .on( 'dfw-deactivate.focus', function() {
+ button.disabled( true );
+ button.hide();
+ } )
+ .on( 'dfw-on.focus', function() {
+ button.active( true );
+ } )
+ .on( 'dfw-off.focus', function() {
+ button.active( false );
+ } );
+ },
+ tooltip: 'Distraction-free writing mode',
+ shortcut: 'Alt+Shift+W'
+ } );
+
+ editor.addCommand( 'wpToggleDFW', toggle );
+ editor.addShortcut( 'access+w', '', 'wpToggleDFW' );
+ } );
+
+ /**
+ * Binds and unbinds events on the editor.
+ *
+ * @since 4.1.0
+ *
+ * @param {event} event The TinyMCE editor init event.
+ * @param {object} editor The editor to bind events on.
+ *
+ * @returns {void}
+ */
+ $document.on( 'tinymce-editor-init.focus', function( event, editor ) {
+ var mceBind, mceUnbind;
+
+ function focus() {
+ editorHasFocus = true;
+ }
+
+ function blur() {
+ editorHasFocus = false;
+ }
+
+ if ( editor.id === 'content' ) {
+ $editorWindow = $( editor.getWin() );
+ $editorIframe = $( editor.getContentAreaContainer() ).find( 'iframe' );
+
+ mceBind = function() {
+ editor.on( 'keydown', fadeOut );
+ editor.on( 'blur', maybeFadeIn );
+ editor.on( 'focus', focus );
+ editor.on( 'blur', blur );
+ editor.on( 'wp-autoresize', recalcEditorRect );
+ };
+
+ mceUnbind = function() {
+ editor.off( 'keydown', fadeOut );
+ editor.off( 'blur', maybeFadeIn );
+ editor.off( 'focus', focus );
+ editor.off( 'blur', blur );
+ editor.off( 'wp-autoresize', recalcEditorRect );
+ };
+
+ if ( _isOn ) {
+ mceBind();
+ }
+
+ // Bind and unbind based on the distraction free writing focus.
+ $document.on( 'dfw-on.focus', mceBind ).on( 'dfw-off.focus', mceUnbind );
+
+ // Focuse the editor when it is the target of the click event.
+ editor.on( 'click', function( event ) {
+ if ( event.target === editor.getDoc().documentElement ) {
+ editor.focus();
+ }
+ } );
+ }
+ } );
+
+ /**
+ * Binds events on quicktags init.
+ *
+ * @since 4.1.0
+ *
+ * @param {event} event The quicktags init event.
+ * @param {object} editor The editor to bind events on.
+ *
+ * @returns {void}
+ */
+ $document.on( 'quicktags-init', function( event, editor ) {
+ var $button;
+
+ // Bind the distraction free writing events if the distraction free writing button is available.
+ if ( editor.settings.buttons && ( ',' + editor.settings.buttons + ',' ).indexOf( ',dfw,' ) !== -1 ) {
+ $button = $( '#' + editor.name + '_dfw' );
+
+ $( document )
+ .on( 'dfw-activate', function() {
+ $button.prop( 'disabled', false );
+ } )
+ .on( 'dfw-deactivate', function() {
+ $button.prop( 'disabled', true );
+ } )
+ .on( 'dfw-on', function() {
+ $button.addClass( 'active' );
+ } )
+ .on( 'dfw-off', function() {
+ $button.removeClass( 'active' );
+ } );
+ }
+ } );
+
+ $document.on( 'editor-expand-on.focus', activate ).on( 'editor-expand-off.focus', deactivate );
+
+ if ( _isOn ) {
+ $content.on( 'keydown.focus', fadeOut );
+
+ $title.add( $content ).on( 'blur.focus', maybeFadeIn );
+ }
+
+ window.wp = window.wp || {};
+ window.wp.editor = window.wp.editor || {};
+ window.wp.editor.dfw = {
+ activate: activate,
+ deactivate: deactivate,
+ isActive: isActive,
+ on: on,
+ off: off,
+ toggle: toggle,
+ isOn: isOn
+ };
+ } );
+} )( window, window.jQuery );
diff --git a/www/crm/wp-admin/js/editor-expand.min.js b/www/crm/wp-admin/js/editor-expand.min.js
new file mode 100644
index 00000000..d7714c1f
--- /dev/null
+++ b/www/crm/wp-admin/js/editor-expand.min.js
@@ -0,0 +1 @@
+!function(a,b,c){"use strict";var d=b(a),e=b(document),f=b("#wpadminbar"),g=b("#wpfooter");b(function(){function c(){var a=x.value.length;o&&!o.isHidden()||(o||"tinymce"!==R)&&(a<y?U():parseInt(x.style.height,10)<x.scrollHeight&&(x.style.height=Math.ceil(x.scrollHeight)+"px",i()),y=a)}function h(){var a=d.width();T={windowHeight:d.height(),windowWidth:a,adminBarHeight:a>600?f.outerHeight():0,toolsHeight:s.outerHeight()||0,menuBarHeight:A.outerHeight()||0,visualTopHeight:t.outerHeight()||0,textTopHeight:v.outerHeight()||0,bottomHeight:z.outerHeight()||0,statusBarHeight:B.outerHeight()||0,sideSortablesHeight:C.height()||0},T.menuBarHeight<3&&(T.menuBarHeight=0)}function i(b){if(!F||!F.settings.visible){var f,i,j,k,l,m,n,p,q,x=d.scrollTop(),y=b&&b.type,G="scroll"!==y,H=o&&!o.isHidden(),N=Q,R=E.offset().top,U=1,V=r.width();!G&&T.windowHeight||h(),H||"resize"!==y||c(),H?(f=t,i=u,n=T.visualTopHeight):(f=v,i=w,n=T.textTopHeight),(H||f.length)&&(m=f.parent().offset().top,p=i.offset().top,q=i.outerHeight(),l=H?Q+n:Q+20,l=q>l+5,l?((!I||G)&&x>=m-T.toolsHeight-T.adminBarHeight&&x<=m-T.toolsHeight-T.adminBarHeight+q-N?(I=!0,s.css({position:"fixed",top:T.adminBarHeight,width:V}),H&&A.length&&A.css({position:"fixed",top:T.adminBarHeight+T.toolsHeight,width:V-2*U-(H?0:f.outerWidth()-f.width())}),f.css({position:"fixed",top:T.adminBarHeight+T.toolsHeight+T.menuBarHeight,width:V-2*U-(H?0:f.outerWidth()-f.width())})):(I||G)&&(x<=m-T.toolsHeight-T.adminBarHeight?(I=!1,s.css({position:"absolute",top:0,width:V}),H&&A.length&&A.css({position:"absolute",top:0,width:V-2*U}),f.css({position:"absolute",top:T.menuBarHeight,width:V-2*U-(H?0:f.outerWidth()-f.width())})):x>=m-T.toolsHeight-T.adminBarHeight+q-N&&(I=!1,s.css({position:"absolute",top:q-N,width:V}),H&&A.length&&A.css({position:"absolute",top:q-N,width:V-2*U}),f.css({position:"absolute",top:q-N+T.menuBarHeight,width:V-2*U-(H?0:f.outerWidth()-f.width())}))),(!J||G&&S)&&x+T.windowHeight<=p+q+T.bottomHeight+T.statusBarHeight+U?b&&b.deltaHeight>0&&b.deltaHeight<100?a.scrollBy(0,b.deltaHeight):H&&S&&(J=!0,B.css({position:"fixed",bottom:T.bottomHeight,visibility:"",width:V-2*U}),z.css({position:"fixed",bottom:0,width:V})):(!S&&J||(J||G)&&x+T.windowHeight>p+q+T.bottomHeight+T.statusBarHeight-U)&&(J=!1,B.attr("style",S?"":"visibility: hidden;"),z.attr("style",""))):G&&(s.css({position:"absolute",top:0,width:V}),H&&A.length&&A.css({position:"absolute",top:0,width:V-2*U}),f.css({position:"absolute",top:T.menuBarHeight,width:V-2*U-(H?0:f.outerWidth()-f.width())}),B.attr("style",S?"":"visibility: hidden;"),z.attr("style","")),D.width()<300&&T.windowWidth>600&&e.height()>C.height()+R+120&&T.windowHeight<q?(T.sideSortablesHeight+O+P>T.windowHeight||K||L?x+O<=R?(C.attr("style",""),K=L=!1):x>M?K?(K=!1,j=C.offset().top-T.adminBarHeight,k=g.offset().top,k<j+T.sideSortablesHeight+P&&(j=k-T.sideSortablesHeight-12),C.css({position:"absolute",top:j,bottom:""})):!L&&T.sideSortablesHeight+C.offset().top+P<x+T.windowHeight&&(L=!0,C.css({position:"fixed",top:"auto",bottom:P})):x<M&&(L?(L=!1,j=C.offset().top-P,k=g.offset().top,k<j+T.sideSortablesHeight+P&&(j=k-T.sideSortablesHeight-12),C.css({position:"absolute",top:j,bottom:""})):!K&&C.offset().top>=x+O&&(K=!0,C.css({position:"fixed",top:O,bottom:""}))):(x>=R-O?C.css({position:"fixed",top:O}):C.attr("style",""),K=L=!1),M=x):(C.attr("style",""),K=L=!1),G&&(r.css({paddingTop:T.toolsHeight}),H?u.css({paddingTop:T.visualTopHeight+T.menuBarHeight}):w.css({marginTop:T.textTopHeight})))}}function j(){c(),i()}function k(a){for(var b=1;b<6;b++)setTimeout(a,500*b)}function l(){clearTimeout(p),p=setTimeout(i,100)}function m(){a.pageYOffset&&a.pageYOffset>N&&a.scrollTo(a.pageXOffset,0),q.addClass("wp-editor-expand"),d.on("scroll.editor-expand resize.editor-expand",function(a){i(a.type),l()}),e.on("wp-collapse-menu.editor-expand postboxes-columnchange.editor-expand editor-classchange.editor-expand",i).on("postbox-toggled.editor-expand postbox-moved.editor-expand",function(){!K&&!L&&a.pageYOffset>O&&(L=!0,a.scrollBy(0,-1),i(),a.scrollBy(0,1)),i()}).on("wp-window-resized.editor-expand",function(){o&&!o.isHidden()?o.execCommand("wpAutoResize"):c()}),w.on("focus.editor-expand input.editor-expand propertychange.editor-expand",c),G(),F&&F.pubsub.subscribe("hidden",j),o&&(o.settings.wp_autoresize_on=!0,o.execCommand("wpAutoResizeOn"),o.isHidden()||o.execCommand("wpAutoResize")),o&&!o.isHidden()||c(),i(),e.trigger("editor-expand-on")}function n(){var c=parseInt(a.getUserSetting("ed_size",300),10);c<50?c=50:c>5e3&&(c=5e3),a.pageYOffset&&a.pageYOffset>N&&a.scrollTo(a.pageXOffset,0),q.removeClass("wp-editor-expand"),d.off(".editor-expand"),e.off(".editor-expand"),w.off(".editor-expand"),H(),F&&F.pubsub.unsubscribe("hidden",j),b.each([t,v,s,A,z,B,r,u,w,C],function(a,b){b&&b.attr("style","")}),I=J=K=L=!1,o&&(o.settings.wp_autoresize_on=!1,o.execCommand("wpAutoResizeOff"),o.isHidden()||(w.hide(),c&&o.theme.resizeTo(null,c))),c&&w.height(c),e.trigger("editor-expand-off")}var o,p,q=b("#postdivrich"),r=b("#wp-content-wrap"),s=b("#wp-content-editor-tools"),t=b(),u=b(),v=b("#ed_toolbar"),w=b("#content"),x=w[0],y=0,z=b("#post-status-info"),A=b(),B=b(),C=b("#side-sortables"),D=b("#postbox-container-1"),E=b("#post-body"),F=a.wp.editor&&a.wp.editor.fullscreen,G=function(){},H=function(){},I=!1,J=!1,K=!1,L=!1,M=0,N=130,O=56,P=20,Q=300,R=r.hasClass("tmce-active")?"tinymce":"html",S=!!parseInt(a.getUserSetting("hidetb"),10),T={windowHeight:0,windowWidth:0,adminBarHeight:0,toolsHeight:0,menuBarHeight:0,visualTopHeight:0,textTopHeight:0,bottomHeight:0,statusBarHeight:0,sideSortablesHeight:0},U=a._.throttle(function(){var b=a.scrollX||document.documentElement.scrollLeft,c=a.scrollY||document.documentElement.scrollTop,d=parseInt(x.style.height,10);x.style.height=Q+"px",x.scrollHeight>Q&&(x.style.height=x.scrollHeight+"px"),"undefined"!=typeof b&&a.scrollTo(b,c),x.scrollHeight<d&&i()},300);e.on("tinymce-editor-init.editor-expand",function(e,f){function g(){var a,b,c,d=f.selection.getNode();if(f.wp&&f.wp.getView&&(b=f.wp.getView(d)))c=b.getBoundingClientRect();else{a=f.selection.getRng();try{c=a.getClientRects()[0]}catch(e){}c||(c=d.getBoundingClientRect())}return!!c.height&&c}function h(a){var b=a.keyCode;b<=47&&b!==s.SPACEBAR&&b!==s.ENTER&&b!==s.DELETE&&b!==s.BACKSPACE&&b!==s.UP&&b!==s.LEFT&&b!==s.DOWN&&b!==s.UP||b>=91&&b<=93||b>=112&&b<=123||144===b||145===b||j(b)}function j(b){var c,d,e,h,i=g(),j=50;i&&(c=i.top+f.iframeElement.getBoundingClientRect().top,d=c+i.height,c-=j,d+=j,e=T.adminBarHeight+T.toolsHeight+T.menuBarHeight+T.visualTopHeight,h=T.windowHeight-(S?T.bottomHeight+T.statusBarHeight:0),h-e<i.height||(c<e&&(b===s.UP||b===s.LEFT||b===s.BACKSPACE)?a.scrollTo(a.pageXOffset,c+a.pageYOffset-e):d>h&&a.scrollTo(a.pageXOffset,d+a.pageYOffset-h)))}function l(a){a.state||i()}function m(){d.on("scroll.mce-float-panels",v),setTimeout(function(){f.execCommand("wpAutoResize"),i()},300)}function n(){d.off("scroll.mce-float-panels"),setTimeout(function(){var b=r.offset().top;a.pageYOffset>b&&a.scrollTo(a.pageXOffset,b-T.adminBarHeight),c(),i()},100),i()}function p(){S=!S}var s=a.tinymce.util.VK,v=_.debounce(function(){!b(".mce-floatpanel:hover").length&&a.tinymce.ui.FloatPanel.hideAll(),b(".mce-tooltip").hide()},1e3,!0);"content"===f.id&&(o=f,f.settings.autoresize_min_height=Q,t=r.find(".mce-toolbar-grp"),u=r.find(".mce-edit-area"),B=r.find(".mce-statusbar"),A=r.find(".mce-menubar"),G=function(){f.on("keyup",h),f.on("show",m),f.on("hide",n),f.on("wp-toolbar-toggle",p),f.on("setcontent wp-autoresize wp-toolbar-toggle",i),f.on("undo redo",j),f.on("FullscreenStateChanged",l),d.off("scroll.mce-float-panels").on("scroll.mce-float-panels",v)},H=function(){f.off("keyup",h),f.off("show",m),f.off("hide",n),f.off("wp-toolbar-toggle",p),f.off("setcontent wp-autoresize wp-toolbar-toggle",i),f.off("undo redo",j),f.off("FullscreenStateChanged",l),d.off("scroll.mce-float-panels")},q.hasClass("wp-editor-expand")&&(G(),k(i)))}),q.hasClass("wp-editor-expand")&&(m(),r.hasClass("html-active")&&k(function(){i(),c()})),b("#adv-settings .editor-expand").show(),b("#editor-expand-toggle").on("change.editor-expand",function(){b(this).prop("checked")?(m(),a.setUserSetting("editor_expand","on")):(n(),a.setUserSetting("editor_expand","off"))}),a.editorExpand={on:m,off:n}}),b(function(){function c(){z=J.offset(),z.right=z.left+J.outerWidth(),z.bottom=z.top+J.outerHeight()}function h(){S||(S=!0,e.trigger("dfw-activate"),L.on("keydown.focus-shortcut",v))}function i(){S&&(l(),S=!1,e.trigger("dfw-deactivate"),L.off("keydown.focus-shortcut"))}function j(){return S}function k(){!T&&S&&(T=!0,L.on("keydown.focus",o),K.add(L).on("blur.focus",q),o(),a.setUserSetting("post_dfw","on"),e.trigger("dfw-on"))}function l(){T&&(T=!1,K.add(L).off(".focus"),p(),J.off(".focus"),a.setUserSetting("post_dfw","off"),e.trigger("dfw-off"))}function m(){T?l():k()}function n(){return T}function o(b){var e,f=b&&b.keyCode;return a.navigator.platform&&(e=a.navigator.platform.indexOf("Mac")>-1),27===f||87===f&&b.altKey&&(!e&&b.shiftKey||e&&b.ctrlKey)?void p(b):void(b&&(b.metaKey||b.ctrlKey&&!b.altKey||b.altKey&&b.shiftKey||f&&(f<=47&&8!==f&&13!==f&&32!==f&&46!==f||f>=91&&f<=93||f>=112&&f<=135||f>=144&&f<=150||f>=224))||(w||(w=!0,clearTimeout(F),F=setTimeout(function(){M.show()},600),J.css("z-index",9998),M.on("mouseenter.focus",function(){c(),d.on("scroll.focus",function(){var b=a.pageYOffset;D&&C&&D!==b&&(C<z.top-W||C>z.bottom+W)&&p(),D=b})}).on("mouseleave.focus",function(){A=B=null,U=V=0,d.off("scroll.focus")}).on("mousemove.focus",function(b){var c=b.clientX,d=b.clientY,e=a.pageYOffset,f=a.pageXOffset;if(A&&B&&(c!==A||d!==B))if(d<=B&&d<z.top-e||d>=B&&d>z.bottom-e||c<=A&&c<z.left-f||c>=A&&c>z.right-f){if(U+=Math.abs(A-c),V+=Math.abs(B-d),(d<=z.top-W-e||d>=z.bottom+W-e||c<=z.left-W-f||c>=z.right+W-f)&&(U>10||V>10))return p(),A=B=null,void(U=V=0)}else U=V=0;A=c,B=d}).on("touchstart.focus",function(a){a.preventDefault(),p()}),J.off("mouseenter.focus"),E&&(clearTimeout(E),E=null),H.addClass("focus-on").removeClass("focus-off")),r(),t()))}function p(a){w&&(w=!1,clearTimeout(F),F=setTimeout(function(){M.hide()},200),J.css("z-index",""),M.off("mouseenter.focus mouseleave.focus mousemove.focus touchstart.focus"),"undefined"==typeof a&&J.on("mouseenter.focus",function(){(b.contains(J.get(0),document.activeElement)||G)&&o()}),E=setTimeout(function(){E=null,J.off("mouseenter.focus")},1e3),H.addClass("focus-off").removeClass("focus-on")),s(),u()}function q(){setTimeout(function(){function a(a){return b.contains(a.get(0),document.activeElement)}var c=document.activeElement.compareDocumentPosition(J.get(0));2!==c&&4!==c||!(a(P)||a(I)||a(g))||p()},0)}function r(){!x&&w&&(x=!0,f.on("mouseenter.focus",function(){f.addClass("focus-off")}).on("mouseleave.focus",function(){f.removeClass("focus-off")}))}function s(){x&&(x=!1,f.off(".focus"))}function t(){y||!w||N.find(":focus").length||(y=!0,N.stop().fadeTo("fast",.3).on("mouseenter.focus",u).off("mouseleave.focus"),O.on("focus.focus",u).off("blur.focus"))}function u(){y&&(y=!1,N.stop().fadeTo("fast",1).on("mouseleave.focus",t).off("mouseenter.focus"),O.on("blur.focus",t).off("focus.focus"))}function v(a){a.altKey&&a.shiftKey&&87===a.keyCode&&m()}var w,x,y,z,A,B,C,D,E,F,G,H=b(document.body),I=b("#wpcontent"),J=b("#post-body-content"),K=b("#title"),L=b("#content"),M=b(document.createElement("DIV")),N=b("#edit-slug-box"),O=N.find("a").add(N.find("button")).add(N.find("input")),P=b("#adminmenuwrap"),Q=b(),R=b(),S="on"===a.getUserSetting("editor_expand","on"),T=!!S&&"on"===a.getUserSetting("post_dfw"),U=0,V=0,W=20;H.append(M),M.css({display:"none",position:"fixed",top:f.height(),right:0,bottom:0,left:0,"z-index":9997}),J.css({position:"relative"}),d.on("mousemove.focus",function(a){C=a.pageY}),b("#postdivrich").hasClass("wp-editor-expand")&&L.on("keydown.focus-shortcut",v),e.on("tinymce-editor-setup.focus",function(a,b){b.addButton("dfw",{active:T,classes:"wp-dfw btn widget",disabled:!S,onclick:m,onPostRender:function(){var a=this;b.on("init",function(){a.disabled()&&a.hide()}),e.on("dfw-activate.focus",function(){a.disabled(!1),a.show()}).on("dfw-deactivate.focus",function(){a.disabled(!0),a.hide()}).on("dfw-on.focus",function(){a.active(!0)}).on("dfw-off.focus",function(){a.active(!1)})},tooltip:"Distraction-free writing mode",shortcut:"Alt+Shift+W"}),b.addCommand("wpToggleDFW",m),b.addShortcut("access+w","","wpToggleDFW")}),e.on("tinymce-editor-init.focus",function(a,d){function f(){G=!0}function g(){G=!1}var h,i;"content"===d.id&&(Q=b(d.getWin()),R=b(d.getContentAreaContainer()).find("iframe"),h=function(){d.on("keydown",o),d.on("blur",q),d.on("focus",f),d.on("blur",g),d.on("wp-autoresize",c)},i=function(){d.off("keydown",o),d.off("blur",q),d.off("focus",f),d.off("blur",g),d.off("wp-autoresize",c)},T&&h(),e.on("dfw-on.focus",h).on("dfw-off.focus",i),d.on("click",function(a){a.target===d.getDoc().documentElement&&d.focus()}))}),e.on("quicktags-init",function(a,c){var d;c.settings.buttons&&(","+c.settings.buttons+",").indexOf(",dfw,")!==-1&&(d=b("#"+c.name+"_dfw"),b(document).on("dfw-activate",function(){d.prop("disabled",!1)}).on("dfw-deactivate",function(){d.prop("disabled",!0)}).on("dfw-on",function(){d.addClass("active")}).on("dfw-off",function(){d.removeClass("active")}))}),e.on("editor-expand-on.focus",h).on("editor-expand-off.focus",i),T&&(L.on("keydown.focus",o),K.add(L).on("blur.focus",q)),a.wp=a.wp||{},a.wp.editor=a.wp.editor||{},a.wp.editor.dfw={activate:h,deactivate:i,isActive:j,on:k,off:l,toggle:m,isOn:n}})}(window,window.jQuery); \ No newline at end of file
diff --git a/www/crm/wp-admin/js/editor.js b/www/crm/wp-admin/js/editor.js
new file mode 100644
index 00000000..0620b312
--- /dev/null
+++ b/www/crm/wp-admin/js/editor.js
@@ -0,0 +1,1414 @@
+/**
+ * @output wp-admin/js/editor.js
+ */
+
+window.wp = window.wp || {};
+
+( function( $, wp ) {
+ wp.editor = wp.editor || {};
+
+ /**
+ * Utility functions for the editor.
+ *
+ * @since 2.5.0
+ */
+ function SwitchEditors() {
+ var tinymce, $$,
+ exports = {};
+
+ function init() {
+ if ( ! tinymce && window.tinymce ) {
+ tinymce = window.tinymce;
+ $$ = tinymce.$;
+
+ /**
+ * Handles onclick events for the Visual/Text tabs.
+ *
+ * @since 4.3.0
+ *
+ * @returns {void}
+ */
+ $$( document ).on( 'click', function( event ) {
+ var id, mode,
+ target = $$( event.target );
+
+ if ( target.hasClass( 'wp-switch-editor' ) ) {
+ id = target.attr( 'data-wp-editor-id' );
+ mode = target.hasClass( 'switch-tmce' ) ? 'tmce' : 'html';
+ switchEditor( id, mode );
+ }
+ });
+ }
+ }
+
+ /**
+ * Returns the height of the editor toolbar(s) in px.
+ *
+ * @since 3.9.0
+ *
+ * @param {Object} editor The TinyMCE editor.
+ * @returns {number} If the height is between 10 and 200 return the height,
+ * else return 30.
+ */
+ function getToolbarHeight( editor ) {
+ var node = $$( '.mce-toolbar-grp', editor.getContainer() )[0],
+ height = node && node.clientHeight;
+
+ if ( height && height > 10 && height < 200 ) {
+ return parseInt( height, 10 );
+ }
+
+ return 30;
+ }
+
+ /**
+ * Switches the editor between Visual and Text mode.
+ *
+ * @since 2.5.0
+ *
+ * @memberof switchEditors
+ *
+ * @param {string} id The id of the editor you want to change the editor mode for. Default: `content`.
+ * @param {string} mode The mode you want to switch to. Default: `toggle`.
+ * @returns {void}
+ */
+ function switchEditor( id, mode ) {
+ id = id || 'content';
+ mode = mode || 'toggle';
+
+ var editorHeight, toolbarHeight, iframe,
+ editor = tinymce.get( id ),
+ wrap = $$( '#wp-' + id + '-wrap' ),
+ $textarea = $$( '#' + id ),
+ textarea = $textarea[0];
+
+ if ( 'toggle' === mode ) {
+ if ( editor && ! editor.isHidden() ) {
+ mode = 'html';
+ } else {
+ mode = 'tmce';
+ }
+ }
+
+ if ( 'tmce' === mode || 'tinymce' === mode ) {
+ // If the editor is visible we are already in `tinymce` mode.
+ if ( editor && ! editor.isHidden() ) {
+ return false;
+ }
+
+ // Insert closing tags for any open tags in QuickTags.
+ if ( typeof( window.QTags ) !== 'undefined' ) {
+ window.QTags.closeAllTags( id );
+ }
+
+ editorHeight = parseInt( textarea.style.height, 10 ) || 0;
+
+ var keepSelection = false;
+ if ( editor ) {
+ keepSelection = editor.getParam( 'wp_keep_scroll_position' );
+ } else {
+ keepSelection = window.tinyMCEPreInit.mceInit[ id ] &&
+ window.tinyMCEPreInit.mceInit[ id ].wp_keep_scroll_position;
+ }
+
+ if ( keepSelection ) {
+ // Save the selection
+ addHTMLBookmarkInTextAreaContent( $textarea );
+ }
+
+ if ( editor ) {
+ editor.show();
+
+ // No point to resize the iframe in iOS.
+ if ( ! tinymce.Env.iOS && editorHeight ) {
+ toolbarHeight = getToolbarHeight( editor );
+ editorHeight = editorHeight - toolbarHeight + 14;
+
+ // Sane limit for the editor height.
+ if ( editorHeight > 50 && editorHeight < 5000 ) {
+ editor.theme.resizeTo( null, editorHeight );
+ }
+ }
+
+ if ( editor.getParam( 'wp_keep_scroll_position' ) ) {
+ // Restore the selection
+ focusHTMLBookmarkInVisualEditor( editor );
+ }
+ } else {
+ tinymce.init( window.tinyMCEPreInit.mceInit[ id ] );
+ }
+
+ wrap.removeClass( 'html-active' ).addClass( 'tmce-active' );
+ $textarea.attr( 'aria-hidden', true );
+ window.setUserSetting( 'editor', 'tinymce' );
+
+ } else if ( 'html' === mode ) {
+ // If the editor is hidden (Quicktags is shown) we don't need to switch.
+ if ( editor && editor.isHidden() ) {
+ return false;
+ }
+
+ if ( editor ) {
+ // Don't resize the textarea in iOS. The iframe is forced to 100% height there, we shouldn't match it.
+ if ( ! tinymce.Env.iOS ) {
+ iframe = editor.iframeElement;
+ editorHeight = iframe ? parseInt( iframe.style.height, 10 ) : 0;
+
+ if ( editorHeight ) {
+ toolbarHeight = getToolbarHeight( editor );
+ editorHeight = editorHeight + toolbarHeight - 14;
+
+ // Sane limit for the textarea height.
+ if ( editorHeight > 50 && editorHeight < 5000 ) {
+ textarea.style.height = editorHeight + 'px';
+ }
+ }
+ }
+
+ var selectionRange = null;
+
+ if ( editor.getParam( 'wp_keep_scroll_position' ) ) {
+ selectionRange = findBookmarkedPosition( editor );
+ }
+
+ editor.hide();
+
+ if ( selectionRange ) {
+ selectTextInTextArea( editor, selectionRange );
+ }
+ } else {
+ // There is probably a JS error on the page. The TinyMCE editor instance doesn't exist. Show the textarea.
+ $textarea.css({ 'display': '', 'visibility': '' });
+ }
+
+ wrap.removeClass( 'tmce-active' ).addClass( 'html-active' );
+ $textarea.attr( 'aria-hidden', false );
+ window.setUserSetting( 'editor', 'html' );
+ }
+ }
+
+ /**
+ * Checks if a cursor is inside an HTML tag or comment.
+ *
+ * In order to prevent breaking HTML tags when selecting text, the cursor
+ * must be moved to either the start or end of the tag.
+ *
+ * This will prevent the selection marker to be inserted in the middle of an HTML tag.
+ *
+ * This function gives information whether the cursor is inside a tag or not, as well as
+ * the tag type, if it is a closing tag and check if the HTML tag is inside a shortcode tag,
+ * e.g. `[caption]<img.../>..`.
+ *
+ * @param {string} content The test content where the cursor is.
+ * @param {number} cursorPosition The cursor position inside the content.
+ *
+ * @returns {(null|Object)} Null if cursor is not in a tag, Object if the cursor is inside a tag.
+ */
+ function getContainingTagInfo( content, cursorPosition ) {
+ var lastLtPos = content.lastIndexOf( '<', cursorPosition - 1 ),
+ lastGtPos = content.lastIndexOf( '>', cursorPosition );
+
+ if ( lastLtPos > lastGtPos || content.substr( cursorPosition, 1 ) === '>' ) {
+ // find what the tag is
+ var tagContent = content.substr( lastLtPos ),
+ tagMatch = tagContent.match( /<\s*(\/)?(\w+|\!-{2}.*-{2})/ );
+
+ if ( ! tagMatch ) {
+ return null;
+ }
+
+ var tagType = tagMatch[2],
+ closingGt = tagContent.indexOf( '>' );
+
+ return {
+ ltPos: lastLtPos,
+ gtPos: lastLtPos + closingGt + 1, // offset by one to get the position _after_ the character,
+ tagType: tagType,
+ isClosingTag: !! tagMatch[1]
+ };
+ }
+ return null;
+ }
+
+ /**
+ * Checks if the cursor is inside a shortcode
+ *
+ * If the cursor is inside a shortcode wrapping tag, e.g. `[caption]` it's better to
+ * move the selection marker to before or after the shortcode.
+ *
+ * For example `[caption]` rewrites/removes anything that's between the `[caption]` tag and the
+ * `<img/>` tag inside.
+ *
+ * `[caption]<span>ThisIsGone</span><img .../>[caption]`
+ *
+ * Moving the selection to before or after the short code is better, since it allows to select
+ * something, instead of just losing focus and going to the start of the content.
+ *
+ * @param {string} content The text content to check against.
+ * @param {number} cursorPosition The cursor position to check.
+ *
+ * @return {(undefined|Object)} Undefined if the cursor is not wrapped in a shortcode tag.
+ * Information about the wrapping shortcode tag if it's wrapped in one.
+ */
+ function getShortcodeWrapperInfo( content, cursorPosition ) {
+ var contentShortcodes = getShortCodePositionsInText( content );
+
+ for ( var i = 0; i < contentShortcodes.length; i++ ) {
+ var element = contentShortcodes[ i ];
+
+ if ( cursorPosition >= element.startIndex && cursorPosition <= element.endIndex ) {
+ return element;
+ }
+ }
+ }
+
+ /**
+ * Gets a list of unique shortcodes or shortcode-look-alikes in the content.
+ *
+ * @param {string} content The content we want to scan for shortcodes.
+ */
+ function getShortcodesInText( content ) {
+ var shortcodes = content.match( /\[+([\w_-])+/g ),
+ result = [];
+
+ if ( shortcodes ) {
+ for ( var i = 0; i < shortcodes.length; i++ ) {
+ var shortcode = shortcodes[ i ].replace( /^\[+/g, '' );
+
+ if ( result.indexOf( shortcode ) === -1 ) {
+ result.push( shortcode );
+ }
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Gets all shortcodes and their positions in the content
+ *
+ * This function returns all the shortcodes that could be found in the textarea content
+ * along with their character positions and boundaries.
+ *
+ * This is used to check if the selection cursor is inside the boundaries of a shortcode
+ * and move it accordingly, to avoid breakage.
+ *
+ * @link adjustTextAreaSelectionCursors
+ *
+ * The information can also be used in other cases when we need to lookup shortcode data,
+ * as it's already structured!
+ *
+ * @param {string} content The content we want to scan for shortcodes
+ */
+ function getShortCodePositionsInText( content ) {
+ var allShortcodes = getShortcodesInText( content ), shortcodeInfo;
+
+ if ( allShortcodes.length === 0 ) {
+ return [];
+ }
+
+ var shortcodeDetailsRegexp = wp.shortcode.regexp( allShortcodes.join( '|' ) ),
+ shortcodeMatch, // Define local scope for the variable to be used in the loop below.
+ shortcodesDetails = [];
+
+ while ( shortcodeMatch = shortcodeDetailsRegexp.exec( content ) ) {
+ /**
+ * Check if the shortcode should be shown as plain text.
+ *
+ * This corresponds to the [[shortcode]] syntax, which doesn't parse the shortcode
+ * and just shows it as text.
+ */
+ var showAsPlainText = shortcodeMatch[1] === '[';
+
+ shortcodeInfo = {
+ shortcodeName: shortcodeMatch[2],
+ showAsPlainText: showAsPlainText,
+ startIndex: shortcodeMatch.index,
+ endIndex: shortcodeMatch.index + shortcodeMatch[0].length,
+ length: shortcodeMatch[0].length
+ };
+
+ shortcodesDetails.push( shortcodeInfo );
+ }
+
+ /**
+ * Get all URL matches, and treat them as embeds.
+ *
+ * Since there isn't a good way to detect if a URL by itself on a line is a previewable
+ * object, it's best to treat all of them as such.
+ *
+ * This means that the selection will capture the whole URL, in a similar way shrotcodes
+ * are treated.
+ */
+ var urlRegexp = new RegExp(
+ '(^|[\\n\\r][\\n\\r]|<p>)(https?:\\/\\/[^\s"]+?)(<\\/p>\s*|[\\n\\r][\\n\\r]|$)', 'gi'
+ );
+
+ while ( shortcodeMatch = urlRegexp.exec( content ) ) {
+ shortcodeInfo = {
+ shortcodeName: 'url',
+ showAsPlainText: false,
+ startIndex: shortcodeMatch.index,
+ endIndex: shortcodeMatch.index + shortcodeMatch[ 0 ].length,
+ length: shortcodeMatch[ 0 ].length,
+ urlAtStartOfContent: shortcodeMatch[ 1 ] === '',
+ urlAtEndOfContent: shortcodeMatch[ 3 ] === ''
+ };
+
+ shortcodesDetails.push( shortcodeInfo );
+ }
+
+ return shortcodesDetails;
+ }
+
+ /**
+ * Generate a cursor marker element to be inserted in the content.
+ *
+ * `span` seems to be the least destructive element that can be used.
+ *
+ * Using DomQuery syntax to create it, since it's used as both text and as a DOM element.
+ *
+ * @param {Object} domLib DOM library instance.
+ * @param {string} content The content to insert into the cusror marker element.
+ */
+ function getCursorMarkerSpan( domLib, content ) {
+ return domLib( '<span>' ).css( {
+ display: 'inline-block',
+ width: 0,
+ overflow: 'hidden',
+ 'line-height': 0
+ } )
+ .html( content ? content : '' );
+ }
+
+ /**
+ * Gets adjusted selection cursor positions according to HTML tags, comments, and shortcodes.
+ *
+ * Shortcodes and HTML codes are a bit of a special case when selecting, since they may render
+ * content in Visual mode. If we insert selection markers somewhere inside them, it's really possible
+ * to break the syntax and render the HTML tag or shortcode broken.
+ *
+ * @link getShortcodeWrapperInfo
+ *
+ * @param {string} content Textarea content that the cursors are in
+ * @param {{cursorStart: number, cursorEnd: number}} cursorPositions Cursor start and end positions
+ *
+ * @return {{cursorStart: number, cursorEnd: number}}
+ */
+ function adjustTextAreaSelectionCursors( content, cursorPositions ) {
+ var voidElements = [
+ 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',
+ 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr'
+ ];
+
+ var cursorStart = cursorPositions.cursorStart,
+ cursorEnd = cursorPositions.cursorEnd,
+ // check if the cursor is in a tag and if so, adjust it
+ isCursorStartInTag = getContainingTagInfo( content, cursorStart );
+
+ if ( isCursorStartInTag ) {
+ /**
+ * Only move to the start of the HTML tag (to select the whole element) if the tag
+ * is part of the voidElements list above.
+ *
+ * This list includes tags that are self-contained and don't need a closing tag, according to the
+ * HTML5 specification.
+ *
+ * This is done in order to make selection of text a bit more consistent when selecting text in
+ * `<p>` tags or such.
+ *
+ * In cases where the tag is not a void element, the cursor is put to the end of the tag,
+ * so it's either between the opening and closing tag elements or after the closing tag.
+ */
+ if ( voidElements.indexOf( isCursorStartInTag.tagType ) !== -1 ) {
+ cursorStart = isCursorStartInTag.ltPos;
+ } else {
+ cursorStart = isCursorStartInTag.gtPos;
+ }
+ }
+
+ var isCursorEndInTag = getContainingTagInfo( content, cursorEnd );
+ if ( isCursorEndInTag ) {
+ cursorEnd = isCursorEndInTag.gtPos;
+ }
+
+ var isCursorStartInShortcode = getShortcodeWrapperInfo( content, cursorStart );
+ if ( isCursorStartInShortcode && ! isCursorStartInShortcode.showAsPlainText ) {
+ /**
+ * If a URL is at the start or the end of the content,
+ * the selection doesn't work, because it inserts a marker in the text,
+ * which breaks the embedURL detection.
+ *
+ * The best way to avoid that and not modify the user content is to
+ * adjust the cursor to either after or before URL.
+ */
+ if ( isCursorStartInShortcode.urlAtStartOfContent ) {
+ cursorStart = isCursorStartInShortcode.endIndex;
+ } else {
+ cursorStart = isCursorStartInShortcode.startIndex;
+ }
+ }
+
+ var isCursorEndInShortcode = getShortcodeWrapperInfo( content, cursorEnd );
+ if ( isCursorEndInShortcode && ! isCursorEndInShortcode.showAsPlainText ) {
+ if ( isCursorEndInShortcode.urlAtEndOfContent ) {
+ cursorEnd = isCursorEndInShortcode.startIndex;
+ } else {
+ cursorEnd = isCursorEndInShortcode.endIndex;
+ }
+ }
+
+ return {
+ cursorStart: cursorStart,
+ cursorEnd: cursorEnd
+ };
+ }
+
+ /**
+ * Adds text selection markers in the editor textarea.
+ *
+ * Adds selection markers in the content of the editor `textarea`.
+ * The method directly manipulates the `textarea` content, to allow TinyMCE plugins
+ * to run after the markers are added.
+ *
+ * @param {object} $textarea TinyMCE's textarea wrapped as a DomQuery object
+ */
+ function addHTMLBookmarkInTextAreaContent( $textarea ) {
+ if ( ! $textarea || ! $textarea.length ) {
+ // If no valid $textarea object is provided, there's nothing we can do.
+ return;
+ }
+
+ var textArea = $textarea[0],
+ textAreaContent = textArea.value,
+
+ adjustedCursorPositions = adjustTextAreaSelectionCursors( textAreaContent, {
+ cursorStart: textArea.selectionStart,
+ cursorEnd: textArea.selectionEnd
+ } ),
+
+ htmlModeCursorStartPosition = adjustedCursorPositions.cursorStart,
+ htmlModeCursorEndPosition = adjustedCursorPositions.cursorEnd,
+
+ mode = htmlModeCursorStartPosition !== htmlModeCursorEndPosition ? 'range' : 'single',
+
+ selectedText = null,
+ cursorMarkerSkeleton = getCursorMarkerSpan( $$, '&#65279;' ).attr( 'data-mce-type','bookmark' );
+
+ if ( mode === 'range' ) {
+ var markedText = textArea.value.slice( htmlModeCursorStartPosition, htmlModeCursorEndPosition ),
+ bookMarkEnd = cursorMarkerSkeleton.clone().addClass( 'mce_SELRES_end' );
+
+ selectedText = [
+ markedText,
+ bookMarkEnd[0].outerHTML
+ ].join( '' );
+ }
+
+ textArea.value = [
+ textArea.value.slice( 0, htmlModeCursorStartPosition ), // text until the cursor/selection position
+ cursorMarkerSkeleton.clone() // cursor/selection start marker
+ .addClass( 'mce_SELRES_start' )[0].outerHTML,
+ selectedText, // selected text with end cursor/position marker
+ textArea.value.slice( htmlModeCursorEndPosition ) // text from last cursor/selection position to end
+ ].join( '' );
+ }
+
+ /**
+ * Focuses the selection markers in Visual mode.
+ *
+ * The method checks for existing selection markers inside the editor DOM (Visual mode)
+ * and create a selection between the two nodes using the DOM `createRange` selection API
+ *
+ * If there is only a single node, select only the single node through TinyMCE's selection API
+ *
+ * @param {Object} editor TinyMCE editor instance.
+ */
+ function focusHTMLBookmarkInVisualEditor( editor ) {
+ var startNode = editor.$( '.mce_SELRES_start' ).attr( 'data-mce-bogus', 1 ),
+ endNode = editor.$( '.mce_SELRES_end' ).attr( 'data-mce-bogus', 1 );
+
+ if ( startNode.length ) {
+ editor.focus();
+
+ if ( ! endNode.length ) {
+ editor.selection.select( startNode[0] );
+ } else {
+ var selection = editor.getDoc().createRange();
+
+ selection.setStartAfter( startNode[0] );
+ selection.setEndBefore( endNode[0] );
+
+ editor.selection.setRng( selection );
+ }
+ }
+
+ if ( editor.getParam( 'wp_keep_scroll_position' ) ) {
+ scrollVisualModeToStartElement( editor, startNode );
+ }
+
+ removeSelectionMarker( startNode );
+ removeSelectionMarker( endNode );
+
+ editor.save();
+ }
+
+ /**
+ * Removes selection marker and the parent node if it is an empty paragraph.
+ *
+ * By default TinyMCE wraps loose inline tags in a `<p>`.
+ * When removing selection markers an empty `<p>` may be left behind, remove it.
+ *
+ * @param {object} $marker The marker to be removed from the editor DOM, wrapped in an instnce of `editor.$`
+ */
+ function removeSelectionMarker( $marker ) {
+ var $markerParent = $marker.parent();
+
+ $marker.remove();
+
+ //Remove empty paragraph left over after removing the marker.
+ if ( $markerParent.is( 'p' ) && ! $markerParent.children().length && ! $markerParent.text() ) {
+ $markerParent.remove();
+ }
+ }
+
+ /**
+ * Scrolls the content to place the selected element in the center of the screen.
+ *
+ * Takes an element, that is usually the selection start element, selected in
+ * `focusHTMLBookmarkInVisualEditor()` and scrolls the screen so the element appears roughly
+ * in the middle of the screen.
+ *
+ * I order to achieve the proper positioning, the editor media bar and toolbar are subtracted
+ * from the window height, to get the proper viewport window, that the user sees.
+ *
+ * @param {Object} editor TinyMCE editor instance.
+ * @param {Object} element HTMLElement that should be scrolled into view.
+ */
+ function scrollVisualModeToStartElement( editor, element ) {
+ var elementTop = editor.$( element ).offset().top,
+ TinyMCEContentAreaTop = editor.$( editor.getContentAreaContainer() ).offset().top,
+
+ toolbarHeight = getToolbarHeight( editor ),
+
+ edTools = $( '#wp-content-editor-tools' ),
+ edToolsHeight = 0,
+ edToolsOffsetTop = 0,
+
+ $scrollArea;
+
+ if ( edTools.length ) {
+ edToolsHeight = edTools.height();
+ edToolsOffsetTop = edTools.offset().top;
+ }
+
+ var windowHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight,
+
+ selectionPosition = TinyMCEContentAreaTop + elementTop,
+ visibleAreaHeight = windowHeight - ( edToolsHeight + toolbarHeight );
+
+ // There's no need to scroll if the selection is inside the visible area.
+ if ( selectionPosition < visibleAreaHeight ) {
+ return;
+ }
+
+ /**
+ * The minimum scroll height should be to the top of the editor, to offer a consistent
+ * experience.
+ *
+ * In order to find the top of the editor, we calculate the offset of `#wp-content-editor-tools` and
+ * subtracting the height. This gives the scroll position where the top of the editor tools aligns with
+ * the top of the viewport (under the Master Bar)
+ */
+ var adjustedScroll;
+ if ( editor.settings.wp_autoresize_on ) {
+ $scrollArea = $( 'html,body' );
+ adjustedScroll = Math.max( selectionPosition - visibleAreaHeight / 2, edToolsOffsetTop - edToolsHeight );
+ } else {
+ $scrollArea = $( editor.contentDocument ).find( 'html,body' );
+ adjustedScroll = elementTop;
+ }
+
+ $scrollArea.animate( {
+ scrollTop: parseInt( adjustedScroll, 10 )
+ }, 100 );
+ }
+
+ /**
+ * This method was extracted from the `SaveContent` hook in
+ * `wp-includes/js/tinymce/plugins/wordpress/plugin.js`.
+ *
+ * It's needed here, since the method changes the content a bit, which confuses the cursor position.
+ *
+ * @param {Object} event TinyMCE event object.
+ */
+ function fixTextAreaContent( event ) {
+ // Keep empty paragraphs :(
+ event.content = event.content.replace( /<p>(?:<br ?\/?>|\u00a0|\uFEFF| )*<\/p>/g, '<p>&nbsp;</p>' );
+ }
+
+ /**
+ * Finds the current selection position in the Visual editor.
+ *
+ * Find the current selection in the Visual editor by inserting marker elements at the start
+ * and end of the selection.
+ *
+ * Uses the standard DOM selection API to achieve that goal.
+ *
+ * Check the notes in the comments in the code below for more information on some gotchas
+ * and why this solution was chosen.
+ *
+ * @param {Object} editor The editor where we must find the selection
+ * @returns {(null|Object)} The selection range position in the editor
+ */
+ function findBookmarkedPosition( editor ) {
+ // Get the TinyMCE `window` reference, since we need to access the raw selection.
+ var TinyMCEWindow = editor.getWin(),
+ selection = TinyMCEWindow.getSelection();
+
+ if ( ! selection || selection.rangeCount < 1 ) {
+ // no selection, no need to continue.
+ return;
+ }
+
+ /**
+ * The ID is used to avoid replacing user generated content, that may coincide with the
+ * format specified below.
+ * @type {string}
+ */
+ var selectionID = 'SELRES_' + Math.random();
+
+ /**
+ * Create two marker elements that will be used to mark the start and the end of the range.
+ *
+ * The elements have hardcoded style that makes them invisible. This is done to avoid seeing
+ * random content flickering in the editor when switching between modes.
+ */
+ var spanSkeleton = getCursorMarkerSpan( editor.$, selectionID ),
+ startElement = spanSkeleton.clone().addClass( 'mce_SELRES_start' ),
+ endElement = spanSkeleton.clone().addClass( 'mce_SELRES_end' );
+
+ /**
+ * Inspired by:
+ * @link https://stackoverflow.com/a/17497803/153310
+ *
+ * Why do it this way and not with TinyMCE's bookmarks?
+ *
+ * TinyMCE's bookmarks are very nice when working with selections and positions, BUT
+ * there is no way to determine the precise position of the bookmark when switching modes, since
+ * TinyMCE does some serialization of the content, to fix things like shortcodes, run plugins, prettify
+ * HTML code and so on. In this process, the bookmark markup gets lost.
+ *
+ * If we decide to hook right after the bookmark is added, we can see where the bookmark is in the raw HTML
+ * in TinyMCE. Unfortunately this state is before the serialization, so any visual markup in the content will
+ * throw off the positioning.
+ *
+ * To avoid this, we insert two custom `span`s that will serve as the markers at the beginning and end of the
+ * selection.
+ *
+ * Why not use TinyMCE's selection API or the DOM API to wrap the contents? Because if we do that, this creates
+ * a new node, which is inserted in the dom. Now this will be fine, if we worked with fixed selections to
+ * full nodes. Unfortunately in our case, the user can select whatever they like, which means that the
+ * selection may start in the middle of one node and end in the middle of a completely different one. If we
+ * wrap the selection in another node, this will create artifacts in the content.
+ *
+ * Using the method below, we insert the custom `span` nodes at the start and at the end of the selection.
+ * This helps us not break the content and also gives us the option to work with multi-node selections without
+ * breaking the markup.
+ */
+ var range = selection.getRangeAt( 0 ),
+ startNode = range.startContainer,
+ startOffset = range.startOffset,
+ boundaryRange = range.cloneRange();
+
+ /**
+ * If the selection is on a shortcode with Live View, TinyMCE creates a bogus markup,
+ * which we have to account for.
+ */
+ if ( editor.$( startNode ).parents( '.mce-offscreen-selection' ).length > 0 ) {
+ startNode = editor.$( '[data-mce-selected]' )[0];
+
+ /**
+ * Marking the start and end element with `data-mce-object-selection` helps
+ * discern when the selected object is a Live Preview selection.
+ *
+ * This way we can adjust the selection to properly select only the content, ignoring
+ * whitespace inserted around the selected object by the Editor.
+ */
+ startElement.attr( 'data-mce-object-selection', 'true' );
+ endElement.attr( 'data-mce-object-selection', 'true' );
+
+ editor.$( startNode ).before( startElement[0] );
+ editor.$( startNode ).after( endElement[0] );
+ } else {
+ boundaryRange.collapse( false );
+ boundaryRange.insertNode( endElement[0] );
+
+ boundaryRange.setStart( startNode, startOffset );
+ boundaryRange.collapse( true );
+ boundaryRange.insertNode( startElement[0] );
+
+ range.setStartAfter( startElement[0] );
+ range.setEndBefore( endElement[0] );
+ selection.removeAllRanges();
+ selection.addRange( range );
+ }
+
+ /**
+ * Now the editor's content has the start/end nodes.
+ *
+ * Unfortunately the content goes through some more changes after this step, before it gets inserted
+ * in the `textarea`. This means that we have to do some minor cleanup on our own here.
+ */
+ editor.on( 'GetContent', fixTextAreaContent );
+
+ var content = removep( editor.getContent() );
+
+ editor.off( 'GetContent', fixTextAreaContent );
+
+ startElement.remove();
+ endElement.remove();
+
+ var startRegex = new RegExp(
+ '<span[^>]*\\s*class="mce_SELRES_start"[^>]+>\\s*' + selectionID + '[^<]*<\\/span>(\\s*)'
+ );
+
+ var endRegex = new RegExp(
+ '(\\s*)<span[^>]*\\s*class="mce_SELRES_end"[^>]+>\\s*' + selectionID + '[^<]*<\\/span>'
+ );
+
+ var startMatch = content.match( startRegex ),
+ endMatch = content.match( endRegex );
+
+ if ( ! startMatch ) {
+ return null;
+ }
+
+ var startIndex = startMatch.index,
+ startMatchLength = startMatch[0].length,
+ endIndex = null;
+
+ if (endMatch) {
+ /**
+ * Adjust the selection index, if the selection contains a Live Preview object or not.
+ *
+ * Check where the `data-mce-object-selection` attribute is set above for more context.
+ */
+ if ( startMatch[0].indexOf( 'data-mce-object-selection' ) !== -1 ) {
+ startMatchLength -= startMatch[1].length;
+ }
+
+ var endMatchIndex = endMatch.index;
+
+ if ( endMatch[0].indexOf( 'data-mce-object-selection' ) !== -1 ) {
+ endMatchIndex -= endMatch[1].length;
+ }
+
+ // We need to adjust the end position to discard the length of the range start marker
+ endIndex = endMatchIndex - startMatchLength;
+ }
+
+ return {
+ start: startIndex,
+ end: endIndex
+ };
+ }
+
+ /**
+ * Selects text in the TinyMCE `textarea`.
+ *
+ * Selects the text in TinyMCE's textarea that's between `selection.start` and `selection.end`.
+ *
+ * For `selection` parameter:
+ * @link findBookmarkedPosition
+ *
+ * @param {Object} editor TinyMCE's editor instance.
+ * @param {Object} selection Selection data.
+ */
+ function selectTextInTextArea( editor, selection ) {
+ // only valid in the text area mode and if we have selection
+ if ( ! selection ) {
+ return;
+ }
+
+ var textArea = editor.getElement(),
+ start = selection.start,
+ end = selection.end || selection.start;
+
+ if ( textArea.focus ) {
+ // Wait for the Visual editor to be hidden, then focus and scroll to the position
+ setTimeout( function() {
+ textArea.setSelectionRange( start, end );
+ if ( textArea.blur ) {
+ // defocus before focusing
+ textArea.blur();
+ }
+ textArea.focus();
+ }, 100 );
+ }
+ }
+
+ // Restore the selection when the editor is initialized. Needed when the Text editor is the default.
+ $( document ).on( 'tinymce-editor-init.keep-scroll-position', function( event, editor ) {
+ if ( editor.$( '.mce_SELRES_start' ).length ) {
+ focusHTMLBookmarkInVisualEditor( editor );
+ }
+ } );
+
+ /**
+ * Replaces <p> tags with two line breaks. "Opposite" of wpautop().
+ *
+ * Replaces <p> tags with two line breaks except where the <p> has attributes.
+ * Unifies whitespace.
+ * Indents <li>, <dt> and <dd> for better readability.
+ *
+ * @since 2.5.0
+ *
+ * @memberof switchEditors
+ *
+ * @param {string} html The content from the editor.
+ * @return {string} The content with stripped paragraph tags.
+ */
+ function removep( html ) {
+ var blocklist = 'blockquote|ul|ol|li|dl|dt|dd|table|thead|tbody|tfoot|tr|th|td|h[1-6]|fieldset|figure',
+ blocklist1 = blocklist + '|div|p',
+ blocklist2 = blocklist + '|pre',
+ preserve_linebreaks = false,
+ preserve_br = false,
+ preserve = [];
+
+ if ( ! html ) {
+ return '';
+ }
+
+ // Protect script and style tags.
+ if ( html.indexOf( '<script' ) !== -1 || html.indexOf( '<style' ) !== -1 ) {
+ html = html.replace( /<(script|style)[^>]*>[\s\S]*?<\/\1>/g, function( match ) {
+ preserve.push( match );
+ return '<wp-preserve>';
+ } );
+ }
+
+ // Protect pre tags.
+ if ( html.indexOf( '<pre' ) !== -1 ) {
+ preserve_linebreaks = true;
+ html = html.replace( /<pre[^>]*>[\s\S]+?<\/pre>/g, function( a ) {
+ a = a.replace( /<br ?\/?>(\r\n|\n)?/g, '<wp-line-break>' );
+ a = a.replace( /<\/?p( [^>]*)?>(\r\n|\n)?/g, '<wp-line-break>' );
+ return a.replace( /\r?\n/g, '<wp-line-break>' );
+ });
+ }
+
+ // Remove line breaks but keep <br> tags inside image captions.
+ if ( html.indexOf( '[caption' ) !== -1 ) {
+ preserve_br = true;
+ html = html.replace( /\[caption[\s\S]+?\[\/caption\]/g, function( a ) {
+ return a.replace( /<br([^>]*)>/g, '<wp-temp-br$1>' ).replace( /[\r\n\t]+/, '' );
+ });
+ }
+
+ // Normalize white space characters before and after block tags.
+ html = html.replace( new RegExp( '\\s*</(' + blocklist1 + ')>\\s*', 'g' ), '</$1>\n' );
+ html = html.replace( new RegExp( '\\s*<((?:' + blocklist1 + ')(?: [^>]*)?)>', 'g' ), '\n<$1>' );
+
+ // Mark </p> if it has any attributes.
+ html = html.replace( /(<p [^>]+>.*?)<\/p>/g, '$1</p#>' );
+
+ // Preserve the first <p> inside a <div>.
+ html = html.replace( /<div( [^>]*)?>\s*<p>/gi, '<div$1>\n\n' );
+
+ // Remove paragraph tags.
+ html = html.replace( /\s*<p>/gi, '' );
+ html = html.replace( /\s*<\/p>\s*/gi, '\n\n' );
+
+ // Normalize white space chars and remove multiple line breaks.
+ html = html.replace( /\n[\s\u00a0]+\n/g, '\n\n' );
+
+ // Replace <br> tags with line breaks.
+ html = html.replace( /(\s*)<br ?\/?>\s*/gi, function( match, space ) {
+ if ( space && space.indexOf( '\n' ) !== -1 ) {
+ return '\n\n';
+ }
+
+ return '\n';
+ });
+
+ // Fix line breaks around <div>.
+ html = html.replace( /\s*<div/g, '\n<div' );
+ html = html.replace( /<\/div>\s*/g, '</div>\n' );
+
+ // Fix line breaks around caption shortcodes.
+ html = html.replace( /\s*\[caption([^\[]+)\[\/caption\]\s*/gi, '\n\n[caption$1[/caption]\n\n' );
+ html = html.replace( /caption\]\n\n+\[caption/g, 'caption]\n\n[caption' );
+
+ // Pad block elements tags with a line break.
+ html = html.replace( new RegExp('\\s*<((?:' + blocklist2 + ')(?: [^>]*)?)\\s*>', 'g' ), '\n<$1>' );
+ html = html.replace( new RegExp('\\s*</(' + blocklist2 + ')>\\s*', 'g' ), '</$1>\n' );
+
+ // Indent <li>, <dt> and <dd> tags.
+ html = html.replace( /<((li|dt|dd)[^>]*)>/g, ' \t<$1>' );
+
+ // Fix line breaks around <select> and <option>.
+ if ( html.indexOf( '<option' ) !== -1 ) {
+ html = html.replace( /\s*<option/g, '\n<option' );
+ html = html.replace( /\s*<\/select>/g, '\n</select>' );
+ }
+
+ // Pad <hr> with two line breaks.
+ if ( html.indexOf( '<hr' ) !== -1 ) {
+ html = html.replace( /\s*<hr( [^>]*)?>\s*/g, '\n\n<hr$1>\n\n' );
+ }
+
+ // Remove line breaks in <object> tags.
+ if ( html.indexOf( '<object' ) !== -1 ) {
+ html = html.replace( /<object[\s\S]+?<\/object>/g, function( a ) {
+ return a.replace( /[\r\n]+/g, '' );
+ });
+ }
+
+ // Unmark special paragraph closing tags.
+ html = html.replace( /<\/p#>/g, '</p>\n' );
+
+ // Pad remaining <p> tags whit a line break.
+ html = html.replace( /\s*(<p [^>]+>[\s\S]*?<\/p>)/g, '\n$1' );
+
+ // Trim.
+ html = html.replace( /^\s+/, '' );
+ html = html.replace( /[\s\u00a0]+$/, '' );
+
+ if ( preserve_linebreaks ) {
+ html = html.replace( /<wp-line-break>/g, '\n' );
+ }
+
+ if ( preserve_br ) {
+ html = html.replace( /<wp-temp-br([^>]*)>/g, '<br$1>' );
+ }
+
+ // Restore preserved tags.
+ if ( preserve.length ) {
+ html = html.replace( /<wp-preserve>/g, function() {
+ return preserve.shift();
+ } );
+ }
+
+ return html;
+ }
+
+ /**
+ * Replaces two line breaks with a paragraph tag and one line break with a <br>.
+ *
+ * Similar to `wpautop()` in formatting.php.
+ *
+ * @since 2.5.0
+ *
+ * @memberof switchEditors
+ *
+ * @param {string} text The text input.
+ * @returns {string} The formatted text.
+ */
+ function autop( text ) {
+ var preserve_linebreaks = false,
+ preserve_br = false,
+ blocklist = 'table|thead|tfoot|caption|col|colgroup|tbody|tr|td|th|div|dl|dd|dt|ul|ol|li|pre' +
+ '|form|map|area|blockquote|address|math|style|p|h[1-6]|hr|fieldset|legend|section' +
+ '|article|aside|hgroup|header|footer|nav|figure|figcaption|details|menu|summary';
+
+ // Normalize line breaks.
+ text = text.replace( /\r\n|\r/g, '\n' );
+
+ // Remove line breaks from <object>.
+ if ( text.indexOf( '<object' ) !== -1 ) {
+ text = text.replace( /<object[\s\S]+?<\/object>/g, function( a ) {
+ return a.replace( /\n+/g, '' );
+ });
+ }
+
+ // Remove line breaks from tags.
+ text = text.replace( /<[^<>]+>/g, function( a ) {
+ return a.replace( /[\n\t ]+/g, ' ' );
+ });
+
+ // Preserve line breaks in <pre> and <script> tags.
+ if ( text.indexOf( '<pre' ) !== -1 || text.indexOf( '<script' ) !== -1 ) {
+ preserve_linebreaks = true;
+ text = text.replace( /<(pre|script)[^>]*>[\s\S]*?<\/\1>/g, function( a ) {
+ return a.replace( /\n/g, '<wp-line-break>' );
+ });
+ }
+
+ if ( text.indexOf( '<figcaption' ) !== -1 ) {
+ text = text.replace( /\s*(<figcaption[^>]*>)/g, '$1' );
+ text = text.replace( /<\/figcaption>\s*/g, '</figcaption>' );
+ }
+
+ // Keep <br> tags inside captions.
+ if ( text.indexOf( '[caption' ) !== -1 ) {
+ preserve_br = true;
+
+ text = text.replace( /\[caption[\s\S]+?\[\/caption\]/g, function( a ) {
+ a = a.replace( /<br([^>]*)>/g, '<wp-temp-br$1>' );
+
+ a = a.replace( /<[^<>]+>/g, function( b ) {
+ return b.replace( /[\n\t ]+/, ' ' );
+ });
+
+ return a.replace( /\s*\n\s*/g, '<wp-temp-br />' );
+ });
+ }
+
+ text = text + '\n\n';
+ text = text.replace( /<br \/>\s*<br \/>/gi, '\n\n' );
+
+ // Pad block tags with two line breaks.
+ text = text.replace( new RegExp( '(<(?:' + blocklist + ')(?: [^>]*)?>)', 'gi' ), '\n\n$1' );
+ text = text.replace( new RegExp( '(</(?:' + blocklist + ')>)', 'gi' ), '$1\n\n' );
+ text = text.replace( /<hr( [^>]*)?>/gi, '<hr$1>\n\n' );
+
+ // Remove white space chars around <option>.
+ text = text.replace( /\s*<option/gi, '<option' );
+ text = text.replace( /<\/option>\s*/gi, '</option>' );
+
+ // Normalize multiple line breaks and white space chars.
+ text = text.replace( /\n\s*\n+/g, '\n\n' );
+
+ // Convert two line breaks to a paragraph.
+ text = text.replace( /([\s\S]+?)\n\n/g, '<p>$1</p>\n' );
+
+ // Remove empty paragraphs.
+ text = text.replace( /<p>\s*?<\/p>/gi, '');
+
+ // Remove <p> tags that are around block tags.
+ text = text.replace( new RegExp( '<p>\\s*(</?(?:' + blocklist + ')(?: [^>]*)?>)\\s*</p>', 'gi' ), '$1' );
+ text = text.replace( /<p>(<li.+?)<\/p>/gi, '$1');
+
+ // Fix <p> in blockquotes.
+ text = text.replace( /<p>\s*<blockquote([^>]*)>/gi, '<blockquote$1><p>');
+ text = text.replace( /<\/blockquote>\s*<\/p>/gi, '</p></blockquote>');
+
+ // Remove <p> tags that are wrapped around block tags.
+ text = text.replace( new RegExp( '<p>\\s*(</?(?:' + blocklist + ')(?: [^>]*)?>)', 'gi' ), '$1' );
+ text = text.replace( new RegExp( '(</?(?:' + blocklist + ')(?: [^>]*)?>)\\s*</p>', 'gi' ), '$1' );
+
+ text = text.replace( /(<br[^>]*>)\s*\n/gi, '$1' );
+
+ // Add <br> tags.
+ text = text.replace( /\s*\n/g, '<br />\n');
+
+ // Remove <br> tags that are around block tags.
+ text = text.replace( new RegExp( '(</?(?:' + blocklist + ')[^>]*>)\\s*<br />', 'gi' ), '$1' );
+ text = text.replace( /<br \/>(\s*<\/?(?:p|li|div|dl|dd|dt|th|pre|td|ul|ol)>)/gi, '$1' );
+
+ // Remove <p> and <br> around captions.
+ text = text.replace( /(?:<p>|<br ?\/?>)*\s*\[caption([^\[]+)\[\/caption\]\s*(?:<\/p>|<br ?\/?>)*/gi, '[caption$1[/caption]' );
+
+ // Make sure there is <p> when there is </p> inside block tags that can contain other blocks.
+ text = text.replace( /(<(?:div|th|td|form|fieldset|dd)[^>]*>)(.*?)<\/p>/g, function( a, b, c ) {
+ if ( c.match( /<p( [^>]*)?>/ ) ) {
+ return a;
+ }
+
+ return b + '<p>' + c + '</p>';
+ });
+
+ // Restore the line breaks in <pre> and <script> tags.
+ if ( preserve_linebreaks ) {
+ text = text.replace( /<wp-line-break>/g, '\n' );
+ }
+
+ // Restore the <br> tags in captions.
+ if ( preserve_br ) {
+ text = text.replace( /<wp-temp-br([^>]*)>/g, '<br$1>' );
+ }
+
+ return text;
+ }
+
+ /**
+ * Fires custom jQuery events `beforePreWpautop` and `afterPreWpautop` when jQuery is available.
+ *
+ * @since 2.9.0
+ *
+ * @memberof switchEditors
+ *
+ * @param {String} html The content from the visual editor.
+ * @returns {String} the filtered content.
+ */
+ function pre_wpautop( html ) {
+ var obj = { o: exports, data: html, unfiltered: html };
+
+ if ( $ ) {
+ $( 'body' ).trigger( 'beforePreWpautop', [ obj ] );
+ }
+
+ obj.data = removep( obj.data );
+
+ if ( $ ) {
+ $( 'body' ).trigger( 'afterPreWpautop', [ obj ] );
+ }
+
+ return obj.data;
+ }
+
+ /**
+ * Fires custom jQuery events `beforeWpautop` and `afterWpautop` when jQuery is available.
+ *
+ * @since 2.9.0
+ *
+ * @memberof switchEditors
+ *
+ * @param {String} text The content from the text editor.
+ * @returns {String} filtered content.
+ */
+ function wpautop( text ) {
+ var obj = { o: exports, data: text, unfiltered: text };
+
+ if ( $ ) {
+ $( 'body' ).trigger( 'beforeWpautop', [ obj ] );
+ }
+
+ obj.data = autop( obj.data );
+
+ if ( $ ) {
+ $( 'body' ).trigger( 'afterWpautop', [ obj ] );
+ }
+
+ return obj.data;
+ }
+
+ if ( $ ) {
+ $( document ).ready( init );
+ } else if ( document.addEventListener ) {
+ document.addEventListener( 'DOMContentLoaded', init, false );
+ window.addEventListener( 'load', init, false );
+ } else if ( window.attachEvent ) {
+ window.attachEvent( 'onload', init );
+ document.attachEvent( 'onreadystatechange', function() {
+ if ( 'complete' === document.readyState ) {
+ init();
+ }
+ } );
+ }
+
+ wp.editor.autop = wpautop;
+ wp.editor.removep = pre_wpautop;
+
+ exports = {
+ go: switchEditor,
+ wpautop: wpautop,
+ pre_wpautop: pre_wpautop,
+ _wp_Autop: autop,
+ _wp_Nop: removep
+ };
+
+ return exports;
+ }
+
+ /**
+ * Expose the switch editors to be used globally.
+ *
+ * @namespace switchEditors
+ */
+ window.switchEditors = new SwitchEditors();
+
+ /**
+ * Initialize TinyMCE and/or Quicktags. For use with wp_enqueue_editor() (PHP).
+ *
+ * Intended for use with an existing textarea that will become the Text editor tab.
+ * The editor width will be the width of the textarea container, height will be adjustable.
+ *
+ * Settings for both TinyMCE and Quicktags can be passed on initialization, and are "filtered"
+ * with custom jQuery events on the document element, wp-before-tinymce-init and wp-before-quicktags-init.
+ *
+ * @since 4.8.0
+ *
+ * @param {string} id The HTML id of the textarea that is used for the editor.
+ * Has to be jQuery compliant. No brackets, special chars, etc.
+ * @param {object} settings Example:
+ * settings = {
+ * // See https://www.tinymce.com/docs/configure/integration-and-setup/.
+ * // Alternatively set to `true` to use the defaults.
+ * tinymce: {
+ * setup: function( editor ) {
+ * console.log( 'Editor initialized', editor );
+ * }
+ * }
+ *
+ * // Alternatively set to `true` to use the defaults.
+ * quicktags: {
+ * buttons: 'strong,em,link'
+ * }
+ * }
+ */
+ wp.editor.initialize = function( id, settings ) {
+ var init;
+ var defaults;
+
+ if ( ! $ || ! id || ! wp.editor.getDefaultSettings ) {
+ return;
+ }
+
+ defaults = wp.editor.getDefaultSettings();
+
+ // Initialize TinyMCE by default
+ if ( ! settings ) {
+ settings = {
+ tinymce: true
+ };
+ }
+
+ // Add wrap and the Visual|Text tabs.
+ if ( settings.tinymce && settings.quicktags ) {
+ var $textarea = $( '#' + id );
+
+ var $wrap = $( '<div>' ).attr( {
+ 'class': 'wp-core-ui wp-editor-wrap tmce-active',
+ id: 'wp-' + id + '-wrap'
+ } );
+
+ var $editorContainer = $( '<div class="wp-editor-container">' );
+
+ var $button = $( '<button>' ).attr( {
+ type: 'button',
+ 'data-wp-editor-id': id
+ } );
+
+ var $editorTools = $( '<div class="wp-editor-tools">' );
+
+ if ( settings.mediaButtons ) {
+ var buttonText = 'Add Media';
+
+ if ( window._wpMediaViewsL10n && window._wpMediaViewsL10n.addMedia ) {
+ buttonText = window._wpMediaViewsL10n.addMedia;
+ }
+
+ var $addMediaButton = $( '<button type="button" class="button insert-media add_media">' );
+
+ $addMediaButton.append( '<span class="wp-media-buttons-icon"></span>' );
+ $addMediaButton.append( document.createTextNode( ' ' + buttonText ) );
+ $addMediaButton.data( 'editor', id );
+
+ $editorTools.append(
+ $( '<div class="wp-media-buttons">' )
+ .append( $addMediaButton )
+ );
+ }
+
+ $wrap.append(
+ $editorTools
+ .append( $( '<div class="wp-editor-tabs">' )
+ .append( $button.clone().attr({
+ id: id + '-tmce',
+ 'class': 'wp-switch-editor switch-tmce'
+ }).text( window.tinymce.translate( 'Visual' ) ) )
+ .append( $button.attr({
+ id: id + '-html',
+ 'class': 'wp-switch-editor switch-html'
+ }).text( window.tinymce.translate( 'Text' ) ) )
+ ).append( $editorContainer )
+ );
+
+ $textarea.after( $wrap );
+ $editorContainer.append( $textarea );
+ }
+
+ if ( window.tinymce && settings.tinymce ) {
+ if ( typeof settings.tinymce !== 'object' ) {
+ settings.tinymce = {};
+ }
+
+ init = $.extend( {}, defaults.tinymce, settings.tinymce );
+ init.selector = '#' + id;
+
+ $( document ).trigger( 'wp-before-tinymce-init', init );
+ window.tinymce.init( init );
+
+ if ( ! window.wpActiveEditor ) {
+ window.wpActiveEditor = id;
+ }
+ }
+
+ if ( window.quicktags && settings.quicktags ) {
+ if ( typeof settings.quicktags !== 'object' ) {
+ settings.quicktags = {};
+ }
+
+ init = $.extend( {}, defaults.quicktags, settings.quicktags );
+ init.id = id;
+
+ $( document ).trigger( 'wp-before-quicktags-init', init );
+ window.quicktags( init );
+
+ if ( ! window.wpActiveEditor ) {
+ window.wpActiveEditor = init.id;
+ }
+ }
+ };
+
+ /**
+ * Remove one editor instance.
+ *
+ * Intended for use with editors that were initialized with wp.editor.initialize().
+ *
+ * @since 4.8.0
+ *
+ * @param {string} id The HTML id of the editor textarea.
+ */
+ wp.editor.remove = function( id ) {
+ var mceInstance, qtInstance,
+ $wrap = $( '#wp-' + id + '-wrap' );
+
+ if ( window.tinymce ) {
+ mceInstance = window.tinymce.get( id );
+
+ if ( mceInstance ) {
+ if ( ! mceInstance.isHidden() ) {
+ mceInstance.save();
+ }
+
+ mceInstance.remove();
+ }
+ }
+
+ if ( window.quicktags ) {
+ qtInstance = window.QTags.getInstance( id );
+
+ if ( qtInstance ) {
+ qtInstance.remove();
+ }
+ }
+
+ if ( $wrap.length ) {
+ $wrap.after( $( '#' + id ) );
+ $wrap.remove();
+ }
+ };
+
+ /**
+ * Get the editor content.
+ *
+ * Intended for use with editors that were initialized with wp.editor.initialize().
+ *
+ * @since 4.8.0
+ *
+ * @param {string} id The HTML id of the editor textarea.
+ * @return The editor content.
+ */
+ wp.editor.getContent = function( id ) {
+ var editor;
+
+ if ( ! $ || ! id ) {
+ return;
+ }
+
+ if ( window.tinymce ) {
+ editor = window.tinymce.get( id );
+
+ if ( editor && ! editor.isHidden() ) {
+ editor.save();
+ }
+ }
+
+ return $( '#' + id ).val();
+ };
+
+}( window.jQuery, window.wp ));
diff --git a/www/crm/wp-admin/js/editor.min.js b/www/crm/wp-admin/js/editor.min.js
new file mode 100644
index 00000000..d80a38d0
--- /dev/null
+++ b/www/crm/wp-admin/js/editor.min.js
@@ -0,0 +1 @@
+window.wp=window.wp||{},function(a,b){function c(){function c(){!w&&window.tinymce&&(w=window.tinymce,x=w.$,x(document).on("click",function(a){var b,c,d=x(a.target);d.hasClass("wp-switch-editor")&&(b=d.attr("data-wp-editor-id"),c=d.hasClass("switch-tmce")?"tmce":"html",e(b,c))}))}function d(a){var b=x(".mce-toolbar-grp",a.getContainer())[0],c=b&&b.clientHeight;return c&&c>10&&c<200?parseInt(c,10):30}function e(a,b){a=a||"content",b=b||"toggle";var c,e,f,g=w.get(a),h=x("#wp-"+a+"-wrap"),i=x("#"+a),j=i[0];if("toggle"===b&&(b=g&&!g.isHidden()?"html":"tmce"),"tmce"===b||"tinymce"===b){if(g&&!g.isHidden())return!1;"undefined"!=typeof window.QTags&&window.QTags.closeAllTags(a),c=parseInt(j.style.height,10)||0;var k=!1;k=g?g.getParam("wp_keep_scroll_position"):window.tinyMCEPreInit.mceInit[a]&&window.tinyMCEPreInit.mceInit[a].wp_keep_scroll_position,k&&l(i),g?(g.show(),!w.Env.iOS&&c&&(e=d(g),c=c-e+14,c>50&&c<5e3&&g.theme.resizeTo(null,c)),g.getParam("wp_keep_scroll_position")&&m(g)):w.init(window.tinyMCEPreInit.mceInit[a]),h.removeClass("html-active").addClass("tmce-active"),i.attr("aria-hidden",!0),window.setUserSetting("editor","tinymce")}else if("html"===b){if(g&&g.isHidden())return!1;if(g){w.Env.iOS||(f=g.iframeElement,c=f?parseInt(f.style.height,10):0,c&&(e=d(g),c=c+e-14,c>50&&c<5e3&&(j.style.height=c+"px")));var n=null;g.getParam("wp_keep_scroll_position")&&(n=q(g)),g.hide(),n&&r(g,n)}else i.css({display:"",visibility:""});h.removeClass("tmce-active").addClass("html-active"),i.attr("aria-hidden",!1),window.setUserSetting("editor","html")}}function f(a,b){var c=a.lastIndexOf("<",b-1),d=a.lastIndexOf(">",b);if(c>d||">"===a.substr(b,1)){var e=a.substr(c),f=e.match(/<\s*(\/)?(\w+|\!-{2}.*-{2})/);if(!f)return null;var g=f[2],h=e.indexOf(">");return{ltPos:c,gtPos:c+h+1,tagType:g,isClosingTag:!!f[1]}}return null}function g(a,b){for(var c=i(a),d=0;d<c.length;d++){var e=c[d];if(b>=e.startIndex&&b<=e.endIndex)return e}}function h(a){var b=a.match(/\[+([\w_-])+/g),c=[];if(b)for(var d=0;d<b.length;d++){var e=b[d].replace(/^\[+/g,"");c.indexOf(e)===-1&&c.push(e)}return c}function i(a){var c,d=h(a);if(0===d.length)return[];for(var e,f=b.shortcode.regexp(d.join("|")),g=[];e=f.exec(a);){var i="["===e[1];c={shortcodeName:e[2],showAsPlainText:i,startIndex:e.index,endIndex:e.index+e[0].length,length:e[0].length},g.push(c)}for(var j=new RegExp('(^|[\\n\\r][\\n\\r]|<p>)(https?:\\/\\/[^s"]+?)(<\\/p>s*|[\\n\\r][\\n\\r]|$)',"gi");e=j.exec(a);)c={shortcodeName:"url",showAsPlainText:!1,startIndex:e.index,endIndex:e.index+e[0].length,length:e[0].length,urlAtStartOfContent:""===e[1],urlAtEndOfContent:""===e[3]},g.push(c);return g}function j(a,b){return a("<span>").css({display:"inline-block",width:0,overflow:"hidden","line-height":0}).html(b?b:"")}function k(a,b){var c=["area","base","br","col","embed","hr","img","input","keygen","link","meta","param","source","track","wbr"],d=b.cursorStart,e=b.cursorEnd,h=f(a,d);h&&(d=c.indexOf(h.tagType)!==-1?h.ltPos:h.gtPos);var i=f(a,e);i&&(e=i.gtPos);var j=g(a,d);j&&!j.showAsPlainText&&(d=j.urlAtStartOfContent?j.endIndex:j.startIndex);var k=g(a,e);return k&&!k.showAsPlainText&&(e=k.urlAtEndOfContent?k.startIndex:k.endIndex),{cursorStart:d,cursorEnd:e}}function l(a){if(a&&a.length){var b=a[0],c=b.value,d=k(c,{cursorStart:b.selectionStart,cursorEnd:b.selectionEnd}),e=d.cursorStart,f=d.cursorEnd,g=e!==f?"range":"single",h=null,i=j(x,"&#65279;").attr("data-mce-type","bookmark");if("range"===g){var l=b.value.slice(e,f),m=i.clone().addClass("mce_SELRES_end");h=[l,m[0].outerHTML].join("")}b.value=[b.value.slice(0,e),i.clone().addClass("mce_SELRES_start")[0].outerHTML,h,b.value.slice(f)].join("")}}function m(a){var b=a.$(".mce_SELRES_start").attr("data-mce-bogus",1),c=a.$(".mce_SELRES_end").attr("data-mce-bogus",1);if(b.length)if(a.focus(),c.length){var d=a.getDoc().createRange();d.setStartAfter(b[0]),d.setEndBefore(c[0]),a.selection.setRng(d)}else a.selection.select(b[0]);a.getParam("wp_keep_scroll_position")&&o(a,b),n(b),n(c),a.save()}function n(a){var b=a.parent();a.remove(),!b.is("p")||b.children().length||b.text()||b.remove()}function o(b,c){var e,f=b.$(c).offset().top,g=b.$(b.getContentAreaContainer()).offset().top,h=d(b),i=a("#wp-content-editor-tools"),j=0,k=0;i.length&&(j=i.height(),k=i.offset().top);var l=window.innerHeight||document.documentElement.clientHeight||document.body.clientHeight,m=g+f,n=l-(j+h);if(!(m<n)){var o;b.settings.wp_autoresize_on?(e=a("html,body"),o=Math.max(m-n/2,k-j)):(e=a(b.contentDocument).find("html,body"),o=f),e.animate({scrollTop:parseInt(o,10)},100)}}function p(a){a.content=a.content.replace(/<p>(?:<br ?\/?>|\u00a0|\uFEFF| )*<\/p>/g,"<p>&nbsp;</p>")}function q(a){var b=a.getWin(),c=b.getSelection();if(c&&!(c.rangeCount<1)){var d="SELRES_"+Math.random(),e=j(a.$,d),f=e.clone().addClass("mce_SELRES_start"),g=e.clone().addClass("mce_SELRES_end"),h=c.getRangeAt(0),i=h.startContainer,k=h.startOffset,l=h.cloneRange();a.$(i).parents(".mce-offscreen-selection").length>0?(i=a.$("[data-mce-selected]")[0],f.attr("data-mce-object-selection","true"),g.attr("data-mce-object-selection","true"),a.$(i).before(f[0]),a.$(i).after(g[0])):(l.collapse(!1),l.insertNode(g[0]),l.setStart(i,k),l.collapse(!0),l.insertNode(f[0]),h.setStartAfter(f[0]),h.setEndBefore(g[0]),c.removeAllRanges(),c.addRange(h)),a.on("GetContent",p);var m=s(a.getContent());a.off("GetContent",p),f.remove(),g.remove();var n=new RegExp('<span[^>]*\\s*class="mce_SELRES_start"[^>]+>\\s*'+d+"[^<]*<\\/span>(\\s*)"),o=new RegExp('(\\s*)<span[^>]*\\s*class="mce_SELRES_end"[^>]+>\\s*'+d+"[^<]*<\\/span>"),q=m.match(n),r=m.match(o);if(!q)return null;var t=q.index,u=q[0].length,v=null;if(r){q[0].indexOf("data-mce-object-selection")!==-1&&(u-=q[1].length);var w=r.index;r[0].indexOf("data-mce-object-selection")!==-1&&(w-=r[1].length),v=w-u}return{start:t,end:v}}}function r(a,b){if(b){var c=a.getElement(),d=b.start,e=b.end||b.start;c.focus&&setTimeout(function(){c.setSelectionRange(d,e),c.blur&&c.blur(),c.focus()},100)}}function s(a){var b="blockquote|ul|ol|li|dl|dt|dd|table|thead|tbody|tfoot|tr|th|td|h[1-6]|fieldset|figure",c=b+"|div|p",d=b+"|pre",e=!1,f=!1,g=[];return a?(a.indexOf("<script")===-1&&a.indexOf("<style")===-1||(a=a.replace(/<(script|style)[^>]*>[\s\S]*?<\/\1>/g,function(a){return g.push(a),"<wp-preserve>"})),a.indexOf("<pre")!==-1&&(e=!0,a=a.replace(/<pre[^>]*>[\s\S]+?<\/pre>/g,function(a){return a=a.replace(/<br ?\/?>(\r\n|\n)?/g,"<wp-line-break>"),a=a.replace(/<\/?p( [^>]*)?>(\r\n|\n)?/g,"<wp-line-break>"),a.replace(/\r?\n/g,"<wp-line-break>")})),a.indexOf("[caption")!==-1&&(f=!0,a=a.replace(/\[caption[\s\S]+?\[\/caption\]/g,function(a){return a.replace(/<br([^>]*)>/g,"<wp-temp-br$1>").replace(/[\r\n\t]+/,"")})),a=a.replace(new RegExp("\\s*</("+c+")>\\s*","g"),"</$1>\n"),a=a.replace(new RegExp("\\s*<((?:"+c+")(?: [^>]*)?)>","g"),"\n<$1>"),a=a.replace(/(<p [^>]+>.*?)<\/p>/g,"$1</p#>"),a=a.replace(/<div( [^>]*)?>\s*<p>/gi,"<div$1>\n\n"),a=a.replace(/\s*<p>/gi,""),a=a.replace(/\s*<\/p>\s*/gi,"\n\n"),a=a.replace(/\n[\s\u00a0]+\n/g,"\n\n"),a=a.replace(/(\s*)<br ?\/?>\s*/gi,function(a,b){return b&&b.indexOf("\n")!==-1?"\n\n":"\n"}),a=a.replace(/\s*<div/g,"\n<div"),a=a.replace(/<\/div>\s*/g,"</div>\n"),a=a.replace(/\s*\[caption([^\[]+)\[\/caption\]\s*/gi,"\n\n[caption$1[/caption]\n\n"),a=a.replace(/caption\]\n\n+\[caption/g,"caption]\n\n[caption"),a=a.replace(new RegExp("\\s*<((?:"+d+")(?: [^>]*)?)\\s*>","g"),"\n<$1>"),a=a.replace(new RegExp("\\s*</("+d+")>\\s*","g"),"</$1>\n"),a=a.replace(/<((li|dt|dd)[^>]*)>/g," \t<$1>"),a.indexOf("<option")!==-1&&(a=a.replace(/\s*<option/g,"\n<option"),a=a.replace(/\s*<\/select>/g,"\n</select>")),a.indexOf("<hr")!==-1&&(a=a.replace(/\s*<hr( [^>]*)?>\s*/g,"\n\n<hr$1>\n\n")),a.indexOf("<object")!==-1&&(a=a.replace(/<object[\s\S]+?<\/object>/g,function(a){return a.replace(/[\r\n]+/g,"")})),a=a.replace(/<\/p#>/g,"</p>\n"),a=a.replace(/\s*(<p [^>]+>[\s\S]*?<\/p>)/g,"\n$1"),a=a.replace(/^\s+/,""),a=a.replace(/[\s\u00a0]+$/,""),e&&(a=a.replace(/<wp-line-break>/g,"\n")),f&&(a=a.replace(/<wp-temp-br([^>]*)>/g,"<br$1>")),g.length&&(a=a.replace(/<wp-preserve>/g,function(){return g.shift()})),a):""}function t(a){var b=!1,c=!1,d="table|thead|tfoot|caption|col|colgroup|tbody|tr|td|th|div|dl|dd|dt|ul|ol|li|pre|form|map|area|blockquote|address|math|style|p|h[1-6]|hr|fieldset|legend|section|article|aside|hgroup|header|footer|nav|figure|figcaption|details|menu|summary";return a=a.replace(/\r\n|\r/g,"\n"),a.indexOf("<object")!==-1&&(a=a.replace(/<object[\s\S]+?<\/object>/g,function(a){return a.replace(/\n+/g,"")})),a=a.replace(/<[^<>]+>/g,function(a){return a.replace(/[\n\t ]+/g," ")}),a.indexOf("<pre")===-1&&a.indexOf("<script")===-1||(b=!0,a=a.replace(/<(pre|script)[^>]*>[\s\S]*?<\/\1>/g,function(a){return a.replace(/\n/g,"<wp-line-break>")})),a.indexOf("<figcaption")!==-1&&(a=a.replace(/\s*(<figcaption[^>]*>)/g,"$1"),a=a.replace(/<\/figcaption>\s*/g,"</figcaption>")),a.indexOf("[caption")!==-1&&(c=!0,a=a.replace(/\[caption[\s\S]+?\[\/caption\]/g,function(a){return a=a.replace(/<br([^>]*)>/g,"<wp-temp-br$1>"),a=a.replace(/<[^<>]+>/g,function(a){return a.replace(/[\n\t ]+/," ")}),a.replace(/\s*\n\s*/g,"<wp-temp-br />")})),a+="\n\n",a=a.replace(/<br \/>\s*<br \/>/gi,"\n\n"),a=a.replace(new RegExp("(<(?:"+d+")(?: [^>]*)?>)","gi"),"\n\n$1"),a=a.replace(new RegExp("(</(?:"+d+")>)","gi"),"$1\n\n"),a=a.replace(/<hr( [^>]*)?>/gi,"<hr$1>\n\n"),a=a.replace(/\s*<option/gi,"<option"),a=a.replace(/<\/option>\s*/gi,"</option>"),a=a.replace(/\n\s*\n+/g,"\n\n"),a=a.replace(/([\s\S]+?)\n\n/g,"<p>$1</p>\n"),a=a.replace(/<p>\s*?<\/p>/gi,""),a=a.replace(new RegExp("<p>\\s*(</?(?:"+d+")(?: [^>]*)?>)\\s*</p>","gi"),"$1"),a=a.replace(/<p>(<li.+?)<\/p>/gi,"$1"),a=a.replace(/<p>\s*<blockquote([^>]*)>/gi,"<blockquote$1><p>"),a=a.replace(/<\/blockquote>\s*<\/p>/gi,"</p></blockquote>"),a=a.replace(new RegExp("<p>\\s*(</?(?:"+d+")(?: [^>]*)?>)","gi"),"$1"),a=a.replace(new RegExp("(</?(?:"+d+")(?: [^>]*)?>)\\s*</p>","gi"),"$1"),a=a.replace(/(<br[^>]*>)\s*\n/gi,"$1"),a=a.replace(/\s*\n/g,"<br />\n"),a=a.replace(new RegExp("(</?(?:"+d+")[^>]*>)\\s*<br />","gi"),"$1"),a=a.replace(/<br \/>(\s*<\/?(?:p|li|div|dl|dd|dt|th|pre|td|ul|ol)>)/gi,"$1"),a=a.replace(/(?:<p>|<br ?\/?>)*\s*\[caption([^\[]+)\[\/caption\]\s*(?:<\/p>|<br ?\/?>)*/gi,"[caption$1[/caption]"),a=a.replace(/(<(?:div|th|td|form|fieldset|dd)[^>]*>)(.*?)<\/p>/g,function(a,b,c){return c.match(/<p( [^>]*)?>/)?a:b+"<p>"+c+"</p>"}),b&&(a=a.replace(/<wp-line-break>/g,"\n")),c&&(a=a.replace(/<wp-temp-br([^>]*)>/g,"<br$1>")),a}function u(b){var c={o:y,data:b,unfiltered:b};return a&&a("body").trigger("beforePreWpautop",[c]),c.data=s(c.data),a&&a("body").trigger("afterPreWpautop",[c]),c.data}function v(b){var c={o:y,data:b,unfiltered:b};return a&&a("body").trigger("beforeWpautop",[c]),c.data=t(c.data),a&&a("body").trigger("afterWpautop",[c]),c.data}var w,x,y={};return a(document).on("tinymce-editor-init.keep-scroll-position",function(a,b){b.$(".mce_SELRES_start").length&&m(b)}),a?a(document).ready(c):document.addEventListener?(document.addEventListener("DOMContentLoaded",c,!1),window.addEventListener("load",c,!1)):window.attachEvent&&(window.attachEvent("onload",c),document.attachEvent("onreadystatechange",function(){"complete"===document.readyState&&c()})),b.editor.autop=v,b.editor.removep=u,y={go:e,wpautop:v,pre_wpautop:u,_wp_Autop:t,_wp_Nop:s}}b.editor=b.editor||{},window.switchEditors=new c,b.editor.initialize=function(c,d){var e,f;if(a&&c&&b.editor.getDefaultSettings){if(f=b.editor.getDefaultSettings(),d||(d={tinymce:!0}),d.tinymce&&d.quicktags){var g=a("#"+c),h=a("<div>").attr({"class":"wp-core-ui wp-editor-wrap tmce-active",id:"wp-"+c+"-wrap"}),i=a('<div class="wp-editor-container">'),j=a("<button>").attr({type:"button","data-wp-editor-id":c}),k=a('<div class="wp-editor-tools">');if(d.mediaButtons){var l="Add Media";window._wpMediaViewsL10n&&window._wpMediaViewsL10n.addMedia&&(l=window._wpMediaViewsL10n.addMedia);var m=a('<button type="button" class="button insert-media add_media">');m.append('<span class="wp-media-buttons-icon"></span>'),m.append(document.createTextNode(" "+l)),m.data("editor",c),k.append(a('<div class="wp-media-buttons">').append(m))}h.append(k.append(a('<div class="wp-editor-tabs">').append(j.clone().attr({id:c+"-tmce","class":"wp-switch-editor switch-tmce"}).text(window.tinymce.translate("Visual"))).append(j.attr({id:c+"-html","class":"wp-switch-editor switch-html"}).text(window.tinymce.translate("Text")))).append(i)),g.after(h),i.append(g)}window.tinymce&&d.tinymce&&("object"!=typeof d.tinymce&&(d.tinymce={}),e=a.extend({},f.tinymce,d.tinymce),e.selector="#"+c,a(document).trigger("wp-before-tinymce-init",e),window.tinymce.init(e),window.wpActiveEditor||(window.wpActiveEditor=c)),window.quicktags&&d.quicktags&&("object"!=typeof d.quicktags&&(d.quicktags={}),e=a.extend({},f.quicktags,d.quicktags),e.id=c,a(document).trigger("wp-before-quicktags-init",e),window.quicktags(e),window.wpActiveEditor||(window.wpActiveEditor=e.id))}},b.editor.remove=function(b){var c,d,e=a("#wp-"+b+"-wrap");window.tinymce&&(c=window.tinymce.get(b),c&&(c.isHidden()||c.save(),c.remove())),window.quicktags&&(d=window.QTags.getInstance(b),d&&d.remove()),e.length&&(e.after(a("#"+b)),e.remove())},b.editor.getContent=function(b){var c;if(a&&b)return window.tinymce&&(c=window.tinymce.get(b),c&&!c.isHidden()&&c.save()),a("#"+b).val()}}(window.jQuery,window.wp); \ No newline at end of file
diff --git a/www/crm/wp-admin/js/farbtastic.js b/www/crm/wp-admin/js/farbtastic.js
new file mode 100644
index 00000000..5404fb6e
--- /dev/null
+++ b/www/crm/wp-admin/js/farbtastic.js
@@ -0,0 +1,276 @@
+/*!
+ * Farbtastic: jQuery color picker plug-in v1.3u
+ *
+ * Licensed under the GPL license:
+ * http://www.gnu.org/licenses/gpl.html
+ */
+(function($) {
+
+$.fn.farbtastic = function (options) {
+ $.farbtastic(this, options);
+ return this;
+};
+
+$.farbtastic = function (container, callback) {
+ var container = $(container).get(0);
+ return container.farbtastic || (container.farbtastic = new $._farbtastic(container, callback));
+};
+
+$._farbtastic = function (container, callback) {
+ // Store farbtastic object
+ var fb = this;
+
+ // Insert markup
+ $(container).html('<div class="farbtastic"><div class="color"></div><div class="wheel"></div><div class="overlay"></div><div class="h-marker marker"></div><div class="sl-marker marker"></div></div>');
+ var e = $('.farbtastic', container);
+ fb.wheel = $('.wheel', container).get(0);
+ // Dimensions
+ fb.radius = 84;
+ fb.square = 100;
+ fb.width = 194;
+
+ // Fix background PNGs in IE6
+ if (navigator.appVersion.match(/MSIE [0-6]\./)) {
+ $('*', e).each(function () {
+ if (this.currentStyle.backgroundImage != 'none') {
+ var image = this.currentStyle.backgroundImage;
+ image = this.currentStyle.backgroundImage.substring(5, image.length - 2);
+ $(this).css({
+ 'backgroundImage': 'none',
+ 'filter': "progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, sizingMethod=crop, src='" + image + "')"
+ });
+ }
+ });
+ }
+
+ /**
+ * Link to the given element(s) or callback.
+ */
+ fb.linkTo = function (callback) {
+ // Unbind previous nodes
+ if (typeof fb.callback == 'object') {
+ $(fb.callback).unbind('keyup', fb.updateValue);
+ }
+
+ // Reset color
+ fb.color = null;
+
+ // Bind callback or elements
+ if (typeof callback == 'function') {
+ fb.callback = callback;
+ }
+ else if (typeof callback == 'object' || typeof callback == 'string') {
+ fb.callback = $(callback);
+ fb.callback.bind('keyup', fb.updateValue);
+ if (fb.callback.get(0).value) {
+ fb.setColor(fb.callback.get(0).value);
+ }
+ }
+ return this;
+ };
+ fb.updateValue = function (event) {
+ if (this.value && this.value != fb.color) {
+ fb.setColor(this.value);
+ }
+ };
+
+ /**
+ * Change color with HTML syntax #123456
+ */
+ fb.setColor = function (color) {
+ var unpack = fb.unpack(color);
+ if (fb.color != color && unpack) {
+ fb.color = color;
+ fb.rgb = unpack;
+ fb.hsl = fb.RGBToHSL(fb.rgb);
+ fb.updateDisplay();
+ }
+ return this;
+ };
+
+ /**
+ * Change color with HSL triplet [0..1, 0..1, 0..1]
+ */
+ fb.setHSL = function (hsl) {
+ fb.hsl = hsl;
+ fb.rgb = fb.HSLToRGB(hsl);
+ fb.color = fb.pack(fb.rgb);
+ fb.updateDisplay();
+ return this;
+ };
+
+ /////////////////////////////////////////////////////
+
+ /**
+ * Retrieve the coordinates of the given event relative to the center
+ * of the widget.
+ */
+ fb.widgetCoords = function (event) {
+ var offset = $(fb.wheel).offset();
+ return { x: (event.pageX - offset.left) - fb.width / 2, y: (event.pageY - offset.top) - fb.width / 2 };
+ };
+
+ /**
+ * Mousedown handler
+ */
+ fb.mousedown = function (event) {
+ // Capture mouse
+ if (!document.dragging) {
+ $(document).bind('mousemove', fb.mousemove).bind('mouseup', fb.mouseup);
+ document.dragging = true;
+ }
+
+ // Check which area is being dragged
+ var pos = fb.widgetCoords(event);
+ fb.circleDrag = Math.max(Math.abs(pos.x), Math.abs(pos.y)) * 2 > fb.square;
+
+ // Process
+ fb.mousemove(event);
+ return false;
+ };
+
+ /**
+ * Mousemove handler
+ */
+ fb.mousemove = function (event) {
+ // Get coordinates relative to color picker center
+ var pos = fb.widgetCoords(event);
+
+ // Set new HSL parameters
+ if (fb.circleDrag) {
+ var hue = Math.atan2(pos.x, -pos.y) / 6.28;
+ if (hue < 0) hue += 1;
+ fb.setHSL([hue, fb.hsl[1], fb.hsl[2]]);
+ }
+ else {
+ var sat = Math.max(0, Math.min(1, -(pos.x / fb.square) + .5));
+ var lum = Math.max(0, Math.min(1, -(pos.y / fb.square) + .5));
+ fb.setHSL([fb.hsl[0], sat, lum]);
+ }
+ return false;
+ };
+
+ /**
+ * Mouseup handler
+ */
+ fb.mouseup = function () {
+ // Uncapture mouse
+ $(document).unbind('mousemove', fb.mousemove);
+ $(document).unbind('mouseup', fb.mouseup);
+ document.dragging = false;
+ };
+
+ /**
+ * Update the markers and styles
+ */
+ fb.updateDisplay = function () {
+ // Markers
+ var angle = fb.hsl[0] * 6.28;
+ $('.h-marker', e).css({
+ left: Math.round(Math.sin(angle) * fb.radius + fb.width / 2) + 'px',
+ top: Math.round(-Math.cos(angle) * fb.radius + fb.width / 2) + 'px'
+ });
+
+ $('.sl-marker', e).css({
+ left: Math.round(fb.square * (.5 - fb.hsl[1]) + fb.width / 2) + 'px',
+ top: Math.round(fb.square * (.5 - fb.hsl[2]) + fb.width / 2) + 'px'
+ });
+
+ // Saturation/Luminance gradient
+ $('.color', e).css('backgroundColor', fb.pack(fb.HSLToRGB([fb.hsl[0], 1, 0.5])));
+
+ // Linked elements or callback
+ if (typeof fb.callback == 'object') {
+ // Set background/foreground color
+ $(fb.callback).css({
+ backgroundColor: fb.color,
+ color: fb.hsl[2] > 0.5 ? '#000' : '#fff'
+ });
+
+ // Change linked value
+ $(fb.callback).each(function() {
+ if (this.value && this.value != fb.color) {
+ this.value = fb.color;
+ }
+ });
+ }
+ else if (typeof fb.callback == 'function') {
+ fb.callback.call(fb, fb.color);
+ }
+ };
+
+ /* Various color utility functions */
+ fb.pack = function (rgb) {
+ var r = Math.round(rgb[0] * 255);
+ var g = Math.round(rgb[1] * 255);
+ var b = Math.round(rgb[2] * 255);
+ return '#' + (r < 16 ? '0' : '') + r.toString(16) +
+ (g < 16 ? '0' : '') + g.toString(16) +
+ (b < 16 ? '0' : '') + b.toString(16);
+ };
+
+ fb.unpack = function (color) {
+ if (color.length == 7) {
+ return [parseInt('0x' + color.substring(1, 3)) / 255,
+ parseInt('0x' + color.substring(3, 5)) / 255,
+ parseInt('0x' + color.substring(5, 7)) / 255];
+ }
+ else if (color.length == 4) {
+ return [parseInt('0x' + color.substring(1, 2)) / 15,
+ parseInt('0x' + color.substring(2, 3)) / 15,
+ parseInt('0x' + color.substring(3, 4)) / 15];
+ }
+ };
+
+ fb.HSLToRGB = function (hsl) {
+ var m1, m2, r, g, b;
+ var h = hsl[0], s = hsl[1], l = hsl[2];
+ m2 = (l <= 0.5) ? l * (s + 1) : l + s - l*s;
+ m1 = l * 2 - m2;
+ return [this.hueToRGB(m1, m2, h+0.33333),
+ this.hueToRGB(m1, m2, h),
+ this.hueToRGB(m1, m2, h-0.33333)];
+ };
+
+ fb.hueToRGB = function (m1, m2, h) {
+ h = (h < 0) ? h + 1 : ((h > 1) ? h - 1 : h);
+ if (h * 6 < 1) return m1 + (m2 - m1) * h * 6;
+ if (h * 2 < 1) return m2;
+ if (h * 3 < 2) return m1 + (m2 - m1) * (0.66666 - h) * 6;
+ return m1;
+ };
+
+ fb.RGBToHSL = function (rgb) {
+ var min, max, delta, h, s, l;
+ var r = rgb[0], g = rgb[1], b = rgb[2];
+ min = Math.min(r, Math.min(g, b));
+ max = Math.max(r, Math.max(g, b));
+ delta = max - min;
+ l = (min + max) / 2;
+ s = 0;
+ if (l > 0 && l < 1) {
+ s = delta / (l < 0.5 ? (2 * l) : (2 - 2 * l));
+ }
+ h = 0;
+ if (delta > 0) {
+ if (max == r && max != g) h += (g - b) / delta;
+ if (max == g && max != b) h += (2 + (b - r) / delta);
+ if (max == b && max != r) h += (4 + (r - g) / delta);
+ h /= 6;
+ }
+ return [h, s, l];
+ };
+
+ // Install mousedown handler (the others are set on the document on-demand)
+ $('*', e).mousedown(fb.mousedown);
+
+ // Init color
+ fb.setColor('#000000');
+
+ // Set linked elements/callback
+ if (callback) {
+ fb.linkTo(callback);
+ }
+};
+
+})(jQuery); \ No newline at end of file
diff --git a/www/crm/wp-admin/js/gallery.js b/www/crm/wp-admin/js/gallery.js
new file mode 100644
index 00000000..36cb0863
--- /dev/null
+++ b/www/crm/wp-admin/js/gallery.js
@@ -0,0 +1,241 @@
+/**
+ * @output wp-admin/js/gallery.js
+ */
+
+/* global unescape, getUserSetting, setUserSetting, wpgallery, tinymce */
+
+jQuery(document).ready(function($) {
+ var gallerySortable, gallerySortableInit, sortIt, clearAll, w, desc = false;
+
+ gallerySortableInit = function() {
+ gallerySortable = $('#media-items').sortable( {
+ items: 'div.media-item',
+ placeholder: 'sorthelper',
+ axis: 'y',
+ distance: 2,
+ handle: 'div.filename',
+ stop: function() {
+ // When an update has occurred, adjust the order for each item
+ var all = $('#media-items').sortable('toArray'), len = all.length;
+ $.each(all, function(i, id) {
+ var order = desc ? (len - i) : (1 + i);
+ $('#' + id + ' .menu_order input').val(order);
+ });
+ }
+ } );
+ };
+
+ sortIt = function() {
+ var all = $('.menu_order_input'), len = all.length;
+ all.each(function(i){
+ var order = desc ? (len - i) : (1 + i);
+ $(this).val(order);
+ });
+ };
+
+ clearAll = function(c) {
+ c = c || 0;
+ $('.menu_order_input').each( function() {
+ if ( this.value === '0' || c ) {
+ this.value = '';
+ }
+ });
+ };
+
+ $('#asc').click( function( e ) {
+ e.preventDefault();
+ desc = false;
+ sortIt();
+ });
+ $('#desc').click( function( e ) {
+ e.preventDefault();
+ desc = true;
+ sortIt();
+ });
+ $('#clear').click( function( e ) {
+ e.preventDefault();
+ clearAll(1);
+ });
+ $('#showall').click( function( e ) {
+ e.preventDefault();
+ $('#sort-buttons span a').toggle();
+ $('a.describe-toggle-on').hide();
+ $('a.describe-toggle-off, table.slidetoggle').show();
+ $('img.pinkynail').toggle(false);
+ });
+ $('#hideall').click( function( e ) {
+ e.preventDefault();
+ $('#sort-buttons span a').toggle();
+ $('a.describe-toggle-on').show();
+ $('a.describe-toggle-off, table.slidetoggle').hide();
+ $('img.pinkynail').toggle(true);
+ });
+
+ // initialize sortable
+ gallerySortableInit();
+ clearAll();
+
+ if ( $('#media-items>*').length > 1 ) {
+ w = wpgallery.getWin();
+
+ $('#save-all, #gallery-settings').show();
+ if ( typeof w.tinyMCE !== 'undefined' && w.tinyMCE.activeEditor && ! w.tinyMCE.activeEditor.isHidden() ) {
+ wpgallery.mcemode = true;
+ wpgallery.init();
+ } else {
+ $('#insert-gallery').show();
+ }
+ }
+});
+
+jQuery(window).unload( function () { window.tinymce = window.tinyMCE = window.wpgallery = null; } ); // Cleanup
+
+/* gallery settings */
+window.tinymce = null;
+
+window.wpgallery = {
+ mcemode : false,
+ editor : {},
+ dom : {},
+ is_update : false,
+ el : {},
+
+ I : function(e) {
+ return document.getElementById(e);
+ },
+
+ init: function() {
+ var t = this, li, q, i, it, w = t.getWin();
+
+ if ( ! t.mcemode ) {
+ return;
+ }
+
+ li = ('' + document.location.search).replace(/^\?/, '').split('&');
+ q = {};
+ for (i=0; i<li.length; i++) {
+ it = li[i].split('=');
+ q[unescape(it[0])] = unescape(it[1]);
+ }
+
+ if ( q.mce_rdomain ) {
+ document.domain = q.mce_rdomain;
+ }
+
+ // Find window & API
+ window.tinymce = w.tinymce;
+ window.tinyMCE = w.tinyMCE;
+ t.editor = tinymce.EditorManager.activeEditor;
+
+ t.setup();
+ },
+
+ getWin : function() {
+ return window.dialogArguments || opener || parent || top;
+ },
+
+ setup : function() {
+ var t = this, a, ed = t.editor, g, columns, link, order, orderby;
+ if ( ! t.mcemode ) {
+ return;
+ }
+
+ t.el = ed.selection.getNode();
+
+ if ( t.el.nodeName !== 'IMG' || ! ed.dom.hasClass(t.el, 'wpGallery') ) {
+ if ( ( g = ed.dom.select('img.wpGallery') ) && g[0] ) {
+ t.el = g[0];
+ } else {
+ if ( getUserSetting('galfile') === '1' ) {
+ t.I('linkto-file').checked = 'checked';
+ }
+ if ( getUserSetting('galdesc') === '1' ) {
+ t.I('order-desc').checked = 'checked';
+ }
+ if ( getUserSetting('galcols') ) {
+ t.I('columns').value = getUserSetting('galcols');
+ }
+ if ( getUserSetting('galord') ) {
+ t.I('orderby').value = getUserSetting('galord');
+ }
+ jQuery('#insert-gallery').show();
+ return;
+ }
+ }
+
+ a = ed.dom.getAttrib(t.el, 'title');
+ a = ed.dom.decode(a);
+
+ if ( a ) {
+ jQuery('#update-gallery').show();
+ t.is_update = true;
+
+ columns = a.match(/columns=['"]([0-9]+)['"]/);
+ link = a.match(/link=['"]([^'"]+)['"]/i);
+ order = a.match(/order=['"]([^'"]+)['"]/i);
+ orderby = a.match(/orderby=['"]([^'"]+)['"]/i);
+
+ if ( link && link[1] ) {
+ t.I('linkto-file').checked = 'checked';
+ }
+ if ( order && order[1] ) {
+ t.I('order-desc').checked = 'checked';
+ }
+ if ( columns && columns[1] ) {
+ t.I('columns').value = '' + columns[1];
+ }
+ if ( orderby && orderby[1] ) {
+ t.I('orderby').value = orderby[1];
+ }
+ } else {
+ jQuery('#insert-gallery').show();
+ }
+ },
+
+ update : function() {
+ var t = this, ed = t.editor, all = '', s;
+
+ if ( ! t.mcemode || ! t.is_update ) {
+ s = '[gallery' + t.getSettings() + ']';
+ t.getWin().send_to_editor(s);
+ return;
+ }
+
+ if ( t.el.nodeName !== 'IMG' ) {
+ return;
+ }
+
+ all = ed.dom.decode( ed.dom.getAttrib( t.el, 'title' ) );
+ all = all.replace(/\s*(order|link|columns|orderby)=['"]([^'"]+)['"]/gi, '');
+ all += t.getSettings();
+
+ ed.dom.setAttrib(t.el, 'title', all);
+ t.getWin().tb_remove();
+ },
+
+ getSettings : function() {
+ var I = this.I, s = '';
+
+ if ( I('linkto-file').checked ) {
+ s += ' link="file"';
+ setUserSetting('galfile', '1');
+ }
+
+ if ( I('order-desc').checked ) {
+ s += ' order="DESC"';
+ setUserSetting('galdesc', '1');
+ }
+
+ if ( I('columns').value !== 3 ) {
+ s += ' columns="' + I('columns').value + '"';
+ setUserSetting('galcols', I('columns').value);
+ }
+
+ if ( I('orderby').value !== 'menu_order' ) {
+ s += ' orderby="' + I('orderby').value + '"';
+ setUserSetting('galord', I('orderby').value);
+ }
+
+ return s;
+ }
+};
diff --git a/www/crm/wp-admin/js/gallery.min.js b/www/crm/wp-admin/js/gallery.min.js
new file mode 100644
index 00000000..96ebba87
--- /dev/null
+++ b/www/crm/wp-admin/js/gallery.min.js
@@ -0,0 +1 @@
+jQuery(document).ready(function(a){var b,c,d,e,f,g=!1;c=function(){b=a("#media-items").sortable({items:"div.media-item",placeholder:"sorthelper",axis:"y",distance:2,handle:"div.filename",stop:function(){var b=a("#media-items").sortable("toArray"),c=b.length;a.each(b,function(b,d){var e=g?c-b:1+b;a("#"+d+" .menu_order input").val(e)})}})},d=function(){var b=a(".menu_order_input"),c=b.length;b.each(function(b){var d=g?c-b:1+b;a(this).val(d)})},e=function(b){b=b||0,a(".menu_order_input").each(function(){("0"===this.value||b)&&(this.value="")})},a("#asc").click(function(a){a.preventDefault(),g=!1,d()}),a("#desc").click(function(a){a.preventDefault(),g=!0,d()}),a("#clear").click(function(a){a.preventDefault(),e(1)}),a("#showall").click(function(b){b.preventDefault(),a("#sort-buttons span a").toggle(),a("a.describe-toggle-on").hide(),a("a.describe-toggle-off, table.slidetoggle").show(),a("img.pinkynail").toggle(!1)}),a("#hideall").click(function(b){b.preventDefault(),a("#sort-buttons span a").toggle(),a("a.describe-toggle-on").show(),a("a.describe-toggle-off, table.slidetoggle").hide(),a("img.pinkynail").toggle(!0)}),c(),e(),a("#media-items>*").length>1&&(f=wpgallery.getWin(),a("#save-all, #gallery-settings").show(),"undefined"!=typeof f.tinyMCE&&f.tinyMCE.activeEditor&&!f.tinyMCE.activeEditor.isHidden()?(wpgallery.mcemode=!0,wpgallery.init()):a("#insert-gallery").show())}),jQuery(window).unload(function(){window.tinymce=window.tinyMCE=window.wpgallery=null}),window.tinymce=null,window.wpgallery={mcemode:!1,editor:{},dom:{},is_update:!1,el:{},I:function(a){return document.getElementById(a)},init:function(){var a,b,c,d,e=this,f=e.getWin();if(e.mcemode){for(a=(""+document.location.search).replace(/^\?/,"").split("&"),b={},c=0;c<a.length;c++)d=a[c].split("="),b[unescape(d[0])]=unescape(d[1]);b.mce_rdomain&&(document.domain=b.mce_rdomain),window.tinymce=f.tinymce,window.tinyMCE=f.tinyMCE,e.editor=tinymce.EditorManager.activeEditor,e.setup()}},getWin:function(){return window.dialogArguments||opener||parent||top},setup:function(){var a,b,c,d,e,f,g=this,h=g.editor;if(g.mcemode){if(g.el=h.selection.getNode(),"IMG"!==g.el.nodeName||!h.dom.hasClass(g.el,"wpGallery")){if(!(b=h.dom.select("img.wpGallery"))||!b[0])return"1"===getUserSetting("galfile")&&(g.I("linkto-file").checked="checked"),"1"===getUserSetting("galdesc")&&(g.I("order-desc").checked="checked"),getUserSetting("galcols")&&(g.I("columns").value=getUserSetting("galcols")),getUserSetting("galord")&&(g.I("orderby").value=getUserSetting("galord")),void jQuery("#insert-gallery").show();g.el=b[0]}a=h.dom.getAttrib(g.el,"title"),a=h.dom.decode(a),a?(jQuery("#update-gallery").show(),g.is_update=!0,c=a.match(/columns=['"]([0-9]+)['"]/),d=a.match(/link=['"]([^'"]+)['"]/i),e=a.match(/order=['"]([^'"]+)['"]/i),f=a.match(/orderby=['"]([^'"]+)['"]/i),d&&d[1]&&(g.I("linkto-file").checked="checked"),e&&e[1]&&(g.I("order-desc").checked="checked"),c&&c[1]&&(g.I("columns").value=""+c[1]),f&&f[1]&&(g.I("orderby").value=f[1])):jQuery("#insert-gallery").show()}},update:function(){var a,b=this,c=b.editor,d="";return b.mcemode&&b.is_update?void("IMG"===b.el.nodeName&&(d=c.dom.decode(c.dom.getAttrib(b.el,"title")),d=d.replace(/\s*(order|link|columns|orderby)=['"]([^'"]+)['"]/gi,""),d+=b.getSettings(),c.dom.setAttrib(b.el,"title",d),b.getWin().tb_remove())):(a="[gallery"+b.getSettings()+"]",void b.getWin().send_to_editor(a))},getSettings:function(){var a=this.I,b="";return a("linkto-file").checked&&(b+=' link="file"',setUserSetting("galfile","1")),a("order-desc").checked&&(b+=' order="DESC"',setUserSetting("galdesc","1")),3!==a("columns").value&&(b+=' columns="'+a("columns").value+'"',setUserSetting("galcols",a("columns").value)),"menu_order"!==a("orderby").value&&(b+=' orderby="'+a("orderby").value+'"',setUserSetting("galord",a("orderby").value)),b}}; \ No newline at end of file
diff --git a/www/crm/wp-admin/js/image-edit.js b/www/crm/wp-admin/js/image-edit.js
new file mode 100644
index 00000000..100f26e7
--- /dev/null
+++ b/www/crm/wp-admin/js/image-edit.js
@@ -0,0 +1,1126 @@
+/**
+ * The functions necessary for editing images.
+ *
+ * @since 2.9.0
+ * @output wp-admin/js/image-edit.js
+ */
+
+ /* global imageEditL10n, ajaxurl, confirm */
+
+(function($) {
+
+ /**
+ * Contains all the methods to initialise and control the image editor.
+ *
+ * @namespace imageEdit
+ */
+ var imageEdit = window.imageEdit = {
+ iasapi : {},
+ hold : {},
+ postid : '',
+ _view : false,
+
+ /**
+ * Handle crop tool clicks.
+ */
+ handleCropToolClick: function( postid, nonce, cropButton ) {
+ var img = $( '#image-preview-' + postid ),
+ selection = this.iasapi.getSelection();
+
+ // Ensure selection is available, otherwise reset to full image.
+ if ( isNaN( selection.x1 ) ) {
+ this.setCropSelection( postid, { 'x1': 0, 'y1': 0, 'x2': img.innerWidth(), 'y2': img.innerHeight(), 'width': img.innerWidth(), 'height': img.innerHeight() } );
+ selection = this.iasapi.getSelection();
+ }
+
+ // If we don't already have a selection, select the entire image.
+ if ( 0 === selection.x1 && 0 === selection.y1 && 0 === selection.x2 && 0 === selection.y2 ) {
+ this.iasapi.setSelection( 0, 0, img.innerWidth(), img.innerHeight(), true );
+ this.iasapi.setOptions( { show: true } );
+ this.iasapi.update();
+ } else {
+
+ // Otherwise, perform the crop.
+ imageEdit.crop( postid, nonce , cropButton );
+ }
+ },
+
+ /**
+ * Converts a value to an integer.
+ *
+ * @memberof imageEdit
+ * @since 2.9.0
+ *
+ * @param {number} f The float value that should be converted.
+ *
+ * @return {number} The integer representation from the float value.
+ */
+ intval : function(f) {
+ /*
+ * Bitwise OR operator: one of the obscure ways to truncate floating point figures,
+ * worth reminding JavaScript doesn't have a distinct "integer" type.
+ */
+ return f | 0;
+ },
+
+ /**
+ * Adds the disabled attribute and class to a single form element or a field set.
+ *
+ * @memberof imageEdit
+ * @since 2.9.0
+ *
+ * @param {jQuery} el The element that should be modified.
+ * @param {bool|number} s The state for the element. If set to true
+ * the element is disabled,
+ * otherwise the element is enabled.
+ * The function is sometimes called with a 0 or 1
+ * instead of true or false.
+ *
+ * @returns {void}
+ */
+ setDisabled : function( el, s ) {
+ /*
+ * `el` can be a single form element or a fieldset. Before #28864, the disabled state on
+ * some text fields was handled targeting $('input', el). Now we need to handle the
+ * disabled state on buttons too so we can just target `el` regardless if it's a single
+ * element or a fieldset because when a fieldset is disabled, its descendants are disabled too.
+ */
+ if ( s ) {
+ el.removeClass( 'disabled' ).prop( 'disabled', false );
+ } else {
+ el.addClass( 'disabled' ).prop( 'disabled', true );
+ }
+ },
+
+ /**
+ * Initializes the image editor.
+ *
+ * @memberof imageEdit
+ * @since 2.9.0
+ *
+ * @param {number} postid The post id.
+ *
+ * @returns {void}
+ */
+ init : function(postid) {
+ var t = this, old = $('#image-editor-' + t.postid),
+ x = t.intval( $('#imgedit-x-' + postid).val() ),
+ y = t.intval( $('#imgedit-y-' + postid).val() );
+
+ if ( t.postid !== postid && old.length ) {
+ t.close(t.postid);
+ }
+
+ t.hold.w = t.hold.ow = x;
+ t.hold.h = t.hold.oh = y;
+ t.hold.xy_ratio = x / y;
+ t.hold.sizer = parseFloat( $('#imgedit-sizer-' + postid).val() );
+ t.postid = postid;
+ $('#imgedit-response-' + postid).empty();
+
+ $('input[type="text"]', '#imgedit-panel-' + postid).keypress(function(e) {
+ var k = e.keyCode;
+
+ // Key codes 37 thru 40 are the arrow keys.
+ if ( 36 < k && k < 41 ) {
+ $(this).blur();
+ }
+
+ // The key code 13 is the enter key.
+ if ( 13 === k ) {
+ e.preventDefault();
+ e.stopPropagation();
+ return false;
+ }
+ });
+ },
+
+ /**
+ * Toggles the wait/load icon in the editor.
+ *
+ * @memberof imageEdit
+ * @since 2.9.0
+ *
+ * @param {number} postid The post id.
+ * @param {number} toggle Is 0 or 1, fades the icon in then 1 and out when 0.
+ *
+ * @returns {void}
+ */
+ toggleEditor : function(postid, toggle) {
+ var wait = $('#imgedit-wait-' + postid);
+
+ if ( toggle ) {
+ wait.fadeIn( 'fast' );
+ } else {
+ wait.fadeOut('fast');
+ }
+ },
+
+ /**
+ * Shows or hides the image edit help box.
+ *
+ * @memberof imageEdit
+ * @since 2.9.0
+ *
+ * @param {HTMLElement} el The element to create the help window in.
+ *
+ * @returns {boolean} Always returns false.
+ */
+ toggleHelp : function(el) {
+ var $el = $( el );
+ $el
+ .attr( 'aria-expanded', 'false' === $el.attr( 'aria-expanded' ) ? 'true' : 'false' )
+ .parents( '.imgedit-group-top' ).toggleClass( 'imgedit-help-toggled' ).find( '.imgedit-help' ).slideToggle( 'fast' );
+
+ return false;
+ },
+
+ /**
+ * Gets the value from the image edit target.
+ *
+ * The image edit target contains the image sizes where the (possible) changes
+ * have to be applied to.
+ *
+ * @memberof imageEdit
+ * @since 2.9.0
+ *
+ * @param {number} postid The post id.
+ *
+ * @returns {string} The value from the imagedit-save-target input field when available,
+ * or 'full' when not available.
+ */
+ getTarget : function(postid) {
+ return $('input[name="imgedit-target-' + postid + '"]:checked', '#imgedit-save-target-' + postid).val() || 'full';
+ },
+
+ /**
+ * Recalculates the height or width and keeps the original aspect ratio.
+ *
+ * If the original image size is exceeded a red exclamation mark is shown.
+ *
+ * @memberof imageEdit
+ * @since 2.9.0
+ *
+ * @param {number} postid The current post id.
+ * @param {number} x Is 0 when it applies the y-axis
+ * and 1 when applicable for the x-axis.
+ * @param {jQuery} el Element.
+ *
+ * @returns {void}
+ */
+ scaleChanged : function( postid, x, el ) {
+ var w = $('#imgedit-scale-width-' + postid), h = $('#imgedit-scale-height-' + postid),
+ warn = $('#imgedit-scale-warn-' + postid), w1 = '', h1 = '';
+
+ if ( false === this.validateNumeric( el ) ) {
+ return;
+ }
+
+ if ( x ) {
+ h1 = ( w.val() !== '' ) ? Math.round( w.val() / this.hold.xy_ratio ) : '';
+ h.val( h1 );
+ } else {
+ w1 = ( h.val() !== '' ) ? Math.round( h.val() * this.hold.xy_ratio ) : '';
+ w.val( w1 );
+ }
+
+ if ( ( h1 && h1 > this.hold.oh ) || ( w1 && w1 > this.hold.ow ) ) {
+ warn.css('visibility', 'visible');
+ } else {
+ warn.css('visibility', 'hidden');
+ }
+ },
+
+ /**
+ * Gets the selected aspect ratio.
+ *
+ * @memberof imageEdit
+ * @since 2.9.0
+ *
+ * @param {number} postid The post id.
+ *
+ * @returns {string} The aspect ratio.
+ */
+ getSelRatio : function(postid) {
+ var x = this.hold.w, y = this.hold.h,
+ X = this.intval( $('#imgedit-crop-width-' + postid).val() ),
+ Y = this.intval( $('#imgedit-crop-height-' + postid).val() );
+
+ if ( X && Y ) {
+ return X + ':' + Y;
+ }
+
+ if ( x && y ) {
+ return x + ':' + y;
+ }
+
+ return '1:1';
+ },
+
+ /**
+ * Removes the last action from the image edit history.
+ * The history consist of (edit) actions performed on the image.
+ *
+ * @memberof imageEdit
+ * @since 2.9.0
+ *
+ * @param {number} postid The post id.
+ * @param {number} setSize 0 or 1, when 1 the image resets to its original size.
+ *
+ * @returns {string} JSON string containing the history or an empty string if no history exists.
+ */
+ filterHistory : function(postid, setSize) {
+ // Apply undo state to history.
+ var history = $('#imgedit-history-' + postid).val(), pop, n, o, i, op = [];
+
+ if ( history !== '' ) {
+ // Read the JSON string with the image edit history.
+ history = JSON.parse(history);
+ pop = this.intval( $('#imgedit-undone-' + postid).val() );
+ if ( pop > 0 ) {
+ while ( pop > 0 ) {
+ history.pop();
+ pop--;
+ }
+ }
+
+ // Reset size to it's original state.
+ if ( setSize ) {
+ if ( !history.length ) {
+ this.hold.w = this.hold.ow;
+ this.hold.h = this.hold.oh;
+ return '';
+ }
+
+ // Restore original 'o'.
+ o = history[history.length - 1];
+
+ // c = 'crop', r = 'rotate', f = 'flip'
+ o = o.c || o.r || o.f || false;
+
+ if ( o ) {
+ // fw = Full image width
+ this.hold.w = o.fw;
+ // fh = Full image height
+ this.hold.h = o.fh;
+ }
+ }
+
+ // Filter the last step/action from the history.
+ for ( n in history ) {
+ i = history[n];
+ if ( i.hasOwnProperty('c') ) {
+ op[n] = { 'c': { 'x': i.c.x, 'y': i.c.y, 'w': i.c.w, 'h': i.c.h } };
+ } else if ( i.hasOwnProperty('r') ) {
+ op[n] = { 'r': i.r.r };
+ } else if ( i.hasOwnProperty('f') ) {
+ op[n] = { 'f': i.f.f };
+ }
+ }
+ return JSON.stringify(op);
+ }
+ return '';
+ },
+ /**
+ * Binds the necessary events to the image.
+ *
+ * When the image source is reloaded the image will be reloaded.
+ *
+ * @memberof imageEdit
+ * @since 2.9.0
+ *
+ * @param {number} postid The post id.
+ * @param {string} nonce The nonce to verify the request.
+ * @param {function} callback Function to execute when the image is loaded.
+ *
+ * @returns {void}
+ */
+ refreshEditor : function(postid, nonce, callback) {
+ var t = this, data, img;
+
+ t.toggleEditor(postid, 1);
+ data = {
+ 'action': 'imgedit-preview',
+ '_ajax_nonce': nonce,
+ 'postid': postid,
+ 'history': t.filterHistory(postid, 1),
+ 'rand': t.intval(Math.random() * 1000000)
+ };
+
+ img = $( '<img id="image-preview-' + postid + '" alt="" />' )
+ .on( 'load', { history: data.history }, function( event ) {
+ var max1, max2,
+ parent = $( '#imgedit-crop-' + postid ),
+ t = imageEdit,
+ historyObj;
+
+ // Checks if there already is some image-edit history.
+ if ( '' !== event.data.history ) {
+ historyObj = JSON.parse( event.data.history );
+ // If last executed action in history is a crop action.
+ if ( historyObj[historyObj.length - 1].hasOwnProperty( 'c' ) ) {
+ /*
+ * A crop action has completed and the crop button gets disabled
+ * ensure the undo button is enabled.
+ */
+ t.setDisabled( $( '#image-undo-' + postid) , true );
+ // Move focus to the undo button to avoid a focus loss.
+ $( '#image-undo-' + postid ).focus();
+ }
+ }
+
+ parent.empty().append(img);
+
+ // w, h are the new full size dims
+ max1 = Math.max( t.hold.w, t.hold.h );
+ max2 = Math.max( $(img).width(), $(img).height() );
+ t.hold.sizer = max1 > max2 ? max2 / max1 : 1;
+
+ t.initCrop(postid, img, parent);
+
+ if ( (typeof callback !== 'undefined') && callback !== null ) {
+ callback();
+ }
+
+ if ( $('#imgedit-history-' + postid).val() && $('#imgedit-undone-' + postid).val() === '0' ) {
+ $('input.imgedit-submit-btn', '#imgedit-panel-' + postid).removeAttr('disabled');
+ } else {
+ $('input.imgedit-submit-btn', '#imgedit-panel-' + postid).prop('disabled', true);
+ }
+
+ t.toggleEditor(postid, 0);
+ })
+ .on('error', function() {
+ $('#imgedit-crop-' + postid).empty().append('<div class="error"><p>' + imageEditL10n.error + '</p></div>');
+ t.toggleEditor(postid, 0);
+ })
+ .attr('src', ajaxurl + '?' + $.param(data));
+ },
+ /**
+ * Performs an image edit action.
+ *
+ * @memberof imageEdit
+ * @since 2.9.0
+ *
+ * @param {number} postid The post id.
+ * @param {string} nonce The nonce to verify the request.
+ * @param {string} action The action to perform on the image.
+ * The possible actions are: "scale" and "restore".
+ *
+ * @returns {boolean|void} Executes a post request that refreshes the page
+ * when the action is performed.
+ * Returns false if a invalid action is given,
+ * or when the action cannot be performed.
+ */
+ action : function(postid, nonce, action) {
+ var t = this, data, w, h, fw, fh;
+
+ if ( t.notsaved(postid) ) {
+ return false;
+ }
+
+ data = {
+ 'action': 'image-editor',
+ '_ajax_nonce': nonce,
+ 'postid': postid
+ };
+
+ if ( 'scale' === action ) {
+ w = $('#imgedit-scale-width-' + postid),
+ h = $('#imgedit-scale-height-' + postid),
+ fw = t.intval(w.val()),
+ fh = t.intval(h.val());
+
+ if ( fw < 1 ) {
+ w.focus();
+ return false;
+ } else if ( fh < 1 ) {
+ h.focus();
+ return false;
+ }
+
+ if ( fw === t.hold.ow || fh === t.hold.oh ) {
+ return false;
+ }
+
+ data['do'] = 'scale';
+ data.fwidth = fw;
+ data.fheight = fh;
+ } else if ( 'restore' === action ) {
+ data['do'] = 'restore';
+ } else {
+ return false;
+ }
+
+ t.toggleEditor(postid, 1);
+ $.post(ajaxurl, data, function(r) {
+ $('#image-editor-' + postid).empty().append(r);
+ t.toggleEditor(postid, 0);
+ // refresh the attachment model so that changes propagate
+ if ( t._view ) {
+ t._view.refresh();
+ }
+ });
+ },
+
+ /**
+ * Stores the changes that are made to the image.
+ *
+ * @memberof imageEdit
+ * @since 2.9.0
+ *
+ * @param {number} postid The post id to get the image from the database.
+ * @param {string} nonce The nonce to verify the request.
+ *
+ * @returns {boolean|void} If the actions are successfully saved a response message is shown.
+ * Returns false if there is no image editing history,
+ * thus there are not edit-actions performed on the image.
+ */
+ save : function(postid, nonce) {
+ var data,
+ target = this.getTarget(postid),
+ history = this.filterHistory(postid, 0),
+ self = this;
+
+ if ( '' === history ) {
+ return false;
+ }
+
+ this.toggleEditor(postid, 1);
+ data = {
+ 'action': 'image-editor',
+ '_ajax_nonce': nonce,
+ 'postid': postid,
+ 'history': history,
+ 'target': target,
+ 'context': $('#image-edit-context').length ? $('#image-edit-context').val() : null,
+ 'do': 'save'
+ };
+ // Post the image edit data to the backend.
+ $.post(ajaxurl, data, function(r) {
+ // Read the response.
+ var ret = JSON.parse(r);
+
+ // If a response is returned, close the editor and show an error.
+ if ( ret.error ) {
+ $('#imgedit-response-' + postid).html('<div class="error"><p>' + ret.error + '</p></div>');
+ imageEdit.close(postid);
+ return;
+ }
+
+ if ( ret.fw && ret.fh ) {
+ $('#media-dims-' + postid).html( ret.fw + ' &times; ' + ret.fh );
+ }
+
+ if ( ret.thumbnail ) {
+ $('.thumbnail', '#thumbnail-head-' + postid).attr('src', ''+ret.thumbnail);
+ }
+
+ if ( ret.msg ) {
+ $('#imgedit-response-' + postid).html('<div class="updated"><p>' + ret.msg + '</p></div>');
+ }
+
+ if ( self._view ) {
+ self._view.save();
+ } else {
+ imageEdit.close(postid);
+ }
+ });
+ },
+
+ /**
+ * Creates the image edit window.
+ *
+ * @memberof imageEdit
+ * @since 2.9.0
+ *
+ * @param {number} postid The post id for the image.
+ * @param {string} nonce The nonce to verify the request.
+ * @param {object} view The image editor view to be used for the editing.
+ *
+ * @returns {void|promise} Either returns void if the button was already activated
+ * or returns an instance of the image editor, wrapped in a promise.
+ */
+ open : function( postid, nonce, view ) {
+ this._view = view;
+
+ var dfd, data, elem = $('#image-editor-' + postid), head = $('#media-head-' + postid),
+ btn = $('#imgedit-open-btn-' + postid), spin = btn.siblings('.spinner');
+
+ /*
+ * Instead of disabling the button, which causes a focus loss and makes screen
+ * readers announce "unavailable", return if the button was already clicked.
+ */
+ if ( btn.hasClass( 'button-activated' ) ) {
+ return;
+ }
+
+ spin.addClass( 'is-active' );
+
+ data = {
+ 'action': 'image-editor',
+ '_ajax_nonce': nonce,
+ 'postid': postid,
+ 'do': 'open'
+ };
+
+ dfd = $.ajax({
+ url: ajaxurl,
+ type: 'post',
+ data: data,
+ beforeSend: function() {
+ btn.addClass( 'button-activated' );
+ }
+ }).done(function( html ) {
+ elem.html( html );
+ head.fadeOut('fast', function(){
+ elem.fadeIn('fast');
+ btn.removeClass( 'button-activated' );
+ spin.removeClass( 'is-active' );
+ });
+ // Initialise the Image Editor now that everything is ready.
+ imageEdit.init( postid );
+ });
+
+ return dfd;
+ },
+
+ /**
+ * Initializes the cropping tool and sets a default cropping selection.
+ *
+ * @memberof imageEdit
+ * @since 2.9.0
+ *
+ * @param {number} postid The post id.
+ *
+ * @returns {void}
+ */
+ imgLoaded : function(postid) {
+ var img = $('#image-preview-' + postid), parent = $('#imgedit-crop-' + postid);
+
+ // Ensure init has run even when directly loaded.
+ if ( 'undefined' === typeof this.hold.sizer ) {
+ this.init( postid );
+ }
+
+ this.initCrop(postid, img, parent);
+ this.setCropSelection( postid, { 'x1': 0, 'y1': 0, 'x2': 0, 'y2': 0, 'width': img.innerWidth(), 'height': img.innerHeight() } );
+
+ this.toggleEditor(postid, 0);
+ // Editor is ready, move focus to the first focusable element.
+ $( '.imgedit-wrap .imgedit-help-toggle' ).eq( 0 ).focus();
+ },
+
+ /**
+ * Initializes the cropping tool.
+ *
+ * @memberof imageEdit
+ * @since 2.9.0
+ *
+ * @param {number} postid The post id.
+ * @param {HTMLElement} image The preview image.
+ * @param {HTMLElement} parent The preview image container.
+ *
+ * @returns {void}
+ */
+ initCrop : function(postid, image, parent) {
+ var t = this,
+ selW = $('#imgedit-sel-width-' + postid),
+ selH = $('#imgedit-sel-height-' + postid),
+ $img;
+
+ t.iasapi = $(image).imgAreaSelect({
+ parent: parent,
+ instance: true,
+ handles: true,
+ keys: true,
+ minWidth: 3,
+ minHeight: 3,
+
+ /**
+ * Sets the CSS styles and binds events for locking the aspect ratio.
+ *
+ * @ignore
+ *
+ * @param {jQuery} img The preview image.
+ */
+ onInit: function( img ) {
+ // Ensure that the imgAreaSelect wrapper elements are position:absolute.
+ // (even if we're in a position:fixed modal)
+ $img = $( img );
+ $img.next().css( 'position', 'absolute' )
+ .nextAll( '.imgareaselect-outer' ).css( 'position', 'absolute' );
+ /**
+ * Binds mouse down event to the cropping container.
+ *
+ * @returns {void}
+ */
+ parent.children().on( 'mousedown, touchstart', function(e){
+ var ratio = false, sel, defRatio;
+
+ if ( e.shiftKey ) {
+ sel = t.iasapi.getSelection();
+ defRatio = t.getSelRatio(postid);
+ ratio = ( sel && sel.width && sel.height ) ? sel.width + ':' + sel.height : defRatio;
+ }
+
+ t.iasapi.setOptions({
+ aspectRatio: ratio
+ });
+ });
+ },
+
+ /**
+ * Event triggered when starting a selection.
+ *
+ * @ignore
+ *
+ * @returns {void}
+ */
+ onSelectStart: function() {
+ imageEdit.setDisabled($('#imgedit-crop-sel-' + postid), 1);
+ },
+ /**
+ * Event triggered when the selection is ended.
+ *
+ * @ignore
+ *
+ * @param {object} img jQuery object representing the image.
+ * @param {object} c The selection.
+ *
+ * @returns {object}
+ */
+ onSelectEnd: function(img, c) {
+ imageEdit.setCropSelection(postid, c);
+ },
+
+ /**
+ * Event triggered when the selection changes.
+ *
+ * @ignore
+ *
+ * @param {object} img jQuery object representing the image.
+ * @param {object} c The selection.
+ *
+ * @returns {void}
+ */
+ onSelectChange: function(img, c) {
+ var sizer = imageEdit.hold.sizer;
+ selW.val( imageEdit.round(c.width / sizer) );
+ selH.val( imageEdit.round(c.height / sizer) );
+ }
+ });
+ },
+
+ /**
+ * Stores the current crop selection.
+ *
+ * @memberof imageEdit
+ * @since 2.9.0
+ *
+ * @param {number} postid The post id.
+ * @param {object} c The selection.
+ *
+ * @returns {boolean}
+ */
+ setCropSelection : function(postid, c) {
+ var sel;
+
+ c = c || 0;
+
+ if ( !c || ( c.width < 3 && c.height < 3 ) ) {
+ this.setDisabled( $( '.imgedit-crop', '#imgedit-panel-' + postid ), 1 );
+ this.setDisabled( $( '#imgedit-crop-sel-' + postid ), 1 );
+ $('#imgedit-sel-width-' + postid).val('');
+ $('#imgedit-sel-height-' + postid).val('');
+ $('#imgedit-selection-' + postid).val('');
+ return false;
+ }
+
+ sel = { 'x': c.x1, 'y': c.y1, 'w': c.width, 'h': c.height };
+ this.setDisabled($('.imgedit-crop', '#imgedit-panel-' + postid), 1);
+ $('#imgedit-selection-' + postid).val( JSON.stringify(sel) );
+ },
+
+
+ /**
+ * Closes the image editor.
+ *
+ * @memberof imageEdit
+ * @since 2.9.0
+ *
+ * @param {number} postid The post id.
+ * @param {bool} warn Warning message.
+ *
+ * @returns {void|bool} Returns false if there is a warning.
+ */
+ close : function(postid, warn) {
+ warn = warn || false;
+
+ if ( warn && this.notsaved(postid) ) {
+ return false;
+ }
+
+ this.iasapi = {};
+ this.hold = {};
+
+ // If we've loaded the editor in the context of a Media Modal, then switch to the previous view,
+ // whatever that might have been.
+ if ( this._view ){
+ this._view.back();
+ }
+
+ // In case we are not accessing the image editor in the context of a View, close the editor the old-skool way
+ else {
+ $('#image-editor-' + postid).fadeOut('fast', function() {
+ $( '#media-head-' + postid ).fadeIn( 'fast', function() {
+ // Move focus back to the Edit Image button. Runs also when saving.
+ $( '#imgedit-open-btn-' + postid ).focus();
+ });
+ $(this).empty();
+ });
+ }
+
+
+ },
+
+ /**
+ * Checks if the image edit history is saved.
+ *
+ * @memberof imageEdit
+ * @since 2.9.0
+ *
+ * @param {number} postid The post id.
+ *
+ * @returns {boolean} Returns true if the history is not saved.
+ */
+ notsaved : function(postid) {
+ var h = $('#imgedit-history-' + postid).val(),
+ history = ( h !== '' ) ? JSON.parse(h) : [],
+ pop = this.intval( $('#imgedit-undone-' + postid).val() );
+
+ if ( pop < history.length ) {
+ if ( confirm( $('#imgedit-leaving-' + postid).html() ) ) {
+ return false;
+ }
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Adds an image edit action to the history.
+ *
+ * @memberof imageEdit
+ * @since 2.9.0
+ *
+ * @param {object} op The original position.
+ * @param {number} postid The post id.
+ * @param {string} nonce The nonce.
+ *
+ * @returns {void}
+ */
+ addStep : function(op, postid, nonce) {
+ var t = this, elem = $('#imgedit-history-' + postid),
+ history = ( elem.val() !== '' ) ? JSON.parse( elem.val() ) : [],
+ undone = $( '#imgedit-undone-' + postid ),
+ pop = t.intval( undone.val() );
+
+ while ( pop > 0 ) {
+ history.pop();
+ pop--;
+ }
+ undone.val(0); // reset
+
+ history.push(op);
+ elem.val( JSON.stringify(history) );
+
+ t.refreshEditor(postid, nonce, function() {
+ t.setDisabled($('#image-undo-' + postid), true);
+ t.setDisabled($('#image-redo-' + postid), false);
+ });
+ },
+
+ /**
+ * Rotates the image.
+ *
+ * @memberof imageEdit
+ * @since 2.9.0
+ *
+ * @param {string} angle The angle the image is rotated with.
+ * @param {number} postid The post id.
+ * @param {string} nonce The nonce.
+ * @param {object} t The target element.
+ *
+ * @returns {boolean}
+ */
+ rotate : function(angle, postid, nonce, t) {
+ if ( $(t).hasClass('disabled') ) {
+ return false;
+ }
+
+ this.addStep({ 'r': { 'r': angle, 'fw': this.hold.h, 'fh': this.hold.w }}, postid, nonce);
+ },
+
+ /**
+ * Flips the image.
+ *
+ * @memberof imageEdit
+ * @since 2.9.0
+ *
+ * @param {number} axis The axle the image is flipped on.
+ * @param {number} postid The post id.
+ * @param {string} nonce The nonce.
+ * @param {object} t The target element.
+ *
+ * @returns {boolean}
+ */
+ flip : function (axis, postid, nonce, t) {
+ if ( $(t).hasClass('disabled') ) {
+ return false;
+ }
+
+ this.addStep({ 'f': { 'f': axis, 'fw': this.hold.w, 'fh': this.hold.h }}, postid, nonce);
+ },
+
+ /**
+ * Crops the image.
+ *
+ * @memberof imageEdit
+ * @since 2.9.0
+ *
+ * @param {number} postid The post id.
+ * @param {string} nonce The nonce.
+ * @param {object} t The target object.
+ *
+ * @returns {void|boolean} Returns false if the crop button is disabled.
+ */
+ crop : function (postid, nonce, t) {
+ var sel = $('#imgedit-selection-' + postid).val(),
+ w = this.intval( $('#imgedit-sel-width-' + postid).val() ),
+ h = this.intval( $('#imgedit-sel-height-' + postid).val() );
+
+ if ( $(t).hasClass('disabled') || sel === '' ) {
+ return false;
+ }
+
+ sel = JSON.parse(sel);
+ if ( sel.w > 0 && sel.h > 0 && w > 0 && h > 0 ) {
+ sel.fw = w;
+ sel.fh = h;
+ this.addStep({ 'c': sel }, postid, nonce);
+ }
+ },
+
+ /**
+ * Undoes an image edit action.
+ *
+ * @memberof imageEdit
+ * @since 2.9.0
+ *
+ * @param {number} postid The post id.
+ * @param {string} nonce The nonce.
+ *
+ * @returns {void|false} Returns false if the undo button is disabled.
+ */
+ undo : function (postid, nonce) {
+ var t = this, button = $('#image-undo-' + postid), elem = $('#imgedit-undone-' + postid),
+ pop = t.intval( elem.val() ) + 1;
+
+ if ( button.hasClass('disabled') ) {
+ return;
+ }
+
+ elem.val(pop);
+ t.refreshEditor(postid, nonce, function() {
+ var elem = $('#imgedit-history-' + postid),
+ history = ( elem.val() !== '' ) ? JSON.parse( elem.val() ) : [];
+
+ t.setDisabled($('#image-redo-' + postid), true);
+ t.setDisabled(button, pop < history.length);
+ // When undo gets disabled, move focus to the redo button to avoid a focus loss.
+ if ( history.length === pop ) {
+ $( '#image-redo-' + postid ).focus();
+ }
+ });
+ },
+
+ /**
+ * Reverts a undo action.
+ *
+ * @memberof imageEdit
+ * @since 2.9.0
+ *
+ * @param {number} postid The post id.
+ * @param {string} nonce The nonce.
+ *
+ * @returns {void}
+ */
+ redo : function(postid, nonce) {
+ var t = this, button = $('#image-redo-' + postid), elem = $('#imgedit-undone-' + postid),
+ pop = t.intval( elem.val() ) - 1;
+
+ if ( button.hasClass('disabled') ) {
+ return;
+ }
+
+ elem.val(pop);
+ t.refreshEditor(postid, nonce, function() {
+ t.setDisabled($('#image-undo-' + postid), true);
+ t.setDisabled(button, pop > 0);
+ // When redo gets disabled, move focus to the undo button to avoid a focus loss.
+ if ( 0 === pop ) {
+ $( '#image-undo-' + postid ).focus();
+ }
+ });
+ },
+
+ /**
+ * Sets the selection for the height and width in pixels.
+ *
+ * @memberof imageEdit
+ * @since 2.9.0
+ *
+ * @param {number} postid The post id.
+ * @param {jQuery} el The element containing the values.
+ *
+ * @returns {void|boolean} Returns false when the x or y value is lower than 1,
+ * void when the value is not numeric or when the operation
+ * is successful.
+ */
+ setNumSelection : function( postid, el ) {
+ var sel, elX = $('#imgedit-sel-width-' + postid), elY = $('#imgedit-sel-height-' + postid),
+ x = this.intval( elX.val() ), y = this.intval( elY.val() ),
+ img = $('#image-preview-' + postid), imgh = img.height(), imgw = img.width(),
+ sizer = this.hold.sizer, x1, y1, x2, y2, ias = this.iasapi;
+
+ if ( false === this.validateNumeric( el ) ) {
+ return;
+ }
+
+ if ( x < 1 ) {
+ elX.val('');
+ return false;
+ }
+
+ if ( y < 1 ) {
+ elY.val('');
+ return false;
+ }
+
+ if ( x && y && ( sel = ias.getSelection() ) ) {
+ x2 = sel.x1 + Math.round( x * sizer );
+ y2 = sel.y1 + Math.round( y * sizer );
+ x1 = sel.x1;
+ y1 = sel.y1;
+
+ if ( x2 > imgw ) {
+ x1 = 0;
+ x2 = imgw;
+ elX.val( Math.round( x2 / sizer ) );
+ }
+
+ if ( y2 > imgh ) {
+ y1 = 0;
+ y2 = imgh;
+ elY.val( Math.round( y2 / sizer ) );
+ }
+
+ ias.setSelection( x1, y1, x2, y2 );
+ ias.update();
+ this.setCropSelection(postid, ias.getSelection());
+ }
+ },
+
+ /**
+ * Rounds a number to a whole.
+ *
+ * @memberof imageEdit
+ * @since 2.9.0
+ *
+ * @param {number} num The number.
+ *
+ * @returns {number} The number rounded to a whole number.
+ */
+ round : function(num) {
+ var s;
+ num = Math.round(num);
+
+ if ( this.hold.sizer > 0.6 ) {
+ return num;
+ }
+
+ s = num.toString().slice(-1);
+
+ if ( '1' === s ) {
+ return num - 1;
+ } else if ( '9' === s ) {
+ return num + 1;
+ }
+
+ return num;
+ },
+
+ /**
+ * Sets a locked aspect ratio for the selection.
+ *
+ * @memberof imageEdit
+ * @since 2.9.0
+ *
+ * @param {number} postid The post id.
+ * @param {number} n The ratio to set.
+ * @param {jQuery} el The element containing the values.
+ *
+ * @returns {void}
+ */
+ setRatioSelection : function(postid, n, el) {
+ var sel, r, x = this.intval( $('#imgedit-crop-width-' + postid).val() ),
+ y = this.intval( $('#imgedit-crop-height-' + postid).val() ),
+ h = $('#image-preview-' + postid).height();
+
+ if ( false === this.validateNumeric( el ) ) {
+ return;
+ }
+
+ if ( x && y ) {
+ this.iasapi.setOptions({
+ aspectRatio: x + ':' + y
+ });
+
+ if ( sel = this.iasapi.getSelection(true) ) {
+ r = Math.ceil( sel.y1 + ( ( sel.x2 - sel.x1 ) / ( x / y ) ) );
+
+ if ( r > h ) {
+ r = h;
+ if ( n ) {
+ $('#imgedit-crop-height-' + postid).val('');
+ } else {
+ $('#imgedit-crop-width-' + postid).val('');
+ }
+ }
+
+ this.iasapi.setSelection( sel.x1, sel.y1, sel.x2, r );
+ this.iasapi.update();
+ }
+ }
+ },
+
+ /**
+ * Validates if a value in a jQuery.HTMLElement is numeric.
+ *
+ * @memberof imageEdit
+ * @since 4.6
+ *
+ * @param {jQuery} el The html element.
+ *
+ * @returns {void|boolean} Returns false if the value is not numeric,
+ * void when it is.
+ */
+ validateNumeric: function( el ) {
+ if ( ! this.intval( $( el ).val() ) ) {
+ $( el ).val( '' );
+ return false;
+ }
+ }
+};
+})(jQuery);
diff --git a/www/crm/wp-admin/js/image-edit.min.js b/www/crm/wp-admin/js/image-edit.min.js
new file mode 100644
index 00000000..43eb5260
--- /dev/null
+++ b/www/crm/wp-admin/js/image-edit.min.js
@@ -0,0 +1 @@
+!function(a){var b=window.imageEdit={iasapi:{},hold:{},postid:"",_view:!1,handleCropToolClick:function(c,d,e){var f=a("#image-preview-"+c),g=this.iasapi.getSelection();isNaN(g.x1)&&(this.setCropSelection(c,{x1:0,y1:0,x2:f.innerWidth(),y2:f.innerHeight(),width:f.innerWidth(),height:f.innerHeight()}),g=this.iasapi.getSelection()),0===g.x1&&0===g.y1&&0===g.x2&&0===g.y2?(this.iasapi.setSelection(0,0,f.innerWidth(),f.innerHeight(),!0),this.iasapi.setOptions({show:!0}),this.iasapi.update()):b.crop(c,d,e)},intval:function(a){return 0|a},setDisabled:function(a,b){b?a.removeClass("disabled").prop("disabled",!1):a.addClass("disabled").prop("disabled",!0)},init:function(b){var c=this,d=a("#image-editor-"+c.postid),e=c.intval(a("#imgedit-x-"+b).val()),f=c.intval(a("#imgedit-y-"+b).val());c.postid!==b&&d.length&&c.close(c.postid),c.hold.w=c.hold.ow=e,c.hold.h=c.hold.oh=f,c.hold.xy_ratio=e/f,c.hold.sizer=parseFloat(a("#imgedit-sizer-"+b).val()),c.postid=b,a("#imgedit-response-"+b).empty(),a('input[type="text"]',"#imgedit-panel-"+b).keypress(function(b){var c=b.keyCode;if(36<c&&c<41&&a(this).blur(),13===c)return b.preventDefault(),b.stopPropagation(),!1})},toggleEditor:function(b,c){var d=a("#imgedit-wait-"+b);c?d.fadeIn("fast"):d.fadeOut("fast")},toggleHelp:function(b){var c=a(b);return c.attr("aria-expanded","false"===c.attr("aria-expanded")?"true":"false").parents(".imgedit-group-top").toggleClass("imgedit-help-toggled").find(".imgedit-help").slideToggle("fast"),!1},getTarget:function(b){return a('input[name="imgedit-target-'+b+'"]:checked',"#imgedit-save-target-"+b).val()||"full"},scaleChanged:function(b,c,d){var e=a("#imgedit-scale-width-"+b),f=a("#imgedit-scale-height-"+b),g=a("#imgedit-scale-warn-"+b),h="",i="";!1!==this.validateNumeric(d)&&(c?(i=""!==e.val()?Math.round(e.val()/this.hold.xy_ratio):"",f.val(i)):(h=""!==f.val()?Math.round(f.val()*this.hold.xy_ratio):"",e.val(h)),i&&i>this.hold.oh||h&&h>this.hold.ow?g.css("visibility","visible"):g.css("visibility","hidden"))},getSelRatio:function(b){var c=this.hold.w,d=this.hold.h,e=this.intval(a("#imgedit-crop-width-"+b).val()),f=this.intval(a("#imgedit-crop-height-"+b).val());return e&&f?e+":"+f:c&&d?c+":"+d:"1:1"},filterHistory:function(b,c){var d,e,f,g,h=a("#imgedit-history-"+b).val(),i=[];if(""!==h){if(h=JSON.parse(h),d=this.intval(a("#imgedit-undone-"+b).val()),d>0)for(;d>0;)h.pop(),d--;if(c){if(!h.length)return this.hold.w=this.hold.ow,this.hold.h=this.hold.oh,"";f=h[h.length-1],f=f.c||f.r||f.f||!1,f&&(this.hold.w=f.fw,this.hold.h=f.fh)}for(e in h)g=h[e],g.hasOwnProperty("c")?i[e]={c:{x:g.c.x,y:g.c.y,w:g.c.w,h:g.c.h}}:g.hasOwnProperty("r")?i[e]={r:g.r.r}:g.hasOwnProperty("f")&&(i[e]={f:g.f.f});return JSON.stringify(i)}return""},refreshEditor:function(c,d,e){var f,g,h=this;h.toggleEditor(c,1),f={action:"imgedit-preview",_ajax_nonce:d,postid:c,history:h.filterHistory(c,1),rand:h.intval(1e6*Math.random())},g=a('<img id="image-preview-'+c+'" alt="" />').on("load",{history:f.history},function(d){var f,h,i,j=a("#imgedit-crop-"+c),k=b;""!==d.data.history&&(i=JSON.parse(d.data.history),i[i.length-1].hasOwnProperty("c")&&(k.setDisabled(a("#image-undo-"+c),!0),a("#image-undo-"+c).focus())),j.empty().append(g),f=Math.max(k.hold.w,k.hold.h),h=Math.max(a(g).width(),a(g).height()),k.hold.sizer=f>h?h/f:1,k.initCrop(c,g,j),"undefined"!=typeof e&&null!==e&&e(),a("#imgedit-history-"+c).val()&&"0"===a("#imgedit-undone-"+c).val()?a("input.imgedit-submit-btn","#imgedit-panel-"+c).removeAttr("disabled"):a("input.imgedit-submit-btn","#imgedit-panel-"+c).prop("disabled",!0),k.toggleEditor(c,0)}).on("error",function(){a("#imgedit-crop-"+c).empty().append('<div class="error"><p>'+imageEditL10n.error+"</p></div>"),h.toggleEditor(c,0)}).attr("src",ajaxurl+"?"+a.param(f))},action:function(b,c,d){var e,f,g,h,i,j=this;if(j.notsaved(b))return!1;if(e={action:"image-editor",_ajax_nonce:c,postid:b},"scale"===d){if(f=a("#imgedit-scale-width-"+b),g=a("#imgedit-scale-height-"+b),h=j.intval(f.val()),i=j.intval(g.val()),h<1)return f.focus(),!1;if(i<1)return g.focus(),!1;if(h===j.hold.ow||i===j.hold.oh)return!1;e["do"]="scale",e.fwidth=h,e.fheight=i}else{if("restore"!==d)return!1;e["do"]="restore"}j.toggleEditor(b,1),a.post(ajaxurl,e,function(c){a("#image-editor-"+b).empty().append(c),j.toggleEditor(b,0),j._view&&j._view.refresh()})},save:function(c,d){var e,f=this.getTarget(c),g=this.filterHistory(c,0),h=this;return""!==g&&(this.toggleEditor(c,1),e={action:"image-editor",_ajax_nonce:d,postid:c,history:g,target:f,context:a("#image-edit-context").length?a("#image-edit-context").val():null,"do":"save"},void a.post(ajaxurl,e,function(d){var e=JSON.parse(d);return e.error?(a("#imgedit-response-"+c).html('<div class="error"><p>'+e.error+"</p></div>"),void b.close(c)):(e.fw&&e.fh&&a("#media-dims-"+c).html(e.fw+" &times; "+e.fh),e.thumbnail&&a(".thumbnail","#thumbnail-head-"+c).attr("src",""+e.thumbnail),e.msg&&a("#imgedit-response-"+c).html('<div class="updated"><p>'+e.msg+"</p></div>"),void(h._view?h._view.save():b.close(c)))}))},open:function(c,d,e){this._view=e;var f,g,h=a("#image-editor-"+c),i=a("#media-head-"+c),j=a("#imgedit-open-btn-"+c),k=j.siblings(".spinner");if(!j.hasClass("button-activated"))return k.addClass("is-active"),g={action:"image-editor",_ajax_nonce:d,postid:c,"do":"open"},f=a.ajax({url:ajaxurl,type:"post",data:g,beforeSend:function(){j.addClass("button-activated")}}).done(function(a){h.html(a),i.fadeOut("fast",function(){h.fadeIn("fast"),j.removeClass("button-activated"),k.removeClass("is-active")}),b.init(c)})},imgLoaded:function(b){var c=a("#image-preview-"+b),d=a("#imgedit-crop-"+b);"undefined"==typeof this.hold.sizer&&this.init(b),this.initCrop(b,c,d),this.setCropSelection(b,{x1:0,y1:0,x2:0,y2:0,width:c.innerWidth(),height:c.innerHeight()}),this.toggleEditor(b,0),a(".imgedit-wrap .imgedit-help-toggle").eq(0).focus()},initCrop:function(c,d,e){var f,g=this,h=a("#imgedit-sel-width-"+c),i=a("#imgedit-sel-height-"+c);g.iasapi=a(d).imgAreaSelect({parent:e,instance:!0,handles:!0,keys:!0,minWidth:3,minHeight:3,onInit:function(b){f=a(b),f.next().css("position","absolute").nextAll(".imgareaselect-outer").css("position","absolute"),e.children().on("mousedown, touchstart",function(a){var b,d,e=!1;a.shiftKey&&(b=g.iasapi.getSelection(),d=g.getSelRatio(c),e=b&&b.width&&b.height?b.width+":"+b.height:d),g.iasapi.setOptions({aspectRatio:e})})},onSelectStart:function(){b.setDisabled(a("#imgedit-crop-sel-"+c),1)},onSelectEnd:function(a,d){b.setCropSelection(c,d)},onSelectChange:function(a,c){var d=b.hold.sizer;h.val(b.round(c.width/d)),i.val(b.round(c.height/d))}})},setCropSelection:function(b,c){var d;return c=c||0,!c||c.width<3&&c.height<3?(this.setDisabled(a(".imgedit-crop","#imgedit-panel-"+b),1),this.setDisabled(a("#imgedit-crop-sel-"+b),1),a("#imgedit-sel-width-"+b).val(""),a("#imgedit-sel-height-"+b).val(""),a("#imgedit-selection-"+b).val(""),!1):(d={x:c.x1,y:c.y1,w:c.width,h:c.height},this.setDisabled(a(".imgedit-crop","#imgedit-panel-"+b),1),void a("#imgedit-selection-"+b).val(JSON.stringify(d)))},close:function(b,c){return c=c||!1,(!c||!this.notsaved(b))&&(this.iasapi={},this.hold={},void(this._view?this._view.back():a("#image-editor-"+b).fadeOut("fast",function(){a("#media-head-"+b).fadeIn("fast",function(){a("#imgedit-open-btn-"+b).focus()}),a(this).empty()})))},notsaved:function(b){var c=a("#imgedit-history-"+b).val(),d=""!==c?JSON.parse(c):[],e=this.intval(a("#imgedit-undone-"+b).val());return e<d.length&&!confirm(a("#imgedit-leaving-"+b).html())},addStep:function(b,c,d){for(var e=this,f=a("#imgedit-history-"+c),g=""!==f.val()?JSON.parse(f.val()):[],h=a("#imgedit-undone-"+c),i=e.intval(h.val());i>0;)g.pop(),i--;h.val(0),g.push(b),f.val(JSON.stringify(g)),e.refreshEditor(c,d,function(){e.setDisabled(a("#image-undo-"+c),!0),e.setDisabled(a("#image-redo-"+c),!1)})},rotate:function(b,c,d,e){return!a(e).hasClass("disabled")&&void this.addStep({r:{r:b,fw:this.hold.h,fh:this.hold.w}},c,d)},flip:function(b,c,d,e){return!a(e).hasClass("disabled")&&void this.addStep({f:{f:b,fw:this.hold.w,fh:this.hold.h}},c,d)},crop:function(b,c,d){var e=a("#imgedit-selection-"+b).val(),f=this.intval(a("#imgedit-sel-width-"+b).val()),g=this.intval(a("#imgedit-sel-height-"+b).val());return!a(d).hasClass("disabled")&&""!==e&&(e=JSON.parse(e),void(e.w>0&&e.h>0&&f>0&&g>0&&(e.fw=f,e.fh=g,this.addStep({c:e},b,c))))},undo:function(b,c){var d=this,e=a("#image-undo-"+b),f=a("#imgedit-undone-"+b),g=d.intval(f.val())+1;e.hasClass("disabled")||(f.val(g),d.refreshEditor(b,c,function(){var c=a("#imgedit-history-"+b),f=""!==c.val()?JSON.parse(c.val()):[];d.setDisabled(a("#image-redo-"+b),!0),d.setDisabled(e,g<f.length),f.length===g&&a("#image-redo-"+b).focus()}))},redo:function(b,c){var d=this,e=a("#image-redo-"+b),f=a("#imgedit-undone-"+b),g=d.intval(f.val())-1;e.hasClass("disabled")||(f.val(g),d.refreshEditor(b,c,function(){d.setDisabled(a("#image-undo-"+b),!0),d.setDisabled(e,g>0),0===g&&a("#image-undo-"+b).focus()}))},setNumSelection:function(b,c){var d,e,f,g,h,i=a("#imgedit-sel-width-"+b),j=a("#imgedit-sel-height-"+b),k=this.intval(i.val()),l=this.intval(j.val()),m=a("#image-preview-"+b),n=m.height(),o=m.width(),p=this.hold.sizer,q=this.iasapi;if(!1!==this.validateNumeric(c))return k<1?(i.val(""),!1):l<1?(j.val(""),!1):void(k&&l&&(d=q.getSelection())&&(g=d.x1+Math.round(k*p),h=d.y1+Math.round(l*p),e=d.x1,f=d.y1,g>o&&(e=0,g=o,i.val(Math.round(g/p))),h>n&&(f=0,h=n,j.val(Math.round(h/p))),q.setSelection(e,f,g,h),q.update(),this.setCropSelection(b,q.getSelection())))},round:function(a){var b;return a=Math.round(a),this.hold.sizer>.6?a:(b=a.toString().slice(-1),"1"===b?a-1:"9"===b?a+1:a)},setRatioSelection:function(b,c,d){var e,f,g=this.intval(a("#imgedit-crop-width-"+b).val()),h=this.intval(a("#imgedit-crop-height-"+b).val()),i=a("#image-preview-"+b).height();!1!==this.validateNumeric(d)&&g&&h&&(this.iasapi.setOptions({aspectRatio:g+":"+h}),(e=this.iasapi.getSelection(!0))&&(f=Math.ceil(e.y1+(e.x2-e.x1)/(g/h)),f>i&&(f=i,c?a("#imgedit-crop-height-"+b).val(""):a("#imgedit-crop-width-"+b).val("")),this.iasapi.setSelection(e.x1,e.y1,e.x2,f),this.iasapi.update()))},validateNumeric:function(b){if(!this.intval(a(b).val()))return a(b).val(""),!1}}}(jQuery); \ No newline at end of file
diff --git a/www/crm/wp-admin/js/inline-edit-post.js b/www/crm/wp-admin/js/inline-edit-post.js
new file mode 100644
index 00000000..cdca55dd
--- /dev/null
+++ b/www/crm/wp-admin/js/inline-edit-post.js
@@ -0,0 +1,556 @@
+/**
+ * This file contains the functions needed for the inline editing of posts.
+ *
+ * @since 2.7.0
+ * @output wp-admin/js/inline-edit-post.js
+ */
+
+/* global inlineEditL10n, ajaxurl, typenow, inlineEditPost */
+
+window.wp = window.wp || {};
+
+/**
+ * Manages the quick edit and bulk edit windows for editing posts or pages.
+ *
+ * @namespace inlineEditPost
+ *
+ * @since 2.7.0
+ *
+ * @type {Object}
+ *
+ * @property {string} type The type of inline editor.
+ * @property {string} what The prefix before the post id.
+ *
+ */
+( function( $, wp ) {
+
+ window.inlineEditPost = {
+
+ /**
+ * Initializes the inline and bulk post editor.
+ *
+ * Binds event handlers to the escape key to close the inline editor
+ * and to the save and close buttons. Changes DOM to be ready for inline
+ * editing. Adds event handler to bulk edit.
+ *
+ * @memberof inlineEditPost
+ * @since 2.7.0
+ *
+ * @returns {void}
+ */
+ init : function(){
+ var t = this, qeRow = $('#inline-edit'), bulkRow = $('#bulk-edit');
+
+ t.type = $('table.widefat').hasClass('pages') ? 'page' : 'post';
+ // Post id prefix.
+ t.what = '#post-';
+
+ /**
+ * Binds the escape key to revert the changes and close the quick editor.
+ *
+ * @returns {boolean} The result of revert.
+ */
+ qeRow.keyup(function(e){
+ // Revert changes if escape key is pressed.
+ if ( e.which === 27 ) {
+ return inlineEditPost.revert();
+ }
+ });
+
+ /**
+ * Binds the escape key to revert the changes and close the bulk editor.
+ *
+ * @returns {boolean} The result of revert.
+ */
+ bulkRow.keyup(function(e){
+ // Revert changes if escape key is pressed.
+ if ( e.which === 27 ) {
+ return inlineEditPost.revert();
+ }
+ });
+
+ /**
+ * Reverts changes and close the quick editor if the cancel button is clicked.
+ *
+ * @returns {boolean} The result of revert.
+ */
+ $( '.cancel', qeRow ).click( function() {
+ return inlineEditPost.revert();
+ });
+
+ /**
+ * Saves changes in the quick editor if the save(named: update) button is clicked.
+ *
+ * @returns {boolean} The result of save.
+ */
+ $( '.save', qeRow ).click( function() {
+ return inlineEditPost.save(this);
+ });
+
+ /**
+ * If enter is pressed, and the target is not the cancel button, save the post.
+ *
+ * @returns {boolean} The result of save.
+ */
+ $('td', qeRow).keydown(function(e){
+ if ( e.which === 13 && ! $( e.target ).hasClass( 'cancel' ) ) {
+ return inlineEditPost.save(this);
+ }
+ });
+
+ /**
+ * Reverts changes and close the bulk editor if the cancel button is clicked.
+ *
+ * @returns {boolean} The result of revert.
+ */
+ $( '.cancel', bulkRow ).click( function() {
+ return inlineEditPost.revert();
+ });
+
+ /**
+ * Disables the password input field when the private post checkbox is checked.
+ */
+ $('#inline-edit .inline-edit-private input[value="private"]').click( function(){
+ var pw = $('input.inline-edit-password-input');
+ if ( $(this).prop('checked') ) {
+ pw.val('').prop('disabled', true);
+ } else {
+ pw.prop('disabled', false);
+ }
+ });
+
+ /**
+ * Binds click event to the .editinline button which opens the quick editor.
+ */
+ $( '#the-list' ).on( 'click', '.editinline', function() {
+ $( this ).attr( 'aria-expanded', 'true' );
+ inlineEditPost.edit( this );
+ });
+
+ $('#bulk-edit').find('fieldset:first').after(
+ $('#inline-edit fieldset.inline-edit-categories').clone()
+ ).siblings( 'fieldset:last' ).prepend(
+ $('#inline-edit label.inline-edit-tags').clone()
+ );
+
+ $('select[name="_status"] option[value="future"]', bulkRow).remove();
+
+ /**
+ * Adds onclick events to the apply buttons.
+ */
+ $('#doaction, #doaction2').click(function(e){
+ var n;
+
+ t.whichBulkButtonId = $( this ).attr( 'id' );
+ n = t.whichBulkButtonId.substr( 2 );
+
+ if ( 'edit' === $( 'select[name="' + n + '"]' ).val() ) {
+ e.preventDefault();
+ t.setBulk();
+ } else if ( $('form#posts-filter tr.inline-editor').length > 0 ) {
+ t.revert();
+ }
+ });
+ },
+
+ /**
+ * Toggles the quick edit window, hiding it when it's active and showing it when
+ * inactive.
+ *
+ * @memberof inlineEditPost
+ * @since 2.7.0
+ *
+ * @param {Object} el Element within a post table row.
+ */
+ toggle : function(el){
+ var t = this;
+ $( t.what + t.getId( el ) ).css( 'display' ) === 'none' ? t.revert() : t.edit( el );
+ },
+
+ /**
+ * Creates the bulk editor row to edit multiple posts at once.
+ *
+ * @memberof inlineEditPost
+ * @since 2.7.0
+ */
+ setBulk : function(){
+ var te = '', type = this.type, c = true;
+ this.revert();
+
+ $( '#bulk-edit td' ).attr( 'colspan', $( 'th:visible, td:visible', '.widefat:first thead' ).length );
+
+ // Insert the editor at the top of the table with an empty row above to maintain zebra striping.
+ $('table.widefat tbody').prepend( $('#bulk-edit') ).prepend('<tr class="hidden"></tr>');
+ $('#bulk-edit').addClass('inline-editor').show();
+
+ /**
+ * Create a HTML div with the title and a link(delete-icon) for each selected
+ * post.
+ *
+ * Get the selected posts based on the checked checkboxes in the post table.
+ */
+ $( 'tbody th.check-column input[type="checkbox"]' ).each( function() {
+
+ // If the checkbox for a post is selected, add the post to the edit list.
+ if ( $(this).prop('checked') ) {
+ c = false;
+ var id = $(this).val(), theTitle;
+ theTitle = $('#inline_'+id+' .post_title').html() || inlineEditL10n.notitle;
+ te += '<div id="ttle'+id+'"><a id="_'+id+'" class="ntdelbutton" title="'+inlineEditL10n.ntdeltitle+'">X</a>'+theTitle+'</div>';
+ }
+ });
+
+ // If no checkboxes where checked, just hide the quick/bulk edit rows.
+ if ( c ) {
+ return this.revert();
+ }
+
+ // Add onclick events to the delete-icons in the bulk editors the post title list.
+ $('#bulk-titles').html(te);
+ /**
+ * Binds on click events to the checkboxes before the posts in the table.
+ *
+ * @listens click
+ */
+ $('#bulk-titles a').click(function(){
+ var id = $(this).attr('id').substr(1);
+
+ $('table.widefat input[value="' + id + '"]').prop('checked', false);
+ $('#ttle'+id).remove();
+ });
+
+ // Enable auto-complete for tags when editing posts.
+ if ( 'post' === type ) {
+ $( 'tr.inline-editor textarea[data-wp-taxonomy]' ).each( function ( i, element ) {
+ /*
+ * While Quick Edit clones the form each time, Bulk Edit always re-uses
+ * the same form. Let's check if an autocomplete instance already exists.
+ */
+ if ( $( element ).autocomplete( 'instance' ) ) {
+ // jQuery equivalent of `continue` within an `each()` loop.
+ return;
+ }
+
+ $( element ).wpTagsSuggest();
+ } );
+ }
+
+ // Scrolls to the top of the table where the editor is rendered.
+ $('html, body').animate( { scrollTop: 0 }, 'fast' );
+ },
+
+ /**
+ * Creates a quick edit window for the post that has been clicked.
+ *
+ * @memberof inlineEditPost
+ * @since 2.7.0
+ *
+ * @param {number|Object} id The id of the clicked post or an element within a post
+ * table row.
+ * @returns {boolean} Always returns false at the end of execution.
+ */
+ edit : function(id) {
+ var t = this, fields, editRow, rowData, status, pageOpt, pageLevel, nextPage, pageLoop = true, nextLevel, f, val, pw;
+ t.revert();
+
+ if ( typeof(id) === 'object' ) {
+ id = t.getId(id);
+ }
+
+ fields = ['post_title', 'post_name', 'post_author', '_status', 'jj', 'mm', 'aa', 'hh', 'mn', 'ss', 'post_password', 'post_format', 'menu_order', 'page_template'];
+ if ( t.type === 'page' ) {
+ fields.push('post_parent');
+ }
+
+ // Add the new edit row with an extra blank row underneath to maintain zebra striping.
+ editRow = $('#inline-edit').clone(true);
+ $( 'td', editRow ).attr( 'colspan', $( 'th:visible, td:visible', '.widefat:first thead' ).length );
+
+ $(t.what+id).removeClass('is-expanded').hide().after(editRow).after('<tr class="hidden"></tr>');
+
+ // Populate fields in the quick edit window.
+ rowData = $('#inline_'+id);
+ if ( !$(':input[name="post_author"] option[value="' + $('.post_author', rowData).text() + '"]', editRow).val() ) {
+
+ // The post author no longer has edit capabilities, so we need to add them to the list of authors.
+ $(':input[name="post_author"]', editRow).prepend('<option value="' + $('.post_author', rowData).text() + '">' + $('#' + t.type + '-' + id + ' .author').text() + '</option>');
+ }
+ if ( $( ':input[name="post_author"] option', editRow ).length === 1 ) {
+ $('label.inline-edit-author', editRow).hide();
+ }
+
+ for ( f = 0; f < fields.length; f++ ) {
+ val = $('.'+fields[f], rowData);
+
+ /**
+ * Replaces the image for a Twemoji(Twitter emoji) with it's alternate text.
+ *
+ * @returns Alternate text from the image.
+ */
+ val.find( 'img' ).replaceWith( function() { return this.alt; } );
+ val = val.text();
+ $(':input[name="' + fields[f] + '"]', editRow).val( val );
+ }
+
+ if ( $( '.comment_status', rowData ).text() === 'open' ) {
+ $( 'input[name="comment_status"]', editRow ).prop( 'checked', true );
+ }
+ if ( $( '.ping_status', rowData ).text() === 'open' ) {
+ $( 'input[name="ping_status"]', editRow ).prop( 'checked', true );
+ }
+ if ( $( '.sticky', rowData ).text() === 'sticky' ) {
+ $( 'input[name="sticky"]', editRow ).prop( 'checked', true );
+ }
+
+ /**
+ * Creates the select boxes for the categories.
+ */
+ $('.post_category', rowData).each(function(){
+ var taxname,
+ term_ids = $(this).text();
+
+ if ( term_ids ) {
+ taxname = $(this).attr('id').replace('_'+id, '');
+ $('ul.'+taxname+'-checklist :checkbox', editRow).val(term_ids.split(','));
+ }
+ });
+
+ /**
+ * Gets all the taxonomies for live auto-fill suggestions when typing the name
+ * of a tag.
+ */
+ $('.tags_input', rowData).each(function(){
+ var terms = $(this),
+ taxname = $(this).attr('id').replace('_' + id, ''),
+ textarea = $('textarea.tax_input_' + taxname, editRow),
+ comma = inlineEditL10n.comma;
+
+ terms.find( 'img' ).replaceWith( function() { return this.alt; } );
+ terms = terms.text();
+
+ if ( terms ) {
+ if ( ',' !== comma ) {
+ terms = terms.replace(/,/g, comma);
+ }
+ textarea.val(terms);
+ }
+
+ textarea.wpTagsSuggest();
+ });
+
+ // Handle the post status.
+ status = $('._status', rowData).text();
+ if ( 'future' !== status ) {
+ $('select[name="_status"] option[value="future"]', editRow).remove();
+ }
+
+ pw = $( '.inline-edit-password-input' ).prop( 'disabled', false );
+ if ( 'private' === status ) {
+ $('input[name="keep_private"]', editRow).prop('checked', true);
+ pw.val( '' ).prop( 'disabled', true );
+ }
+
+ // Remove the current page and children from the parent dropdown.
+ pageOpt = $('select[name="post_parent"] option[value="' + id + '"]', editRow);
+ if ( pageOpt.length > 0 ) {
+ pageLevel = pageOpt[0].className.split('-')[1];
+ nextPage = pageOpt;
+ while ( pageLoop ) {
+ nextPage = nextPage.next('option');
+ if ( nextPage.length === 0 ) {
+ break;
+ }
+
+ nextLevel = nextPage[0].className.split('-')[1];
+
+ if ( nextLevel <= pageLevel ) {
+ pageLoop = false;
+ } else {
+ nextPage.remove();
+ nextPage = pageOpt;
+ }
+ }
+ pageOpt.remove();
+ }
+
+ $(editRow).attr('id', 'edit-'+id).addClass('inline-editor').show();
+ $('.ptitle', editRow).focus();
+
+ return false;
+ },
+
+ /**
+ * Saves the changes made in the quick edit window to the post.
+ * AJAX saving is only for Quick Edit and not for bulk edit.
+ *
+ * @since 2.7.0
+ *
+ * @param {int} id The id for the post that has been changed.
+ * @returns {boolean} false, so the form does not submit when pressing
+ * Enter on a focused field.
+ */
+ save : function(id) {
+ var params, fields, page = $('.post_status_page').val() || '';
+
+ if ( typeof(id) === 'object' ) {
+ id = this.getId(id);
+ }
+
+ $( 'table.widefat .spinner' ).addClass( 'is-active' );
+
+ params = {
+ action: 'inline-save',
+ post_type: typenow,
+ post_ID: id,
+ edit_date: 'true',
+ post_status: page
+ };
+
+ fields = $('#edit-'+id).find(':input').serialize();
+ params = fields + '&' + $.param(params);
+
+ // Make ajax request.
+ $.post( ajaxurl, params,
+ function(r) {
+ var $errorNotice = $( '#edit-' + id + ' .inline-edit-save .notice-error' ),
+ $error = $errorNotice.find( '.error' );
+
+ $( 'table.widefat .spinner' ).removeClass( 'is-active' );
+ $( '.ac_results' ).hide();
+
+ if (r) {
+ if ( -1 !== r.indexOf( '<tr' ) ) {
+ $(inlineEditPost.what+id).siblings('tr.hidden').addBack().remove();
+ $('#edit-'+id).before(r).remove();
+ $( inlineEditPost.what + id ).hide().fadeIn( 400, function() {
+ // Move focus back to the Quick Edit button. $( this ) is the row being animated.
+ $( this ).find( '.editinline' )
+ .attr( 'aria-expanded', 'false' )
+ .focus();
+ wp.a11y.speak( inlineEditL10n.saved );
+ });
+ } else {
+ r = r.replace( /<.[^<>]*?>/g, '' );
+ $errorNotice.removeClass( 'hidden' );
+ $error.html( r );
+ wp.a11y.speak( $error.text() );
+ }
+ } else {
+ $errorNotice.removeClass( 'hidden' );
+ $error.html( inlineEditL10n.error );
+ wp.a11y.speak( inlineEditL10n.error );
+ }
+ },
+ 'html');
+
+ // Prevent submitting the form when pressing Enter on a focused field.
+ return false;
+ },
+
+ /**
+ * Hides and empties the Quick Edit and/or Bulk Edit windows.
+ *
+ * @memberof inlineEditPost
+ * @since 2.7.0
+ *
+ * @returns {boolean} Always returns false.
+ */
+ revert : function(){
+ var $tableWideFat = $( '.widefat' ),
+ id = $( '.inline-editor', $tableWideFat ).attr( 'id' );
+
+ if ( id ) {
+ $( '.spinner', $tableWideFat ).removeClass( 'is-active' );
+ $( '.ac_results' ).hide();
+
+ if ( 'bulk-edit' === id ) {
+
+ // Hide the bulk editor.
+ $( '#bulk-edit', $tableWideFat ).removeClass( 'inline-editor' ).hide().siblings( '.hidden' ).remove();
+ $('#bulk-titles').empty();
+
+ // Store the empty bulk editor in a hidden element.
+ $('#inlineedit').append( $('#bulk-edit') );
+
+ // Move focus back to the Bulk Action button that was activated.
+ $( '#' + inlineEditPost.whichBulkButtonId ).focus();
+ } else {
+
+ // Remove both the inline-editor and its hidden tr siblings.
+ $('#'+id).siblings('tr.hidden').addBack().remove();
+ id = id.substr( id.lastIndexOf('-') + 1 );
+
+ // Show the post row and move focus back to the Quick Edit button.
+ $( this.what + id ).show().find( '.editinline' )
+ .attr( 'aria-expanded', 'false' )
+ .focus();
+ }
+ }
+
+ return false;
+ },
+
+ /**
+ * Gets the id for a the post that you want to quick edit from the row in the quick
+ * edit table.
+ *
+ * @memberof inlineEditPost
+ * @since 2.7.0
+ *
+ * @param {Object} o DOM row object to get the id for.
+ * @returns {string} The post id extracted from the table row in the object.
+ */
+ getId : function(o) {
+ var id = $(o).closest('tr').attr('id'),
+ parts = id.split('-');
+ return parts[parts.length - 1];
+ }
+};
+
+$( document ).ready( function(){ inlineEditPost.init(); } );
+
+// Show/hide locks on posts.
+$( document ).on( 'heartbeat-tick.wp-check-locked-posts', function( e, data ) {
+ var locked = data['wp-check-locked-posts'] || {};
+
+ $('#the-list tr').each( function(i, el) {
+ var key = el.id, row = $(el), lock_data, avatar;
+
+ if ( locked.hasOwnProperty( key ) ) {
+ if ( ! row.hasClass('wp-locked') ) {
+ lock_data = locked[key];
+ row.find('.column-title .locked-text').text( lock_data.text );
+ row.find('.check-column checkbox').prop('checked', false);
+
+ if ( lock_data.avatar_src ) {
+ avatar = $( '<img class="avatar avatar-18 photo" width="18" height="18" alt="" />' ).attr( 'src', lock_data.avatar_src.replace( /&amp;/g, '&' ) );
+ row.find('.column-title .locked-avatar').empty().append( avatar );
+ }
+ row.addClass('wp-locked');
+ }
+ } else if ( row.hasClass('wp-locked') ) {
+ // Make room for the CSS animation
+ row.removeClass('wp-locked').delay(1000).find('.locked-info span').empty();
+ }
+ });
+}).on( 'heartbeat-send.wp-check-locked-posts', function( e, data ) {
+ var check = [];
+
+ $('#the-list tr').each( function(i, el) {
+ if ( el.id ) {
+ check.push( el.id );
+ }
+ });
+
+ if ( check.length ) {
+ data['wp-check-locked-posts'] = check;
+ }
+}).ready( function() {
+
+ // Set the heartbeat interval to 15 sec.
+ if ( typeof wp !== 'undefined' && wp.heartbeat ) {
+ wp.heartbeat.interval( 15 );
+ }
+});
+
+})( jQuery, window.wp );
diff --git a/www/crm/wp-admin/js/inline-edit-post.min.js b/www/crm/wp-admin/js/inline-edit-post.min.js
new file mode 100644
index 00000000..4ddda711
--- /dev/null
+++ b/www/crm/wp-admin/js/inline-edit-post.min.js
@@ -0,0 +1 @@
+window.wp=window.wp||{},function(a,b){window.inlineEditPost={init:function(){var b=this,c=a("#inline-edit"),d=a("#bulk-edit");b.type=a("table.widefat").hasClass("pages")?"page":"post",b.what="#post-",c.keyup(function(a){if(27===a.which)return inlineEditPost.revert()}),d.keyup(function(a){if(27===a.which)return inlineEditPost.revert()}),a(".cancel",c).click(function(){return inlineEditPost.revert()}),a(".save",c).click(function(){return inlineEditPost.save(this)}),a("td",c).keydown(function(b){if(13===b.which&&!a(b.target).hasClass("cancel"))return inlineEditPost.save(this)}),a(".cancel",d).click(function(){return inlineEditPost.revert()}),a('#inline-edit .inline-edit-private input[value="private"]').click(function(){var b=a("input.inline-edit-password-input");a(this).prop("checked")?b.val("").prop("disabled",!0):b.prop("disabled",!1)}),a("#the-list").on("click",".editinline",function(){a(this).attr("aria-expanded","true"),inlineEditPost.edit(this)}),a("#bulk-edit").find("fieldset:first").after(a("#inline-edit fieldset.inline-edit-categories").clone()).siblings("fieldset:last").prepend(a("#inline-edit label.inline-edit-tags").clone()),a('select[name="_status"] option[value="future"]',d).remove(),a("#doaction, #doaction2").click(function(c){var d;b.whichBulkButtonId=a(this).attr("id"),d=b.whichBulkButtonId.substr(2),"edit"===a('select[name="'+d+'"]').val()?(c.preventDefault(),b.setBulk()):a("form#posts-filter tr.inline-editor").length>0&&b.revert()})},toggle:function(b){var c=this;"none"===a(c.what+c.getId(b)).css("display")?c.revert():c.edit(b)},setBulk:function(){var b="",c=this.type,d=!0;return this.revert(),a("#bulk-edit td").attr("colspan",a("th:visible, td:visible",".widefat:first thead").length),a("table.widefat tbody").prepend(a("#bulk-edit")).prepend('<tr class="hidden"></tr>'),a("#bulk-edit").addClass("inline-editor").show(),a('tbody th.check-column input[type="checkbox"]').each(function(){if(a(this).prop("checked")){d=!1;var c,e=a(this).val();c=a("#inline_"+e+" .post_title").html()||inlineEditL10n.notitle,b+='<div id="ttle'+e+'"><a id="_'+e+'" class="ntdelbutton" title="'+inlineEditL10n.ntdeltitle+'">X</a>'+c+"</div>"}}),d?this.revert():(a("#bulk-titles").html(b),a("#bulk-titles a").click(function(){var b=a(this).attr("id").substr(1);a('table.widefat input[value="'+b+'"]').prop("checked",!1),a("#ttle"+b).remove()}),"post"===c&&a("tr.inline-editor textarea[data-wp-taxonomy]").each(function(b,c){a(c).autocomplete("instance")||a(c).wpTagsSuggest()}),void a("html, body").animate({scrollTop:0},"fast"))},edit:function(b){var c,d,e,f,g,h,i,j,k,l,m,n=this,o=!0;for(n.revert(),"object"==typeof b&&(b=n.getId(b)),c=["post_title","post_name","post_author","_status","jj","mm","aa","hh","mn","ss","post_password","post_format","menu_order","page_template"],"page"===n.type&&c.push("post_parent"),d=a("#inline-edit").clone(!0),a("td",d).attr("colspan",a("th:visible, td:visible",".widefat:first thead").length),a(n.what+b).removeClass("is-expanded").hide().after(d).after('<tr class="hidden"></tr>'),e=a("#inline_"+b),a(':input[name="post_author"] option[value="'+a(".post_author",e).text()+'"]',d).val()||a(':input[name="post_author"]',d).prepend('<option value="'+a(".post_author",e).text()+'">'+a("#"+n.type+"-"+b+" .author").text()+"</option>"),1===a(':input[name="post_author"] option',d).length&&a("label.inline-edit-author",d).hide(),k=0;k<c.length;k++)l=a("."+c[k],e),l.find("img").replaceWith(function(){return this.alt}),l=l.text(),a(':input[name="'+c[k]+'"]',d).val(l);if("open"===a(".comment_status",e).text()&&a('input[name="comment_status"]',d).prop("checked",!0),"open"===a(".ping_status",e).text()&&a('input[name="ping_status"]',d).prop("checked",!0),"sticky"===a(".sticky",e).text()&&a('input[name="sticky"]',d).prop("checked",!0),a(".post_category",e).each(function(){var c,e=a(this).text();e&&(c=a(this).attr("id").replace("_"+b,""),a("ul."+c+"-checklist :checkbox",d).val(e.split(",")))}),a(".tags_input",e).each(function(){var c=a(this),e=a(this).attr("id").replace("_"+b,""),f=a("textarea.tax_input_"+e,d),g=inlineEditL10n.comma;c.find("img").replaceWith(function(){return this.alt}),c=c.text(),c&&(","!==g&&(c=c.replace(/,/g,g)),f.val(c)),f.wpTagsSuggest()}),f=a("._status",e).text(),"future"!==f&&a('select[name="_status"] option[value="future"]',d).remove(),m=a(".inline-edit-password-input").prop("disabled",!1),"private"===f&&(a('input[name="keep_private"]',d).prop("checked",!0),m.val("").prop("disabled",!0)),g=a('select[name="post_parent"] option[value="'+b+'"]',d),g.length>0){for(h=g[0].className.split("-")[1],i=g;o&&(i=i.next("option"),0!==i.length);)j=i[0].className.split("-")[1],j<=h?o=!1:(i.remove(),i=g);g.remove()}return a(d).attr("id","edit-"+b).addClass("inline-editor").show(),a(".ptitle",d).focus(),!1},save:function(c){var d,e,f=a(".post_status_page").val()||"";return"object"==typeof c&&(c=this.getId(c)),a("table.widefat .spinner").addClass("is-active"),d={action:"inline-save",post_type:typenow,post_ID:c,edit_date:"true",post_status:f},e=a("#edit-"+c).find(":input").serialize(),d=e+"&"+a.param(d),a.post(ajaxurl,d,function(d){var e=a("#edit-"+c+" .inline-edit-save .notice-error"),f=e.find(".error");a("table.widefat .spinner").removeClass("is-active"),a(".ac_results").hide(),d?-1!==d.indexOf("<tr")?(a(inlineEditPost.what+c).siblings("tr.hidden").addBack().remove(),a("#edit-"+c).before(d).remove(),a(inlineEditPost.what+c).hide().fadeIn(400,function(){a(this).find(".editinline").attr("aria-expanded","false").focus(),b.a11y.speak(inlineEditL10n.saved)})):(d=d.replace(/<.[^<>]*?>/g,""),e.removeClass("hidden"),f.html(d),b.a11y.speak(f.text())):(e.removeClass("hidden"),f.html(inlineEditL10n.error),b.a11y.speak(inlineEditL10n.error))},"html"),!1},revert:function(){var b=a(".widefat"),c=a(".inline-editor",b).attr("id");return c&&(a(".spinner",b).removeClass("is-active"),a(".ac_results").hide(),"bulk-edit"===c?(a("#bulk-edit",b).removeClass("inline-editor").hide().siblings(".hidden").remove(),a("#bulk-titles").empty(),a("#inlineedit").append(a("#bulk-edit")),a("#"+inlineEditPost.whichBulkButtonId).focus()):(a("#"+c).siblings("tr.hidden").addBack().remove(),c=c.substr(c.lastIndexOf("-")+1),a(this.what+c).show().find(".editinline").attr("aria-expanded","false").focus())),!1},getId:function(b){var c=a(b).closest("tr").attr("id"),d=c.split("-");return d[d.length-1]}},a(document).ready(function(){inlineEditPost.init()}),a(document).on("heartbeat-tick.wp-check-locked-posts",function(b,c){var d=c["wp-check-locked-posts"]||{};a("#the-list tr").each(function(b,c){var e,f,g=c.id,h=a(c);d.hasOwnProperty(g)?h.hasClass("wp-locked")||(e=d[g],h.find(".column-title .locked-text").text(e.text),h.find(".check-column checkbox").prop("checked",!1),e.avatar_src&&(f=a('<img class="avatar avatar-18 photo" width="18" height="18" alt="" />').attr("src",e.avatar_src.replace(/&amp;/g,"&")),h.find(".column-title .locked-avatar").empty().append(f)),h.addClass("wp-locked")):h.hasClass("wp-locked")&&h.removeClass("wp-locked").delay(1e3).find(".locked-info span").empty()})}).on("heartbeat-send.wp-check-locked-posts",function(b,c){var d=[];a("#the-list tr").each(function(a,b){b.id&&d.push(b.id)}),d.length&&(c["wp-check-locked-posts"]=d)}).ready(function(){"undefined"!=typeof b&&b.heartbeat&&b.heartbeat.interval(15)})}(jQuery,window.wp); \ No newline at end of file
diff --git a/www/crm/wp-admin/js/inline-edit-tax.js b/www/crm/wp-admin/js/inline-edit-tax.js
new file mode 100644
index 00000000..bea01963
--- /dev/null
+++ b/www/crm/wp-admin/js/inline-edit-tax.js
@@ -0,0 +1,294 @@
+/**
+ * This file is used on the term overview page to power quick-editing terms.
+ *
+ * @output wp-admin/js/inline-edit-tax.js
+ */
+
+/* global inlineEditL10n, ajaxurl, inlineEditTax */
+
+window.wp = window.wp || {};
+
+/**
+ * Consists of functions relevant to the inline taxonomy editor.
+ *
+ * @namespace inlineEditTax
+ *
+ * @property {string} type The type of inline edit we are currently on.
+ * @property {string} what The type property with a hash prefixed and a dash
+ * suffixed.
+ */
+( function( $, wp ) {
+
+window.inlineEditTax = {
+
+ /**
+ * Initializes the inline taxonomy editor by adding event handlers to be able to
+ * quick edit.
+ *
+ * @since 2.7.0
+ *
+ * @this inlineEditTax
+ * @memberof inlineEditTax
+ * @returns {void}
+ */
+ init : function() {
+ var t = this, row = $('#inline-edit');
+
+ t.type = $('#the-list').attr('data-wp-lists').substr(5);
+ t.what = '#'+t.type+'-';
+
+ $( '#the-list' ).on( 'click', '.editinline', function() {
+ $( this ).attr( 'aria-expanded', 'true' );
+ inlineEditTax.edit( this );
+ });
+
+ /**
+ * Cancels inline editing when pressing escape inside the inline editor.
+ *
+ * @param {Object} e The keyup event that has been triggered.
+ */
+ row.keyup( function( e ) {
+ // 27 = [escape]
+ if ( e.which === 27 ) {
+ return inlineEditTax.revert();
+ }
+ });
+
+ /**
+ * Cancels inline editing when clicking the cancel button.
+ */
+ $( '.cancel', row ).click( function() {
+ return inlineEditTax.revert();
+ });
+
+ /**
+ * Saves the inline edits when clicking the save button.
+ */
+ $( '.save', row ).click( function() {
+ return inlineEditTax.save(this);
+ });
+
+ /**
+ * Saves the inline edits when pressing enter inside the inline editor.
+ */
+ $( 'input, select', row ).keydown( function( e ) {
+ // 13 = [enter]
+ if ( e.which === 13 ) {
+ return inlineEditTax.save( this );
+ }
+ });
+
+ /**
+ * Saves the inline edits on submitting the inline edit form.
+ */
+ $( '#posts-filter input[type="submit"]' ).mousedown( function() {
+ t.revert();
+ });
+ },
+
+ /**
+ * Toggles the quick edit based on if it is currently shown or hidden.
+ *
+ * @since 2.7.0
+ *
+ * @this inlineEditTax
+ * @memberof inlineEditTax
+ *
+ * @param {HTMLElement} el An element within the table row or the table row
+ * itself that we want to quick edit.
+ * @returns {void}
+ */
+ toggle : function(el) {
+ var t = this;
+
+ $(t.what+t.getId(el)).css('display') === 'none' ? t.revert() : t.edit(el);
+ },
+
+ /**
+ * Shows the quick editor
+ *
+ * @since 2.7.0
+ *
+ * @this inlineEditTax
+ * @memberof inlineEditTax
+ *
+ * @param {string|HTMLElement} id The ID of the term we want to quick edit or an
+ * element within the table row or the
+ * table row itself.
+ * @returns {boolean} Always returns false.
+ */
+ edit : function(id) {
+ var editRow, rowData, val,
+ t = this;
+ t.revert();
+
+ // Makes sure we can pass an HTMLElement as the ID.
+ if ( typeof(id) === 'object' ) {
+ id = t.getId(id);
+ }
+
+ editRow = $('#inline-edit').clone(true), rowData = $('#inline_'+id);
+ $( 'td', editRow ).attr( 'colspan', $( 'th:visible, td:visible', '.wp-list-table.widefat:first thead' ).length );
+
+ $(t.what+id).hide().after(editRow).after('<tr class="hidden"></tr>');
+
+ val = $('.name', rowData);
+ val.find( 'img' ).replaceWith( function() { return this.alt; } );
+ val = val.text();
+ $(':input[name="name"]', editRow).val( val );
+
+ val = $('.slug', rowData);
+ val.find( 'img' ).replaceWith( function() { return this.alt; } );
+ val = val.text();
+ $(':input[name="slug"]', editRow).val( val );
+
+ $(editRow).attr('id', 'edit-'+id).addClass('inline-editor').show();
+ $('.ptitle', editRow).eq(0).focus();
+
+ return false;
+ },
+
+ /**
+ * Saves the quick edit data.
+ *
+ * Saves the quick edit data to the server and replaces the table row with the
+ * HTML retrieved from the server.
+ *
+ * @since 2.7.0
+ *
+ * @this inlineEditTax
+ * @memberof inlineEditTax
+ *
+ * @param {string|HTMLElement} id The ID of the term we want to quick edit or an
+ * element within the table row or the
+ * table row itself.
+ * @returns {boolean} Always returns false.
+ */
+ save : function(id) {
+ var params, fields, tax = $('input[name="taxonomy"]').val() || '';
+
+ // Makes sure we can pass an HTMLElement as the ID.
+ if( typeof(id) === 'object' ) {
+ id = this.getId(id);
+ }
+
+ $( 'table.widefat .spinner' ).addClass( 'is-active' );
+
+ params = {
+ action: 'inline-save-tax',
+ tax_type: this.type,
+ tax_ID: id,
+ taxonomy: tax
+ };
+
+ fields = $('#edit-'+id).find(':input').serialize();
+ params = fields + '&' + $.param(params);
+
+ // Do the ajax request to save the data to the server.
+ $.post( ajaxurl, params,
+ /**
+ * Handles the response from the server
+ *
+ * Handles the response from the server, replaces the table row with the response
+ * from the server.
+ *
+ * @param {string} r The string with which to replace the table row.
+ */
+ function(r) {
+ var row, new_id, option_value,
+ $errorNotice = $( '#edit-' + id + ' .inline-edit-save .notice-error' ),
+ $error = $errorNotice.find( '.error' );
+
+ $( 'table.widefat .spinner' ).removeClass( 'is-active' );
+
+ if (r) {
+ if ( -1 !== r.indexOf( '<tr' ) ) {
+ $(inlineEditTax.what+id).siblings('tr.hidden').addBack().remove();
+ new_id = $(r).attr('id');
+
+ $('#edit-'+id).before(r).remove();
+
+ if ( new_id ) {
+ option_value = new_id.replace( inlineEditTax.type + '-', '' );
+ row = $( '#' + new_id );
+ } else {
+ option_value = id;
+ row = $( inlineEditTax.what + id );
+ }
+
+ // Update the value in the Parent dropdown.
+ $( '#parent' ).find( 'option[value=' + option_value + ']' ).text( row.find( '.row-title' ).text() );
+
+ row.hide().fadeIn( 400, function() {
+ // Move focus back to the Quick Edit button.
+ row.find( '.editinline' )
+ .attr( 'aria-expanded', 'false' )
+ .focus();
+ wp.a11y.speak( inlineEditL10n.saved );
+ });
+
+ } else {
+ $errorNotice.removeClass( 'hidden' );
+ $error.html( r );
+ /*
+ * Some error strings may contain HTML entities (e.g. `&#8220`), let's use
+ * the HTML element's text.
+ */
+ wp.a11y.speak( $error.text() );
+ }
+ } else {
+ $errorNotice.removeClass( 'hidden' );
+ $error.html( inlineEditL10n.error );
+ wp.a11y.speak( inlineEditL10n.error );
+ }
+ }
+ );
+
+ // Prevent submitting the form when pressing Enter on a focused field.
+ return false;
+ },
+
+ /**
+ * Closes the quick edit form.
+ *
+ * @since 2.7.0
+ *
+ * @this inlineEditTax
+ * @memberof inlineEditTax
+ * @returns {void}
+ */
+ revert : function() {
+ var id = $('table.widefat tr.inline-editor').attr('id');
+
+ if ( id ) {
+ $( 'table.widefat .spinner' ).removeClass( 'is-active' );
+ $('#'+id).siblings('tr.hidden').addBack().remove();
+ id = id.substr( id.lastIndexOf('-') + 1 );
+
+ // Show the taxonomy row and move focus back to the Quick Edit button.
+ $( this.what + id ).show().find( '.editinline' )
+ .attr( 'aria-expanded', 'false' )
+ .focus();
+ }
+ },
+
+ /**
+ * Retrieves the ID of the term of the element inside the table row.
+ *
+ * @since 2.7.0
+ *
+ * @memberof inlineEditTax
+ *
+ * @param {HTMLElement} o An element within the table row or the table row itself.
+ * @returns {string} The ID of the term based on the element.
+ */
+ getId : function(o) {
+ var id = o.tagName === 'TR' ? o.id : $(o).parents('tr').attr('id'), parts = id.split('-');
+
+ return parts[parts.length - 1];
+ }
+};
+
+$(document).ready(function(){inlineEditTax.init();});
+
+})( jQuery, window.wp );
diff --git a/www/crm/wp-admin/js/inline-edit-tax.min.js b/www/crm/wp-admin/js/inline-edit-tax.min.js
new file mode 100644
index 00000000..be26ff87
--- /dev/null
+++ b/www/crm/wp-admin/js/inline-edit-tax.min.js
@@ -0,0 +1 @@
+window.wp=window.wp||{},function(a,b){window.inlineEditTax={init:function(){var b=this,c=a("#inline-edit");b.type=a("#the-list").attr("data-wp-lists").substr(5),b.what="#"+b.type+"-",a("#the-list").on("click",".editinline",function(){a(this).attr("aria-expanded","true"),inlineEditTax.edit(this)}),c.keyup(function(a){if(27===a.which)return inlineEditTax.revert()}),a(".cancel",c).click(function(){return inlineEditTax.revert()}),a(".save",c).click(function(){return inlineEditTax.save(this)}),a("input, select",c).keydown(function(a){if(13===a.which)return inlineEditTax.save(this)}),a('#posts-filter input[type="submit"]').mousedown(function(){b.revert()})},toggle:function(b){var c=this;"none"===a(c.what+c.getId(b)).css("display")?c.revert():c.edit(b)},edit:function(b){var c,d,e,f=this;return f.revert(),"object"==typeof b&&(b=f.getId(b)),c=a("#inline-edit").clone(!0),d=a("#inline_"+b),a("td",c).attr("colspan",a("th:visible, td:visible",".wp-list-table.widefat:first thead").length),a(f.what+b).hide().after(c).after('<tr class="hidden"></tr>'),e=a(".name",d),e.find("img").replaceWith(function(){return this.alt}),e=e.text(),a(':input[name="name"]',c).val(e),e=a(".slug",d),e.find("img").replaceWith(function(){return this.alt}),e=e.text(),a(':input[name="slug"]',c).val(e),a(c).attr("id","edit-"+b).addClass("inline-editor").show(),a(".ptitle",c).eq(0).focus(),!1},save:function(c){var d,e,f=a('input[name="taxonomy"]').val()||"";return"object"==typeof c&&(c=this.getId(c)),a("table.widefat .spinner").addClass("is-active"),d={action:"inline-save-tax",tax_type:this.type,tax_ID:c,taxonomy:f},e=a("#edit-"+c).find(":input").serialize(),d=e+"&"+a.param(d),a.post(ajaxurl,d,function(d){var e,f,g,h=a("#edit-"+c+" .inline-edit-save .notice-error"),i=h.find(".error");a("table.widefat .spinner").removeClass("is-active"),d?-1!==d.indexOf("<tr")?(a(inlineEditTax.what+c).siblings("tr.hidden").addBack().remove(),f=a(d).attr("id"),a("#edit-"+c).before(d).remove(),f?(g=f.replace(inlineEditTax.type+"-",""),e=a("#"+f)):(g=c,e=a(inlineEditTax.what+c)),a("#parent").find("option[value="+g+"]").text(e.find(".row-title").text()),e.hide().fadeIn(400,function(){e.find(".editinline").attr("aria-expanded","false").focus(),b.a11y.speak(inlineEditL10n.saved)})):(h.removeClass("hidden"),i.html(d),b.a11y.speak(i.text())):(h.removeClass("hidden"),i.html(inlineEditL10n.error),b.a11y.speak(inlineEditL10n.error))}),!1},revert:function(){var b=a("table.widefat tr.inline-editor").attr("id");b&&(a("table.widefat .spinner").removeClass("is-active"),a("#"+b).siblings("tr.hidden").addBack().remove(),b=b.substr(b.lastIndexOf("-")+1),a(this.what+b).show().find(".editinline").attr("aria-expanded","false").focus())},getId:function(b){var c="TR"===b.tagName?b.id:a(b).parents("tr").attr("id"),d=c.split("-");return d[d.length-1]}},a(document).ready(function(){inlineEditTax.init()})}(jQuery,window.wp); \ No newline at end of file
diff --git a/www/crm/wp-admin/js/iris.min.js b/www/crm/wp-admin/js/iris.min.js
new file mode 100644
index 00000000..e9ae4b6c
--- /dev/null
+++ b/www/crm/wp-admin/js/iris.min.js
@@ -0,0 +1,4 @@
+/*! Iris Color Picker - v1.1.0-beta - 2016-10-25
+* https://github.com/Automattic/Iris
+* Copyright (c) 2016 Matt Wiebe; Licensed GPLv2 */
+!function(a,b){function c(){var b,c,d="backgroundImage";j?k="filter":(b=a('<div id="iris-gradtest" />'),c="linear-gradient(top,#fff,#000)",a.each(l,function(a,e){if(b.css(d,e+c),b.css(d).match("gradient"))return k=a,!1}),k===!1&&(b.css("background","-webkit-gradient(linear,0% 0%,0% 100%,from(#fff),to(#000))"),b.css(d).match("gradient")&&(k="webkit")),b.remove())}function d(b,c){return b="top"===b?"top":"left",c=a.isArray(c)?c:Array.prototype.slice.call(arguments,1),"webkit"===k?f(b,c):l[k]+"linear-gradient("+b+", "+c.join(", ")+")"}function e(b,c){var d,e,f,h,i,j,k,l,m;b="top"===b?"top":"left",c=a.isArray(c)?c:Array.prototype.slice.call(arguments,1),d="top"===b?0:1,e=a(this),f=c.length-1,h="filter",i=1===d?"left":"top",j=1===d?"right":"bottom",k=1===d?"height":"width",l='<div class="iris-ie-gradient-shim" style="position:absolute;'+k+":100%;"+i+":%start%;"+j+":%end%;"+h+':%filter%;" data-color:"%color%"></div>',m="","static"===e.css("position")&&e.css({position:"relative"}),c=g(c),a.each(c,function(a,b){var e,g,h;return a!==f&&(e=c[a+1],void(b.stop!==e.stop&&(g=100-parseFloat(e.stop)+"%",b.octoHex=new Color(b.color).toIEOctoHex(),e.octoHex=new Color(e.color).toIEOctoHex(),h="progid:DXImageTransform.Microsoft.Gradient(GradientType="+d+", StartColorStr='"+b.octoHex+"', EndColorStr='"+e.octoHex+"')",m+=l.replace("%start%",b.stop).replace("%end%",g).replace("%filter%",h))))}),e.find(".iris-ie-gradient-shim").remove(),a(m).prependTo(e)}function f(b,c){var d=[];return b="top"===b?"0% 0%,0% 100%,":"0% 100%,100% 100%,",c=g(c),a.each(c,function(a,b){d.push("color-stop("+parseFloat(b.stop)/100+", "+b.color+")")}),"-webkit-gradient(linear,"+b+d.join(",")+")"}function g(b){var c=[],d=[],e=[],f=b.length-1;return a.each(b,function(a,b){var e=b,f=!1,g=b.match(/1?[0-9]{1,2}%$/);g&&(e=b.replace(/\s?1?[0-9]{1,2}%$/,""),f=g.shift()),c.push(e),d.push(f)}),d[0]===!1&&(d[0]="0%"),d[f]===!1&&(d[f]="100%"),d=h(d),a.each(d,function(a){e[a]={color:c[a],stop:d[a]}}),e}function h(b){var c,d,e,f,g=0,i=b.length-1,j=0,k=!1;if(b.length<=2||a.inArray(!1,b)<0)return b;for(;j<b.length-1;)k||b[j]!==!1?k&&b[j]!==!1&&(i=j,j=b.length):(g=j-1,k=!0),j++;for(d=i-g,f=parseInt(b[g].replace("%"),10),c=(parseFloat(b[i].replace("%"))-f)/d,j=g+1,e=1;j<i;)b[j]=f+e*c+"%",e++,j++;return h(b)}var i,j,k,l,m,n,o,p,q;return i='<div class="iris-picker"><div class="iris-picker-inner"><div class="iris-square"><a class="iris-square-value" href="#"><span class="iris-square-handle ui-slider-handle"></span></a><div class="iris-square-inner iris-square-horiz"></div><div class="iris-square-inner iris-square-vert"></div></div><div class="iris-slider iris-strip"><div class="iris-slider-offset"></div></div></div></div>',m='.iris-picker{display:block;position:relative}.iris-picker,.iris-picker *{-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input+.iris-picker{margin-top:4px}.iris-error{background-color:#ffafaf}.iris-border{border-radius:3px;border:1px solid #aaa;width:200px;background-color:#fff}.iris-picker-inner{position:absolute;top:0;right:0;left:0;bottom:0}.iris-border .iris-picker-inner{top:10px;right:10px;left:10px;bottom:10px}.iris-picker .iris-square-inner{position:absolute;left:0;right:0;top:0;bottom:0}.iris-picker .iris-square,.iris-picker .iris-slider,.iris-picker .iris-square-inner,.iris-picker .iris-palette{border-radius:3px;box-shadow:inset 0 0 5px rgba(0,0,0,.4);height:100%;width:12.5%;float:left;margin-right:5%}.iris-only-strip .iris-slider{width:100%}.iris-picker .iris-square{width:76%;margin-right:10%;position:relative}.iris-only-strip .iris-square{display:none}.iris-picker .iris-square-inner{width:auto;margin:0}.iris-ie-9 .iris-square,.iris-ie-9 .iris-slider,.iris-ie-9 .iris-square-inner,.iris-ie-9 .iris-palette{box-shadow:none;border-radius:0}.iris-ie-9 .iris-square,.iris-ie-9 .iris-slider,.iris-ie-9 .iris-palette{outline:1px solid rgba(0,0,0,.1)}.iris-ie-lt9 .iris-square,.iris-ie-lt9 .iris-slider,.iris-ie-lt9 .iris-square-inner,.iris-ie-lt9 .iris-palette{outline:1px solid #aaa}.iris-ie-lt9 .iris-square .ui-slider-handle{outline:1px solid #aaa;background-color:#fff;-ms-filter:"alpha(Opacity=30)"}.iris-ie-lt9 .iris-square .iris-square-handle{background:0 0;border:3px solid #fff;-ms-filter:"alpha(Opacity=50)"}.iris-picker .iris-strip{margin-right:0;position:relative}.iris-picker .iris-strip .ui-slider-handle{position:absolute;background:0 0;margin:0;right:-3px;left:-3px;border:4px solid #aaa;border-width:4px 3px;width:auto;height:6px;border-radius:4px;box-shadow:0 1px 2px rgba(0,0,0,.2);opacity:.9;z-index:5;cursor:ns-resize}.iris-strip-horiz .iris-strip .ui-slider-handle{right:auto;left:auto;bottom:-3px;top:-3px;height:auto;width:6px;cursor:ew-resize}.iris-strip .ui-slider-handle:before{content:" ";position:absolute;left:-2px;right:-2px;top:-3px;bottom:-3px;border:2px solid #fff;border-radius:3px}.iris-picker .iris-slider-offset{position:absolute;top:11px;left:0;right:0;bottom:-3px;width:auto;height:auto;background:transparent;border:0;border-radius:0}.iris-strip-horiz .iris-slider-offset{top:0;bottom:0;right:11px;left:-3px}.iris-picker .iris-square-handle{background:transparent;border:5px solid #aaa;border-radius:50%;border-color:rgba(128,128,128,.5);box-shadow:none;width:12px;height:12px;position:absolute;left:-10px;top:-10px;cursor:move;opacity:1;z-index:10}.iris-picker .ui-state-focus .iris-square-handle{opacity:.8}.iris-picker .iris-square-handle:hover{border-color:#999}.iris-picker .iris-square-value:focus .iris-square-handle{box-shadow:0 0 2px rgba(0,0,0,.75);opacity:.8}.iris-picker .iris-square-handle:hover::after{border-color:#fff}.iris-picker .iris-square-handle::after{position:absolute;bottom:-4px;right:-4px;left:-4px;top:-4px;border:3px solid #f9f9f9;border-color:rgba(255,255,255,.8);border-radius:50%;content:" "}.iris-picker .iris-square-value{width:8px;height:8px;position:absolute}.iris-ie-lt9 .iris-square-value,.iris-mozilla .iris-square-value{width:1px;height:1px}.iris-palette-container{position:absolute;bottom:0;left:0;margin:0;padding:0}.iris-border .iris-palette-container{left:10px;bottom:10px}.iris-picker .iris-palette{margin:0;cursor:pointer}.iris-square-handle,.ui-slider-handle{border:0;outline:0}',o=navigator.userAgent.toLowerCase(),p="Microsoft Internet Explorer"===navigator.appName,q=p?parseFloat(o.match(/msie ([0-9]{1,}[\.0-9]{0,})/)[1]):0,j=p&&q<10,k=!1,l=["-moz-","-webkit-","-o-","-ms-"],j&&q<=7?(a.fn.iris=a.noop,void(a.support.iris=!1)):(a.support.iris=!0,a.fn.gradient=function(){var b=arguments;return this.each(function(){j?e.apply(this,b):a(this).css("backgroundImage",d.apply(this,b))})},a.fn.rainbowGradient=function(b,c){var d,e,f,g;for(b=b||"top",d=a.extend({},{s:100,l:50},c),e="hsl(%h%,"+d.s+"%,"+d.l+"%)",f=0,g=[];f<=360;)g.push(e.replace("%h%",f)),f+=30;return this.each(function(){a(this).gradient(b,g)})},n={options:{color:!1,mode:"hsl",controls:{horiz:"s",vert:"l",strip:"h"},hide:!0,border:!0,target:!1,width:200,palettes:!1,type:"full",slider:"horizontal"},_color:"",_palettes:["#000","#fff","#d33","#d93","#ee2","#81d742","#1e73be","#8224e3"],_inited:!1,_defaultHSLControls:{horiz:"s",vert:"l",strip:"h"},_defaultHSVControls:{horiz:"h",vert:"v",strip:"s"},_scale:{h:360,s:100,l:100,v:100},_create:function(){var b=this,d=b.element,e=b.options.color||d.val();k===!1&&c(),d.is("input")?(b.options.target?b.picker=a(i).appendTo(b.options.target):b.picker=a(i).insertAfter(d),b._addInputListeners(d)):(d.append(i),b.picker=d.find(".iris-picker")),p?9===q?b.picker.addClass("iris-ie-9"):q<=8&&b.picker.addClass("iris-ie-lt9"):o.indexOf("compatible")<0&&o.indexOf("khtml")<0&&o.match(/mozilla/)&&b.picker.addClass("iris-mozilla"),b.options.palettes&&b._addPalettes(),b.onlySlider="hue"===b.options.type,b.horizontalSlider=b.onlySlider&&"horizontal"===b.options.slider,b.onlySlider&&(b.options.controls.strip="h",e||(e="hsl(10,100,50)")),b._color=new Color(e).setHSpace(b.options.mode),b.options.color=b._color.toString(),b.controls={square:b.picker.find(".iris-square"),squareDrag:b.picker.find(".iris-square-value"),horiz:b.picker.find(".iris-square-horiz"),vert:b.picker.find(".iris-square-vert"),strip:b.picker.find(".iris-strip"),stripSlider:b.picker.find(".iris-strip .iris-slider-offset")},"hsv"===b.options.mode&&b._has("l",b.options.controls)?b.options.controls=b._defaultHSVControls:"hsl"===b.options.mode&&b._has("v",b.options.controls)&&(b.options.controls=b._defaultHSLControls),b.hue=b._color.h(),b.options.hide&&b.picker.hide(),b.options.border&&!b.onlySlider&&b.picker.addClass("iris-border"),b._initControls(),b.active="external",b._dimensions(),b._change()},_has:function(b,c){var d=!1;return a.each(c,function(a,c){if(b===c)return d=!0,!1}),d},_addPalettes:function(){var b=a('<div class="iris-palette-container" />'),c=a('<a class="iris-palette" tabindex="0" />'),d=a.isArray(this.options.palettes)?this.options.palettes:this._palettes;this.picker.find(".iris-palette-container").length&&(b=this.picker.find(".iris-palette-container").detach().html("")),a.each(d,function(a,d){c.clone().data("color",d).css("backgroundColor",d).appendTo(b).height(10).width(10)}),this.picker.append(b)},_paint:function(){var a=this;a.horizontalSlider?a._paintDimension("left","strip"):a._paintDimension("top","strip"),a._paintDimension("top","vert"),a._paintDimension("left","horiz")},_paintDimension:function(a,b){var c,d=this,e=d._color,f=d.options.mode,g=d._getHSpaceColor(),h=d.controls[b],i=d.options.controls;if(b!==d.active&&("square"!==d.active||"strip"===b))switch(i[b]){case"h":if("hsv"===f){switch(g=e.clone(),b){case"horiz":g[i.vert](100);break;case"vert":g[i.horiz](100);break;case"strip":g.setHSpace("hsl")}c=g.toHsl()}else c="strip"===b?{s:g.s,l:g.l}:{s:100,l:g.l};h.rainbowGradient(a,c);break;case"s":"hsv"===f?"vert"===b?c=[e.clone().a(0).s(0).toCSS("rgba"),e.clone().a(1).s(0).toCSS("rgba")]:"strip"===b?c=[e.clone().s(100).toCSS("hsl"),e.clone().s(0).toCSS("hsl")]:"horiz"===b&&(c=["#fff","hsl("+g.h+",100%,50%)"]):c="vert"===b&&"h"===d.options.controls.horiz?["hsla(0, 0%, "+g.l+"%, 0)","hsla(0, 0%, "+g.l+"%, 1)"]:["hsl("+g.h+",0%,50%)","hsl("+g.h+",100%,50%)"],h.gradient(a,c);break;case"l":c="strip"===b?["hsl("+g.h+",100%,100%)","hsl("+g.h+", "+g.s+"%,50%)","hsl("+g.h+",100%,0%)"]:["#fff","rgba(255,255,255,0) 50%","rgba(0,0,0,0) 50%","rgba(0,0,0,1)"],h.gradient(a,c);break;case"v":c="strip"===b?[e.clone().v(100).toCSS(),e.clone().v(0).toCSS()]:["rgba(0,0,0,0)","#000"],h.gradient(a,c)}},_getHSpaceColor:function(){return"hsv"===this.options.mode?this._color.toHsv():this._color.toHsl()},_stripOnlyDimensions:function(){var a=this,b=this.options.width,c=.12*b;a.horizontalSlider?a.picker.css({width:b,height:c}).addClass("iris-only-strip iris-strip-horiz"):a.picker.css({width:c,height:b}).addClass("iris-only-strip iris-strip-vert")},_dimensions:function(b){if("hue"===this.options.type)return this._stripOnlyDimensions();var c,d,e,f,g=this,h=g.options,i=g.controls,j=i.square,k=g.picker.find(".iris-strip"),l="77.5%",m="12%",n=20,o=h.border?h.width-n:h.width,p=a.isArray(h.palettes)?h.palettes.length:g._palettes.length;return b&&(j.css("width",""),k.css("width",""),g.picker.css({width:"",height:""})),l=o*(parseFloat(l)/100),m=o*(parseFloat(m)/100),c=h.border?l+n:l,j.width(l).height(l),k.height(l).width(m),g.picker.css({width:h.width,height:c}),h.palettes?(d=2*l/100,f=l-(p-1)*d,e=f/p,g.picker.find(".iris-palette").each(function(b){var c=0===b?0:d;a(this).css({width:e,height:e,marginLeft:c})}),g.picker.css("paddingBottom",e+d),void k.height(e+d+l)):g.picker.css("paddingBottom","")},_addInputListeners:function(a){var b=this,c=100,d=function(c){var d=new Color(a.val()),e=a.val().replace(/^#/,"");a.removeClass("iris-error"),d.error?""!==e&&a.addClass("iris-error"):d.toString()!==b._color.toString()&&("keyup"===c.type&&e.match(/^[0-9a-fA-F]{3}$/)||b._setOption("color",d.toString()))};a.on("change",d).on("keyup",b._debounce(d,c)),b.options.hide&&a.one("focus",function(){b.show()})},_initControls:function(){var b=this,c=b.controls,d=c.square,e=b.options.controls,f=b._scale[e.strip],g=b.horizontalSlider?"horizontal":"vertical";c.stripSlider.slider({orientation:g,max:f,slide:function(a,c){b.active="strip","h"===e.strip&&"vertical"===g&&(c.value=f-c.value),b._color[e.strip](c.value),b._change.apply(b,arguments)}}),c.squareDrag.draggable({containment:c.square.find(".iris-square-inner"),zIndex:1e3,cursor:"move",drag:function(a,c){b._squareDrag(a,c)},start:function(){d.addClass("iris-dragging"),a(this).addClass("ui-state-focus")},stop:function(){d.removeClass("iris-dragging"),a(this).removeClass("ui-state-focus")}}).on("mousedown mouseup",function(c){var d="ui-state-focus";c.preventDefault(),"mousedown"===c.type?(b.picker.find("."+d).removeClass(d).blur(),a(this).addClass(d).focus()):a(this).removeClass(d)}).on("keydown",function(a){var d=c.square,e=c.squareDrag,f=e.position(),g=b.options.width/100;switch(a.altKey&&(g*=10),a.keyCode){case 37:f.left-=g;break;case 38:f.top-=g;break;case 39:f.left+=g;break;case 40:f.top+=g;break;default:return!0}f.left=Math.max(0,Math.min(f.left,d.width())),f.top=Math.max(0,Math.min(f.top,d.height())),e.css(f),b._squareDrag(a,{position:f}),a.preventDefault()}),d.mousedown(function(c){var d,e;1===c.which&&a(c.target).is("div")&&(d=b.controls.square.offset(),e={top:c.pageY-d.top,left:c.pageX-d.left},c.preventDefault(),b._squareDrag(c,{position:e}),c.target=b.controls.squareDrag.get(0),b.controls.squareDrag.css(e).trigger(c))}),b.options.palettes&&b._paletteListeners()},_paletteListeners:function(){var b=this;b.picker.find(".iris-palette-container").on("click.palette",".iris-palette",function(){b._color.fromCSS(a(this).data("color")),b.active="external",b._change()}).on("keydown.palette",".iris-palette",function(b){return 13!==b.keyCode&&32!==b.keyCode||(b.stopPropagation(),void a(this).click())})},_squareDrag:function(a,b){var c=this,d=c.options.controls,e=c._squareDimensions(),f=Math.round((e.h-b.position.top)/e.h*c._scale[d.vert]),g=c._scale[d.horiz]-Math.round((e.w-b.position.left)/e.w*c._scale[d.horiz]);c._color[d.horiz](g)[d.vert](f),c.active="square",c._change.apply(c,arguments)},_setOption:function(b,c){var d,e,f,g=this,h=g.options[b],i=!1;switch(g.options[b]=c,b){case"color":g.onlySlider?(c=parseInt(c,10),c=isNaN(c)||c<0||c>359?h:"hsl("+c+",100,50)",g.options.color=g.options[b]=c,g._color=new Color(c).setHSpace(g.options.mode),g.active="external",g._change()):(c=""+c,d=c.replace(/^#/,""),e=new Color(c).setHSpace(g.options.mode),e.error?g.options[b]=h:(g._color=e,g.options.color=g.options[b]=g._color.toString(),g.active="external",g._change()));break;case"palettes":i=!0,c?g._addPalettes():g.picker.find(".iris-palette-container").remove(),h||g._paletteListeners();break;case"width":i=!0;break;case"border":i=!0,f=c?"addClass":"removeClass",g.picker[f]("iris-border");break;case"mode":case"controls":if(h===c)return;return f=g.element,h=g.options,h.hide=!g.picker.is(":visible"),g.destroy(),g.picker.remove(),a(g.element).iris(h)}i&&g._dimensions(!0)},_squareDimensions:function(a){var c,d,e=this.controls.square;return a!==b&&e.data("dimensions")?e.data("dimensions"):(d=this.controls.squareDrag,c={w:e.width(),h:e.height()},e.data("dimensions",c),c)},_isNonHueControl:function(a,b){return"square"===a&&"h"===this.options.controls.strip||"external"!==b&&("h"!==b||"strip"!==a)},_change:function(){var b=this,c=b.controls,d=b._getHSpaceColor(),e=["square","strip"],f=b.options.controls,g=f[b.active]||"external",h=b.hue;"strip"===b.active?e=[]:"external"!==b.active&&e.pop(),a.each(e,function(a,e){var g,h,i;if(e!==b.active)switch(e){case"strip":g="h"!==f.strip||b.horizontalSlider?d[f.strip]:b._scale[f.strip]-d[f.strip],c.stripSlider.slider("value",g);break;case"square":h=b._squareDimensions(),i={left:d[f.horiz]/b._scale[f.horiz]*h.w,top:h.h-d[f.vert]/b._scale[f.vert]*h.h},b.controls.squareDrag.css(i)}}),d.h!==h&&b._isNonHueControl(b.active,g)&&b._color.h(h),b.hue=b._color.h(),b.options.color=b._color.toString(),b._inited&&b._trigger("change",{type:b.active},{color:b._color}),b.element.is(":input")&&!b._color.error&&(b.element.removeClass("iris-error"),b.onlySlider?b.element.val()!==b.hue&&b.element.val(b.hue):b.element.val()!==b._color.toString()&&b.element.val(b._color.toString())),b._paint(),b._inited=!0,b.active=!1},_debounce:function(a,b,c){var d,e;return function(){var f,g,h=this,i=arguments;return f=function(){d=null,c||(e=a.apply(h,i))},g=c&&!d,clearTimeout(d),d=setTimeout(f,b),g&&(e=a.apply(h,i)),e}},show:function(){this.picker.show()},hide:function(){this.picker.hide()},toggle:function(){this.picker.toggle()},color:function(a){return a===!0?this._color.clone():a===b?this._color.toString():void this.option("color",a)}},a.widget("a8c.iris",n),void a('<style id="iris-css">'+m+"</style>").appendTo("head"))}(jQuery),function(a,b){var c=function(a,b){return this instanceof c?this._init(a,b):new c(a,b)};c.fn=c.prototype={_color:0,_alpha:1,error:!1,_hsl:{h:0,s:0,l:0},_hsv:{h:0,s:0,v:0},_hSpace:"hsl",_init:function(a){var c="noop";switch(typeof a){case"object":return a.a!==b&&this.a(a.a),c=a.r!==b?"fromRgb":a.l!==b?"fromHsl":a.v!==b?"fromHsv":c,this[c](a);case"string":return this.fromCSS(a);case"number":return this.fromInt(parseInt(a,10))}return this},_error:function(){return this.error=!0,this},clone:function(){for(var a=new c(this.toInt()),b=["_alpha","_hSpace","_hsl","_hsv","error"],d=b.length-1;d>=0;d--)a[b[d]]=this[b[d]];return a},setHSpace:function(a){return this._hSpace="hsv"===a?a:"hsl",this},noop:function(){return this},fromCSS:function(a){var b,c=/^(rgb|hs(l|v))a?\(/;if(this.error=!1,a=a.replace(/^\s+/,"").replace(/\s+$/,"").replace(/;$/,""),a.match(c)&&a.match(/\)$/)){if(b=a.replace(/(\s|%)/g,"").replace(c,"").replace(/,?\);?$/,"").split(","),b.length<3)return this._error();if(4===b.length&&(this.a(parseFloat(b.pop())),this.error))return this;for(var d=b.length-1;d>=0;d--)if(b[d]=parseInt(b[d],10),isNaN(b[d]))return this._error();return a.match(/^rgb/)?this.fromRgb({r:b[0],g:b[1],b:b[2]}):a.match(/^hsv/)?this.fromHsv({h:b[0],s:b[1],v:b[2]}):this.fromHsl({h:b[0],s:b[1],l:b[2]})}return this.fromHex(a)},fromRgb:function(a,c){return"object"!=typeof a||a.r===b||a.g===b||a.b===b?this._error():(this.error=!1,this.fromInt(parseInt((a.r<<16)+(a.g<<8)+a.b,10),c))},fromHex:function(a){return a=a.replace(/^#/,"").replace(/^0x/,""),3===a.length&&(a=a[0]+a[0]+a[1]+a[1]+a[2]+a[2]),this.error=!/^[0-9A-F]{6}$/i.test(a),this.fromInt(parseInt(a,16))},fromHsl:function(a){var c,d,e,f,g,h,i,j;return"object"!=typeof a||a.h===b||a.s===b||a.l===b?this._error():(this._hsl=a,this._hSpace="hsl",h=a.h/360,i=a.s/100,j=a.l/100,0===i?c=d=e=j:(f=j<.5?j*(1+i):j+i-j*i,g=2*j-f,c=this.hue2rgb(g,f,h+1/3),d=this.hue2rgb(g,f,h),e=this.hue2rgb(g,f,h-1/3)),this.fromRgb({r:255*c,g:255*d,b:255*e},!0))},fromHsv:function(a){var c,d,e,f,g,h,i,j,k,l,m;if("object"!=typeof a||a.h===b||a.s===b||a.v===b)return this._error();switch(this._hsv=a,this._hSpace="hsv",c=a.h/360,d=a.s/100,e=a.v/100,i=Math.floor(6*c),j=6*c-i,k=e*(1-d),l=e*(1-j*d),m=e*(1-(1-j)*d),i%6){case 0:f=e,g=m,h=k;break;case 1:f=l,g=e,h=k;break;case 2:f=k,g=e,h=m;break;case 3:f=k,g=l,h=e;break;case 4:f=m,g=k,h=e;break;case 5:f=e,g=k,h=l}return this.fromRgb({r:255*f,g:255*g,b:255*h},!0)},fromInt:function(a,c){return this._color=parseInt(a,10),isNaN(this._color)&&(this._color=0),this._color>16777215?this._color=16777215:this._color<0&&(this._color=0),c===b&&(this._hsv.h=this._hsv.s=this._hsl.h=this._hsl.s=0),this},hue2rgb:function(a,b,c){return c<0&&(c+=1),c>1&&(c-=1),c<1/6?a+6*(b-a)*c:c<.5?b:c<2/3?a+(b-a)*(2/3-c)*6:a},toString:function(){var a=parseInt(this._color,10).toString(16);if(this.error)return"";if(a.length<6)for(var b=6-a.length-1;b>=0;b--)a="0"+a;return"#"+a},toCSS:function(a,b){switch(a=a||"hex",b=parseFloat(b||this._alpha),a){case"rgb":case"rgba":var c=this.toRgb();return b<1?"rgba( "+c.r+", "+c.g+", "+c.b+", "+b+" )":"rgb( "+c.r+", "+c.g+", "+c.b+" )";case"hsl":case"hsla":var d=this.toHsl();return b<1?"hsla( "+d.h+", "+d.s+"%, "+d.l+"%, "+b+" )":"hsl( "+d.h+", "+d.s+"%, "+d.l+"% )";default:return this.toString()}},toRgb:function(){return{r:255&this._color>>16,g:255&this._color>>8,b:255&this._color}},toHsl:function(){var a,b,c=this.toRgb(),d=c.r/255,e=c.g/255,f=c.b/255,g=Math.max(d,e,f),h=Math.min(d,e,f),i=(g+h)/2;if(g===h)a=b=0;else{var j=g-h;switch(b=i>.5?j/(2-g-h):j/(g+h),g){case d:a=(e-f)/j+(e<f?6:0);break;case e:a=(f-d)/j+2;break;case f:a=(d-e)/j+4}a/=6}return a=Math.round(360*a),0===a&&this._hsl.h!==a&&(a=this._hsl.h),b=Math.round(100*b),0===b&&this._hsl.s&&(b=this._hsl.s),{h:a,s:b,l:Math.round(100*i)}},toHsv:function(){var a,b,c=this.toRgb(),d=c.r/255,e=c.g/255,f=c.b/255,g=Math.max(d,e,f),h=Math.min(d,e,f),i=g,j=g-h;if(b=0===g?0:j/g,g===h)a=b=0;else{switch(g){case d:a=(e-f)/j+(e<f?6:0);break;case e:a=(f-d)/j+2;break;case f:a=(d-e)/j+4}a/=6}return a=Math.round(360*a),0===a&&this._hsv.h!==a&&(a=this._hsv.h),b=Math.round(100*b),0===b&&this._hsv.s&&(b=this._hsv.s),{h:a,s:b,v:Math.round(100*i)}},toInt:function(){return this._color},toIEOctoHex:function(){var a=this.toString(),b=parseInt(255*this._alpha,10).toString(16);return 1===b.length&&(b="0"+b),"#"+b+a.replace(/^#/,"")},toLuminosity:function(){var a=this.toRgb(),b={};for(var c in a)if(a.hasOwnProperty(c)){var d=a[c]/255;b[c]=d<=.03928?d/12.92:Math.pow((d+.055)/1.055,2.4)}return.2126*b.r+.7152*b.g+.0722*b.b},getDistanceLuminosityFrom:function(a){if(!(a instanceof c))throw"getDistanceLuminosityFrom requires a Color object";var b=this.toLuminosity(),d=a.toLuminosity();return b>d?(b+.05)/(d+.05):(d+.05)/(b+.05)},getMaxContrastColor:function(){var a=this.getDistanceLuminosityFrom(new c("#000")),b=this.getDistanceLuminosityFrom(new c("#fff")),d=a>=b?"#000":"#fff";return new c(d)},getReadableContrastingColor:function(a,d){if(!(a instanceof c))return this;var e,f,g,h=d===b?5:d,i=a.getDistanceLuminosityFrom(this);if(i>=h)return this;if(e=a.getMaxContrastColor(),f=e.getDistanceLuminosityFrom(a),f<=h)return e;for(g=0===e.toInt()?-1:1;i<h&&(this.l(g,!0),i=this.getDistanceLuminosityFrom(a),0!==this._color&&16777215!==this._color););return this},a:function(a){if(a===b)return this._alpha;var c=parseFloat(a);return isNaN(c)?this._error():(this._alpha=c,this)},darken:function(a){return a=a||5,this.l(-a,!0)},lighten:function(a){return a=a||5,this.l(a,!0)},saturate:function(a){return a=a||15,this.s(a,!0)},desaturate:function(a){return a=a||15,this.s(-a,!0)},toGrayscale:function(){return this.setHSpace("hsl").s(0)},getComplement:function(){return this.h(180,!0)},getSplitComplement:function(a){a=a||1;var b=180+30*a;return this.h(b,!0)},getAnalog:function(a){a=a||1;var b=30*a;return this.h(b,!0)},getTetrad:function(a){a=a||1;var b=60*a;return this.h(b,!0)},getTriad:function(a){a=a||1;var b=120*a;return this.h(b,!0)},_partial:function(a){var c=d[a];return function(d,e){var f=this._spaceFunc("to",c.space);return d===b?f[a]:(e===!0&&(d=f[a]+d),c.mod&&(d%=c.mod),c.range&&(d=d<c.range[0]?c.range[0]:d>c.range[1]?c.range[1]:d),f[a]=d,this._spaceFunc("from",c.space,f))}},_spaceFunc:function(a,b,c){var d=b||this._hSpace,e=a+d.charAt(0).toUpperCase()+d.substr(1);return this[e](c)}};var d={h:{mod:360},s:{range:[0,100]},l:{space:"hsl",range:[0,100]},v:{space:"hsv",range:[0,100]},r:{space:"rgb",range:[0,255]},g:{space:"rgb",range:[0,255]},b:{space:"rgb",range:[0,255]}};for(var e in d)d.hasOwnProperty(e)&&(c.fn[e]=c.fn._partial(e));"object"==typeof exports?module.exports=c:a.Color=c}(this); \ No newline at end of file
diff --git a/www/crm/wp-admin/js/language-chooser.js b/www/crm/wp-admin/js/language-chooser.js
new file mode 100644
index 00000000..8eb33b44
--- /dev/null
+++ b/www/crm/wp-admin/js/language-chooser.js
@@ -0,0 +1,36 @@
+/**
+ * @output wp-admin/js/language-chooser.js
+ */
+
+jQuery( function($) {
+/*
+ * Set the correct translation to the continue button and show a spinner
+ * when downloading a language.
+ */
+var select = $( '#language' ),
+ submit = $( '#language-continue' );
+
+if ( ! $( 'body' ).hasClass( 'language-chooser' ) ) {
+ return;
+}
+
+select.focus().on( 'change', function() {
+ /*
+ * When a language is selected, set matching translation to continue button
+ * and attach the language attribute.
+ */
+ var option = select.children( 'option:selected' );
+ submit.attr({
+ value: option.data( 'continue' ),
+ lang: option.attr( 'lang' )
+ });
+});
+
+$( 'form' ).submit( function() {
+ // Show spinner for languages that need to be downloaded.
+ if ( ! select.children( 'option:selected' ).data( 'installed' ) ) {
+ $( this ).find( '.step .spinner' ).css( 'visibility', 'visible' );
+ }
+});
+
+});
diff --git a/www/crm/wp-admin/js/language-chooser.min.js b/www/crm/wp-admin/js/language-chooser.min.js
new file mode 100644
index 00000000..38d65ebd
--- /dev/null
+++ b/www/crm/wp-admin/js/language-chooser.min.js
@@ -0,0 +1 @@
+jQuery(function(a){var b=a("#language"),c=a("#language-continue");a("body").hasClass("language-chooser")&&(b.focus().on("change",function(){var a=b.children("option:selected");c.attr({value:a.data("continue"),lang:a.attr("lang")})}),a("form").submit(function(){b.children("option:selected").data("installed")||a(this).find(".step .spinner").css("visibility","visible")}))}); \ No newline at end of file
diff --git a/www/crm/wp-admin/js/link.js b/www/crm/wp-admin/js/link.js
new file mode 100644
index 00000000..5b73fe61
--- /dev/null
+++ b/www/crm/wp-admin/js/link.js
@@ -0,0 +1,136 @@
+/**
+ * @output wp-admin/js/link.js
+ */
+
+/* global postboxes, deleteUserSetting, setUserSetting, getUserSetting */
+
+jQuery(document).ready( function($) {
+
+ var newCat, noSyncChecks = false, syncChecks, catAddAfter;
+
+ $('#link_name').focus();
+ // postboxes
+ postboxes.add_postbox_toggles('link');
+
+ /**
+ * Adds event that opens a particular category tab.
+ *
+ * @ignore
+ *
+ * @return {boolean} Always returns false to prevent the default behavior.
+ */
+ $('#category-tabs a').click(function(){
+ var t = $(this).attr('href');
+ $(this).parent().addClass('tabs').siblings('li').removeClass('tabs');
+ $('.tabs-panel').hide();
+ $(t).show();
+ if ( '#categories-all' == t )
+ deleteUserSetting('cats');
+ else
+ setUserSetting('cats','pop');
+ return false;
+ });
+ if ( getUserSetting('cats') )
+ $('#category-tabs a[href="#categories-pop"]').click();
+
+ // Ajax Cat
+ newCat = $('#newcat').one( 'focus', function() { $(this).val( '' ).removeClass( 'form-input-tip' ); } );
+
+ /**
+ * After adding a new category, focus on the category add input field.
+ *
+ * @return {void}
+ */
+ $('#link-category-add-submit').click( function() { newCat.focus(); } );
+
+ /**
+ * Synchronize category checkboxes.
+ *
+ * This function makes sure that the checkboxes are synced between the all
+ * categories tab and the most used categories tab.
+ *
+ * @since 2.5.0
+ *
+ * @return {void}
+ */
+ syncChecks = function() {
+ if ( noSyncChecks )
+ return;
+ noSyncChecks = true;
+ var th = $(this), c = th.is(':checked'), id = th.val().toString();
+ $('#in-link-category-' + id + ', #in-popular-link_category-' + id).prop( 'checked', c );
+ noSyncChecks = false;
+ };
+
+ /**
+ * Adds event listeners to an added category.
+ *
+ * This is run on the addAfter event to make sure the correct event listeners
+ * are bound to the DOM elements.
+ *
+ * @since 2.5.0
+ *
+ * @param {string} r Raw XML response returned from the server after adding a
+ * category.
+ * @param {Object} s List manager configuration object; settings for the Ajax
+ * request.
+ *
+ * @return {void}
+ */
+ catAddAfter = function( r, s ) {
+ $(s.what + ' response_data', r).each( function() {
+ var t = $($(this).text());
+ t.find( 'label' ).each( function() {
+ var th = $(this), val = th.find('input').val(), id = th.find('input')[0].id, name = $.trim( th.text() ), o;
+ $('#' + id).change( syncChecks );
+ o = $( '<option value="' + parseInt( val, 10 ) + '"></option>' ).text( name );
+ } );
+ } );
+ };
+
+ /*
+ * Instantiates the list manager.
+ *
+ * @see js/_enqueues/lib/lists.js
+ */
+ $('#categorychecklist').wpList( {
+ // CSS class name for alternate styling.
+ alt: '',
+
+ // The type of list.
+ what: 'link-category',
+
+ // ID of the element the parsed Ajax response will be stored in.
+ response: 'category-ajax-response',
+
+ // Callback that's run after an item got added to the list.
+ addAfter: catAddAfter
+ } );
+
+ // All categories is the default tab, so we delete the user setting.
+ $('a[href="#categories-all"]').click(function(){deleteUserSetting('cats');});
+
+ // Set a preference for the popular categories to cookies.
+ $('a[href="#categories-pop"]').click(function(){setUserSetting('cats','pop');});
+
+ if ( 'pop' == getUserSetting('cats') )
+ $('a[href="#categories-pop"]').click();
+
+ /**
+ * Adds event handler that shows the interface controls to add a new category.
+ *
+ * @ignore
+ *
+ * @param {Event} event The event object.
+ * @returns {boolean} Always returns false to prevent regular link
+ * functionality.
+ */
+ $('#category-add-toggle').click( function() {
+ $(this).parents('div:first').toggleClass( 'wp-hidden-children' );
+ $('#category-tabs a[href="#categories-all"]').click();
+ $('#newcategory').focus();
+ return false;
+ } );
+
+ $('.categorychecklist :checkbox').change( syncChecks ).filter( ':checked' ).change();
+});
diff --git a/www/crm/wp-admin/js/link.min.js b/www/crm/wp-admin/js/link.min.js
new file mode 100644
index 00000000..f1487770
--- /dev/null
+++ b/www/crm/wp-admin/js/link.min.js
@@ -0,0 +1 @@
+jQuery(document).ready(function(a){var b,c,d,e=!1;a("#link_name").focus(),postboxes.add_postbox_toggles("link"),a("#category-tabs a").click(function(){var b=a(this).attr("href");return a(this).parent().addClass("tabs").siblings("li").removeClass("tabs"),a(".tabs-panel").hide(),a(b).show(),"#categories-all"==b?deleteUserSetting("cats"):setUserSetting("cats","pop"),!1}),getUserSetting("cats")&&a('#category-tabs a[href="#categories-pop"]').click(),b=a("#newcat").one("focus",function(){a(this).val("").removeClass("form-input-tip")}),a("#link-category-add-submit").click(function(){b.focus()}),c=function(){if(!e){e=!0;var b=a(this),c=b.is(":checked"),d=b.val().toString();a("#in-link-category-"+d+", #in-popular-link_category-"+d).prop("checked",c),e=!1}},d=function(b,d){a(d.what+" response_data",b).each(function(){var b=a(a(this).text());b.find("label").each(function(){var b,d=a(this),e=d.find("input").val(),f=d.find("input")[0].id,g=a.trim(d.text());a("#"+f).change(c),b=a('<option value="'+parseInt(e,10)+'"></option>').text(g)})})},a("#categorychecklist").wpList({alt:"",what:"link-category",response:"category-ajax-response",addAfter:d}),a('a[href="#categories-all"]').click(function(){deleteUserSetting("cats")}),a('a[href="#categories-pop"]').click(function(){setUserSetting("cats","pop")}),"pop"==getUserSetting("cats")&&a('a[href="#categories-pop"]').click(),a("#category-add-toggle").click(function(){return a(this).parents("div:first").toggleClass("wp-hidden-children"),a('#category-tabs a[href="#categories-all"]').click(),a("#newcategory").focus(),!1}),a(".categorychecklist :checkbox").change(c).filter(":checked").change()}); \ No newline at end of file
diff --git a/www/crm/wp-admin/js/media-gallery.js b/www/crm/wp-admin/js/media-gallery.js
new file mode 100644
index 00000000..725d2beb
--- /dev/null
+++ b/www/crm/wp-admin/js/media-gallery.js
@@ -0,0 +1,41 @@
+/**
+ * This file is used on media-upload.php which has been replaced by media-new.php and upload.php
+ *
+ * @deprecated 3.5.0
+ * @output wp-admin/js/media-gallery.js
+ */
+
+ /* global ajaxurl */
+jQuery(function($) {
+ /**
+ * Adds a click event handler to the element with a 'wp-gallery' class.
+ */
+ $( 'body' ).bind( 'click.wp-gallery', function(e) {
+ var target = $( e.target ), id, img_size;
+
+ if ( target.hasClass( 'wp-set-header' ) ) {
+ // Opens the image to preview it full size.
+ ( window.dialogArguments || opener || parent || top ).location.href = target.data( 'location' );
+ e.preventDefault();
+ } else if ( target.hasClass( 'wp-set-background' ) ) {
+ // Sets the image as background of the theme.
+ id = target.data( 'attachment-id' );
+ img_size = $( 'input[name="attachments[' + id + '][image-size]"]:checked').val();
+
+ /**
+ * This AJAX action has been deprecated since 3.5.0, see custom-background.php
+ */
+ jQuery.post(ajaxurl, {
+ action: 'set-background-image',
+ attachment_id: id,
+ size: img_size
+ }, function() {
+ var win = window.dialogArguments || opener || parent || top;
+ win.tb_remove();
+ win.location.reload();
+ });
+
+ e.preventDefault();
+ }
+ });
+});
diff --git a/www/crm/wp-admin/js/media-gallery.min.js b/www/crm/wp-admin/js/media-gallery.min.js
new file mode 100644
index 00000000..dc66a26c
--- /dev/null
+++ b/www/crm/wp-admin/js/media-gallery.min.js
@@ -0,0 +1 @@
+jQuery(function(a){a("body").bind("click.wp-gallery",function(b){var c,d,e=a(b.target);e.hasClass("wp-set-header")?((window.dialogArguments||opener||parent||top).location.href=e.data("location"),b.preventDefault()):e.hasClass("wp-set-background")&&(c=e.data("attachment-id"),d=a('input[name="attachments['+c+'][image-size]"]:checked').val(),jQuery.post(ajaxurl,{action:"set-background-image",attachment_id:c,size:d},function(){var a=window.dialogArguments||opener||parent||top;a.tb_remove(),a.location.reload()}),b.preventDefault())})}); \ No newline at end of file
diff --git a/www/crm/wp-admin/js/media-upload.js b/www/crm/wp-admin/js/media-upload.js
new file mode 100644
index 00000000..9055cf9c
--- /dev/null
+++ b/www/crm/wp-admin/js/media-upload.js
@@ -0,0 +1,113 @@
+/**
+ * Contains global functions for the media upload within the post edit screen.
+ *
+ * Updates the ThickBox anchor href and the ThickBox's own properties in order
+ * to set the size and position on every resize event. Also adds a function to
+ * send HTML or text to the currently active editor.
+ *
+ * @file
+ * @since 2.5.0
+ * @output wp-admin/js/media-upload.js
+ *
+ * @requires jQuery
+ */
+
+/* global tinymce, QTags, wpActiveEditor, tb_position */
+
+/**
+ * Sends the HTML passed in the parameters to TinyMCE.
+ *
+ * @since 2.5.0
+ *
+ * @global
+ *
+ * @param {string} html The HTML to be sent to the editor.
+ * @returns {void|boolean} Returns false when both TinyMCE and QTags instances
+ * are unavailable. This means that the HTML was not
+ * sent to the editor.
+ */
+window.send_to_editor = function( html ) {
+ var editor,
+ hasTinymce = typeof tinymce !== 'undefined',
+ hasQuicktags = typeof QTags !== 'undefined';
+
+ // If no active editor is set, try to set it.
+ if ( ! wpActiveEditor ) {
+ if ( hasTinymce && tinymce.activeEditor ) {
+ editor = tinymce.activeEditor;
+ window.wpActiveEditor = editor.id;
+ } else if ( ! hasQuicktags ) {
+ return false;
+ }
+ } else if ( hasTinymce ) {
+ editor = tinymce.get( wpActiveEditor );
+ }
+
+ // If the editor is set and not hidden, insert the HTML into the content of the
+ // editor.
+ if ( editor && ! editor.isHidden() ) {
+ editor.execCommand( 'mceInsertContent', false, html );
+ } else if ( hasQuicktags ) {
+ // If quick tags are available, insert the HTML into its content.
+ QTags.insertContent( html );
+ } else {
+ // If neither the TinyMCE editor and the quick tags are available, add the HTML
+ // to the current active editor.
+ document.getElementById( wpActiveEditor ).value += html;
+ }
+
+ // If the old thickbox remove function exists, call it.
+ if ( window.tb_remove ) {
+ try { window.tb_remove(); } catch( e ) {}
+ }
+};
+
+(function($) {
+ /**
+ * Recalculates and applies the new ThickBox position based on the current
+ * window size.
+ *
+ * @since 2.6.0
+ *
+ * @global
+ *
+ * @returns {Object[]} Array containing jQuery objects for all the found
+ * ThickBox anchors.
+ */
+ window.tb_position = function() {
+ var tbWindow = $('#TB_window'),
+ width = $(window).width(),
+ H = $(window).height(),
+ W = ( 833 < width ) ? 833 : width,
+ adminbar_height = 0;
+
+ if ( $('#wpadminbar').length ) {
+ adminbar_height = parseInt( $('#wpadminbar').css('height'), 10 );
+ }
+
+ if ( tbWindow.length ) {
+ tbWindow.width( W - 50 ).height( H - 45 - adminbar_height );
+ $('#TB_iframeContent').width( W - 50 ).height( H - 75 - adminbar_height );
+ tbWindow.css({'margin-left': '-' + parseInt( ( ( W - 50 ) / 2 ), 10 ) + 'px'});
+ if ( typeof document.body.style.maxWidth !== 'undefined' )
+ tbWindow.css({'top': 20 + adminbar_height + 'px', 'margin-top': '0'});
+ }
+
+ /**
+ * Recalculates the new height and width for all links with a ThickBox class.
+ *
+ * @since 2.6.0
+ */
+ return $('a.thickbox').each( function() {
+ var href = $(this).attr('href');
+ if ( ! href ) return;
+ href = href.replace(/&width=[0-9]+/g, '');
+ href = href.replace(/&height=[0-9]+/g, '');
+ $(this).attr( 'href', href + '&width=' + ( W - 80 ) + '&height=' + ( H - 85 - adminbar_height ) );
+ });
+ };
+
+ // Add handler to recalculates the ThickBox position when the window is resized.
+ $(window).resize(function(){ tb_position(); });
+
+})(jQuery);
diff --git a/www/crm/wp-admin/js/media-upload.min.js b/www/crm/wp-admin/js/media-upload.min.js
new file mode 100644
index 00000000..42b1cef3
--- /dev/null
+++ b/www/crm/wp-admin/js/media-upload.min.js
@@ -0,0 +1 @@
+window.send_to_editor=function(a){var b,c="undefined"!=typeof tinymce,d="undefined"!=typeof QTags;if(wpActiveEditor)c&&(b=tinymce.get(wpActiveEditor));else if(c&&tinymce.activeEditor)b=tinymce.activeEditor,window.wpActiveEditor=b.id;else if(!d)return!1;if(b&&!b.isHidden()?b.execCommand("mceInsertContent",!1,a):d?QTags.insertContent(a):document.getElementById(wpActiveEditor).value+=a,window.tb_remove)try{window.tb_remove()}catch(e){}},function(a){window.tb_position=function(){var b=a("#TB_window"),c=a(window).width(),d=a(window).height(),e=833<c?833:c,f=0;return a("#wpadminbar").length&&(f=parseInt(a("#wpadminbar").css("height"),10)),b.length&&(b.width(e-50).height(d-45-f),a("#TB_iframeContent").width(e-50).height(d-75-f),b.css({"margin-left":"-"+parseInt((e-50)/2,10)+"px"}),"undefined"!=typeof document.body.style.maxWidth&&b.css({top:20+f+"px","margin-top":"0"})),a("a.thickbox").each(function(){var b=a(this).attr("href");b&&(b=b.replace(/&width=[0-9]+/g,""),b=b.replace(/&height=[0-9]+/g,""),a(this).attr("href",b+"&width="+(e-80)+"&height="+(d-85-f)))})},a(window).resize(function(){tb_position()})}(jQuery); \ No newline at end of file
diff --git a/www/crm/wp-admin/js/media.js b/www/crm/wp-admin/js/media.js
new file mode 100644
index 00000000..4d9a5ec4
--- /dev/null
+++ b/www/crm/wp-admin/js/media.js
@@ -0,0 +1,207 @@
+/**
+ * Creates a dialog containing posts that can have a particular media attached
+ * to it.
+ *
+ * @since 2.7.0
+ * @output wp-admin/js/media.js
+ *
+ * @namespace findPosts
+ *
+ * @requires jQuery
+ */
+
+/* global ajaxurl, attachMediaBoxL10n, _wpMediaGridSettings, showNotice, findPosts */
+
+( function( $ ){
+ window.findPosts = {
+ /**
+ * Opens a dialog to attach media to a post.
+ *
+ * Adds an overlay prior to retrieving a list of posts to attach the media to.
+ *
+ * @since 2.7.0
+ *
+ * @memberOf findPosts
+ *
+ * @param {string} af_name The name of the affected element.
+ * @param {string} af_val The value of the affected post element.
+ *
+ * @returns {boolean} Always returns false.
+ */
+ open: function( af_name, af_val ) {
+ var overlay = $( '.ui-find-overlay' );
+
+ if ( overlay.length === 0 ) {
+ $( 'body' ).append( '<div class="ui-find-overlay"></div>' );
+ findPosts.overlay();
+ }
+
+ overlay.show();
+
+ if ( af_name && af_val ) {
+ // #affected is a hidden input field in the dialog that keeps track of which media should be attached.
+ $( '#affected' ).attr( 'name', af_name ).val( af_val );
+ }
+
+ $( '#find-posts' ).show();
+
+ // Close the dialog when the escape key is pressed.
+ $('#find-posts-input').focus().keyup( function( event ){
+ if ( event.which == 27 ) {
+ findPosts.close();
+ }
+ });
+
+ // Retrieves a list of applicable posts for media attachment and shows them.
+ findPosts.send();
+
+ return false;
+ },
+
+ /**
+ * Clears the found posts lists before hiding the attach media dialog.
+ *
+ * @since 2.7.0
+ *
+ * @memberOf findPosts
+ *
+ * @returns {void}
+ */
+ close: function() {
+ $('#find-posts-response').empty();
+ $('#find-posts').hide();
+ $( '.ui-find-overlay' ).hide();
+ },
+
+ /**
+ * Binds a click event listener to the overlay which closes the attach media
+ * dialog.
+ *
+ * @since 3.5.0
+ *
+ * @memberOf findPosts
+ *
+ * @returns {void}
+ */
+ overlay: function() {
+ $( '.ui-find-overlay' ).on( 'click', function () {
+ findPosts.close();
+ });
+ },
+
+ /**
+ * Retrieves and displays posts based on the search term.
+ *
+ * Sends a post request to the admin_ajax.php, requesting posts based on the
+ * search term provided by the user. Defaults to all posts if no search term is
+ * provided.
+ *
+ * @since 2.7.0
+ *
+ * @memberOf findPosts
+ *
+ * @returns {void}
+ */
+ send: function() {
+ var post = {
+ ps: $( '#find-posts-input' ).val(),
+ action: 'find_posts',
+ _ajax_nonce: $('#_ajax_nonce').val()
+ },
+ spinner = $( '.find-box-search .spinner' );
+
+ spinner.addClass( 'is-active' );
+
+ /**
+ * Send a POST request to admin_ajax.php, hide the spinner and replace the list
+ * of posts with the response data. If an error occurs, display it.
+ */
+ $.ajax( ajaxurl, {
+ type: 'POST',
+ data: post,
+ dataType: 'json'
+ }).always( function() {
+ spinner.removeClass( 'is-active' );
+ }).done( function( x ) {
+ if ( ! x.success ) {
+ $( '#find-posts-response' ).text( attachMediaBoxL10n.error );
+ }
+
+ $( '#find-posts-response' ).html( x.data );
+ }).fail( function() {
+ $( '#find-posts-response' ).text( attachMediaBoxL10n.error );
+ });
+ }
+ };
+
+ /**
+ * Initializes the file once the DOM is fully loaded and attaches events to the
+ * various form elements.
+ *
+ * @returns {void}
+ */
+ $( document ).ready( function() {
+ var settings, $mediaGridWrap = $( '#wp-media-grid' );
+
+ // Opens a manage media frame into the grid.
+ if ( $mediaGridWrap.length && window.wp && window.wp.media ) {
+ settings = _wpMediaGridSettings;
+
+ window.wp.media({
+ frame: 'manage',
+ container: $mediaGridWrap,
+ library: settings.queryVars
+ }).open();
+ }
+
+ // Prevents form submission if no post has been selected.
+ $( '#find-posts-submit' ).click( function( event ) {
+ if ( ! $( '#find-posts-response input[type="radio"]:checked' ).length )
+ event.preventDefault();
+ });
+
+ // Submits the search query when hitting the enter key in the search input.
+ $( '#find-posts .find-box-search :input' ).keypress( function( event ) {
+ if ( 13 == event.which ) {
+ findPosts.send();
+ return false;
+ }
+ });
+
+ // Binds the click event to the search button.
+ $( '#find-posts-search' ).click( findPosts.send );
+
+ // Binds the close dialog click event.
+ $( '#find-posts-close' ).click( findPosts.close );
+
+ // Binds the bulk action events to the submit buttons.
+ $( '#doaction, #doaction2' ).click( function( event ) {
+
+ /*
+ * Retrieves all select elements for bulk actions that have a name starting with `action`
+ * and handle its action based on its value.
+ */
+ $( 'select[name^="action"]' ).each( function() {
+ var optionValue = $( this ).val();
+
+ if ( 'attach' === optionValue ) {
+ event.preventDefault();
+ findPosts.open();
+ } else if ( 'delete' === optionValue ) {
+ if ( ! showNotice.warn() ) {
+ event.preventDefault();
+ }
+ }
+ });
+ });
+
+ /**
+ * Enables clicking on the entire table row.
+ *
+ * @returns {void}
+ */
+ $( '.find-box-inside' ).on( 'click', 'tr', function() {
+ $( this ).find( '.found-radio input' ).prop( 'checked', true );
+ });
+ });
+})( jQuery );
diff --git a/www/crm/wp-admin/js/media.min.js b/www/crm/wp-admin/js/media.min.js
new file mode 100644
index 00000000..2dd57558
--- /dev/null
+++ b/www/crm/wp-admin/js/media.min.js
@@ -0,0 +1 @@
+!function(a){window.findPosts={open:function(b,c){var d=a(".ui-find-overlay");return 0===d.length&&(a("body").append('<div class="ui-find-overlay"></div>'),findPosts.overlay()),d.show(),b&&c&&a("#affected").attr("name",b).val(c),a("#find-posts").show(),a("#find-posts-input").focus().keyup(function(a){27==a.which&&findPosts.close()}),findPosts.send(),!1},close:function(){a("#find-posts-response").empty(),a("#find-posts").hide(),a(".ui-find-overlay").hide()},overlay:function(){a(".ui-find-overlay").on("click",function(){findPosts.close()})},send:function(){var b={ps:a("#find-posts-input").val(),action:"find_posts",_ajax_nonce:a("#_ajax_nonce").val()},c=a(".find-box-search .spinner");c.addClass("is-active"),a.ajax(ajaxurl,{type:"POST",data:b,dataType:"json"}).always(function(){c.removeClass("is-active")}).done(function(b){b.success||a("#find-posts-response").text(attachMediaBoxL10n.error),a("#find-posts-response").html(b.data)}).fail(function(){a("#find-posts-response").text(attachMediaBoxL10n.error)})}},a(document).ready(function(){var b,c=a("#wp-media-grid");c.length&&window.wp&&window.wp.media&&(b=_wpMediaGridSettings,window.wp.media({frame:"manage",container:c,library:b.queryVars}).open()),a("#find-posts-submit").click(function(b){a('#find-posts-response input[type="radio"]:checked').length||b.preventDefault()}),a("#find-posts .find-box-search :input").keypress(function(a){if(13==a.which)return findPosts.send(),!1}),a("#find-posts-search").click(findPosts.send),a("#find-posts-close").click(findPosts.close),a("#doaction, #doaction2").click(function(b){a('select[name^="action"]').each(function(){var c=a(this).val();"attach"===c?(b.preventDefault(),findPosts.open()):"delete"===c&&(showNotice.warn()||b.preventDefault())})}),a(".find-box-inside").on("click","tr",function(){a(this).find(".found-radio input").prop("checked",!0)})})}(jQuery); \ No newline at end of file
diff --git a/www/crm/wp-admin/js/nav-menu.js b/www/crm/wp-admin/js/nav-menu.js
new file mode 100644
index 00000000..3f1f9a74
--- /dev/null
+++ b/www/crm/wp-admin/js/nav-menu.js
@@ -0,0 +1,1302 @@
+/**
+ * WordPress Administration Navigation Menu
+ * Interface JS functions
+ *
+ * @version 2.0.0
+ *
+ * @package WordPress
+ * @subpackage Administration
+ * @output wp-admin/js/nav-menu.js
+ */
+
+/* global menus, postboxes, columns, isRtl, navMenuL10n, ajaxurl, wpNavMenu */
+
+(function($) {
+
+ var api;
+
+ /**
+ * Contains all the functions to handle WordPress navigation menus administration.
+ *
+ * @namespace wpNavMenu
+ */
+ api = window.wpNavMenu = {
+
+ options : {
+ menuItemDepthPerLevel : 30, // Do not use directly. Use depthToPx and pxToDepth instead.
+ globalMaxDepth: 11,
+ sortableItems: '> *',
+ targetTolerance: 0
+ },
+
+ menuList : undefined, // Set in init.
+ targetList : undefined, // Set in init.
+ menusChanged : false,
+ isRTL: !! ( 'undefined' != typeof isRtl && isRtl ),
+ negateIfRTL: ( 'undefined' != typeof isRtl && isRtl ) ? -1 : 1,
+ lastSearch: '',
+
+ // Functions that run on init.
+ init : function() {
+ api.menuList = $('#menu-to-edit');
+ api.targetList = api.menuList;
+
+ this.jQueryExtensions();
+
+ this.attachMenuEditListeners();
+
+ this.attachQuickSearchListeners();
+ this.attachThemeLocationsListeners();
+ this.attachMenuSaveSubmitListeners();
+
+ this.attachTabsPanelListeners();
+
+ this.attachUnsavedChangesListener();
+
+ if ( api.menuList.length )
+ this.initSortables();
+
+ if ( menus.oneThemeLocationNoMenus )
+ $( '#posttype-page' ).addSelectedToMenu( api.addMenuItemToBottom );
+
+ this.initManageLocations();
+
+ this.initAccessibility();
+
+ this.initToggles();
+
+ this.initPreviewing();
+ },
+
+ jQueryExtensions : function() {
+ // jQuery extensions
+ $.fn.extend({
+ menuItemDepth : function() {
+ var margin = api.isRTL ? this.eq(0).css('margin-right') : this.eq(0).css('margin-left');
+ return api.pxToDepth( margin && -1 != margin.indexOf('px') ? margin.slice(0, -2) : 0 );
+ },
+ updateDepthClass : function(current, prev) {
+ return this.each(function(){
+ var t = $(this);
+ prev = prev || t.menuItemDepth();
+ $(this).removeClass('menu-item-depth-'+ prev )
+ .addClass('menu-item-depth-'+ current );
+ });
+ },
+ shiftDepthClass : function(change) {
+ return this.each(function(){
+ var t = $(this),
+ depth = t.menuItemDepth(),
+ newDepth = depth + change;
+
+ t.removeClass( 'menu-item-depth-'+ depth )
+ .addClass( 'menu-item-depth-'+ ( newDepth ) );
+
+ if ( 0 === newDepth ) {
+ t.find( '.is-submenu' ).hide();
+ }
+ });
+ },
+ childMenuItems : function() {
+ var result = $();
+ this.each(function(){
+ var t = $(this), depth = t.menuItemDepth(), next = t.next( '.menu-item' );
+ while( next.length && next.menuItemDepth() > depth ) {
+ result = result.add( next );
+ next = next.next( '.menu-item' );
+ }
+ });
+ return result;
+ },
+ shiftHorizontally : function( dir ) {
+ return this.each(function(){
+ var t = $(this),
+ depth = t.menuItemDepth(),
+ newDepth = depth + dir;
+
+ // Change .menu-item-depth-n class
+ t.moveHorizontally( newDepth, depth );
+ });
+ },
+ moveHorizontally : function( newDepth, depth ) {
+ return this.each(function(){
+ var t = $(this),
+ children = t.childMenuItems(),
+ diff = newDepth - depth,
+ subItemText = t.find('.is-submenu');
+
+ // Change .menu-item-depth-n class
+ t.updateDepthClass( newDepth, depth ).updateParentMenuItemDBId();
+
+ // If it has children, move those too
+ if ( children ) {
+ children.each(function() {
+ var t = $(this),
+ thisDepth = t.menuItemDepth(),
+ newDepth = thisDepth + diff;
+ t.updateDepthClass(newDepth, thisDepth).updateParentMenuItemDBId();
+ });
+ }
+
+ // Show "Sub item" helper text
+ if (0 === newDepth)
+ subItemText.hide();
+ else
+ subItemText.show();
+ });
+ },
+ updateParentMenuItemDBId : function() {
+ return this.each(function(){
+ var item = $(this),
+ input = item.find( '.menu-item-data-parent-id' ),
+ depth = parseInt( item.menuItemDepth(), 10 ),
+ parentDepth = depth - 1,
+ parent = item.prevAll( '.menu-item-depth-' + parentDepth ).first();
+
+ if ( 0 === depth ) { // Item is on the top level, has no parent
+ input.val(0);
+ } else { // Find the parent item, and retrieve its object id.
+ input.val( parent.find( '.menu-item-data-db-id' ).val() );
+ }
+ });
+ },
+ hideAdvancedMenuItemFields : function() {
+ return this.each(function(){
+ var that = $(this);
+ $('.hide-column-tog').not(':checked').each(function(){
+ that.find('.field-' + $(this).val() ).addClass('hidden-field');
+ });
+ });
+ },
+ /**
+ * Adds selected menu items to the menu.
+ *
+ * @ignore
+ *
+ * @param jQuery metabox The metabox jQuery object.
+ */
+ addSelectedToMenu : function(processMethod) {
+ if ( 0 === $('#menu-to-edit').length ) {
+ return false;
+ }
+
+ return this.each(function() {
+ var t = $(this), menuItems = {},
+ checkboxes = ( menus.oneThemeLocationNoMenus && 0 === t.find( '.tabs-panel-active .categorychecklist li input:checked' ).length ) ? t.find( '#page-all li input[type="checkbox"]' ) : t.find( '.tabs-panel-active .categorychecklist li input:checked' ),
+ re = /menu-item\[([^\]]*)/;
+
+ processMethod = processMethod || api.addMenuItemToBottom;
+
+ // If no items are checked, bail.
+ if ( !checkboxes.length )
+ return false;
+
+ // Show the ajax spinner
+ t.find( '.button-controls .spinner' ).addClass( 'is-active' );
+
+ // Retrieve menu item data
+ $(checkboxes).each(function(){
+ var t = $(this),
+ listItemDBIDMatch = re.exec( t.attr('name') ),
+ listItemDBID = 'undefined' == typeof listItemDBIDMatch[1] ? 0 : parseInt(listItemDBIDMatch[1], 10);
+
+ if ( this.className && -1 != this.className.indexOf('add-to-top') )
+ processMethod = api.addMenuItemToTop;
+ menuItems[listItemDBID] = t.closest('li').getItemData( 'add-menu-item', listItemDBID );
+ });
+
+ // Add the items
+ api.addItemToMenu(menuItems, processMethod, function(){
+ // Deselect the items and hide the ajax spinner
+ checkboxes.removeAttr('checked');
+ t.find( '.button-controls .spinner' ).removeClass( 'is-active' );
+ });
+ });
+ },
+ getItemData : function( itemType, id ) {
+ itemType = itemType || 'menu-item';
+
+ var itemData = {}, i,
+ fields = [
+ 'menu-item-db-id',
+ 'menu-item-object-id',
+ 'menu-item-object',
+ 'menu-item-parent-id',
+ 'menu-item-position',
+ 'menu-item-type',
+ 'menu-item-title',
+ 'menu-item-url',
+ 'menu-item-description',
+ 'menu-item-attr-title',
+ 'menu-item-target',
+ 'menu-item-classes',
+ 'menu-item-xfn'
+ ];
+
+ if( !id && itemType == 'menu-item' ) {
+ id = this.find('.menu-item-data-db-id').val();
+ }
+
+ if( !id ) return itemData;
+
+ this.find('input').each(function() {
+ var field;
+ i = fields.length;
+ while ( i-- ) {
+ if( itemType == 'menu-item' )
+ field = fields[i] + '[' + id + ']';
+ else if( itemType == 'add-menu-item' )
+ field = 'menu-item[' + id + '][' + fields[i] + ']';
+
+ if (
+ this.name &&
+ field == this.name
+ ) {
+ itemData[fields[i]] = this.value;
+ }
+ }
+ });
+
+ return itemData;
+ },
+ setItemData : function( itemData, itemType, id ) { // Can take a type, such as 'menu-item', or an id.
+ itemType = itemType || 'menu-item';
+
+ if( !id && itemType == 'menu-item' ) {
+ id = $('.menu-item-data-db-id', this).val();
+ }
+
+ if( !id ) return this;
+
+ this.find('input').each(function() {
+ var t = $(this), field;
+ $.each( itemData, function( attr, val ) {
+ if( itemType == 'menu-item' )
+ field = attr + '[' + id + ']';
+ else if( itemType == 'add-menu-item' )
+ field = 'menu-item[' + id + '][' + attr + ']';
+
+ if ( field == t.attr('name') ) {
+ t.val( val );
+ }
+ });
+ });
+ return this;
+ }
+ });
+ },
+
+ countMenuItems : function( depth ) {
+ return $( '.menu-item-depth-' + depth ).length;
+ },
+
+ moveMenuItem : function( $this, dir ) {
+
+ var items, newItemPosition, newDepth,
+ menuItems = $( '#menu-to-edit li' ),
+ menuItemsCount = menuItems.length,
+ thisItem = $this.parents( 'li.menu-item' ),
+ thisItemChildren = thisItem.childMenuItems(),
+ thisItemData = thisItem.getItemData(),
+ thisItemDepth = parseInt( thisItem.menuItemDepth(), 10 ),
+ thisItemPosition = parseInt( thisItem.index(), 10 ),
+ nextItem = thisItem.next(),
+ nextItemChildren = nextItem.childMenuItems(),
+ nextItemDepth = parseInt( nextItem.menuItemDepth(), 10 ) + 1,
+ prevItem = thisItem.prev(),
+ prevItemDepth = parseInt( prevItem.menuItemDepth(), 10 ),
+ prevItemId = prevItem.getItemData()['menu-item-db-id'];
+
+ switch ( dir ) {
+ case 'up':
+ newItemPosition = thisItemPosition - 1;
+
+ // Already at top
+ if ( 0 === thisItemPosition )
+ break;
+
+ // If a sub item is moved to top, shift it to 0 depth
+ if ( 0 === newItemPosition && 0 !== thisItemDepth )
+ thisItem.moveHorizontally( 0, thisItemDepth );
+
+ // If prev item is sub item, shift to match depth
+ if ( 0 !== prevItemDepth )
+ thisItem.moveHorizontally( prevItemDepth, thisItemDepth );
+
+ // Does this item have sub items?
+ if ( thisItemChildren ) {
+ items = thisItem.add( thisItemChildren );
+ // Move the entire block
+ items.detach().insertBefore( menuItems.eq( newItemPosition ) ).updateParentMenuItemDBId();
+ } else {
+ thisItem.detach().insertBefore( menuItems.eq( newItemPosition ) ).updateParentMenuItemDBId();
+ }
+ break;
+ case 'down':
+ // Does this item have sub items?
+ if ( thisItemChildren ) {
+ items = thisItem.add( thisItemChildren ),
+ nextItem = menuItems.eq( items.length + thisItemPosition ),
+ nextItemChildren = 0 !== nextItem.childMenuItems().length;
+
+ if ( nextItemChildren ) {
+ newDepth = parseInt( nextItem.menuItemDepth(), 10 ) + 1;
+ thisItem.moveHorizontally( newDepth, thisItemDepth );
+ }
+
+ // Have we reached the bottom?
+ if ( menuItemsCount === thisItemPosition + items.length )
+ break;
+
+ items.detach().insertAfter( menuItems.eq( thisItemPosition + items.length ) ).updateParentMenuItemDBId();
+ } else {
+ // If next item has sub items, shift depth
+ if ( 0 !== nextItemChildren.length )
+ thisItem.moveHorizontally( nextItemDepth, thisItemDepth );
+
+ // Have we reached the bottom
+ if ( menuItemsCount === thisItemPosition + 1 )
+ break;
+ thisItem.detach().insertAfter( menuItems.eq( thisItemPosition + 1 ) ).updateParentMenuItemDBId();
+ }
+ break;
+ case 'top':
+ // Already at top
+ if ( 0 === thisItemPosition )
+ break;
+ // Does this item have sub items?
+ if ( thisItemChildren ) {
+ items = thisItem.add( thisItemChildren );
+ // Move the entire block
+ items.detach().insertBefore( menuItems.eq( 0 ) ).updateParentMenuItemDBId();
+ } else {
+ thisItem.detach().insertBefore( menuItems.eq( 0 ) ).updateParentMenuItemDBId();
+ }
+ break;
+ case 'left':
+ // As far left as possible
+ if ( 0 === thisItemDepth )
+ break;
+ thisItem.shiftHorizontally( -1 );
+ break;
+ case 'right':
+ // Can't be sub item at top
+ if ( 0 === thisItemPosition )
+ break;
+ // Already sub item of prevItem
+ if ( thisItemData['menu-item-parent-id'] === prevItemId )
+ break;
+ thisItem.shiftHorizontally( 1 );
+ break;
+ }
+ $this.focus();
+ api.registerChange();
+ api.refreshKeyboardAccessibility();
+ api.refreshAdvancedAccessibility();
+ },
+
+ initAccessibility : function() {
+ var menu = $( '#menu-to-edit' );
+
+ api.refreshKeyboardAccessibility();
+ api.refreshAdvancedAccessibility();
+
+ // Refresh the accessibility when the user comes close to the item in any way
+ menu.on( 'mouseenter.refreshAccessibility focus.refreshAccessibility touchstart.refreshAccessibility' , '.menu-item' , function(){
+ api.refreshAdvancedAccessibilityOfItem( $( this ).find( 'a.item-edit' ) );
+ } );
+
+ // We have to update on click as well because we might hover first, change the item, and then click.
+ menu.on( 'click', 'a.item-edit', function() {
+ api.refreshAdvancedAccessibilityOfItem( $( this ) );
+ } );
+
+ // Links for moving items
+ menu.on( 'click', '.menus-move', function () {
+ var $this = $( this ),
+ dir = $this.data( 'dir' );
+
+ if ( 'undefined' !== typeof dir ) {
+ api.moveMenuItem( $( this ).parents( 'li.menu-item' ).find( 'a.item-edit' ), dir );
+ }
+ });
+ },
+
+ /**
+ * refreshAdvancedAccessibilityOfItem( [itemToRefresh] )
+ *
+ * Refreshes advanced accessibility buttons for one menu item.
+ * Shows or hides buttons based on the location of the menu item.
+ *
+ * @param {object} itemToRefresh The menu item that might need its advanced accessibility buttons refreshed
+ */
+ refreshAdvancedAccessibilityOfItem : function( itemToRefresh ) {
+
+ // Only refresh accessibility when necessary
+ if ( true !== $( itemToRefresh ).data( 'needs_accessibility_refresh' ) ) {
+ return;
+ }
+
+ var thisLink, thisLinkText, primaryItems, itemPosition, title,
+ parentItem, parentItemId, parentItemName, subItems,
+ $this = $( itemToRefresh ),
+ menuItem = $this.closest( 'li.menu-item' ).first(),
+ depth = menuItem.menuItemDepth(),
+ isPrimaryMenuItem = ( 0 === depth ),
+ itemName = $this.closest( '.menu-item-handle' ).find( '.menu-item-title' ).text(),
+ position = parseInt( menuItem.index(), 10 ),
+ prevItemDepth = ( isPrimaryMenuItem ) ? depth : parseInt( depth - 1, 10 ),
+ prevItemNameLeft = menuItem.prevAll('.menu-item-depth-' + prevItemDepth).first().find( '.menu-item-title' ).text(),
+ prevItemNameRight = menuItem.prevAll('.menu-item-depth-' + depth).first().find( '.menu-item-title' ).text(),
+ totalMenuItems = $('#menu-to-edit li').length,
+ hasSameDepthSibling = menuItem.nextAll( '.menu-item-depth-' + depth ).length;
+
+ menuItem.find( '.field-move' ).toggle( totalMenuItems > 1 );
+
+ // Where can they move this menu item?
+ if ( 0 !== position ) {
+ thisLink = menuItem.find( '.menus-move-up' );
+ thisLink.attr( 'aria-label', menus.moveUp ).css( 'display', 'inline' );
+ }
+
+ if ( 0 !== position && isPrimaryMenuItem ) {
+ thisLink = menuItem.find( '.menus-move-top' );
+ thisLink.attr( 'aria-label', menus.moveToTop ).css( 'display', 'inline' );
+ }
+
+ if ( position + 1 !== totalMenuItems && 0 !== position ) {
+ thisLink = menuItem.find( '.menus-move-down' );
+ thisLink.attr( 'aria-label', menus.moveDown ).css( 'display', 'inline' );
+ }
+
+ if ( 0 === position && 0 !== hasSameDepthSibling ) {
+ thisLink = menuItem.find( '.menus-move-down' );
+ thisLink.attr( 'aria-label', menus.moveDown ).css( 'display', 'inline' );
+ }
+
+ if ( ! isPrimaryMenuItem ) {
+ thisLink = menuItem.find( '.menus-move-left' ),
+ thisLinkText = menus.outFrom.replace( '%s', prevItemNameLeft );
+ thisLink.attr( 'aria-label', menus.moveOutFrom.replace( '%s', prevItemNameLeft ) ).text( thisLinkText ).css( 'display', 'inline' );
+ }
+
+ if ( 0 !== position ) {
+ if ( menuItem.find( '.menu-item-data-parent-id' ).val() !== menuItem.prev().find( '.menu-item-data-db-id' ).val() ) {
+ thisLink = menuItem.find( '.menus-move-right' ),
+ thisLinkText = menus.under.replace( '%s', prevItemNameRight );
+ thisLink.attr( 'aria-label', menus.moveUnder.replace( '%s', prevItemNameRight ) ).text( thisLinkText ).css( 'display', 'inline' );
+ }
+ }
+
+ if ( isPrimaryMenuItem ) {
+ primaryItems = $( '.menu-item-depth-0' ),
+ itemPosition = primaryItems.index( menuItem ) + 1,
+ totalMenuItems = primaryItems.length,
+
+ // String together help text for primary menu items
+ title = menus.menuFocus.replace( '%1$s', itemName ).replace( '%2$d', itemPosition ).replace( '%3$d', totalMenuItems );
+ } else {
+ parentItem = menuItem.prevAll( '.menu-item-depth-' + parseInt( depth - 1, 10 ) ).first(),
+ parentItemId = parentItem.find( '.menu-item-data-db-id' ).val(),
+ parentItemName = parentItem.find( '.menu-item-title' ).text(),
+ subItems = $( '.menu-item .menu-item-data-parent-id[value="' + parentItemId + '"]' ),
+ itemPosition = $( subItems.parents('.menu-item').get().reverse() ).index( menuItem ) + 1;
+
+ // String together help text for sub menu items
+ title = menus.subMenuFocus.replace( '%1$s', itemName ).replace( '%2$d', itemPosition ).replace( '%3$s', parentItemName );
+ }
+
+ $this.attr( 'aria-label', title );
+
+ // Mark this item's accessibility as refreshed
+ $this.data( 'needs_accessibility_refresh', false );
+ },
+
+ /**
+ * refreshAdvancedAccessibility
+ *
+ * Hides all advanced accessibility buttons and marks them for refreshing.
+ */
+ refreshAdvancedAccessibility : function() {
+
+ // Hide all the move buttons by default.
+ $( '.menu-item-settings .field-move .menus-move' ).hide();
+
+ // Mark all menu items as unprocessed
+ $( 'a.item-edit' ).data( 'needs_accessibility_refresh', true );
+
+ // All open items have to be refreshed or they will show no links
+ $( '.menu-item-edit-active a.item-edit' ).each( function() {
+ api.refreshAdvancedAccessibilityOfItem( this );
+ } );
+ },
+
+ refreshKeyboardAccessibility : function() {
+ $( 'a.item-edit' ).off( 'focus' ).on( 'focus', function(){
+ $(this).off( 'keydown' ).on( 'keydown', function(e){
+
+ var arrows,
+ $this = $( this ),
+ thisItem = $this.parents( 'li.menu-item' ),
+ thisItemData = thisItem.getItemData();
+
+ // Bail if it's not an arrow key
+ if ( 37 != e.which && 38 != e.which && 39 != e.which && 40 != e.which )
+ return;
+
+ // Avoid multiple keydown events
+ $this.off('keydown');
+
+ // Bail if there is only one menu item
+ if ( 1 === $('#menu-to-edit li').length )
+ return;
+
+ // If RTL, swap left/right arrows
+ arrows = { '38': 'up', '40': 'down', '37': 'left', '39': 'right' };
+ if ( $('body').hasClass('rtl') )
+ arrows = { '38' : 'up', '40' : 'down', '39' : 'left', '37' : 'right' };
+
+ switch ( arrows[e.which] ) {
+ case 'up':
+ api.moveMenuItem( $this, 'up' );
+ break;
+ case 'down':
+ api.moveMenuItem( $this, 'down' );
+ break;
+ case 'left':
+ api.moveMenuItem( $this, 'left' );
+ break;
+ case 'right':
+ api.moveMenuItem( $this, 'right' );
+ break;
+ }
+ // Put focus back on same menu item
+ $( '#edit-' + thisItemData['menu-item-db-id'] ).focus();
+ return false;
+ });
+ });
+ },
+
+ initPreviewing : function() {
+ // Update the item handle title when the navigation label is changed.
+ $( '#menu-to-edit' ).on( 'change input', '.edit-menu-item-title', function(e) {
+ var input = $( e.currentTarget ), title, titleEl;
+ title = input.val();
+ titleEl = input.closest( '.menu-item' ).find( '.menu-item-title' );
+ // Don't update to empty title.
+ if ( title ) {
+ titleEl.text( title ).removeClass( 'no-title' );
+ } else {
+ titleEl.text( navMenuL10n.untitled ).addClass( 'no-title' );
+ }
+ } );
+ },
+
+ initToggles : function() {
+ // init postboxes
+ postboxes.add_postbox_toggles('nav-menus');
+
+ // adjust columns functions for menus UI
+ columns.useCheckboxesForHidden();
+ columns.checked = function(field) {
+ $('.field-' + field).removeClass('hidden-field');
+ };
+ columns.unchecked = function(field) {
+ $('.field-' + field).addClass('hidden-field');
+ };
+ // hide fields
+ api.menuList.hideAdvancedMenuItemFields();
+
+ $('.hide-postbox-tog').click(function () {
+ var hidden = $( '.accordion-container li.accordion-section' ).filter(':hidden').map(function() { return this.id; }).get().join(',');
+ $.post(ajaxurl, {
+ action: 'closed-postboxes',
+ hidden: hidden,
+ closedpostboxesnonce: jQuery('#closedpostboxesnonce').val(),
+ page: 'nav-menus'
+ });
+ });
+ },
+
+ initSortables : function() {
+ var currentDepth = 0, originalDepth, minDepth, maxDepth,
+ prev, next, prevBottom, nextThreshold, helperHeight, transport,
+ menuEdge = api.menuList.offset().left,
+ body = $('body'), maxChildDepth,
+ menuMaxDepth = initialMenuMaxDepth();
+
+ if( 0 !== $( '#menu-to-edit li' ).length )
+ $( '.drag-instructions' ).show();
+
+ // Use the right edge if RTL.
+ menuEdge += api.isRTL ? api.menuList.width() : 0;
+
+ api.menuList.sortable({
+ handle: '.menu-item-handle',
+ placeholder: 'sortable-placeholder',
+ items: api.options.sortableItems,
+ start: function(e, ui) {
+ var height, width, parent, children, tempHolder;
+
+ // handle placement for rtl orientation
+ if ( api.isRTL )
+ ui.item[0].style.right = 'auto';
+
+ transport = ui.item.children('.menu-item-transport');
+
+ // Set depths. currentDepth must be set before children are located.
+ originalDepth = ui.item.menuItemDepth();
+ updateCurrentDepth(ui, originalDepth);
+
+ // Attach child elements to parent
+ // Skip the placeholder
+ parent = ( ui.item.next()[0] == ui.placeholder[0] ) ? ui.item.next() : ui.item;
+ children = parent.childMenuItems();
+ transport.append( children );
+
+ // Update the height of the placeholder to match the moving item.
+ height = transport.outerHeight();
+ // If there are children, account for distance between top of children and parent
+ height += ( height > 0 ) ? (ui.placeholder.css('margin-top').slice(0, -2) * 1) : 0;
+ height += ui.helper.outerHeight();
+ helperHeight = height;
+ height -= 2; // Subtract 2 for borders
+ ui.placeholder.height(height);
+
+ // Update the width of the placeholder to match the moving item.
+ maxChildDepth = originalDepth;
+ children.each(function(){
+ var depth = $(this).menuItemDepth();
+ maxChildDepth = (depth > maxChildDepth) ? depth : maxChildDepth;
+ });
+ width = ui.helper.find('.menu-item-handle').outerWidth(); // Get original width
+ width += api.depthToPx(maxChildDepth - originalDepth); // Account for children
+ width -= 2; // Subtract 2 for borders
+ ui.placeholder.width(width);
+
+ // Update the list of menu items.
+ tempHolder = ui.placeholder.next( '.menu-item' );
+ tempHolder.css( 'margin-top', helperHeight + 'px' ); // Set the margin to absorb the placeholder
+ ui.placeholder.detach(); // detach or jQuery UI will think the placeholder is a menu item
+ $(this).sortable( 'refresh' ); // The children aren't sortable. We should let jQ UI know.
+ ui.item.after( ui.placeholder ); // reattach the placeholder.
+ tempHolder.css('margin-top', 0); // reset the margin
+
+ // Now that the element is complete, we can update...
+ updateSharedVars(ui);
+ },
+ stop: function(e, ui) {
+ var children, subMenuTitle,
+ depthChange = currentDepth - originalDepth;
+
+ // Return child elements to the list
+ children = transport.children().insertAfter(ui.item);
+
+ // Add "sub menu" description
+ subMenuTitle = ui.item.find( '.item-title .is-submenu' );
+ if ( 0 < currentDepth )
+ subMenuTitle.show();
+ else
+ subMenuTitle.hide();
+
+ // Update depth classes
+ if ( 0 !== depthChange ) {
+ ui.item.updateDepthClass( currentDepth );
+ children.shiftDepthClass( depthChange );
+ updateMenuMaxDepth( depthChange );
+ }
+ // Register a change
+ api.registerChange();
+ // Update the item data.
+ ui.item.updateParentMenuItemDBId();
+
+ // address sortable's incorrectly-calculated top in opera
+ ui.item[0].style.top = 0;
+
+ // handle drop placement for rtl orientation
+ if ( api.isRTL ) {
+ ui.item[0].style.left = 'auto';
+ ui.item[0].style.right = 0;
+ }
+
+ api.refreshKeyboardAccessibility();
+ api.refreshAdvancedAccessibility();
+ },
+ change: function(e, ui) {
+ // Make sure the placeholder is inside the menu.
+ // Otherwise fix it, or we're in trouble.
+ if( ! ui.placeholder.parent().hasClass('menu') )
+ (prev.length) ? prev.after( ui.placeholder ) : api.menuList.prepend( ui.placeholder );
+
+ updateSharedVars(ui);
+ },
+ sort: function(e, ui) {
+ var offset = ui.helper.offset(),
+ edge = api.isRTL ? offset.left + ui.helper.width() : offset.left,
+ depth = api.negateIfRTL * api.pxToDepth( edge - menuEdge );
+
+ // Check and correct if depth is not within range.
+ // Also, if the dragged element is dragged upwards over
+ // an item, shift the placeholder to a child position.
+ if ( depth > maxDepth || offset.top < ( prevBottom - api.options.targetTolerance ) ) {
+ depth = maxDepth;
+ } else if ( depth < minDepth ) {
+ depth = minDepth;
+ }
+
+ if( depth != currentDepth )
+ updateCurrentDepth(ui, depth);
+
+ // If we overlap the next element, manually shift downwards
+ if( nextThreshold && offset.top + helperHeight > nextThreshold ) {
+ next.after( ui.placeholder );
+ updateSharedVars( ui );
+ $( this ).sortable( 'refreshPositions' );
+ }
+ }
+ });
+
+ function updateSharedVars(ui) {
+ var depth;
+
+ prev = ui.placeholder.prev( '.menu-item' );
+ next = ui.placeholder.next( '.menu-item' );
+
+ // Make sure we don't select the moving item.
+ if( prev[0] == ui.item[0] ) prev = prev.prev( '.menu-item' );
+ if( next[0] == ui.item[0] ) next = next.next( '.menu-item' );
+
+ prevBottom = (prev.length) ? prev.offset().top + prev.height() : 0;
+ nextThreshold = (next.length) ? next.offset().top + next.height() / 3 : 0;
+ minDepth = (next.length) ? next.menuItemDepth() : 0;
+
+ if( prev.length )
+ maxDepth = ( (depth = prev.menuItemDepth() + 1) > api.options.globalMaxDepth ) ? api.options.globalMaxDepth : depth;
+ else
+ maxDepth = 0;
+ }
+
+ function updateCurrentDepth(ui, depth) {
+ ui.placeholder.updateDepthClass( depth, currentDepth );
+ currentDepth = depth;
+ }
+
+ function initialMenuMaxDepth() {
+ if( ! body[0].className ) return 0;
+ var match = body[0].className.match(/menu-max-depth-(\d+)/);
+ return match && match[1] ? parseInt( match[1], 10 ) : 0;
+ }
+
+ function updateMenuMaxDepth( depthChange ) {
+ var depth, newDepth = menuMaxDepth;
+ if ( depthChange === 0 ) {
+ return;
+ } else if ( depthChange > 0 ) {
+ depth = maxChildDepth + depthChange;
+ if( depth > menuMaxDepth )
+ newDepth = depth;
+ } else if ( depthChange < 0 && maxChildDepth == menuMaxDepth ) {
+ while( ! $('.menu-item-depth-' + newDepth, api.menuList).length && newDepth > 0 )
+ newDepth--;
+ }
+ // Update the depth class.
+ body.removeClass( 'menu-max-depth-' + menuMaxDepth ).addClass( 'menu-max-depth-' + newDepth );
+ menuMaxDepth = newDepth;
+ }
+ },
+
+ initManageLocations : function () {
+ $('#menu-locations-wrap form').submit(function(){
+ window.onbeforeunload = null;
+ });
+ $('.menu-location-menus select').on('change', function () {
+ var editLink = $(this).closest('tr').find('.locations-edit-menu-link');
+ if ($(this).find('option:selected').data('orig'))
+ editLink.show();
+ else
+ editLink.hide();
+ });
+ },
+
+ attachMenuEditListeners : function() {
+ var that = this;
+ $('#update-nav-menu').bind('click', function(e) {
+ if ( e.target && e.target.className ) {
+ if ( -1 != e.target.className.indexOf('item-edit') ) {
+ return that.eventOnClickEditLink(e.target);
+ } else if ( -1 != e.target.className.indexOf('menu-save') ) {
+ return that.eventOnClickMenuSave(e.target);
+ } else if ( -1 != e.target.className.indexOf('menu-delete') ) {
+ return that.eventOnClickMenuDelete(e.target);
+ } else if ( -1 != e.target.className.indexOf('item-delete') ) {
+ return that.eventOnClickMenuItemDelete(e.target);
+ } else if ( -1 != e.target.className.indexOf('item-cancel') ) {
+ return that.eventOnClickCancelLink(e.target);
+ }
+ }
+ });
+
+ $( '#menu-name' ).on( 'input', _.debounce( function () {
+ var menuName = $( document.getElementById( 'menu-name' ) ),
+ menuNameVal = menuName.val();
+
+ if ( ! menuNameVal || ! menuNameVal.replace( /\s+/, '' ) ) {
+ // Add warning for invalid menu name.
+ menuName.parent().addClass( 'form-invalid' );
+ } else {
+ // Remove warning for valid menu name.
+ menuName.parent().removeClass( 'form-invalid' );
+ }
+ }, 500 ) );
+
+ $('#add-custom-links input[type="text"]').keypress(function(e){
+ $('#customlinkdiv').removeClass('form-invalid');
+
+ if ( e.keyCode === 13 ) {
+ e.preventDefault();
+ $( '#submit-customlinkdiv' ).click();
+ }
+ });
+ },
+
+ attachMenuSaveSubmitListeners : function() {
+ /*
+ * When a navigation menu is saved, store a JSON representation of all form data
+ * in a single input to avoid PHP `max_input_vars` limitations. See #14134.
+ */
+ $( '#update-nav-menu' ).submit( function() {
+ var navMenuData = $( '#update-nav-menu' ).serializeArray();
+ $( '[name="nav-menu-data"]' ).val( JSON.stringify( navMenuData ) );
+ });
+ },
+
+ attachThemeLocationsListeners : function() {
+ var loc = $('#nav-menu-theme-locations'), params = {};
+ params.action = 'menu-locations-save';
+ params['menu-settings-column-nonce'] = $('#menu-settings-column-nonce').val();
+ loc.find('input[type="submit"]').click(function() {
+ loc.find('select').each(function() {
+ params[this.name] = $(this).val();
+ });
+ loc.find( '.spinner' ).addClass( 'is-active' );
+ $.post( ajaxurl, params, function() {
+ loc.find( '.spinner' ).removeClass( 'is-active' );
+ });
+ return false;
+ });
+ },
+
+ attachQuickSearchListeners : function() {
+ var searchTimer;
+
+ // Prevent form submission.
+ $( '#nav-menu-meta' ).on( 'submit', function( event ) {
+ event.preventDefault();
+ });
+
+ $( '#nav-menu-meta' ).on( 'input', '.quick-search', function() {
+ var $this = $( this );
+
+ $this.attr( 'autocomplete', 'off' );
+
+ if ( searchTimer ) {
+ clearTimeout( searchTimer );
+ }
+
+ searchTimer = setTimeout( function() {
+ api.updateQuickSearchResults( $this );
+ }, 500 );
+ }).on( 'blur', '.quick-search', function() {
+ api.lastSearch = '';
+ });
+ },
+
+ updateQuickSearchResults : function(input) {
+ var panel, params,
+ minSearchLength = 2,
+ q = input.val();
+
+ /*
+ * Minimum characters for a search. Also avoid a new AJAX search when
+ * the pressed key (e.g. arrows) doesn't change the searched term.
+ */
+ if ( q.length < minSearchLength || api.lastSearch == q ) {
+ return;
+ }
+
+ api.lastSearch = q;
+
+ panel = input.parents('.tabs-panel');
+ params = {
+ 'action': 'menu-quick-search',
+ 'response-format': 'markup',
+ 'menu': $('#menu').val(),
+ 'menu-settings-column-nonce': $('#menu-settings-column-nonce').val(),
+ 'q': q,
+ 'type': input.attr('name')
+ };
+
+ $( '.spinner', panel ).addClass( 'is-active' );
+
+ $.post( ajaxurl, params, function(menuMarkup) {
+ api.processQuickSearchQueryResponse(menuMarkup, params, panel);
+ });
+ },
+
+ addCustomLink : function( processMethod ) {
+ var url = $('#custom-menu-item-url').val().trim(),
+ label = $('#custom-menu-item-name').val();
+
+ processMethod = processMethod || api.addMenuItemToBottom;
+
+ if ( '' === url || 'http://' == url ) {
+ $('#customlinkdiv').addClass('form-invalid');
+ return false;
+ }
+
+ // Show the ajax spinner
+ $( '.customlinkdiv .spinner' ).addClass( 'is-active' );
+ this.addLinkToMenu( url, label, processMethod, function() {
+ // Remove the ajax spinner
+ $( '.customlinkdiv .spinner' ).removeClass( 'is-active' );
+ // Set custom link form back to defaults
+ $('#custom-menu-item-name').val('').blur();
+ $('#custom-menu-item-url').val('http://');
+ });
+ },
+
+ addLinkToMenu : function(url, label, processMethod, callback) {
+ processMethod = processMethod || api.addMenuItemToBottom;
+ callback = callback || function(){};
+
+ api.addItemToMenu({
+ '-1': {
+ 'menu-item-type': 'custom',
+ 'menu-item-url': url,
+ 'menu-item-title': label
+ }
+ }, processMethod, callback);
+ },
+
+ addItemToMenu : function(menuItem, processMethod, callback) {
+ var menu = $('#menu').val(),
+ nonce = $('#menu-settings-column-nonce').val(),
+ params;
+
+ processMethod = processMethod || function(){};
+ callback = callback || function(){};
+
+ params = {
+ 'action': 'add-menu-item',
+ 'menu': menu,
+ 'menu-settings-column-nonce': nonce,
+ 'menu-item': menuItem
+ };
+
+ $.post( ajaxurl, params, function(menuMarkup) {
+ var ins = $('#menu-instructions');
+
+ menuMarkup = $.trim( menuMarkup ); // Trim leading whitespaces
+ processMethod(menuMarkup, params);
+
+ // Make it stand out a bit more visually, by adding a fadeIn
+ $( 'li.pending' ).hide().fadeIn('slow');
+ $( '.drag-instructions' ).show();
+ if( ! ins.hasClass( 'menu-instructions-inactive' ) && ins.siblings().length )
+ ins.addClass( 'menu-instructions-inactive' );
+
+ callback();
+ });
+ },
+
+ /**
+ * Process the add menu item request response into menu list item. Appends to menu.
+ *
+ * @param {string} menuMarkup The text server response of menu item markup.
+ *
+ * @fires document#menu-item-added Passes menuMarkup as a jQuery object.
+ */
+ addMenuItemToBottom : function( menuMarkup ) {
+ var $menuMarkup = $( menuMarkup );
+ $menuMarkup.hideAdvancedMenuItemFields().appendTo( api.targetList );
+ api.refreshKeyboardAccessibility();
+ api.refreshAdvancedAccessibility();
+ $( document ).trigger( 'menu-item-added', [ $menuMarkup ] );
+ },
+
+ /**
+ * Process the add menu item request response into menu list item. Prepends to menu.
+ *
+ * @param {string} menuMarkup The text server response of menu item markup.
+ *
+ * @fires document#menu-item-added Passes menuMarkup as a jQuery object.
+ */
+ addMenuItemToTop : function( menuMarkup ) {
+ var $menuMarkup = $( menuMarkup );
+ $menuMarkup.hideAdvancedMenuItemFields().prependTo( api.targetList );
+ api.refreshKeyboardAccessibility();
+ api.refreshAdvancedAccessibility();
+ $( document ).trigger( 'menu-item-added', [ $menuMarkup ] );
+ },
+
+ attachUnsavedChangesListener : function() {
+ $('#menu-management input, #menu-management select, #menu-management, #menu-management textarea, .menu-location-menus select').change(function(){
+ api.registerChange();
+ });
+
+ if ( 0 !== $('#menu-to-edit').length || 0 !== $('.menu-location-menus select').length ) {
+ window.onbeforeunload = function(){
+ if ( api.menusChanged )
+ return navMenuL10n.saveAlert;
+ };
+ } else {
+ // Make the post boxes read-only, as they can't be used yet
+ $( '#menu-settings-column' ).find( 'input,select' ).end().find( 'a' ).attr( 'href', '#' ).unbind( 'click' );
+ }
+ },
+
+ registerChange : function() {
+ api.menusChanged = true;
+ },
+
+ attachTabsPanelListeners : function() {
+ $('#menu-settings-column').bind('click', function(e) {
+ var selectAreaMatch, panelId, wrapper, items,
+ target = $(e.target);
+
+ if ( target.hasClass('nav-tab-link') ) {
+
+ panelId = target.data( 'type' );
+
+ wrapper = target.parents('.accordion-section-content').first();
+
+ // upon changing tabs, we want to uncheck all checkboxes
+ $('input', wrapper).removeAttr('checked');
+
+ $('.tabs-panel-active', wrapper).removeClass('tabs-panel-active').addClass('tabs-panel-inactive');
+ $('#' + panelId, wrapper).removeClass('tabs-panel-inactive').addClass('tabs-panel-active');
+
+ $('.tabs', wrapper).removeClass('tabs');
+ target.parent().addClass('tabs');
+
+ // select the search bar
+ $('.quick-search', wrapper).focus();
+
+ // Hide controls in the search tab if no items found.
+ if ( ! wrapper.find( '.tabs-panel-active .menu-item-title' ).length ) {
+ wrapper.addClass( 'has-no-menu-item' );
+ } else {
+ wrapper.removeClass( 'has-no-menu-item' );
+ }
+
+ e.preventDefault();
+ } else if ( target.hasClass('select-all') ) {
+ selectAreaMatch = /#(.*)$/.exec(e.target.href);
+ if ( selectAreaMatch && selectAreaMatch[1] ) {
+ items = $('#' + selectAreaMatch[1] + ' .tabs-panel-active .menu-item-title input');
+ if( items.length === items.filter(':checked').length )
+ items.removeAttr('checked');
+ else
+ items.prop('checked', true);
+ return false;
+ }
+ } else if ( target.hasClass('submit-add-to-menu') ) {
+ api.registerChange();
+
+ if ( e.target.id && 'submit-customlinkdiv' == e.target.id )
+ api.addCustomLink( api.addMenuItemToBottom );
+ else if ( e.target.id && -1 != e.target.id.indexOf('submit-') )
+ $('#' + e.target.id.replace(/submit-/, '')).addSelectedToMenu( api.addMenuItemToBottom );
+ return false;
+ }
+ });
+
+ /*
+ * Delegate the `click` event and attach it just to the pagination
+ * links thus excluding the current page `<span>`. See ticket #35577.
+ */
+ $( '#nav-menu-meta' ).on( 'click', 'a.page-numbers', function() {
+ var $container = $( this ).closest( '.inside' );
+
+ $.post( ajaxurl, this.href.replace( /.*\?/, '' ).replace( /action=([^&]*)/, '' ) + '&action=menu-get-metabox',
+ function( resp ) {
+ var metaBoxData = $.parseJSON( resp ),
+ toReplace;
+
+ if ( -1 === resp.indexOf( 'replace-id' ) ) {
+ return;
+ }
+
+ // Get the post type menu meta box to update.
+ toReplace = document.getElementById( metaBoxData['replace-id'] );
+
+ if ( ! metaBoxData.markup || ! toReplace ) {
+ return;
+ }
+
+ // Update the post type menu meta box with new content from the response.
+ $container.html( metaBoxData.markup );
+ }
+ );
+
+ return false;
+ });
+ },
+
+ eventOnClickEditLink : function(clickedEl) {
+ var settings, item,
+ matchedSection = /#(.*)$/.exec(clickedEl.href);
+ if ( matchedSection && matchedSection[1] ) {
+ settings = $('#'+matchedSection[1]);
+ item = settings.parent();
+ if( 0 !== item.length ) {
+ if( item.hasClass('menu-item-edit-inactive') ) {
+ if( ! settings.data('menu-item-data') ) {
+ settings.data( 'menu-item-data', settings.getItemData() );
+ }
+ settings.slideDown('fast');
+ item.removeClass('menu-item-edit-inactive')
+ .addClass('menu-item-edit-active');
+ } else {
+ settings.slideUp('fast');
+ item.removeClass('menu-item-edit-active')
+ .addClass('menu-item-edit-inactive');
+ }
+ return false;
+ }
+ }
+ },
+
+ eventOnClickCancelLink : function(clickedEl) {
+ var settings = $( clickedEl ).closest( '.menu-item-settings' ),
+ thisMenuItem = $( clickedEl ).closest( '.menu-item' );
+ thisMenuItem.removeClass('menu-item-edit-active').addClass('menu-item-edit-inactive');
+ settings.setItemData( settings.data('menu-item-data') ).hide();
+ return false;
+ },
+
+ eventOnClickMenuSave : function() {
+ var locs = '',
+ menuName = $('#menu-name'),
+ menuNameVal = menuName.val();
+ // Cancel and warn if invalid menu name
+ if ( ! menuNameVal || ! menuNameVal.replace( /\s+/, '' ) ) {
+ menuName.parent().addClass( 'form-invalid' );
+ return false;
+ }
+ // Copy menu theme locations
+ $('#nav-menu-theme-locations select').each(function() {
+ locs += '<input type="hidden" name="' + this.name + '" value="' + $(this).val() + '" />';
+ });
+ $('#update-nav-menu').append( locs );
+ // Update menu item position data
+ api.menuList.find('.menu-item-data-position').val( function(index) { return index + 1; } );
+ window.onbeforeunload = null;
+
+ return true;
+ },
+
+ eventOnClickMenuDelete : function() {
+ // Delete warning AYS
+ if ( window.confirm( navMenuL10n.warnDeleteMenu ) ) {
+ window.onbeforeunload = null;
+ return true;
+ }
+ return false;
+ },
+
+ eventOnClickMenuItemDelete : function(clickedEl) {
+ var itemID = parseInt(clickedEl.id.replace('delete-', ''), 10);
+ api.removeMenuItem( $('#menu-item-' + itemID) );
+ api.registerChange();
+ return false;
+ },
+
+ /**
+ * Process the quick search response into a search result
+ *
+ * @param string resp The server response to the query.
+ * @param object req The request arguments.
+ * @param jQuery panel The tabs panel we're searching in.
+ */
+ processQuickSearchQueryResponse : function(resp, req, panel) {
+ var matched, newID,
+ takenIDs = {},
+ form = document.getElementById('nav-menu-meta'),
+ pattern = /menu-item[(\[^]\]*/,
+ $items = $('<div>').html(resp).find('li'),
+ wrapper = panel.closest( '.accordion-section-content' ),
+ $item;
+
+ if( ! $items.length ) {
+ $('.categorychecklist', panel).html( '<li><p>' + navMenuL10n.noResultsFound + '</p></li>' );
+ $( '.spinner', panel ).removeClass( 'is-active' );
+ wrapper.addClass( 'has-no-menu-item' );
+ return;
+ }
+
+ $items.each(function(){
+ $item = $(this);
+
+ // make a unique DB ID number
+ matched = pattern.exec($item.html());
+
+ if ( matched && matched[1] ) {
+ newID = matched[1];
+ while( form.elements['menu-item[' + newID + '][menu-item-type]'] || takenIDs[ newID ] ) {
+ newID--;
+ }
+
+ takenIDs[newID] = true;
+ if ( newID != matched[1] ) {
+ $item.html( $item.html().replace(new RegExp(
+ 'menu-item\\[' + matched[1] + '\\]', 'g'),
+ 'menu-item[' + newID + ']'
+ ) );
+ }
+ }
+ });
+
+ $('.categorychecklist', panel).html( $items );
+ $( '.spinner', panel ).removeClass( 'is-active' );
+ wrapper.removeClass( 'has-no-menu-item' );
+ },
+
+ /**
+ * Remove a menu item.
+ * @param {object} el The element to be removed as a jQuery object.
+ *
+ * @fires document#menu-removing-item Passes the element to be removed.
+ */
+ removeMenuItem : function(el) {
+ var children = el.childMenuItems();
+
+ $( document ).trigger( 'menu-removing-item', [ el ] );
+ el.addClass('deleting').animate({
+ opacity : 0,
+ height: 0
+ }, 350, function() {
+ var ins = $('#menu-instructions');
+ el.remove();
+ children.shiftDepthClass( -1 ).updateParentMenuItemDBId();
+ if ( 0 === $( '#menu-to-edit li' ).length ) {
+ $( '.drag-instructions' ).hide();
+ ins.removeClass( 'menu-instructions-inactive' );
+ }
+ api.refreshAdvancedAccessibility();
+ });
+ },
+
+ depthToPx : function(depth) {
+ return depth * api.options.menuItemDepthPerLevel;
+ },
+
+ pxToDepth : function(px) {
+ return Math.floor(px / api.options.menuItemDepthPerLevel);
+ }
+
+ };
+
+ $(document).ready(function(){ wpNavMenu.init(); });
+
+})(jQuery);
diff --git a/www/crm/wp-admin/js/nav-menu.min.js b/www/crm/wp-admin/js/nav-menu.min.js
new file mode 100644
index 00000000..b8e8f56a
--- /dev/null
+++ b/www/crm/wp-admin/js/nav-menu.min.js
@@ -0,0 +1 @@
+!function(a){var b;b=window.wpNavMenu={options:{menuItemDepthPerLevel:30,globalMaxDepth:11,sortableItems:"> *",targetTolerance:0},menuList:void 0,targetList:void 0,menusChanged:!1,isRTL:!("undefined"==typeof isRtl||!isRtl),negateIfRTL:"undefined"!=typeof isRtl&&isRtl?-1:1,lastSearch:"",init:function(){b.menuList=a("#menu-to-edit"),b.targetList=b.menuList,this.jQueryExtensions(),this.attachMenuEditListeners(),this.attachQuickSearchListeners(),this.attachThemeLocationsListeners(),this.attachMenuSaveSubmitListeners(),this.attachTabsPanelListeners(),this.attachUnsavedChangesListener(),b.menuList.length&&this.initSortables(),menus.oneThemeLocationNoMenus&&a("#posttype-page").addSelectedToMenu(b.addMenuItemToBottom),this.initManageLocations(),this.initAccessibility(),this.initToggles(),this.initPreviewing()},jQueryExtensions:function(){a.fn.extend({menuItemDepth:function(){var a=b.isRTL?this.eq(0).css("margin-right"):this.eq(0).css("margin-left");return b.pxToDepth(a&&-1!=a.indexOf("px")?a.slice(0,-2):0)},updateDepthClass:function(b,c){return this.each(function(){var d=a(this);c=c||d.menuItemDepth(),a(this).removeClass("menu-item-depth-"+c).addClass("menu-item-depth-"+b)})},shiftDepthClass:function(b){return this.each(function(){var c=a(this),d=c.menuItemDepth(),e=d+b;c.removeClass("menu-item-depth-"+d).addClass("menu-item-depth-"+e),0===e&&c.find(".is-submenu").hide()})},childMenuItems:function(){var b=a();return this.each(function(){for(var c=a(this),d=c.menuItemDepth(),e=c.next(".menu-item");e.length&&e.menuItemDepth()>d;)b=b.add(e),e=e.next(".menu-item")}),b},shiftHorizontally:function(b){return this.each(function(){var c=a(this),d=c.menuItemDepth(),e=d+b;c.moveHorizontally(e,d)})},moveHorizontally:function(b,c){return this.each(function(){var d=a(this),e=d.childMenuItems(),f=b-c,g=d.find(".is-submenu");d.updateDepthClass(b,c).updateParentMenuItemDBId(),e&&e.each(function(){var b=a(this),c=b.menuItemDepth(),d=c+f;b.updateDepthClass(d,c).updateParentMenuItemDBId()}),0===b?g.hide():g.show()})},updateParentMenuItemDBId:function(){return this.each(function(){var b=a(this),c=b.find(".menu-item-data-parent-id"),d=parseInt(b.menuItemDepth(),10),e=d-1,f=b.prevAll(".menu-item-depth-"+e).first();0===d?c.val(0):c.val(f.find(".menu-item-data-db-id").val())})},hideAdvancedMenuItemFields:function(){return this.each(function(){var b=a(this);a(".hide-column-tog").not(":checked").each(function(){b.find(".field-"+a(this).val()).addClass("hidden-field")})})},addSelectedToMenu:function(c){return 0!==a("#menu-to-edit").length&&this.each(function(){var d=a(this),e={},f=menus.oneThemeLocationNoMenus&&0===d.find(".tabs-panel-active .categorychecklist li input:checked").length?d.find('#page-all li input[type="checkbox"]'):d.find(".tabs-panel-active .categorychecklist li input:checked"),g=/menu-item\[([^\]]*)/;return c=c||b.addMenuItemToBottom,!!f.length&&(d.find(".button-controls .spinner").addClass("is-active"),a(f).each(function(){var d=a(this),f=g.exec(d.attr("name")),h="undefined"==typeof f[1]?0:parseInt(f[1],10);this.className&&-1!=this.className.indexOf("add-to-top")&&(c=b.addMenuItemToTop),e[h]=d.closest("li").getItemData("add-menu-item",h)}),void b.addItemToMenu(e,c,function(){f.removeAttr("checked"),d.find(".button-controls .spinner").removeClass("is-active")}))})},getItemData:function(a,b){a=a||"menu-item";var c,d={},e=["menu-item-db-id","menu-item-object-id","menu-item-object","menu-item-parent-id","menu-item-position","menu-item-type","menu-item-title","menu-item-url","menu-item-description","menu-item-attr-title","menu-item-target","menu-item-classes","menu-item-xfn"];return b||"menu-item"!=a||(b=this.find(".menu-item-data-db-id").val()),b?(this.find("input").each(function(){var f;for(c=e.length;c--;)"menu-item"==a?f=e[c]+"["+b+"]":"add-menu-item"==a&&(f="menu-item["+b+"]["+e[c]+"]"),this.name&&f==this.name&&(d[e[c]]=this.value)}),d):d},setItemData:function(b,c,d){return c=c||"menu-item",d||"menu-item"!=c||(d=a(".menu-item-data-db-id",this).val()),d?(this.find("input").each(function(){var e,f=a(this);a.each(b,function(a,b){"menu-item"==c?e=a+"["+d+"]":"add-menu-item"==c&&(e="menu-item["+d+"]["+a+"]"),e==f.attr("name")&&f.val(b)})}),this):this}})},countMenuItems:function(b){return a(".menu-item-depth-"+b).length},moveMenuItem:function(c,d){var e,f,g,h=a("#menu-to-edit li"),i=h.length,j=c.parents("li.menu-item"),k=j.childMenuItems(),l=j.getItemData(),m=parseInt(j.menuItemDepth(),10),n=parseInt(j.index(),10),o=j.next(),p=o.childMenuItems(),q=parseInt(o.menuItemDepth(),10)+1,r=j.prev(),s=parseInt(r.menuItemDepth(),10),t=r.getItemData()["menu-item-db-id"];switch(d){case"up":if(f=n-1,0===n)break;0===f&&0!==m&&j.moveHorizontally(0,m),0!==s&&j.moveHorizontally(s,m),k?(e=j.add(k),e.detach().insertBefore(h.eq(f)).updateParentMenuItemDBId()):j.detach().insertBefore(h.eq(f)).updateParentMenuItemDBId();break;case"down":if(k){if(e=j.add(k),o=h.eq(e.length+n),p=0!==o.childMenuItems().length,p&&(g=parseInt(o.menuItemDepth(),10)+1,j.moveHorizontally(g,m)),i===n+e.length)break;e.detach().insertAfter(h.eq(n+e.length)).updateParentMenuItemDBId()}else{if(0!==p.length&&j.moveHorizontally(q,m),i===n+1)break;j.detach().insertAfter(h.eq(n+1)).updateParentMenuItemDBId()}break;case"top":if(0===n)break;k?(e=j.add(k),e.detach().insertBefore(h.eq(0)).updateParentMenuItemDBId()):j.detach().insertBefore(h.eq(0)).updateParentMenuItemDBId();break;case"left":if(0===m)break;j.shiftHorizontally(-1);break;case"right":if(0===n)break;if(l["menu-item-parent-id"]===t)break;j.shiftHorizontally(1)}c.focus(),b.registerChange(),b.refreshKeyboardAccessibility(),b.refreshAdvancedAccessibility()},initAccessibility:function(){var c=a("#menu-to-edit");b.refreshKeyboardAccessibility(),b.refreshAdvancedAccessibility(),c.on("mouseenter.refreshAccessibility focus.refreshAccessibility touchstart.refreshAccessibility",".menu-item",function(){b.refreshAdvancedAccessibilityOfItem(a(this).find("a.item-edit"))}),c.on("click","a.item-edit",function(){b.refreshAdvancedAccessibilityOfItem(a(this))}),c.on("click",".menus-move",function(){var c=a(this),d=c.data("dir");"undefined"!=typeof d&&b.moveMenuItem(a(this).parents("li.menu-item").find("a.item-edit"),d)})},refreshAdvancedAccessibilityOfItem:function(b){if(!0===a(b).data("needs_accessibility_refresh")){var c,d,e,f,g,h,i,j,k,l=a(b),m=l.closest("li.menu-item").first(),n=m.menuItemDepth(),o=0===n,p=l.closest(".menu-item-handle").find(".menu-item-title").text(),q=parseInt(m.index(),10),r=o?n:parseInt(n-1,10),s=m.prevAll(".menu-item-depth-"+r).first().find(".menu-item-title").text(),t=m.prevAll(".menu-item-depth-"+n).first().find(".menu-item-title").text(),u=a("#menu-to-edit li").length,v=m.nextAll(".menu-item-depth-"+n).length;m.find(".field-move").toggle(u>1),0!==q&&(c=m.find(".menus-move-up"),c.attr("aria-label",menus.moveUp).css("display","inline")),0!==q&&o&&(c=m.find(".menus-move-top"),c.attr("aria-label",menus.moveToTop).css("display","inline")),q+1!==u&&0!==q&&(c=m.find(".menus-move-down"),c.attr("aria-label",menus.moveDown).css("display","inline")),0===q&&0!==v&&(c=m.find(".menus-move-down"),c.attr("aria-label",menus.moveDown).css("display","inline")),o||(c=m.find(".menus-move-left"),d=menus.outFrom.replace("%s",s),c.attr("aria-label",menus.moveOutFrom.replace("%s",s)).text(d).css("display","inline")),0!==q&&m.find(".menu-item-data-parent-id").val()!==m.prev().find(".menu-item-data-db-id").val()&&(c=m.find(".menus-move-right"),d=menus.under.replace("%s",t),c.attr("aria-label",menus.moveUnder.replace("%s",t)).text(d).css("display","inline")),o?(e=a(".menu-item-depth-0"),f=e.index(m)+1,u=e.length,g=menus.menuFocus.replace("%1$s",p).replace("%2$d",f).replace("%3$d",u)):(h=m.prevAll(".menu-item-depth-"+parseInt(n-1,10)).first(),i=h.find(".menu-item-data-db-id").val(),j=h.find(".menu-item-title").text(),k=a('.menu-item .menu-item-data-parent-id[value="'+i+'"]'),f=a(k.parents(".menu-item").get().reverse()).index(m)+1,g=menus.subMenuFocus.replace("%1$s",p).replace("%2$d",f).replace("%3$s",j)),l.attr("aria-label",g),l.data("needs_accessibility_refresh",!1)}},refreshAdvancedAccessibility:function(){a(".menu-item-settings .field-move .menus-move").hide(),a("a.item-edit").data("needs_accessibility_refresh",!0),a(".menu-item-edit-active a.item-edit").each(function(){b.refreshAdvancedAccessibilityOfItem(this)})},refreshKeyboardAccessibility:function(){a("a.item-edit").off("focus").on("focus",function(){a(this).off("keydown").on("keydown",function(c){var d,e=a(this),f=e.parents("li.menu-item"),g=f.getItemData();if((37==c.which||38==c.which||39==c.which||40==c.which)&&(e.off("keydown"),1!==a("#menu-to-edit li").length)){switch(d={38:"up",40:"down",37:"left",39:"right"},a("body").hasClass("rtl")&&(d={38:"up",40:"down",39:"left",37:"right"}),d[c.which]){case"up":b.moveMenuItem(e,"up");break;case"down":b.moveMenuItem(e,"down");break;case"left":b.moveMenuItem(e,"left");break;case"right":b.moveMenuItem(e,"right")}return a("#edit-"+g["menu-item-db-id"]).focus(),!1}})})},initPreviewing:function(){a("#menu-to-edit").on("change input",".edit-menu-item-title",function(b){var c,d,e=a(b.currentTarget);c=e.val(),d=e.closest(".menu-item").find(".menu-item-title"),c?d.text(c).removeClass("no-title"):d.text(navMenuL10n.untitled).addClass("no-title")})},initToggles:function(){postboxes.add_postbox_toggles("nav-menus"),columns.useCheckboxesForHidden(),columns.checked=function(b){a(".field-"+b).removeClass("hidden-field")},columns.unchecked=function(b){a(".field-"+b).addClass("hidden-field")},b.menuList.hideAdvancedMenuItemFields(),a(".hide-postbox-tog").click(function(){var b=a(".accordion-container li.accordion-section").filter(":hidden").map(function(){return this.id}).get().join(",");a.post(ajaxurl,{action:"closed-postboxes",hidden:b,closedpostboxesnonce:jQuery("#closedpostboxesnonce").val(),page:"nav-menus"})})},initSortables:function(){function c(a){var c;j=a.placeholder.prev(".menu-item"),k=a.placeholder.next(".menu-item"),j[0]==a.item[0]&&(j=j.prev(".menu-item")),k[0]==a.item[0]&&(k=k.next(".menu-item")),l=j.length?j.offset().top+j.height():0,m=k.length?k.offset().top+k.height()/3:0,h=k.length?k.menuItemDepth():0,i=j.length?(c=j.menuItemDepth()+1)>b.options.globalMaxDepth?b.options.globalMaxDepth:c:0}function d(a,b){a.placeholder.updateDepthClass(b,q),q=b}function e(){if(!s[0].className)return 0;var a=s[0].className.match(/menu-max-depth-(\d+)/);return a&&a[1]?parseInt(a[1],10):0}function f(c){var d,e=t;if(0!==c){if(c>0)d=p+c,d>t&&(e=d);else if(c<0&&p==t)for(;!a(".menu-item-depth-"+e,b.menuList).length&&e>0;)e--;s.removeClass("menu-max-depth-"+t).addClass("menu-max-depth-"+e),t=e}}var g,h,i,j,k,l,m,n,o,p,q=0,r=b.menuList.offset().left,s=a("body"),t=e();0!==a("#menu-to-edit li").length&&a(".drag-instructions").show(),r+=b.isRTL?b.menuList.width():0,b.menuList.sortable({handle:".menu-item-handle",placeholder:"sortable-placeholder",items:b.options.sortableItems,start:function(e,f){var h,i,j,k,l;b.isRTL&&(f.item[0].style.right="auto"),o=f.item.children(".menu-item-transport"),g=f.item.menuItemDepth(),d(f,g),j=f.item.next()[0]==f.placeholder[0]?f.item.next():f.item,k=j.childMenuItems(),o.append(k),h=o.outerHeight(),h+=h>0?1*f.placeholder.css("margin-top").slice(0,-2):0,h+=f.helper.outerHeight(),n=h,h-=2,f.placeholder.height(h),p=g,k.each(function(){var b=a(this).menuItemDepth();p=b>p?b:p}),i=f.helper.find(".menu-item-handle").outerWidth(),i+=b.depthToPx(p-g),i-=2,f.placeholder.width(i),l=f.placeholder.next(".menu-item"),l.css("margin-top",n+"px"),f.placeholder.detach(),a(this).sortable("refresh"),f.item.after(f.placeholder),l.css("margin-top",0),c(f)},stop:function(a,c){var d,e,h=q-g;d=o.children().insertAfter(c.item),e=c.item.find(".item-title .is-submenu"),0<q?e.show():e.hide(),0!==h&&(c.item.updateDepthClass(q),d.shiftDepthClass(h),f(h)),b.registerChange(),c.item.updateParentMenuItemDBId(),c.item[0].style.top=0,b.isRTL&&(c.item[0].style.left="auto",c.item[0].style.right=0),b.refreshKeyboardAccessibility(),b.refreshAdvancedAccessibility()},change:function(a,d){d.placeholder.parent().hasClass("menu")||(j.length?j.after(d.placeholder):b.menuList.prepend(d.placeholder)),c(d)},sort:function(e,f){var g=f.helper.offset(),j=b.isRTL?g.left+f.helper.width():g.left,o=b.negateIfRTL*b.pxToDepth(j-r);o>i||g.top<l-b.options.targetTolerance?o=i:o<h&&(o=h),o!=q&&d(f,o),m&&g.top+n>m&&(k.after(f.placeholder),c(f),a(this).sortable("refreshPositions"))}})},initManageLocations:function(){a("#menu-locations-wrap form").submit(function(){window.onbeforeunload=null}),a(".menu-location-menus select").on("change",function(){var b=a(this).closest("tr").find(".locations-edit-menu-link");a(this).find("option:selected").data("orig")?b.show():b.hide()})},attachMenuEditListeners:function(){var b=this;a("#update-nav-menu").bind("click",function(a){if(a.target&&a.target.className){if(-1!=a.target.className.indexOf("item-edit"))return b.eventOnClickEditLink(a.target);if(-1!=a.target.className.indexOf("menu-save"))return b.eventOnClickMenuSave(a.target);if(-1!=a.target.className.indexOf("menu-delete"))return b.eventOnClickMenuDelete(a.target);if(-1!=a.target.className.indexOf("item-delete"))return b.eventOnClickMenuItemDelete(a.target);if(-1!=a.target.className.indexOf("item-cancel"))return b.eventOnClickCancelLink(a.target)}}),a("#menu-name").on("input",_.debounce(function(){var b=a(document.getElementById("menu-name")),c=b.val();c&&c.replace(/\s+/,"")?b.parent().removeClass("form-invalid"):b.parent().addClass("form-invalid")},500)),a('#add-custom-links input[type="text"]').keypress(function(b){a("#customlinkdiv").removeClass("form-invalid"),13===b.keyCode&&(b.preventDefault(),a("#submit-customlinkdiv").click())})},attachMenuSaveSubmitListeners:function(){a("#update-nav-menu").submit(function(){var b=a("#update-nav-menu").serializeArray();a('[name="nav-menu-data"]').val(JSON.stringify(b))})},attachThemeLocationsListeners:function(){var b=a("#nav-menu-theme-locations"),c={};c.action="menu-locations-save",c["menu-settings-column-nonce"]=a("#menu-settings-column-nonce").val(),b.find('input[type="submit"]').click(function(){return b.find("select").each(function(){c[this.name]=a(this).val()}),b.find(".spinner").addClass("is-active"),a.post(ajaxurl,c,function(){b.find(".spinner").removeClass("is-active")}),!1})},attachQuickSearchListeners:function(){var c;a("#nav-menu-meta").on("submit",function(a){a.preventDefault()}),a("#nav-menu-meta").on("input",".quick-search",function(){var d=a(this);d.attr("autocomplete","off"),c&&clearTimeout(c),c=setTimeout(function(){b.updateQuickSearchResults(d)},500)}).on("blur",".quick-search",function(){b.lastSearch=""})},updateQuickSearchResults:function(c){var d,e,f=2,g=c.val();g.length<f||b.lastSearch==g||(b.lastSearch=g,d=c.parents(".tabs-panel"),e={action:"menu-quick-search","response-format":"markup",menu:a("#menu").val(),"menu-settings-column-nonce":a("#menu-settings-column-nonce").val(),q:g,type:c.attr("name")},a(".spinner",d).addClass("is-active"),a.post(ajaxurl,e,function(a){b.processQuickSearchQueryResponse(a,e,d)}))},addCustomLink:function(c){var d=a("#custom-menu-item-url").val().trim(),e=a("#custom-menu-item-name").val();return c=c||b.addMenuItemToBottom,""===d||"http://"==d?(a("#customlinkdiv").addClass("form-invalid"),!1):(a(".customlinkdiv .spinner").addClass("is-active"),void this.addLinkToMenu(d,e,c,function(){a(".customlinkdiv .spinner").removeClass("is-active"),a("#custom-menu-item-name").val("").blur(),a("#custom-menu-item-url").val("http://")}))},addLinkToMenu:function(a,c,d,e){d=d||b.addMenuItemToBottom,e=e||function(){},b.addItemToMenu({"-1":{"menu-item-type":"custom","menu-item-url":a,"menu-item-title":c}},d,e)},addItemToMenu:function(b,c,d){var e,f=a("#menu").val(),g=a("#menu-settings-column-nonce").val();c=c||function(){},d=d||function(){},e={action:"add-menu-item",menu:f,"menu-settings-column-nonce":g,"menu-item":b},a.post(ajaxurl,e,function(b){var f=a("#menu-instructions");b=a.trim(b),c(b,e),a("li.pending").hide().fadeIn("slow"),a(".drag-instructions").show(),!f.hasClass("menu-instructions-inactive")&&f.siblings().length&&f.addClass("menu-instructions-inactive"),d()})},addMenuItemToBottom:function(c){var d=a(c);d.hideAdvancedMenuItemFields().appendTo(b.targetList),b.refreshKeyboardAccessibility(),b.refreshAdvancedAccessibility(),a(document).trigger("menu-item-added",[d])},addMenuItemToTop:function(c){var d=a(c);d.hideAdvancedMenuItemFields().prependTo(b.targetList),b.refreshKeyboardAccessibility(),b.refreshAdvancedAccessibility(),a(document).trigger("menu-item-added",[d])},attachUnsavedChangesListener:function(){a("#menu-management input, #menu-management select, #menu-management, #menu-management textarea, .menu-location-menus select").change(function(){b.registerChange()}),0!==a("#menu-to-edit").length||0!==a(".menu-location-menus select").length?window.onbeforeunload=function(){if(b.menusChanged)return navMenuL10n.saveAlert}:a("#menu-settings-column").find("input,select").end().find("a").attr("href","#").unbind("click")},registerChange:function(){b.menusChanged=!0},attachTabsPanelListeners:function(){a("#menu-settings-column").bind("click",function(c){var d,e,f,g,h=a(c.target);if(h.hasClass("nav-tab-link"))e=h.data("type"),f=h.parents(".accordion-section-content").first(),a("input",f).removeAttr("checked"),a(".tabs-panel-active",f).removeClass("tabs-panel-active").addClass("tabs-panel-inactive"),a("#"+e,f).removeClass("tabs-panel-inactive").addClass("tabs-panel-active"),a(".tabs",f).removeClass("tabs"),h.parent().addClass("tabs"),a(".quick-search",f).focus(),f.find(".tabs-panel-active .menu-item-title").length?f.removeClass("has-no-menu-item"):f.addClass("has-no-menu-item"),c.preventDefault();else if(h.hasClass("select-all")){if(d=/#(.*)$/.exec(c.target.href),d&&d[1])return g=a("#"+d[1]+" .tabs-panel-active .menu-item-title input"),g.length===g.filter(":checked").length?g.removeAttr("checked"):g.prop("checked",!0),!1}else if(h.hasClass("submit-add-to-menu"))return b.registerChange(),c.target.id&&"submit-customlinkdiv"==c.target.id?b.addCustomLink(b.addMenuItemToBottom):c.target.id&&-1!=c.target.id.indexOf("submit-")&&a("#"+c.target.id.replace(/submit-/,"")).addSelectedToMenu(b.addMenuItemToBottom),!1}),a("#nav-menu-meta").on("click","a.page-numbers",function(){var b=a(this).closest(".inside");return a.post(ajaxurl,this.href.replace(/.*\?/,"").replace(/action=([^&]*)/,"")+"&action=menu-get-metabox",function(c){var d,e=a.parseJSON(c);-1!==c.indexOf("replace-id")&&(d=document.getElementById(e["replace-id"]),e.markup&&d&&b.html(e.markup))}),!1})},eventOnClickEditLink:function(b){var c,d,e=/#(.*)$/.exec(b.href);if(e&&e[1]&&(c=a("#"+e[1]),d=c.parent(),0!==d.length))return d.hasClass("menu-item-edit-inactive")?(c.data("menu-item-data")||c.data("menu-item-data",c.getItemData()),c.slideDown("fast"),d.removeClass("menu-item-edit-inactive").addClass("menu-item-edit-active")):(c.slideUp("fast"),d.removeClass("menu-item-edit-active").addClass("menu-item-edit-inactive")),!1},eventOnClickCancelLink:function(b){var c=a(b).closest(".menu-item-settings"),d=a(b).closest(".menu-item");return d.removeClass("menu-item-edit-active").addClass("menu-item-edit-inactive"),c.setItemData(c.data("menu-item-data")).hide(),!1},eventOnClickMenuSave:function(){var c="",d=a("#menu-name"),e=d.val();return e&&e.replace(/\s+/,"")?(a("#nav-menu-theme-locations select").each(function(){c+='<input type="hidden" name="'+this.name+'" value="'+a(this).val()+'" />'}),a("#update-nav-menu").append(c),b.menuList.find(".menu-item-data-position").val(function(a){return a+1}),window.onbeforeunload=null,!0):(d.parent().addClass("form-invalid"),!1)},eventOnClickMenuDelete:function(){return!!window.confirm(navMenuL10n.warnDeleteMenu)&&(window.onbeforeunload=null,!0)},eventOnClickMenuItemDelete:function(c){var d=parseInt(c.id.replace("delete-",""),10);return b.removeMenuItem(a("#menu-item-"+d)),b.registerChange(),!1},processQuickSearchQueryResponse:function(b,c,d){var e,f,g,h={},i=document.getElementById("nav-menu-meta"),j=/menu-item[(\[^]\]*/,k=a("<div>").html(b).find("li"),l=d.closest(".accordion-section-content");return k.length?(k.each(function(){if(g=a(this),e=j.exec(g.html()),e&&e[1]){for(f=e[1];i.elements["menu-item["+f+"][menu-item-type]"]||h[f];)f--;h[f]=!0,f!=e[1]&&g.html(g.html().replace(new RegExp("menu-item\\["+e[1]+"\\]","g"),"menu-item["+f+"]"))}}),a(".categorychecklist",d).html(k),a(".spinner",d).removeClass("is-active"),void l.removeClass("has-no-menu-item")):(a(".categorychecklist",d).html("<li><p>"+navMenuL10n.noResultsFound+"</p></li>"),a(".spinner",d).removeClass("is-active"),void l.addClass("has-no-menu-item"))},removeMenuItem:function(c){var d=c.childMenuItems();a(document).trigger("menu-removing-item",[c]),c.addClass("deleting").animate({opacity:0,height:0},350,function(){var e=a("#menu-instructions");c.remove(),d.shiftDepthClass(-1).updateParentMenuItemDBId(),0===a("#menu-to-edit li").length&&(a(".drag-instructions").hide(),e.removeClass("menu-instructions-inactive")),b.refreshAdvancedAccessibility()})},depthToPx:function(a){return a*b.options.menuItemDepthPerLevel},pxToDepth:function(a){return Math.floor(a/b.options.menuItemDepthPerLevel)}},a(document).ready(function(){wpNavMenu.init()})}(jQuery); \ No newline at end of file
diff --git a/www/crm/wp-admin/js/password-strength-meter.js b/www/crm/wp-admin/js/password-strength-meter.js
new file mode 100644
index 00000000..753f697c
--- /dev/null
+++ b/www/crm/wp-admin/js/password-strength-meter.js
@@ -0,0 +1,121 @@
+/**
+ * @output wp-admin/js/password-strength-meter.js
+ */
+
+/* global zxcvbn */
+window.wp = window.wp || {};
+
+(function($){
+
+ /**
+ * Contains functions to determine the password strength.
+ *
+ * @since 3.7.0
+ *
+ * @namespace
+ */
+ wp.passwordStrength = {
+ /**
+ * Determines the strength of a given password.
+ *
+ * Compares first password to the password confirmation.
+ *
+ * @since 3.7.0
+ *
+ * @param {string} password1 The subject password.
+ * @param {Array} blacklist An array of words that will lower the entropy of
+ * the password.
+ * @param {string} password2 The password confirmation.
+ *
+ * @returns {number} The password strength score.
+ */
+ meter : function( password1, blacklist, password2 ) {
+ if ( ! $.isArray( blacklist ) )
+ blacklist = [ blacklist.toString() ];
+
+ if (password1 != password2 && password2 && password2.length > 0)
+ return 5;
+
+ if ( 'undefined' === typeof window.zxcvbn ) {
+ // Password strength unknown.
+ return -1;
+ }
+
+ var result = zxcvbn( password1, blacklist );
+ return result.score;
+ },
+
+ /**
+ * Builds an array of words that should be penalized.
+ *
+ * Certain words need to be penalized because it would lower the entropy of a
+ * password if they were used. The blacklist is based on user input fields such
+ * as username, first name, email etc.
+ *
+ * @since 3.7.0
+ *
+ * @returns {string[]} The array of words to be blacklisted.
+ */
+ userInputBlacklist : function() {
+ var i, userInputFieldsLength, rawValuesLength, currentField,
+ rawValues = [],
+ blacklist = [],
+ userInputFields = [ 'user_login', 'first_name', 'last_name', 'nickname', 'display_name', 'email', 'url', 'description', 'weblog_title', 'admin_email' ];
+
+ // Collect all the strings we want to blacklist.
+ rawValues.push( document.title );
+ rawValues.push( document.URL );
+
+ userInputFieldsLength = userInputFields.length;
+ for ( i = 0; i < userInputFieldsLength; i++ ) {
+ currentField = $( '#' + userInputFields[ i ] );
+
+ if ( 0 === currentField.length ) {
+ continue;
+ }
+
+ rawValues.push( currentField[0].defaultValue );
+ rawValues.push( currentField.val() );
+ }
+
+ /*
+ * Strip out non-alphanumeric characters and convert each word to an
+ * individual entry.
+ */
+ rawValuesLength = rawValues.length;
+ for ( i = 0; i < rawValuesLength; i++ ) {
+ if ( rawValues[ i ] ) {
+ blacklist = blacklist.concat( rawValues[ i ].replace( /\W/g, ' ' ).split( ' ' ) );
+ }
+ }
+
+ /*
+ * Remove empty values, short words and duplicates. Short words are likely to
+ * cause many false positives.
+ */
+ blacklist = $.grep( blacklist, function( value, key ) {
+ if ( '' === value || 4 > value.length ) {
+ return false;
+ }
+
+ return $.inArray( value, blacklist ) === key;
+ });
+
+ return blacklist;
+ }
+ };
+
+ // Backward compatibility.
+
+ /**
+ * Password strength meter function.
+ *
+ * @since 2.5.0
+ * @deprecated 3.7.0 Use wp.passwordStrength.meter instead.
+ *
+ * @global
+ *
+ * @type {wp.passwordStrength.meter}
+ */
+ window.passwordStrength = wp.passwordStrength.meter;
+})(jQuery);
diff --git a/www/crm/wp-admin/js/password-strength-meter.min.js b/www/crm/wp-admin/js/password-strength-meter.min.js
new file mode 100644
index 00000000..95f2d07d
--- /dev/null
+++ b/www/crm/wp-admin/js/password-strength-meter.min.js
@@ -0,0 +1 @@
+window.wp=window.wp||{},function(a){wp.passwordStrength={meter:function(b,c,d){if(a.isArray(c)||(c=[c.toString()]),b!=d&&d&&d.length>0)return 5;if("undefined"==typeof window.zxcvbn)return-1;var e=zxcvbn(b,c);return e.score},userInputBlacklist:function(){var b,c,d,e,f=[],g=[],h=["user_login","first_name","last_name","nickname","display_name","email","url","description","weblog_title","admin_email"];for(f.push(document.title),f.push(document.URL),c=h.length,b=0;b<c;b++)e=a("#"+h[b]),0!==e.length&&(f.push(e[0].defaultValue),f.push(e.val()));for(d=f.length,b=0;b<d;b++)f[b]&&(g=g.concat(f[b].replace(/\W/g," ").split(" ")));return g=a.grep(g,function(b,c){return!(""===b||4>b.length)&&a.inArray(b,g)===c})}},window.passwordStrength=wp.passwordStrength.meter}(jQuery); \ No newline at end of file
diff --git a/www/crm/wp-admin/js/plugin-install.js b/www/crm/wp-admin/js/plugin-install.js
new file mode 100644
index 00000000..b6cd1d74
--- /dev/null
+++ b/www/crm/wp-admin/js/plugin-install.js
@@ -0,0 +1,222 @@
+/**
+ * @file Functionality for the plugin install screens.
+ *
+ * @output wp-admin/js/plugin-install.js
+ */
+
+/* global plugininstallL10n, tb_click, tb_remove, tb_position */
+
+jQuery( document ).ready( function( $ ) {
+
+ var tbWindow,
+ $iframeBody,
+ $tabbables,
+ $firstTabbable,
+ $lastTabbable,
+ $focusedBefore = $(),
+ $uploadViewToggle = $( '.upload-view-toggle' ),
+ $wrap = $ ( '.wrap' ),
+ $body = $( document.body );
+
+ window.tb_position = function() {
+ var width = $( window ).width(),
+ H = $( window ).height() - ( ( 792 < width ) ? 60 : 20 ),
+ W = ( 792 < width ) ? 772 : width - 20;
+
+ tbWindow = $( '#TB_window' );
+
+ if ( tbWindow.length ) {
+ tbWindow.width( W ).height( H );
+ $( '#TB_iframeContent' ).width( W ).height( H );
+ tbWindow.css({
+ 'margin-left': '-' + parseInt( ( W / 2 ), 10 ) + 'px'
+ });
+ if ( typeof document.body.style.maxWidth !== 'undefined' ) {
+ tbWindow.css({
+ 'top': '30px',
+ 'margin-top': '0'
+ });
+ }
+ }
+
+ return $( 'a.thickbox' ).each( function() {
+ var href = $( this ).attr( 'href' );
+ if ( ! href ) {
+ return;
+ }
+ href = href.replace( /&width=[0-9]+/g, '' );
+ href = href.replace( /&height=[0-9]+/g, '' );
+ $(this).attr( 'href', href + '&width=' + W + '&height=' + ( H ) );
+ });
+ };
+
+ $( window ).resize( function() {
+ tb_position();
+ });
+
+ /*
+ * Custom events: when a Thickbox iframe has loaded and when the Thickbox
+ * modal gets removed from the DOM.
+ */
+ $body
+ .on( 'thickbox:iframe:loaded', tbWindow, function() {
+ /*
+ * Return if it's not the modal with the plugin details iframe. Other
+ * thickbox instances might want to load an iframe with content from
+ * an external domain. Avoid to access the iframe contents when we're
+ * not sure the iframe loads from the same domain.
+ */
+ if ( ! tbWindow.hasClass( 'plugin-details-modal' ) ) {
+ return;
+ }
+
+ iframeLoaded();
+ })
+ .on( 'thickbox:removed', function() {
+ // Set focus back to the element that opened the modal dialog.
+ // Note: IE 8 would need this wrapped in a fake setTimeout `0`.
+ $focusedBefore.focus();
+ });
+
+ function iframeLoaded() {
+ var $iframe = tbWindow.find( '#TB_iframeContent' );
+
+ // Get the iframe body.
+ $iframeBody = $iframe.contents().find( 'body' );
+
+ // Get the tabbable elements and handle the keydown event on first load.
+ handleTabbables();
+
+ // Set initial focus on the "Close" button.
+ $firstTabbable.focus();
+
+ /*
+ * When the "Install" button is disabled (e.g. the Plugin is already installed)
+ * then we can't predict where the last focusable element is. We need to get
+ * the tabbable elements and handle the keydown event again and again,
+ * each time the active tab panel changes.
+ */
+ $( '#plugin-information-tabs a', $iframeBody ).on( 'click', function() {
+ handleTabbables();
+ });
+
+ // Close the modal when pressing Escape.
+ $iframeBody.on( 'keydown', function( event ) {
+ if ( 27 !== event.which ) {
+ return;
+ }
+ tb_remove();
+ });
+ }
+
+ /*
+ * Get the tabbable elements and detach/attach the keydown event.
+ * Called after the iframe has fully loaded so we have all the elements we need.
+ * Called again each time a Tab gets clicked.
+ * @todo Consider to implement a WordPress general utility for this and don't use jQuery UI.
+ */
+ function handleTabbables() {
+ var $firstAndLast;
+ // Get all the tabbable elements.
+ $tabbables = $( ':tabbable', $iframeBody );
+ // Our first tabbable element is always the "Close" button.
+ $firstTabbable = tbWindow.find( '#TB_closeWindowButton' );
+ // Get the last tabbable element.
+ $lastTabbable = $tabbables.last();
+ // Make a jQuery collection.
+ $firstAndLast = $firstTabbable.add( $lastTabbable );
+ // Detach any previously attached keydown event.
+ $firstAndLast.off( 'keydown.wp-plugin-details' );
+ // Attach again the keydown event on the first and last focusable elements.
+ $firstAndLast.on( 'keydown.wp-plugin-details', function( event ) {
+ constrainTabbing( event );
+ });
+ }
+
+ // Constrain tabbing within the plugin modal dialog.
+ function constrainTabbing( event ) {
+ if ( 9 !== event.which ) {
+ return;
+ }
+
+ if ( $lastTabbable[0] === event.target && ! event.shiftKey ) {
+ event.preventDefault();
+ $firstTabbable.focus();
+ } else if ( $firstTabbable[0] === event.target && event.shiftKey ) {
+ event.preventDefault();
+ $lastTabbable.focus();
+ }
+ }
+
+ /*
+ * Open the Plugin details modal. The event is delegated to get also the links
+ * in the plugins search tab, after the AJAX search rebuilds the HTML. It's
+ * delegated on the closest ancestor and not on the body to avoid conflicts
+ * with other handlers, see Trac ticket #43082.
+ */
+ $( '.wrap' ).on( 'click', '.thickbox.open-plugin-details-modal', function( e ) {
+ // The `data-title` attribute is used only in the Plugin screens.
+ var title = $( this ).data( 'title' ) ? plugininstallL10n.plugin_information + ' ' + $( this ).data( 'title' ) : plugininstallL10n.plugin_modal_label;
+
+ e.preventDefault();
+ e.stopPropagation();
+
+ // Store the element that has focus before opening the modal dialog, i.e. the control which opens it.
+ $focusedBefore = $( this );
+
+ tb_click.call(this);
+
+ // Set ARIA role, ARIA label, and add a CSS class.
+ tbWindow
+ .attr({
+ 'role': 'dialog',
+ 'aria-label': plugininstallL10n.plugin_modal_label
+ })
+ .addClass( 'plugin-details-modal' );
+
+ // Set title attribute on the iframe.
+ tbWindow.find( '#TB_iframeContent' ).attr( 'title', title );
+ });
+
+ /* Plugin install related JS */
+ $( '#plugin-information-tabs a' ).click( function( event ) {
+ var tab = $( this ).attr( 'name' );
+ event.preventDefault();
+
+ // Flip the tab
+ $( '#plugin-information-tabs a.current' ).removeClass( 'current' );
+ $( this ).addClass( 'current' );
+
+ // Only show the fyi box in the description section, on smaller screen, where it's otherwise always displayed at the top.
+ if ( 'description' !== tab && $( window ).width() < 772 ) {
+ $( '#plugin-information-content' ).find( '.fyi' ).hide();
+ } else {
+ $( '#plugin-information-content' ).find( '.fyi' ).show();
+ }
+
+ // Flip the content.
+ $( '#section-holder div.section' ).hide(); // Hide 'em all.
+ $( '#section-' + tab ).show();
+ });
+
+ /*
+ * When a user presses the "Upload Plugin" button, show the upload form in place
+ * rather than sending them to the devoted upload plugin page.
+ * The `?tab=upload` page still exists for no-js support and for plugins that
+ * might access it directly. When we're in this page, let the link behave
+ * like a link. Otherwise we're in the normal plugin installer pages and the
+ * link should behave like a toggle button.
+ */
+ if ( ! $wrap.hasClass( 'plugin-install-tab-upload' ) ) {
+ $uploadViewToggle
+ .attr({
+ role: 'button',
+ 'aria-expanded': 'false'
+ })
+ .on( 'click', function( event ) {
+ event.preventDefault();
+ $body.toggleClass( 'show-upload-view' );
+ $uploadViewToggle.attr( 'aria-expanded', $body.hasClass( 'show-upload-view' ) );
+ });
+ }
+});
diff --git a/www/crm/wp-admin/js/plugin-install.min.js b/www/crm/wp-admin/js/plugin-install.min.js
new file mode 100644
index 00000000..c34a7c0e
--- /dev/null
+++ b/www/crm/wp-admin/js/plugin-install.min.js
@@ -0,0 +1 @@
+jQuery(document).ready(function(a){function b(){var b=e.find("#TB_iframeContent");f=b.contents().find("body"),c(),h.focus(),a("#plugin-information-tabs a",f).on("click",function(){c()}),f.on("keydown",function(a){27===a.which&&tb_remove()})}function c(){var b;g=a(":tabbable",f),h=e.find("#TB_closeWindowButton"),i=g.last(),b=h.add(i),b.off("keydown.wp-plugin-details"),b.on("keydown.wp-plugin-details",function(a){d(a)})}function d(a){9===a.which&&(i[0]!==a.target||a.shiftKey?h[0]===a.target&&a.shiftKey&&(a.preventDefault(),i.focus()):(a.preventDefault(),h.focus()))}var e,f,g,h,i,j=a(),k=a(".upload-view-toggle"),l=a(".wrap"),m=a(document.body);window.tb_position=function(){var b=a(window).width(),c=a(window).height()-(792<b?60:20),d=792<b?772:b-20;return e=a("#TB_window"),e.length&&(e.width(d).height(c),a("#TB_iframeContent").width(d).height(c),e.css({"margin-left":"-"+parseInt(d/2,10)+"px"}),"undefined"!=typeof document.body.style.maxWidth&&e.css({top:"30px","margin-top":"0"})),a("a.thickbox").each(function(){var b=a(this).attr("href");b&&(b=b.replace(/&width=[0-9]+/g,""),b=b.replace(/&height=[0-9]+/g,""),a(this).attr("href",b+"&width="+d+"&height="+c))})},a(window).resize(function(){tb_position()}),m.on("thickbox:iframe:loaded",e,function(){e.hasClass("plugin-details-modal")&&b()}).on("thickbox:removed",function(){j.focus()}),a(".wrap").on("click",".thickbox.open-plugin-details-modal",function(b){var c=a(this).data("title")?plugininstallL10n.plugin_information+" "+a(this).data("title"):plugininstallL10n.plugin_modal_label;b.preventDefault(),b.stopPropagation(),j=a(this),tb_click.call(this),e.attr({role:"dialog","aria-label":plugininstallL10n.plugin_modal_label}).addClass("plugin-details-modal"),e.find("#TB_iframeContent").attr("title",c)}),a("#plugin-information-tabs a").click(function(b){var c=a(this).attr("name");b.preventDefault(),a("#plugin-information-tabs a.current").removeClass("current"),a(this).addClass("current"),"description"!==c&&a(window).width()<772?a("#plugin-information-content").find(".fyi").hide():a("#plugin-information-content").find(".fyi").show(),a("#section-holder div.section").hide(),a("#section-"+c).show()}),l.hasClass("plugin-install-tab-upload")||k.attr({role:"button","aria-expanded":"false"}).on("click",function(a){a.preventDefault(),m.toggleClass("show-upload-view"),k.attr("aria-expanded",m.hasClass("show-upload-view"))})}); \ No newline at end of file
diff --git a/www/crm/wp-admin/js/post.js b/www/crm/wp-admin/js/post.js
new file mode 100644
index 00000000..ec1a0d71
--- /dev/null
+++ b/www/crm/wp-admin/js/post.js
@@ -0,0 +1,1273 @@
+/**
+ * @file Contains all dynamic functionality needed on post and term pages.
+ *
+ * @output wp-admin/js/post.js
+ */
+
+ /* global postL10n, ajaxurl, wpAjax, setPostThumbnailL10n, postboxes, pagenow, tinymce, alert, deleteUserSetting */
+ /* global theList:true, theExtraList:true, getUserSetting, setUserSetting, commentReply, commentsBox */
+ /* global WPSetThumbnailHTML, wptitlehint */
+
+// Backwards compatibility: prevent fatal errors.
+window.makeSlugeditClickable = window.editPermalink = function(){};
+
+// Make sure the wp object exists.
+window.wp = window.wp || {};
+
+( function( $ ) {
+ var titleHasFocus = false;
+
+ /**
+ * Control loading of comments on the post and term edit pages.
+ *
+ * @type {{st: number, get: commentsBox.get, load: commentsBox.load}}
+ *
+ * @namespace commentsBox
+ */
+ window.commentsBox = {
+ // Comment offset to use when fetching new comments.
+ st : 0,
+
+ /**
+ * Fetch comments using AJAX and display them in the box.
+ *
+ * @memberof commentsBox
+ *
+ * @param {int} total Total number of comments for this post.
+ * @param {int} num Optional. Number of comments to fetch, defaults to 20.
+ * @returns {boolean} Always returns false.
+ */
+ get : function(total, num) {
+ var st = this.st, data;
+ if ( ! num )
+ num = 20;
+
+ this.st += num;
+ this.total = total;
+ $( '#commentsdiv .spinner' ).addClass( 'is-active' );
+
+ data = {
+ 'action' : 'get-comments',
+ 'mode' : 'single',
+ '_ajax_nonce' : $('#add_comment_nonce').val(),
+ 'p' : $('#post_ID').val(),
+ 'start' : st,
+ 'number' : num
+ };
+
+ $.post(
+ ajaxurl,
+ data,
+ function(r) {
+ r = wpAjax.parseAjaxResponse(r);
+ $('#commentsdiv .widefat').show();
+ $( '#commentsdiv .spinner' ).removeClass( 'is-active' );
+
+ if ( 'object' == typeof r && r.responses[0] ) {
+ $('#the-comment-list').append( r.responses[0].data );
+
+ theList = theExtraList = null;
+ $( 'a[className*=\':\']' ).unbind();
+
+ // If the offset is over the total number of comments we cannot fetch any more, so hide the button.
+ if ( commentsBox.st > commentsBox.total )
+ $('#show-comments').hide();
+ else
+ $('#show-comments').show().children('a').html(postL10n.showcomm);
+
+ return;
+ } else if ( 1 == r ) {
+ $('#show-comments').html(postL10n.endcomm);
+ return;
+ }
+
+ $('#the-comment-list').append('<tr><td colspan="2">'+wpAjax.broken+'</td></tr>');
+ }
+ );
+
+ return false;
+ },
+
+ /**
+ * Load the next batch of comments.
+ *
+ * @param {int} total Total number of comments to load.
+ *
+ * @memberof commentsBox
+ */
+ load: function(total){
+ this.st = jQuery('#the-comment-list tr.comment:visible').length;
+ this.get(total);
+ }
+ };
+
+ /**
+ * Overwrite the content of the Featured Image postbox
+ *
+ * @param {string} html New HTML to be displayed in the content area of the postbox.
+ *
+ * @global
+ */
+ window.WPSetThumbnailHTML = function(html){
+ $('.inside', '#postimagediv').html(html);
+ };
+
+ /**
+ * Set the Image ID of the Featured Image
+ *
+ * @param {int} id The post_id of the image to use as Featured Image.
+ *
+ * @global
+ */
+ window.WPSetThumbnailID = function(id){
+ var field = $('input[value="_thumbnail_id"]', '#list-table');
+ if ( field.length > 0 ) {
+ $('#meta\\[' + field.attr('id').match(/[0-9]+/) + '\\]\\[value\\]').text(id);
+ }
+ };
+
+ /**
+ * Remove the Featured Image
+ *
+ * @param {string} nonce Nonce to use in the request.
+ *
+ * @global
+ */
+ window.WPRemoveThumbnail = function(nonce){
+ $.post(ajaxurl, {
+ action: 'set-post-thumbnail', post_id: $( '#post_ID' ).val(), thumbnail_id: -1, _ajax_nonce: nonce, cookie: encodeURIComponent( document.cookie )
+ },
+ /**
+ * Handle server response
+ *
+ * @param {string} str Response, will be '0' when an error occurred otherwise contains link to add Featured Image.
+ */
+ function(str){
+ if ( str == '0' ) {
+ alert( setPostThumbnailL10n.error );
+ } else {
+ WPSetThumbnailHTML(str);
+ }
+ }
+ );
+ };
+
+ /**
+ * Heartbeat locks.
+ *
+ * Used to lock editing of an object by only one user at a time.
+ *
+ * When the user does not send a heartbeat in a heartbeat-time
+ * the user is no longer editing and another user can start editing.
+ */
+ $(document).on( 'heartbeat-send.refresh-lock', function( e, data ) {
+ var lock = $('#active_post_lock').val(),
+ post_id = $('#post_ID').val(),
+ send = {};
+
+ if ( ! post_id || ! $('#post-lock-dialog').length )
+ return;
+
+ send.post_id = post_id;
+
+ if ( lock )
+ send.lock = lock;
+
+ data['wp-refresh-post-lock'] = send;
+
+ }).on( 'heartbeat-tick.refresh-lock', function( e, data ) {
+ // Post locks: update the lock string or show the dialog if somebody has taken over editing.
+ var received, wrap, avatar;
+
+ if ( data['wp-refresh-post-lock'] ) {
+ received = data['wp-refresh-post-lock'];
+
+ if ( received.lock_error ) {
+ // Show "editing taken over" message.
+ wrap = $('#post-lock-dialog');
+
+ if ( wrap.length && ! wrap.is(':visible') ) {
+ if ( wp.autosave ) {
+ // Save the latest changes and disable.
+ $(document).one( 'heartbeat-tick', function() {
+ wp.autosave.server.suspend();
+ wrap.removeClass('saving').addClass('saved');
+ $(window).off( 'beforeunload.edit-post' );
+ });
+
+ wrap.addClass('saving');
+ wp.autosave.server.triggerSave();
+ }
+
+ if ( received.lock_error.avatar_src ) {
+ avatar = $( '<img class="avatar avatar-64 photo" width="64" height="64" alt="" />' ).attr( 'src', received.lock_error.avatar_src.replace( /&amp;/g, '&' ) );
+ wrap.find('div.post-locked-avatar').empty().append( avatar );
+ }
+
+ wrap.show().find('.currently-editing').text( received.lock_error.text );
+ wrap.find('.wp-tab-first').focus();
+ }
+ } else if ( received.new_lock ) {
+ $('#active_post_lock').val( received.new_lock );
+ }
+ }
+ }).on( 'before-autosave.update-post-slug', function() {
+ titleHasFocus = document.activeElement && document.activeElement.id === 'title';
+ }).on( 'after-autosave.update-post-slug', function() {
+
+ /*
+ * Create slug area only if not already there
+ * and the title field was not focused (user was not typing a title) when autosave ran.
+ */
+ if ( ! $('#edit-slug-box > *').length && ! titleHasFocus ) {
+ $.post( ajaxurl, {
+ action: 'sample-permalink',
+ post_id: $('#post_ID').val(),
+ new_title: $('#title').val(),
+ samplepermalinknonce: $('#samplepermalinknonce').val()
+ },
+ function( data ) {
+ if ( data != '-1' ) {
+ $('#edit-slug-box').html(data);
+ }
+ }
+ );
+ }
+ });
+
+}(jQuery));
+
+/**
+ * Heartbeat refresh nonces.
+ */
+(function($) {
+ var check, timeout;
+
+ /**
+ * Only allow to check for nonce refresh every 30 seconds.
+ */
+ function schedule() {
+ check = false;
+ window.clearTimeout( timeout );
+ timeout = window.setTimeout( function(){ check = true; }, 300000 );
+ }
+
+ $(document).on( 'heartbeat-send.wp-refresh-nonces', function( e, data ) {
+ var post_id,
+ $authCheck = $('#wp-auth-check-wrap');
+
+ if ( check || ( $authCheck.length && ! $authCheck.hasClass( 'hidden' ) ) ) {
+ if ( ( post_id = $('#post_ID').val() ) && $('#_wpnonce').val() ) {
+ data['wp-refresh-post-nonces'] = {
+ post_id: post_id
+ };
+ }
+ }
+ }).on( 'heartbeat-tick.wp-refresh-nonces', function( e, data ) {
+ var nonces = data['wp-refresh-post-nonces'];
+
+ if ( nonces ) {
+ schedule();
+
+ if ( nonces.replace ) {
+ $.each( nonces.replace, function( selector, value ) {
+ $( '#' + selector ).val( value );
+ });
+ }
+
+ if ( nonces.heartbeatNonce )
+ window.heartbeatSettings.nonce = nonces.heartbeatNonce;
+ }
+ }).ready( function() {
+ schedule();
+ });
+}(jQuery));
+
+/**
+ * All post and postbox controls and functionality.
+ */
+jQuery(document).ready( function($) {
+ var stamp, visibility, $submitButtons, updateVisibility, updateText,
+ sticky = '',
+ $textarea = $('#content'),
+ $document = $(document),
+ postId = $('#post_ID').val() || 0,
+ $submitpost = $('#submitpost'),
+ releaseLock = true,
+ $postVisibilitySelect = $('#post-visibility-select'),
+ $timestampdiv = $('#timestampdiv'),
+ $postStatusSelect = $('#post-status-select'),
+ isMac = window.navigator.platform ? window.navigator.platform.indexOf( 'Mac' ) !== -1 : false;
+
+ postboxes.add_postbox_toggles(pagenow);
+
+ /*
+ * Clear the window name. Otherwise if this is a former preview window where the user navigated to edit another post,
+ * and the first post is still being edited, clicking Preview there will use this window to show the preview.
+ */
+ window.name = '';
+
+ // Post locks: contain focus inside the dialog. If the dialog is shown, focus the first item.
+ $('#post-lock-dialog .notification-dialog').on( 'keydown', function(e) {
+ // Don't do anything when [tab] is pressed.
+ if ( e.which != 9 )
+ return;
+
+ var target = $(e.target);
+
+ // [shift] + [tab] on first tab cycles back to last tab.
+ if ( target.hasClass('wp-tab-first') && e.shiftKey ) {
+ $(this).find('.wp-tab-last').focus();
+ e.preventDefault();
+ // [tab] on last tab cycles back to first tab.
+ } else if ( target.hasClass('wp-tab-last') && ! e.shiftKey ) {
+ $(this).find('.wp-tab-first').focus();
+ e.preventDefault();
+ }
+ }).filter(':visible').find('.wp-tab-first').focus();
+
+ // Set the heartbeat interval to 15 sec. if post lock dialogs are enabled.
+ if ( wp.heartbeat && $('#post-lock-dialog').length ) {
+ wp.heartbeat.interval( 15 );
+ }
+
+ // The form is being submitted by the user.
+ $submitButtons = $submitpost.find( ':submit, a.submitdelete, #post-preview' ).on( 'click.edit-post', function( event ) {
+ var $button = $(this);
+
+ if ( $button.hasClass('disabled') ) {
+ event.preventDefault();
+ return;
+ }
+
+ if ( $button.hasClass('submitdelete') || $button.is( '#post-preview' ) ) {
+ return;
+ }
+
+ // The form submission can be blocked from JS or by using HTML 5.0 validation on some fields.
+ // Run this only on an actual 'submit'.
+ $('form#post').off( 'submit.edit-post' ).on( 'submit.edit-post', function( event ) {
+ if ( event.isDefaultPrevented() ) {
+ return;
+ }
+
+ // Stop auto save.
+ if ( wp.autosave ) {
+ wp.autosave.server.suspend();
+ }
+
+ if ( typeof commentReply !== 'undefined' ) {
+ /*
+ * Warn the user they have an unsaved comment before submitting
+ * the post data for update.
+ */
+ if ( ! commentReply.discardCommentChanges() ) {
+ return false;
+ }
+
+ /*
+ * Close the comment edit/reply form if open to stop the form
+ * action from interfering with the post's form action.
+ */
+ commentReply.close();
+ }
+
+ releaseLock = false;
+ $(window).off( 'beforeunload.edit-post' );
+
+ $submitButtons.addClass( 'disabled' );
+
+ if ( $button.attr('id') === 'publish' ) {
+ $submitpost.find( '#major-publishing-actions .spinner' ).addClass( 'is-active' );
+ } else {
+ $submitpost.find( '#minor-publishing .spinner' ).addClass( 'is-active' );
+ }
+ });
+ });
+
+ // Submit the form saving a draft or an autosave, and show a preview in a new tab
+ $('#post-preview').on( 'click.post-preview', function( event ) {
+ var $this = $(this),
+ $form = $('form#post'),
+ $previewField = $('input#wp-preview'),
+ target = $this.attr('target') || 'wp-preview',
+ ua = navigator.userAgent.toLowerCase();
+
+ event.preventDefault();
+
+ if ( $this.hasClass('disabled') ) {
+ return;
+ }
+
+ if ( wp.autosave ) {
+ wp.autosave.server.tempBlockSave();
+ }
+
+ $previewField.val('dopreview');
+ $form.attr( 'target', target ).submit().attr( 'target', '' );
+
+ // Workaround for WebKit bug preventing a form submitting twice to the same action.
+ // https://bugs.webkit.org/show_bug.cgi?id=28633
+ if ( ua.indexOf('safari') !== -1 && ua.indexOf('chrome') === -1 ) {
+ $form.attr( 'action', function( index, value ) {
+ return value + '?t=' + ( new Date() ).getTime();
+ });
+ }
+
+ $previewField.val('');
+ });
+
+ // This code is meant to allow tabbing from Title to Post content.
+ $('#title').on( 'keydown.editor-focus', function( event ) {
+ var editor;
+
+ if ( event.keyCode === 9 && ! event.ctrlKey && ! event.altKey && ! event.shiftKey ) {
+ editor = typeof tinymce != 'undefined' && tinymce.get('content');
+
+ if ( editor && ! editor.isHidden() ) {
+ editor.focus();
+ } else if ( $textarea.length ) {
+ $textarea.focus();
+ } else {
+ return;
+ }
+
+ event.preventDefault();
+ }
+ });
+
+ // Auto save new posts after a title is typed.
+ if ( $( '#auto_draft' ).val() ) {
+ $( '#title' ).blur( function() {
+ var cancel;
+
+ if ( ! this.value || $('#edit-slug-box > *').length ) {
+ return;
+ }
+
+ // Cancel the auto save when the blur was triggered by the user submitting the form.
+ $('form#post').one( 'submit', function() {
+ cancel = true;
+ });
+
+ window.setTimeout( function() {
+ if ( ! cancel && wp.autosave ) {
+ wp.autosave.server.triggerSave();
+ }
+ }, 200 );
+ });
+ }
+
+ $document.on( 'autosave-disable-buttons.edit-post', function() {
+ $submitButtons.addClass( 'disabled' );
+ }).on( 'autosave-enable-buttons.edit-post', function() {
+ if ( ! wp.heartbeat || ! wp.heartbeat.hasConnectionError() ) {
+ $submitButtons.removeClass( 'disabled' );
+ }
+ }).on( 'before-autosave.edit-post', function() {
+ $( '.autosave-message' ).text( postL10n.savingText );
+ }).on( 'after-autosave.edit-post', function( event, data ) {
+ $( '.autosave-message' ).text( data.message );
+
+ if ( $( document.body ).hasClass( 'post-new-php' ) ) {
+ $( '.submitbox .submitdelete' ).show();
+ }
+ });
+
+ /*
+ * When the user is trying to load another page, or reloads current page
+ * show a confirmation dialog when there are unsaved changes.
+ */
+ $(window).on( 'beforeunload.edit-post', function() {
+ var editor = typeof tinymce !== 'undefined' && tinymce.get('content');
+
+ if ( ( editor && ! editor.isHidden() && editor.isDirty() ) ||
+ ( wp.autosave && wp.autosave.server.postChanged() ) ) {
+
+ return postL10n.saveAlert;
+ }
+ }).on( 'unload.edit-post', function( event ) {
+ if ( ! releaseLock ) {
+ return;
+ }
+
+ /*
+ * Unload is triggered (by hand) on removing the Thickbox iframe.
+ * Make sure we process only the main document unload.
+ */
+ if ( event.target && event.target.nodeName != '#document' ) {
+ return;
+ }
+
+ var postID = $('#post_ID').val();
+ var postLock = $('#active_post_lock').val();
+
+ if ( ! postID || ! postLock ) {
+ return;
+ }
+
+ var data = {
+ action: 'wp-remove-post-lock',
+ _wpnonce: $('#_wpnonce').val(),
+ post_ID: postID,
+ active_post_lock: postLock
+ };
+
+ if ( window.FormData && window.navigator.sendBeacon ) {
+ var formData = new window.FormData();
+
+ $.each( data, function( key, value ) {
+ formData.append( key, value );
+ });
+
+ if ( window.navigator.sendBeacon( ajaxurl, formData ) ) {
+ return;
+ }
+ }
+
+ // Fall back to a synchronous POST request.
+ // See https://developer.mozilla.org/en-US/docs/Web/API/Navigator/sendBeacon
+ $.post({
+ async: false,
+ data: data,
+ url: ajaxurl
+ });
+ });
+
+ // Multiple Taxonomies.
+ if ( $('#tagsdiv-post_tag').length ) {
+ window.tagBox && window.tagBox.init();
+ } else {
+ $('.meta-box-sortables').children('div.postbox').each(function(){
+ if ( this.id.indexOf('tagsdiv-') === 0 ) {
+ window.tagBox && window.tagBox.init();
+ return false;
+ }
+ });
+ }
+
+ // Handle categories.
+ $('.categorydiv').each( function(){
+ var this_id = $(this).attr('id'), catAddBefore, catAddAfter, taxonomyParts, taxonomy, settingName;
+
+ taxonomyParts = this_id.split('-');
+ taxonomyParts.shift();
+ taxonomy = taxonomyParts.join('-');
+ settingName = taxonomy + '_tab';
+
+ if ( taxonomy == 'category' ) {
+ settingName = 'cats';
+ }
+
+ // TODO: move to jQuery 1.3+, support for multiple hierarchical taxonomies, see wp-lists.js
+ $('a', '#' + taxonomy + '-tabs').click( function( e ) {
+ e.preventDefault();
+ var t = $(this).attr('href');
+ $(this).parent().addClass('tabs').siblings('li').removeClass('tabs');
+ $('#' + taxonomy + '-tabs').siblings('.tabs-panel').hide();
+ $(t).show();
+ if ( '#' + taxonomy + '-all' == t ) {
+ deleteUserSetting( settingName );
+ } else {
+ setUserSetting( settingName, 'pop' );
+ }
+ });
+
+ if ( getUserSetting( settingName ) )
+ $('a[href="#' + taxonomy + '-pop"]', '#' + taxonomy + '-tabs').click();
+
+ // Add category button controls.
+ $('#new' + taxonomy).one( 'focus', function() {
+ $( this ).val( '' ).removeClass( 'form-input-tip' );
+ });
+
+ // On [enter] submit the taxonomy.
+ $('#new' + taxonomy).keypress( function(event){
+ if( 13 === event.keyCode ) {
+ event.preventDefault();
+ $('#' + taxonomy + '-add-submit').click();
+ }
+ });
+
+ // After submitting a new taxonomy, re-focus the input field.
+ $('#' + taxonomy + '-add-submit').click( function() {
+ $('#new' + taxonomy).focus();
+ });
+
+ /**
+ * Before adding a new taxonomy, disable submit button.
+ *
+ * @param {Object} s Taxonomy object which will be added.
+ *
+ * @returns {Object}
+ */
+ catAddBefore = function( s ) {
+ if ( !$('#new'+taxonomy).val() ) {
+ return false;
+ }
+
+ s.data += '&' + $( ':checked', '#'+taxonomy+'checklist' ).serialize();
+ $( '#' + taxonomy + '-add-submit' ).prop( 'disabled', true );
+ return s;
+ };
+
+ /**
+ * Re-enable submit button after a taxonomy has been added.
+ *
+ * Re-enable submit button.
+ * If the taxonomy has a parent place the taxonomy underneath the parent.
+ *
+ * @param {Object} r Response.
+ * @param {Object} s Taxonomy data.
+ *
+ * @returns void
+ */
+ catAddAfter = function( r, s ) {
+ var sup, drop = $('#new'+taxonomy+'_parent');
+
+ $( '#' + taxonomy + '-add-submit' ).prop( 'disabled', false );
+ if ( 'undefined' != s.parsed.responses[0] && (sup = s.parsed.responses[0].supplemental.newcat_parent) ) {
+ drop.before(sup);
+ drop.remove();
+ }
+ };
+
+ $('#' + taxonomy + 'checklist').wpList({
+ alt: '',
+ response: taxonomy + '-ajax-response',
+ addBefore: catAddBefore,
+ addAfter: catAddAfter
+ });
+
+ // Add new taxonomy button toggles input form visibility.
+ $('#' + taxonomy + '-add-toggle').click( function( e ) {
+ e.preventDefault();
+ $('#' + taxonomy + '-adder').toggleClass( 'wp-hidden-children' );
+ $('a[href="#' + taxonomy + '-all"]', '#' + taxonomy + '-tabs').click();
+ $('#new'+taxonomy).focus();
+ });
+
+ // Sync checked items between "All {taxonomy}" and "Most used" lists.
+ $('#' + taxonomy + 'checklist, #' + taxonomy + 'checklist-pop').on( 'click', 'li.popular-category > label input[type="checkbox"]', function() {
+ var t = $(this), c = t.is(':checked'), id = t.val();
+ if ( id && t.parents('#taxonomy-'+taxonomy).length )
+ $('#in-' + taxonomy + '-' + id + ', #in-popular-' + taxonomy + '-' + id).prop( 'checked', c );
+ });
+
+ }); // end cats
+
+ // Custom Fields postbox.
+ if ( $('#postcustom').length ) {
+ $( '#the-list' ).wpList( {
+ /**
+ * Add current post_ID to request to fetch custom fields
+ *
+ * @ignore
+ *
+ * @param {Object} s Request object.
+ *
+ * @returns {Object} Data modified with post_ID attached.
+ */
+ addBefore: function( s ) {
+ s.data += '&post_id=' + $('#post_ID').val();
+ return s;
+ },
+ /**
+ * Show the listing of custom fields after fetching.
+ *
+ * @ignore
+ */
+ addAfter: function() {
+ $('table#list-table').show();
+ }
+ });
+ }
+
+ /*
+ * Publish Post box (#submitdiv)
+ */
+ if ( $('#submitdiv').length ) {
+ stamp = $('#timestamp').html();
+ visibility = $('#post-visibility-display').html();
+
+ /**
+ * When the visibility of a post changes sub-options should be shown or hidden.
+ *
+ * @ignore
+ *
+ * @returns void
+ */
+ updateVisibility = function() {
+ // Show sticky for public posts.
+ if ( $postVisibilitySelect.find('input:radio:checked').val() != 'public' ) {
+ $('#sticky').prop('checked', false);
+ $('#sticky-span').hide();
+ } else {
+ $('#sticky-span').show();
+ }
+
+ // Show password input field for password protected post.
+ if ( $postVisibilitySelect.find('input:radio:checked').val() != 'password' ) {
+ $('#password-span').hide();
+ } else {
+ $('#password-span').show();
+ }
+ };
+
+ /**
+ * Make sure all labels represent the current settings.
+ *
+ * @ignore
+ *
+ * @returns {boolean} False when an invalid timestamp has been selected, otherwise True.
+ */
+ updateText = function() {
+
+ if ( ! $timestampdiv.length )
+ return true;
+
+ var attemptedDate, originalDate, currentDate, publishOn, postStatus = $('#post_status'),
+ optPublish = $('option[value="publish"]', postStatus), aa = $('#aa').val(),
+ mm = $('#mm').val(), jj = $('#jj').val(), hh = $('#hh').val(), mn = $('#mn').val();
+
+ attemptedDate = new Date( aa, mm - 1, jj, hh, mn );
+ originalDate = new Date( $('#hidden_aa').val(), $('#hidden_mm').val() -1, $('#hidden_jj').val(), $('#hidden_hh').val(), $('#hidden_mn').val() );
+ currentDate = new Date( $('#cur_aa').val(), $('#cur_mm').val() -1, $('#cur_jj').val(), $('#cur_hh').val(), $('#cur_mn').val() );
+
+ // Catch unexpected date problems.
+ if ( attemptedDate.getFullYear() != aa || (1 + attemptedDate.getMonth()) != mm || attemptedDate.getDate() != jj || attemptedDate.getMinutes() != mn ) {
+ $timestampdiv.find('.timestamp-wrap').addClass('form-invalid');
+ return false;
+ } else {
+ $timestampdiv.find('.timestamp-wrap').removeClass('form-invalid');
+ }
+
+ // Determine what the publish should be depending on the date and post status.
+ if ( attemptedDate > currentDate && $('#original_post_status').val() != 'future' ) {
+ publishOn = postL10n.publishOnFuture;
+ $('#publish').val( postL10n.schedule );
+ } else if ( attemptedDate <= currentDate && $('#original_post_status').val() != 'publish' ) {
+ publishOn = postL10n.publishOn;
+ $('#publish').val( postL10n.publish );
+ } else {
+ publishOn = postL10n.publishOnPast;
+ $('#publish').val( postL10n.update );
+ }
+
+ // If the date is the same, set it to trigger update events.
+ if ( originalDate.toUTCString() == attemptedDate.toUTCString() ) {
+ // Re-set to the current value.
+ $('#timestamp').html(stamp);
+ } else {
+ $('#timestamp').html(
+ '\n' + publishOn + ' <b>' +
+ postL10n.dateFormat
+ .replace( '%1$s', $( 'option[value="' + mm + '"]', '#mm' ).attr( 'data-text' ) )
+ .replace( '%2$s', parseInt( jj, 10 ) )
+ .replace( '%3$s', aa )
+ .replace( '%4$s', ( '00' + hh ).slice( -2 ) )
+ .replace( '%5$s', ( '00' + mn ).slice( -2 ) ) +
+ '</b> '
+ );
+ }
+
+ // Add "privately published" to post status when applies.
+ if ( $postVisibilitySelect.find('input:radio:checked').val() == 'private' ) {
+ $('#publish').val( postL10n.update );
+ if ( 0 === optPublish.length ) {
+ postStatus.append('<option value="publish">' + postL10n.privatelyPublished + '</option>');
+ } else {
+ optPublish.html( postL10n.privatelyPublished );
+ }
+ $('option[value="publish"]', postStatus).prop('selected', true);
+ $('#misc-publishing-actions .edit-post-status').hide();
+ } else {
+ if ( $('#original_post_status').val() == 'future' || $('#original_post_status').val() == 'draft' ) {
+ if ( optPublish.length ) {
+ optPublish.remove();
+ postStatus.val($('#hidden_post_status').val());
+ }
+ } else {
+ optPublish.html( postL10n.published );
+ }
+ if ( postStatus.is(':hidden') )
+ $('#misc-publishing-actions .edit-post-status').show();
+ }
+
+ // Update "Status:" to currently selected status.
+ $('#post-status-display').text(
+ wp.sanitize.stripTagsAndEncodeText( $('option:selected', postStatus).text() ) // Remove any potential tags from post status text.
+ );
+
+ // Show or hide the "Save Draft" button.
+ if ( $('option:selected', postStatus).val() == 'private' || $('option:selected', postStatus).val() == 'publish' ) {
+ $('#save-post').hide();
+ } else {
+ $('#save-post').show();
+ if ( $('option:selected', postStatus).val() == 'pending' ) {
+ $('#save-post').show().val( postL10n.savePending );
+ } else {
+ $('#save-post').show().val( postL10n.saveDraft );
+ }
+ }
+ return true;
+ };
+
+ // Show the visibility options and hide the toggle button when opened.
+ $( '#visibility .edit-visibility').click( function( e ) {
+ e.preventDefault();
+ if ( $postVisibilitySelect.is(':hidden') ) {
+ updateVisibility();
+ $postVisibilitySelect.slideDown( 'fast', function() {
+ $postVisibilitySelect.find( 'input[type="radio"]' ).first().focus();
+ } );
+ $(this).hide();
+ }
+ });
+
+ // Cancel visibility selection area and hide it from view.
+ $postVisibilitySelect.find('.cancel-post-visibility').click( function( event ) {
+ $postVisibilitySelect.slideUp('fast');
+ $('#visibility-radio-' + $('#hidden-post-visibility').val()).prop('checked', true);
+ $('#post_password').val($('#hidden-post-password').val());
+ $('#sticky').prop('checked', $('#hidden-post-sticky').prop('checked'));
+ $('#post-visibility-display').html(visibility);
+ $('#visibility .edit-visibility').show().focus();
+ updateText();
+ event.preventDefault();
+ });
+
+ // Set the selected visibility as current.
+ $postVisibilitySelect.find('.save-post-visibility').click( function( event ) { // crazyhorse - multiple ok cancels
+ $postVisibilitySelect.slideUp('fast');
+ $('#visibility .edit-visibility').show().focus();
+ updateText();
+
+ if ( $postVisibilitySelect.find('input:radio:checked').val() != 'public' ) {
+ $('#sticky').prop('checked', false);
+ }
+
+ if ( $('#sticky').prop('checked') ) {
+ sticky = 'Sticky';
+ } else {
+ sticky = '';
+ }
+
+ $('#post-visibility-display').html( postL10n[ $postVisibilitySelect.find('input:radio:checked').val() + sticky ] );
+ event.preventDefault();
+ });
+
+ // When the selection changes, update labels.
+ $postVisibilitySelect.find('input:radio').change( function() {
+ updateVisibility();
+ });
+
+ // Edit publish time click.
+ $timestampdiv.siblings('a.edit-timestamp').click( function( event ) {
+ if ( $timestampdiv.is( ':hidden' ) ) {
+ $timestampdiv.slideDown( 'fast', function() {
+ $( 'input, select', $timestampdiv.find( '.timestamp-wrap' ) ).first().focus();
+ } );
+ $(this).hide();
+ }
+ event.preventDefault();
+ });
+
+ // Cancel editing the publish time and hide the settings.
+ $timestampdiv.find('.cancel-timestamp').click( function( event ) {
+ $timestampdiv.slideUp('fast').siblings('a.edit-timestamp').show().focus();
+ $('#mm').val($('#hidden_mm').val());
+ $('#jj').val($('#hidden_jj').val());
+ $('#aa').val($('#hidden_aa').val());
+ $('#hh').val($('#hidden_hh').val());
+ $('#mn').val($('#hidden_mn').val());
+ updateText();
+ event.preventDefault();
+ });
+
+ // Save the changed timestamp.
+ $timestampdiv.find('.save-timestamp').click( function( event ) { // crazyhorse - multiple ok cancels
+ if ( updateText() ) {
+ $timestampdiv.slideUp('fast');
+ $timestampdiv.siblings('a.edit-timestamp').show().focus();
+ }
+ event.preventDefault();
+ });
+
+ // Cancel submit when an invalid timestamp has been selected.
+ $('#post').on( 'submit', function( event ) {
+ if ( ! updateText() ) {
+ event.preventDefault();
+ $timestampdiv.show();
+
+ if ( wp.autosave ) {
+ wp.autosave.enableButtons();
+ }
+
+ $( '#publishing-action .spinner' ).removeClass( 'is-active' );
+ }
+ });
+
+ // Post Status edit click.
+ $postStatusSelect.siblings('a.edit-post-status').click( function( event ) {
+ if ( $postStatusSelect.is( ':hidden' ) ) {
+ $postStatusSelect.slideDown( 'fast', function() {
+ $postStatusSelect.find('select').focus();
+ } );
+ $(this).hide();
+ }
+ event.preventDefault();
+ });
+
+ // Save the Post Status changes and hide the options.
+ $postStatusSelect.find('.save-post-status').click( function( event ) {
+ $postStatusSelect.slideUp( 'fast' ).siblings( 'a.edit-post-status' ).show().focus();
+ updateText();
+ event.preventDefault();
+ });
+
+ // Cancel Post Status editing and hide the options.
+ $postStatusSelect.find('.cancel-post-status').click( function( event ) {
+ $postStatusSelect.slideUp( 'fast' ).siblings( 'a.edit-post-status' ).show().focus();
+ $('#post_status').val( $('#hidden_post_status').val() );
+ updateText();
+ event.preventDefault();
+ });
+ }
+
+ /**
+ * Handle the editing of the post_name. Create the required HTML elements and
+ * update the changes via AJAX.
+ *
+ * @global
+ *
+ * @returns void
+ */
+ function editPermalink() {
+ var i, slug_value,
+ $el, revert_e,
+ c = 0,
+ real_slug = $('#post_name'),
+ revert_slug = real_slug.val(),
+ permalink = $( '#sample-permalink' ),
+ permalinkOrig = permalink.html(),
+ permalinkInner = $( '#sample-permalink a' ).html(),
+ buttons = $('#edit-slug-buttons'),
+ buttonsOrig = buttons.html(),
+ full = $('#editable-post-name-full');
+
+ // Deal with Twemoji in the post-name.
+ full.find( 'img' ).replaceWith( function() { return this.alt; } );
+ full = full.html();
+
+ permalink.html( permalinkInner );
+
+ // Save current content to revert to when cancelling.
+ $el = $( '#editable-post-name' );
+ revert_e = $el.html();
+
+ buttons.html( '<button type="button" class="save button button-small">' + postL10n.ok + '</button> <button type="button" class="cancel button-link">' + postL10n.cancel + '</button>' );
+
+ // Save permalink changes.
+ buttons.children( '.save' ).click( function() {
+ var new_slug = $el.children( 'input' ).val();
+
+ if ( new_slug == $('#editable-post-name-full').text() ) {
+ buttons.children('.cancel').click();
+ return;
+ }
+
+ $.post(
+ ajaxurl,
+ {
+ action: 'sample-permalink',
+ post_id: postId,
+ new_slug: new_slug,
+ new_title: $('#title').val(),
+ samplepermalinknonce: $('#samplepermalinknonce').val()
+ },
+ function(data) {
+ var box = $('#edit-slug-box');
+ box.html(data);
+ if (box.hasClass('hidden')) {
+ box.fadeIn('fast', function () {
+ box.removeClass('hidden');
+ });
+ }
+
+ buttons.html(buttonsOrig);
+ permalink.html(permalinkOrig);
+ real_slug.val(new_slug);
+ $( '.edit-slug' ).focus();
+ wp.a11y.speak( postL10n.permalinkSaved );
+ }
+ );
+ });
+
+ // Cancel editing of permalink.
+ buttons.children( '.cancel' ).click( function() {
+ $('#view-post-btn').show();
+ $el.html(revert_e);
+ buttons.html(buttonsOrig);
+ permalink.html(permalinkOrig);
+ real_slug.val(revert_slug);
+ $( '.edit-slug' ).focus();
+ });
+
+ // If more than 1/4th of 'full' is '%', make it empty.
+ for ( i = 0; i < full.length; ++i ) {
+ if ( '%' == full.charAt(i) )
+ c++;
+ }
+ slug_value = ( c > full.length / 4 ) ? '' : full;
+
+ $el.html( '<input type="text" id="new-post-slug" value="' + slug_value + '" autocomplete="off" />' ).children( 'input' ).keydown( function( e ) {
+ var key = e.which;
+ // On [enter], just save the new slug, don't save the post.
+ if ( 13 === key ) {
+ e.preventDefault();
+ buttons.children( '.save' ).click();
+ }
+ // On [esc] cancel the editing.
+ if ( 27 === key ) {
+ buttons.children( '.cancel' ).click();
+ }
+ } ).keyup( function() {
+ real_slug.val( this.value );
+ }).focus();
+ }
+
+ $( '#titlediv' ).on( 'click', '.edit-slug', function() {
+ editPermalink();
+ });
+
+ /**
+ * Adds screen reader text to the title label when needed.
+ *
+ * Use the 'screen-reader-text' class to emulate a placeholder attribute
+ * and hide the label when entering a value.
+ *
+ * @param {string} id Optional. HTML ID to add the screen reader helper text to.
+ *
+ * @global
+ *
+ * @returns void
+ */
+ window.wptitlehint = function( id ) {
+ id = id || 'title';
+
+ var title = $( '#' + id ), titleprompt = $( '#' + id + '-prompt-text' );
+
+ if ( '' === title.val() ) {
+ titleprompt.removeClass( 'screen-reader-text' );
+ }
+
+ title.on( 'input', function() {
+ if ( '' === this.value ) {
+ titleprompt.removeClass( 'screen-reader-text' );
+ return;
+ }
+
+ titleprompt.addClass( 'screen-reader-text' );
+ } );
+ };
+
+ wptitlehint();
+
+ // Resize the WYSIWYG and plain text editors.
+ ( function() {
+ var editor, offset, mce,
+ $handle = $('#post-status-info'),
+ $postdivrich = $('#postdivrich');
+
+ // If there are no textareas or we are on a touch device, we can't do anything.
+ if ( ! $textarea.length || 'ontouchstart' in window ) {
+ // Hide the resize handle.
+ $('#content-resize-handle').hide();
+ return;
+ }
+
+ /**
+ * Handle drag event.
+ *
+ * @param {Object} event Event containing details about the drag.
+ */
+ function dragging( event ) {
+ if ( $postdivrich.hasClass( 'wp-editor-expand' ) ) {
+ return;
+ }
+
+ if ( mce ) {
+ editor.theme.resizeTo( null, offset + event.pageY );
+ } else {
+ $textarea.height( Math.max( 50, offset + event.pageY ) );
+ }
+
+ event.preventDefault();
+ }
+
+ /**
+ * When the dragging stopped make sure we return focus and do a sanity check on the height.
+ */
+ function endDrag() {
+ var height, toolbarHeight;
+
+ if ( $postdivrich.hasClass( 'wp-editor-expand' ) ) {
+ return;
+ }
+
+ if ( mce ) {
+ editor.focus();
+ toolbarHeight = parseInt( $( '#wp-content-editor-container .mce-toolbar-grp' ).height(), 10 );
+
+ if ( toolbarHeight < 10 || toolbarHeight > 200 ) {
+ toolbarHeight = 30;
+ }
+
+ height = parseInt( $('#content_ifr').css('height'), 10 ) + toolbarHeight - 28;
+ } else {
+ $textarea.focus();
+ height = parseInt( $textarea.css('height'), 10 );
+ }
+
+ $document.off( '.wp-editor-resize' );
+
+ // Sanity check: normalize height to stay within acceptable ranges.
+ if ( height && height > 50 && height < 5000 ) {
+ setUserSetting( 'ed_size', height );
+ }
+ }
+
+ $handle.on( 'mousedown.wp-editor-resize', function( event ) {
+ if ( typeof tinymce !== 'undefined' ) {
+ editor = tinymce.get('content');
+ }
+
+ if ( editor && ! editor.isHidden() ) {
+ mce = true;
+ offset = $('#content_ifr').height() - event.pageY;
+ } else {
+ mce = false;
+ offset = $textarea.height() - event.pageY;
+ $textarea.blur();
+ }
+
+ $document.on( 'mousemove.wp-editor-resize', dragging )
+ .on( 'mouseup.wp-editor-resize mouseleave.wp-editor-resize', endDrag );
+
+ event.preventDefault();
+ }).on( 'mouseup.wp-editor-resize', endDrag );
+ })();
+
+ // TinyMCE specific handling of Post Format changes to reflect in the editor.
+ if ( typeof tinymce !== 'undefined' ) {
+ // When changing post formats, change the editor body class.
+ $( '#post-formats-select input.post-format' ).on( 'change.set-editor-class', function() {
+ var editor, body, format = this.id;
+
+ if ( format && $( this ).prop( 'checked' ) && ( editor = tinymce.get( 'content' ) ) ) {
+ body = editor.getBody();
+ body.className = body.className.replace( /\bpost-format-[^ ]+/, '' );
+ editor.dom.addClass( body, format == 'post-format-0' ? 'post-format-standard' : format );
+ $( document ).trigger( 'editor-classchange' );
+ }
+ });
+
+ // When changing page template, change the editor body class
+ $( '#page_template' ).on( 'change.set-editor-class', function() {
+ var editor, body, pageTemplate = $( this ).val() || '';
+
+ pageTemplate = pageTemplate.substr( pageTemplate.lastIndexOf( '/' ) + 1, pageTemplate.length )
+ .replace( /\.php$/, '' )
+ .replace( /\./g, '-' );
+
+ if ( pageTemplate && ( editor = tinymce.get( 'content' ) ) ) {
+ body = editor.getBody();
+ body.className = body.className.replace( /\bpage-template-[^ ]+/, '' );
+ editor.dom.addClass( body, 'page-template-' + pageTemplate );
+ $( document ).trigger( 'editor-classchange' );
+ }
+ });
+
+ }
+
+ // Save on pressing [ctrl]/[command] + [s] in the Text editor.
+ $textarea.on( 'keydown.wp-autosave', function( event ) {
+ // Key [s] has code 83.
+ if ( event.which === 83 ) {
+ if ( event.shiftKey || event.altKey || ( isMac && ( ! event.metaKey || event.ctrlKey ) ) || ( ! isMac && ! event.ctrlKey ) ) {
+ return;
+ }
+
+ wp.autosave && wp.autosave.server.triggerSave();
+ event.preventDefault();
+ }
+ });
+
+ // If the last status was auto-draft and the save is triggered, edit the current URL.
+ if ( $( '#original_post_status' ).val() === 'auto-draft' && window.history.replaceState ) {
+ var location;
+
+ $( '#publish' ).on( 'click', function() {
+ location = window.location.href;
+ location += ( location.indexOf( '?' ) !== -1 ) ? '&' : '?';
+ location += 'wp-post-new-reload=true';
+
+ window.history.replaceState( null, null, location );
+ });
+ }
+});
+
+/**
+ * TinyMCE word count display
+ */
+( function( $, counter ) {
+ $( function() {
+ var $content = $( '#content' ),
+ $count = $( '#wp-word-count' ).find( '.word-count' ),
+ prevCount = 0,
+ contentEditor;
+
+ /**
+ * Get the word count from TinyMCE and display it
+ */
+ function update() {
+ var text, count;
+
+ if ( ! contentEditor || contentEditor.isHidden() ) {
+ text = $content.val();
+ } else {
+ text = contentEditor.getContent( { format: 'raw' } );
+ }
+
+ count = counter.count( text );
+
+ if ( count !== prevCount ) {
+ $count.text( count );
+ }
+
+ prevCount = count;
+ }
+
+ /**
+ * Bind the word count update triggers.
+ *
+ * When a node change in the main TinyMCE editor has been triggered.
+ * When a key has been released in the plain text content editor.
+ */
+ $( document ).on( 'tinymce-editor-init', function( event, editor ) {
+ if ( editor.id !== 'content' ) {
+ return;
+ }
+
+ contentEditor = editor;
+
+ editor.on( 'nodechange keyup', _.debounce( update, 1000 ) );
+ } );
+
+ $content.on( 'input keyup', _.debounce( update, 1000 ) );
+
+ update();
+ } );
+
+} )( jQuery, new wp.utils.WordCounter() );
diff --git a/www/crm/wp-admin/js/post.min.js b/www/crm/wp-admin/js/post.min.js
new file mode 100644
index 00000000..bc93e1bc
--- /dev/null
+++ b/www/crm/wp-admin/js/post.min.js
@@ -0,0 +1 @@
+window.makeSlugeditClickable=window.editPermalink=function(){},window.wp=window.wp||{},function(a){var b=!1;window.commentsBox={st:0,get:function(b,c){var d,e=this.st;return c||(c=20),this.st+=c,this.total=b,a("#commentsdiv .spinner").addClass("is-active"),d={action:"get-comments",mode:"single",_ajax_nonce:a("#add_comment_nonce").val(),p:a("#post_ID").val(),start:e,number:c},a.post(ajaxurl,d,function(b){return b=wpAjax.parseAjaxResponse(b),a("#commentsdiv .widefat").show(),a("#commentsdiv .spinner").removeClass("is-active"),"object"==typeof b&&b.responses[0]?(a("#the-comment-list").append(b.responses[0].data),theList=theExtraList=null,a("a[className*=':']").unbind(),void(commentsBox.st>commentsBox.total?a("#show-comments").hide():a("#show-comments").show().children("a").html(postL10n.showcomm))):1==b?void a("#show-comments").html(postL10n.endcomm):void a("#the-comment-list").append('<tr><td colspan="2">'+wpAjax.broken+"</td></tr>")}),!1},load:function(a){this.st=jQuery("#the-comment-list tr.comment:visible").length,this.get(a)}},window.WPSetThumbnailHTML=function(b){a(".inside","#postimagediv").html(b)},window.WPSetThumbnailID=function(b){var c=a('input[value="_thumbnail_id"]',"#list-table");c.length>0&&a("#meta\\["+c.attr("id").match(/[0-9]+/)+"\\]\\[value\\]").text(b)},window.WPRemoveThumbnail=function(b){a.post(ajaxurl,{action:"set-post-thumbnail",post_id:a("#post_ID").val(),thumbnail_id:-1,_ajax_nonce:b,cookie:encodeURIComponent(document.cookie)},function(a){"0"==a?alert(setPostThumbnailL10n.error):WPSetThumbnailHTML(a)})},a(document).on("heartbeat-send.refresh-lock",function(b,c){var d=a("#active_post_lock").val(),e=a("#post_ID").val(),f={};e&&a("#post-lock-dialog").length&&(f.post_id=e,d&&(f.lock=d),c["wp-refresh-post-lock"]=f)}).on("heartbeat-tick.refresh-lock",function(b,c){var d,e,f;c["wp-refresh-post-lock"]&&(d=c["wp-refresh-post-lock"],d.lock_error?(e=a("#post-lock-dialog"),e.length&&!e.is(":visible")&&(wp.autosave&&(a(document).one("heartbeat-tick",function(){wp.autosave.server.suspend(),e.removeClass("saving").addClass("saved"),a(window).off("beforeunload.edit-post")}),e.addClass("saving"),wp.autosave.server.triggerSave()),d.lock_error.avatar_src&&(f=a('<img class="avatar avatar-64 photo" width="64" height="64" alt="" />').attr("src",d.lock_error.avatar_src.replace(/&amp;/g,"&")),e.find("div.post-locked-avatar").empty().append(f)),e.show().find(".currently-editing").text(d.lock_error.text),e.find(".wp-tab-first").focus())):d.new_lock&&a("#active_post_lock").val(d.new_lock))}).on("before-autosave.update-post-slug",function(){b=document.activeElement&&"title"===document.activeElement.id}).on("after-autosave.update-post-slug",function(){a("#edit-slug-box > *").length||b||a.post(ajaxurl,{action:"sample-permalink",post_id:a("#post_ID").val(),new_title:a("#title").val(),samplepermalinknonce:a("#samplepermalinknonce").val()},function(b){"-1"!=b&&a("#edit-slug-box").html(b)})})}(jQuery),function(a){function b(){c=!1,window.clearTimeout(d),d=window.setTimeout(function(){c=!0},3e5)}var c,d;a(document).on("heartbeat-send.wp-refresh-nonces",function(b,d){var e,f=a("#wp-auth-check-wrap");(c||f.length&&!f.hasClass("hidden"))&&(e=a("#post_ID").val())&&a("#_wpnonce").val()&&(d["wp-refresh-post-nonces"]={post_id:e})}).on("heartbeat-tick.wp-refresh-nonces",function(c,d){var e=d["wp-refresh-post-nonces"];e&&(b(),e.replace&&a.each(e.replace,function(b,c){a("#"+b).val(c)}),e.heartbeatNonce&&(window.heartbeatSettings.nonce=e.heartbeatNonce))}).ready(function(){b()})}(jQuery),jQuery(document).ready(function(a){function b(){var b,c,d,e,f=0,g=a("#post_name"),h=g.val(),i=a("#sample-permalink"),j=i.html(),l=a("#sample-permalink a").html(),m=a("#edit-slug-buttons"),n=m.html(),o=a("#editable-post-name-full");for(o.find("img").replaceWith(function(){return this.alt}),o=o.html(),i.html(l),d=a("#editable-post-name"),e=d.html(),m.html('<button type="button" class="save button button-small">'+postL10n.ok+'</button> <button type="button" class="cancel button-link">'+postL10n.cancel+"</button>"),m.children(".save").click(function(){var b=d.children("input").val();return b==a("#editable-post-name-full").text()?void m.children(".cancel").click():void a.post(ajaxurl,{action:"sample-permalink",post_id:k,new_slug:b,new_title:a("#title").val(),samplepermalinknonce:a("#samplepermalinknonce").val()},function(c){var d=a("#edit-slug-box");d.html(c),d.hasClass("hidden")&&d.fadeIn("fast",function(){d.removeClass("hidden")}),m.html(n),i.html(j),g.val(b),a(".edit-slug").focus(),wp.a11y.speak(postL10n.permalinkSaved)})}),m.children(".cancel").click(function(){a("#view-post-btn").show(),d.html(e),m.html(n),i.html(j),g.val(h),a(".edit-slug").focus()}),b=0;b<o.length;++b)"%"==o.charAt(b)&&f++;c=f>o.length/4?"":o,d.html('<input type="text" id="new-post-slug" value="'+c+'" autocomplete="off" />').children("input").keydown(function(a){var b=a.which;13===b&&(a.preventDefault(),m.children(".save").click()),27===b&&m.children(".cancel").click()}).keyup(function(){g.val(this.value)}).focus()}var c,d,e,f,g,h="",i=a("#content"),j=a(document),k=a("#post_ID").val()||0,l=a("#submitpost"),m=!0,n=a("#post-visibility-select"),o=a("#timestampdiv"),p=a("#post-status-select"),q=!!window.navigator.platform&&window.navigator.platform.indexOf("Mac")!==-1;if(postboxes.add_postbox_toggles(pagenow),window.name="",a("#post-lock-dialog .notification-dialog").on("keydown",function(b){if(9==b.which){var c=a(b.target);c.hasClass("wp-tab-first")&&b.shiftKey?(a(this).find(".wp-tab-last").focus(),b.preventDefault()):c.hasClass("wp-tab-last")&&!b.shiftKey&&(a(this).find(".wp-tab-first").focus(),b.preventDefault())}}).filter(":visible").find(".wp-tab-first").focus(),wp.heartbeat&&a("#post-lock-dialog").length&&wp.heartbeat.interval(15),e=l.find(":submit, a.submitdelete, #post-preview").on("click.edit-post",function(b){var c=a(this);return c.hasClass("disabled")?void b.preventDefault():void(c.hasClass("submitdelete")||c.is("#post-preview")||a("form#post").off("submit.edit-post").on("submit.edit-post",function(b){if(!b.isDefaultPrevented()){if(wp.autosave&&wp.autosave.server.suspend(),"undefined"!=typeof commentReply){if(!commentReply.discardCommentChanges())return!1;commentReply.close()}m=!1,a(window).off("beforeunload.edit-post"),e.addClass("disabled"),"publish"===c.attr("id")?l.find("#major-publishing-actions .spinner").addClass("is-active"):l.find("#minor-publishing .spinner").addClass("is-active")}}))}),a("#post-preview").on("click.post-preview",function(b){var c=a(this),d=a("form#post"),e=a("input#wp-preview"),f=c.attr("target")||"wp-preview",g=navigator.userAgent.toLowerCase();b.preventDefault(),c.hasClass("disabled")||(wp.autosave&&wp.autosave.server.tempBlockSave(),e.val("dopreview"),d.attr("target",f).submit().attr("target",""),g.indexOf("safari")!==-1&&g.indexOf("chrome")===-1&&d.attr("action",function(a,b){return b+"?t="+(new Date).getTime()}),e.val(""))}),a("#title").on("keydown.editor-focus",function(a){var b;if(9===a.keyCode&&!a.ctrlKey&&!a.altKey&&!a.shiftKey){if(b="undefined"!=typeof tinymce&&tinymce.get("content"),b&&!b.isHidden())b.focus();else{if(!i.length)return;i.focus()}a.preventDefault()}}),a("#auto_draft").val()&&a("#title").blur(function(){var b;this.value&&!a("#edit-slug-box > *").length&&(a("form#post").one("submit",function(){b=!0}),window.setTimeout(function(){!b&&wp.autosave&&wp.autosave.server.triggerSave()},200))}),j.on("autosave-disable-buttons.edit-post",function(){e.addClass("disabled")}).on("autosave-enable-buttons.edit-post",function(){wp.heartbeat&&wp.heartbeat.hasConnectionError()||e.removeClass("disabled")}).on("before-autosave.edit-post",function(){a(".autosave-message").text(postL10n.savingText)}).on("after-autosave.edit-post",function(b,c){a(".autosave-message").text(c.message),a(document.body).hasClass("post-new-php")&&a(".submitbox .submitdelete").show()}),a(window).on("beforeunload.edit-post",function(){var a="undefined"!=typeof tinymce&&tinymce.get("content");if(a&&!a.isHidden()&&a.isDirty()||wp.autosave&&wp.autosave.server.postChanged())return postL10n.saveAlert}).on("unload.edit-post",function(b){if(m&&(!b.target||"#document"==b.target.nodeName)){var c=a("#post_ID").val(),d=a("#active_post_lock").val();if(c&&d){var e={action:"wp-remove-post-lock",_wpnonce:a("#_wpnonce").val(),post_ID:c,active_post_lock:d};if(window.FormData&&window.navigator.sendBeacon){var f=new window.FormData;if(a.each(e,function(a,b){f.append(a,b)}),window.navigator.sendBeacon(ajaxurl,f))return}a.post({async:!1,data:e,url:ajaxurl})}}}),a("#tagsdiv-post_tag").length?window.tagBox&&window.tagBox.init():a(".meta-box-sortables").children("div.postbox").each(function(){if(0===this.id.indexOf("tagsdiv-"))return window.tagBox&&window.tagBox.init(),!1}),a(".categorydiv").each(function(){var b,c,d,e,f,g=a(this).attr("id");d=g.split("-"),d.shift(),e=d.join("-"),f=e+"_tab","category"==e&&(f="cats"),a("a","#"+e+"-tabs").click(function(b){b.preventDefault();var c=a(this).attr("href");a(this).parent().addClass("tabs").siblings("li").removeClass("tabs"),a("#"+e+"-tabs").siblings(".tabs-panel").hide(),a(c).show(),"#"+e+"-all"==c?deleteUserSetting(f):setUserSetting(f,"pop")}),getUserSetting(f)&&a('a[href="#'+e+'-pop"]',"#"+e+"-tabs").click(),a("#new"+e).one("focus",function(){a(this).val("").removeClass("form-input-tip")}),a("#new"+e).keypress(function(b){13===b.keyCode&&(b.preventDefault(),a("#"+e+"-add-submit").click())}),a("#"+e+"-add-submit").click(function(){a("#new"+e).focus()}),b=function(b){return!!a("#new"+e).val()&&(b.data+="&"+a(":checked","#"+e+"checklist").serialize(),a("#"+e+"-add-submit").prop("disabled",!0),b)},c=function(b,c){var d,f=a("#new"+e+"_parent");a("#"+e+"-add-submit").prop("disabled",!1),"undefined"!=c.parsed.responses[0]&&(d=c.parsed.responses[0].supplemental.newcat_parent)&&(f.before(d),f.remove())},a("#"+e+"checklist").wpList({alt:"",response:e+"-ajax-response",addBefore:b,addAfter:c}),a("#"+e+"-add-toggle").click(function(b){b.preventDefault(),a("#"+e+"-adder").toggleClass("wp-hidden-children"),a('a[href="#'+e+'-all"]',"#"+e+"-tabs").click(),a("#new"+e).focus()}),a("#"+e+"checklist, #"+e+"checklist-pop").on("click",'li.popular-category > label input[type="checkbox"]',function(){var b=a(this),c=b.is(":checked"),d=b.val();d&&b.parents("#taxonomy-"+e).length&&a("#in-"+e+"-"+d+", #in-popular-"+e+"-"+d).prop("checked",c)})}),a("#postcustom").length&&a("#the-list").wpList({addBefore:function(b){return b.data+="&post_id="+a("#post_ID").val(),b},addAfter:function(){a("table#list-table").show()}}),a("#submitdiv").length&&(c=a("#timestamp").html(),d=a("#post-visibility-display").html(),f=function(){"public"!=n.find("input:radio:checked").val()?(a("#sticky").prop("checked",!1),a("#sticky-span").hide()):a("#sticky-span").show(),"password"!=n.find("input:radio:checked").val()?a("#password-span").hide():a("#password-span").show()},g=function(){if(!o.length)return!0;var b,d,e,f,g=a("#post_status"),h=a('option[value="publish"]',g),i=a("#aa").val(),j=a("#mm").val(),k=a("#jj").val(),l=a("#hh").val(),m=a("#mn").val();return b=new Date(i,j-1,k,l,m),d=new Date(a("#hidden_aa").val(),a("#hidden_mm").val()-1,a("#hidden_jj").val(),a("#hidden_hh").val(),a("#hidden_mn").val()),e=new Date(a("#cur_aa").val(),a("#cur_mm").val()-1,a("#cur_jj").val(),a("#cur_hh").val(),a("#cur_mn").val()),b.getFullYear()!=i||1+b.getMonth()!=j||b.getDate()!=k||b.getMinutes()!=m?(o.find(".timestamp-wrap").addClass("form-invalid"),!1):(o.find(".timestamp-wrap").removeClass("form-invalid"),b>e&&"future"!=a("#original_post_status").val()?(f=postL10n.publishOnFuture,a("#publish").val(postL10n.schedule)):b<=e&&"publish"!=a("#original_post_status").val()?(f=postL10n.publishOn,a("#publish").val(postL10n.publish)):(f=postL10n.publishOnPast,a("#publish").val(postL10n.update)),d.toUTCString()==b.toUTCString()?a("#timestamp").html(c):a("#timestamp").html("\n"+f+" <b>"+postL10n.dateFormat.replace("%1$s",a('option[value="'+j+'"]',"#mm").attr("data-text")).replace("%2$s",parseInt(k,10)).replace("%3$s",i).replace("%4$s",("00"+l).slice(-2)).replace("%5$s",("00"+m).slice(-2))+"</b> "),"private"==n.find("input:radio:checked").val()?(a("#publish").val(postL10n.update),0===h.length?g.append('<option value="publish">'+postL10n.privatelyPublished+"</option>"):h.html(postL10n.privatelyPublished),a('option[value="publish"]',g).prop("selected",!0),a("#misc-publishing-actions .edit-post-status").hide()):("future"==a("#original_post_status").val()||"draft"==a("#original_post_status").val()?h.length&&(h.remove(),g.val(a("#hidden_post_status").val())):h.html(postL10n.published),g.is(":hidden")&&a("#misc-publishing-actions .edit-post-status").show()),a("#post-status-display").text(wp.sanitize.stripTagsAndEncodeText(a("option:selected",g).text())),"private"==a("option:selected",g).val()||"publish"==a("option:selected",g).val()?a("#save-post").hide():(a("#save-post").show(),"pending"==a("option:selected",g).val()?a("#save-post").show().val(postL10n.savePending):a("#save-post").show().val(postL10n.saveDraft)),!0)},a("#visibility .edit-visibility").click(function(b){b.preventDefault(),n.is(":hidden")&&(f(),n.slideDown("fast",function(){n.find('input[type="radio"]').first().focus()}),a(this).hide())}),n.find(".cancel-post-visibility").click(function(b){n.slideUp("fast"),a("#visibility-radio-"+a("#hidden-post-visibility").val()).prop("checked",!0),a("#post_password").val(a("#hidden-post-password").val()),a("#sticky").prop("checked",a("#hidden-post-sticky").prop("checked")),a("#post-visibility-display").html(d),a("#visibility .edit-visibility").show().focus(),g(),b.preventDefault()}),n.find(".save-post-visibility").click(function(b){n.slideUp("fast"),a("#visibility .edit-visibility").show().focus(),g(),"public"!=n.find("input:radio:checked").val()&&a("#sticky").prop("checked",!1),h=a("#sticky").prop("checked")?"Sticky":"",a("#post-visibility-display").html(postL10n[n.find("input:radio:checked").val()+h]),b.preventDefault()}),n.find("input:radio").change(function(){f()}),o.siblings("a.edit-timestamp").click(function(b){o.is(":hidden")&&(o.slideDown("fast",function(){a("input, select",o.find(".timestamp-wrap")).first().focus()}),a(this).hide()),b.preventDefault()}),o.find(".cancel-timestamp").click(function(b){o.slideUp("fast").siblings("a.edit-timestamp").show().focus(),a("#mm").val(a("#hidden_mm").val()),a("#jj").val(a("#hidden_jj").val()),a("#aa").val(a("#hidden_aa").val()),a("#hh").val(a("#hidden_hh").val()),a("#mn").val(a("#hidden_mn").val()),g(),b.preventDefault()}),o.find(".save-timestamp").click(function(a){g()&&(o.slideUp("fast"),o.siblings("a.edit-timestamp").show().focus()),a.preventDefault()}),a("#post").on("submit",function(b){g()||(b.preventDefault(),o.show(),wp.autosave&&wp.autosave.enableButtons(),a("#publishing-action .spinner").removeClass("is-active"))}),p.siblings("a.edit-post-status").click(function(b){p.is(":hidden")&&(p.slideDown("fast",function(){p.find("select").focus()}),a(this).hide()),b.preventDefault()}),p.find(".save-post-status").click(function(a){p.slideUp("fast").siblings("a.edit-post-status").show().focus(),g(),a.preventDefault()}),p.find(".cancel-post-status").click(function(b){p.slideUp("fast").siblings("a.edit-post-status").show().focus(),a("#post_status").val(a("#hidden_post_status").val()),g(),b.preventDefault()})),a("#titlediv").on("click",".edit-slug",function(){b()}),window.wptitlehint=function(b){b=b||"title";var c=a("#"+b),d=a("#"+b+"-prompt-text");""===c.val()&&d.removeClass("screen-reader-text"),c.on("input",function(){return""===this.value?void d.removeClass("screen-reader-text"):void d.addClass("screen-reader-text")})},wptitlehint(),function(){function b(a){h.hasClass("wp-editor-expand")||(f?d.theme.resizeTo(null,e+a.pageY):i.height(Math.max(50,e+a.pageY)),a.preventDefault())}function c(){var b,c;h.hasClass("wp-editor-expand")||(f?(d.focus(),c=parseInt(a("#wp-content-editor-container .mce-toolbar-grp").height(),10),(c<10||c>200)&&(c=30),b=parseInt(a("#content_ifr").css("height"),10)+c-28):(i.focus(),b=parseInt(i.css("height"),10)),j.off(".wp-editor-resize"),b&&b>50&&b<5e3&&setUserSetting("ed_size",b))}var d,e,f,g=a("#post-status-info"),h=a("#postdivrich");return!i.length||"ontouchstart"in window?void a("#content-resize-handle").hide():void g.on("mousedown.wp-editor-resize",function(g){"undefined"!=typeof tinymce&&(d=tinymce.get("content")),d&&!d.isHidden()?(f=!0,e=a("#content_ifr").height()-g.pageY):(f=!1,e=i.height()-g.pageY,i.blur()),j.on("mousemove.wp-editor-resize",b).on("mouseup.wp-editor-resize mouseleave.wp-editor-resize",c),g.preventDefault()}).on("mouseup.wp-editor-resize",c)}(),"undefined"!=typeof tinymce&&(a("#post-formats-select input.post-format").on("change.set-editor-class",function(){var b,c,d=this.id;d&&a(this).prop("checked")&&(b=tinymce.get("content"))&&(c=b.getBody(),c.className=c.className.replace(/\bpost-format-[^ ]+/,""),b.dom.addClass(c,"post-format-0"==d?"post-format-standard":d),a(document).trigger("editor-classchange"))}),a("#page_template").on("change.set-editor-class",function(){var b,c,d=a(this).val()||"";d=d.substr(d.lastIndexOf("/")+1,d.length).replace(/\.php$/,"").replace(/\./g,"-"),d&&(b=tinymce.get("content"))&&(c=b.getBody(),c.className=c.className.replace(/\bpage-template-[^ ]+/,""),b.dom.addClass(c,"page-template-"+d),a(document).trigger("editor-classchange"))})),i.on("keydown.wp-autosave",function(a){if(83===a.which){if(a.shiftKey||a.altKey||q&&(!a.metaKey||a.ctrlKey)||!q&&!a.ctrlKey)return;wp.autosave&&wp.autosave.server.triggerSave(),a.preventDefault()}}),"auto-draft"===a("#original_post_status").val()&&window.history.replaceState){var r;a("#publish").on("click",function(){r=window.location.href,r+=r.indexOf("?")!==-1?"&":"?",r+="wp-post-new-reload=true",window.history.replaceState(null,null,r)})}}),function(a,b){a(function(){function c(){var a,c;a=!d||d.isHidden()?e.val():d.getContent({format:"raw"}),c=b.count(a),c!==g&&f.text(c),g=c}var d,e=a("#content"),f=a("#wp-word-count").find(".word-count"),g=0;a(document).on("tinymce-editor-init",function(a,b){"content"===b.id&&(d=b,b.on("nodechange keyup",_.debounce(c,1e3)))}),e.on("input keyup",_.debounce(c,1e3)),c()})}(jQuery,new wp.utils.WordCounter); \ No newline at end of file
diff --git a/www/crm/wp-admin/js/postbox.js b/www/crm/wp-admin/js/postbox.js
new file mode 100644
index 00000000..88facb1a
--- /dev/null
+++ b/www/crm/wp-admin/js/postbox.js
@@ -0,0 +1,445 @@
+/**
+ * Contains the postboxes logic, opening and closing postboxes, reordering and saving
+ * the state and ordering to the database.
+ *
+ * @since 2.5.0
+ * @requires jQuery
+ * @output wp-admin/js/postbox.js
+ */
+
+/* global ajaxurl, postBoxL10n, postboxes */
+
+(function($) {
+ var $document = $( document );
+
+ /**
+ * This object contains all function to handle the behaviour of the post boxes. The post boxes are the boxes you see
+ * around the content on the edit page.
+ *
+ * @since 2.7.0
+ *
+ * @namespace postboxes
+ *
+ * @type {Object}
+ */
+ window.postboxes = {
+
+ /**
+ * Handles a click on either the postbox heading or the postbox open/close icon.
+ *
+ * Opens or closes the postbox. Expects `this` to equal the clicked element.
+ * Calls postboxes.pbshow if the postbox has been opened, calls postboxes.pbhide
+ * if the postbox has been closed.
+ *
+ * @since 4.4.0
+ * @memberof postboxes
+ * @fires postboxes#postbox-toggled
+ *
+ * @returns {void}
+ */
+ handle_click : function () {
+ var $el = $( this ),
+ p = $el.parent( '.postbox' ),
+ id = p.attr( 'id' ),
+ ariaExpandedValue;
+
+ if ( 'dashboard_browser_nag' === id ) {
+ return;
+ }
+
+ p.toggleClass( 'closed' );
+
+ ariaExpandedValue = ! p.hasClass( 'closed' );
+
+ if ( $el.hasClass( 'handlediv' ) ) {
+ // The handle button was clicked.
+ $el.attr( 'aria-expanded', ariaExpandedValue );
+ } else {
+ // The handle heading was clicked.
+ $el.closest( '.postbox' ).find( 'button.handlediv' )
+ .attr( 'aria-expanded', ariaExpandedValue );
+ }
+
+ if ( postboxes.page !== 'press-this' ) {
+ postboxes.save_state( postboxes.page );
+ }
+
+ if ( id ) {
+ if ( !p.hasClass('closed') && $.isFunction( postboxes.pbshow ) ) {
+ postboxes.pbshow( id );
+ } else if ( p.hasClass('closed') && $.isFunction( postboxes.pbhide ) ) {
+ postboxes.pbhide( id );
+ }
+ }
+
+ /**
+ * Fires when a postbox has been opened or closed.
+ *
+ * Contains a jQuery object with the relevant postbox element.
+ *
+ * @since 4.0.0
+ * @ignore
+ *
+ * @event postboxes#postbox-toggled
+ * @type {Object}
+ */
+ $document.trigger( 'postbox-toggled', p );
+ },
+
+ /**
+ * Adds event handlers to all postboxes and screen option on the current page.
+ *
+ * @since 2.7.0
+ * @memberof postboxes
+ *
+ * @param {string} page The page we are currently on.
+ * @param {Object} [args]
+ * @param {Function} args.pbshow A callback that is called when a postbox opens.
+ * @param {Function} args.pbhide A callback that is called when a postbox closes.
+ * @returns {void}
+ */
+ add_postbox_toggles : function (page, args) {
+ var $handles = $( '.postbox .hndle, .postbox .handlediv' );
+
+ this.page = page;
+ this.init( page, args );
+
+ $handles.on( 'click.postboxes', this.handle_click );
+
+ /**
+ * @since 2.7.0
+ */
+ $('.postbox .hndle a').click( function(e) {
+ e.stopPropagation();
+ });
+
+ /**
+ * Hides a postbox.
+ *
+ * Event handler for the postbox dismiss button. After clicking the button
+ * the postbox will be hidden.
+ *
+ * @since 3.2.0
+ *
+ * @returns {void}
+ */
+ $( '.postbox a.dismiss' ).on( 'click.postboxes', function( e ) {
+ var hide_id = $(this).parents('.postbox').attr('id') + '-hide';
+ e.preventDefault();
+ $( '#' + hide_id ).prop('checked', false).triggerHandler('click');
+ });
+
+ /**
+ * Hides the postbox element
+ *
+ * Event handler for the screen options checkboxes. When a checkbox is
+ * clicked this function will hide or show the relevant postboxes.
+ *
+ * @since 2.7.0
+ * @ignore
+ *
+ * @fires postboxes#postbox-toggled
+ *
+ * @returns {void}
+ */
+ $('.hide-postbox-tog').bind('click.postboxes', function() {
+ var $el = $(this),
+ boxId = $el.val(),
+ $postbox = $( '#' + boxId );
+
+ if ( $el.prop( 'checked' ) ) {
+ $postbox.show();
+ if ( $.isFunction( postboxes.pbshow ) ) {
+ postboxes.pbshow( boxId );
+ }
+ } else {
+ $postbox.hide();
+ if ( $.isFunction( postboxes.pbhide ) ) {
+ postboxes.pbhide( boxId );
+ }
+ }
+
+ postboxes.save_state( page );
+ postboxes._mark_area();
+
+ /**
+ * @since 4.0.0
+ * @see postboxes.handle_click
+ */
+ $document.trigger( 'postbox-toggled', $postbox );
+ });
+
+ /**
+ * Changes the amount of columns based on the layout preferences.
+ *
+ * @since 2.8.0
+ *
+ * @returns {void}
+ */
+ $('.columns-prefs input[type="radio"]').bind('click.postboxes', function(){
+ var n = parseInt($(this).val(), 10);
+
+ if ( n ) {
+ postboxes._pb_edit(n);
+ postboxes.save_order( page );
+ }
+ });
+ },
+
+ /**
+ * Initializes all the postboxes, mainly their sortable behaviour.
+ *
+ * @since 2.7.0
+ * @memberof postboxes
+ *
+ * @param {string} page The page we are currently on.
+ * @param {Object} [args={}] The arguments for the postbox initializer.
+ * @param {Function} args.pbshow A callback that is called when a postbox opens.
+ * @param {Function} args.pbhide A callback that is called when a postbox
+ * closes.
+ *
+ * @returns {void}
+ */
+ init : function(page, args) {
+ var isMobile = $( document.body ).hasClass( 'mobile' ),
+ $handleButtons = $( '.postbox .handlediv' );
+
+ $.extend( this, args || {} );
+ $('#wpbody-content').css('overflow','hidden');
+ $('.meta-box-sortables').sortable({
+ placeholder: 'sortable-placeholder',
+ connectWith: '.meta-box-sortables',
+ items: '.postbox',
+ handle: '.hndle',
+ cursor: 'move',
+ delay: ( isMobile ? 200 : 0 ),
+ distance: 2,
+ tolerance: 'pointer',
+ forcePlaceholderSize: true,
+ helper: function( event, element ) {
+ /* `helper: 'clone'` is equivalent to `return element.clone();`
+ * Cloning a checked radio and then inserting that clone next to the original
+ * radio unchecks the original radio (since only one of the two can be checked).
+ * We get around this by renaming the helper's inputs' name attributes so that,
+ * when the helper is inserted into the DOM for the sortable, no radios are
+ * duplicated, and no original radio gets unchecked.
+ */
+ return element.clone()
+ .find( ':input' )
+ .attr( 'name', function( i, currentName ) {
+ return 'sort_' + parseInt( Math.random() * 100000, 10 ).toString() + '_' + currentName;
+ } )
+ .end();
+ },
+ opacity: 0.65,
+ stop: function() {
+ var $el = $( this );
+
+ if ( $el.find( '#dashboard_browser_nag' ).is( ':visible' ) && 'dashboard_browser_nag' != this.firstChild.id ) {
+ $el.sortable('cancel');
+ return;
+ }
+
+ postboxes.save_order(page);
+ },
+ receive: function(e,ui) {
+ if ( 'dashboard_browser_nag' == ui.item[0].id )
+ $(ui.sender).sortable('cancel');
+
+ postboxes._mark_area();
+ $document.trigger( 'postbox-moved', ui.item );
+ }
+ });
+
+ if ( isMobile ) {
+ $(document.body).bind('orientationchange.postboxes', function(){ postboxes._pb_change(); });
+ this._pb_change();
+ }
+
+ this._mark_area();
+
+ // Set the handle buttons `aria-expanded` attribute initial value on page load.
+ $handleButtons.each( function () {
+ var $el = $( this );
+ $el.attr( 'aria-expanded', ! $el.parent( '.postbox' ).hasClass( 'closed' ) );
+ });
+ },
+
+ /**
+ * Saves the state of the postboxes to the server.
+ *
+ * It sends two lists, one with all the closed postboxes, one with all the
+ * hidden postboxes.
+ *
+ * @since 2.7.0
+ * @memberof postboxes
+ *
+ * @param {string} page The page we are currently on.
+ * @returns {void}
+ */
+ save_state : function(page) {
+ var closed, hidden;
+
+ // Return on the nav-menus.php screen, see #35112.
+ if ( 'nav-menus' === page ) {
+ return;
+ }
+
+ closed = $( '.postbox' ).filter( '.closed' ).map( function() { return this.id; } ).get().join( ',' );
+ hidden = $( '.postbox' ).filter( ':hidden' ).map( function() { return this.id; } ).get().join( ',' );
+
+ $.post(ajaxurl, {
+ action: 'closed-postboxes',
+ closed: closed,
+ hidden: hidden,
+ closedpostboxesnonce: jQuery('#closedpostboxesnonce').val(),
+ page: page
+ });
+ },
+
+ /**
+ * Saves the order of the postboxes to the server.
+ *
+ * Sends a list of all postboxes inside a sortable area to the server.
+ *
+ * @since 2.8.0
+ * @memberof postboxes
+ *
+ * @param {string} page The page we are currently on.
+ * @returns {void}
+ */
+ save_order : function(page) {
+ var postVars, page_columns = $('.columns-prefs input:checked').val() || 0;
+
+ postVars = {
+ action: 'meta-box-order',
+ _ajax_nonce: $('#meta-box-order-nonce').val(),
+ page_columns: page_columns,
+ page: page
+ };
+
+ $('.meta-box-sortables').each( function() {
+ postVars[ 'order[' + this.id.split( '-' )[0] + ']' ] = $( this ).sortable( 'toArray' ).join( ',' );
+ } );
+
+ $.post( ajaxurl, postVars );
+ },
+
+ /**
+ * Marks empty postbox areas.
+ *
+ * Adds a message to empty sortable areas on the dashboard page. Also adds a
+ * border around the side area on the post edit screen if there are no postboxes
+ * present.
+ *
+ * @since 3.3.0
+ * @memberof postboxes
+ * @access private
+ *
+ * @returns {void}
+ */
+ _mark_area : function() {
+ var visible = $('div.postbox:visible').length, side = $('#post-body #side-sortables');
+
+ $( '#dashboard-widgets .meta-box-sortables:visible' ).each( function() {
+ var t = $(this);
+
+ if ( visible == 1 || t.children('.postbox:visible').length ) {
+ t.removeClass('empty-container');
+ }
+ else {
+ t.addClass('empty-container');
+ t.attr('data-emptyString', postBoxL10n.postBoxEmptyString);
+ }
+ });
+
+ if ( side.length ) {
+ if ( side.children('.postbox:visible').length )
+ side.removeClass('empty-container');
+ else if ( $('#postbox-container-1').css('width') == '280px' )
+ side.addClass('empty-container');
+ }
+ },
+
+ /**
+ * Changes the amount of columns on the post edit page.
+ *
+ * @since 3.3.0
+ * @memberof postboxes
+ * @fires postboxes#postboxes-columnchange
+ * @access private
+ *
+ * @param {number} n The amount of columns to divide the post edit page in.
+ * @returns {void}
+ */
+ _pb_edit : function(n) {
+ var el = $('.metabox-holder').get(0);
+
+ if ( el ) {
+ el.className = el.className.replace(/columns-\d+/, 'columns-' + n);
+ }
+
+ /**
+ * Fires when the amount of columns on the post edit page has been changed.
+ *
+ * @since 4.0.0
+ * @ignore
+ *
+ * @event postboxes#postboxes-columnchange
+ */
+ $( document ).trigger( 'postboxes-columnchange' );
+ },
+
+ /**
+ * Changes the amount of columns the postboxes are in based on the current
+ * orientation of the browser.
+ *
+ * @since 3.3.0
+ * @memberof postboxes
+ * @access private
+ *
+ * @returns {void}
+ */
+ _pb_change : function() {
+ var check = $( 'label.columns-prefs-1 input[type="radio"]' );
+
+ switch ( window.orientation ) {
+ case 90:
+ case -90:
+ if ( !check.length || !check.is(':checked') )
+ this._pb_edit(2);
+ break;
+ case 0:
+ case 180:
+ if ( $('#poststuff').length ) {
+ this._pb_edit(1);
+ } else {
+ if ( !check.length || !check.is(':checked') )
+ this._pb_edit(2);
+ }
+ break;
+ }
+ },
+
+ /* Callbacks */
+
+ /**
+ * @since 2.7.0
+ * @memberof postboxes
+ * @access public
+ * @property {Function|boolean} pbshow A callback that is called when a postbox
+ * is opened.
+ */
+ pbshow : false,
+
+ /**
+ * @since 2.7.0
+ * @memberof postboxes
+ * @access public
+ * @property {Function|boolean} pbhide A callback that is called when a postbox
+ * is closed.
+ */
+ pbhide : false
+ };
+
+}(jQuery));
diff --git a/www/crm/wp-admin/js/postbox.min.js b/www/crm/wp-admin/js/postbox.min.js
new file mode 100644
index 00000000..c0634d18
--- /dev/null
+++ b/www/crm/wp-admin/js/postbox.min.js
@@ -0,0 +1 @@
+!function(a){var b=a(document);window.postboxes={handle_click:function(){var c,d=a(this),e=d.parent(".postbox"),f=e.attr("id");"dashboard_browser_nag"!==f&&(e.toggleClass("closed"),c=!e.hasClass("closed"),d.hasClass("handlediv")?d.attr("aria-expanded",c):d.closest(".postbox").find("button.handlediv").attr("aria-expanded",c),"press-this"!==postboxes.page&&postboxes.save_state(postboxes.page),f&&(!e.hasClass("closed")&&a.isFunction(postboxes.pbshow)?postboxes.pbshow(f):e.hasClass("closed")&&a.isFunction(postboxes.pbhide)&&postboxes.pbhide(f)),b.trigger("postbox-toggled",e))},add_postbox_toggles:function(c,d){var e=a(".postbox .hndle, .postbox .handlediv");this.page=c,this.init(c,d),e.on("click.postboxes",this.handle_click),a(".postbox .hndle a").click(function(a){a.stopPropagation()}),a(".postbox a.dismiss").on("click.postboxes",function(b){var c=a(this).parents(".postbox").attr("id")+"-hide";b.preventDefault(),a("#"+c).prop("checked",!1).triggerHandler("click")}),a(".hide-postbox-tog").bind("click.postboxes",function(){var d=a(this),e=d.val(),f=a("#"+e);d.prop("checked")?(f.show(),a.isFunction(postboxes.pbshow)&&postboxes.pbshow(e)):(f.hide(),a.isFunction(postboxes.pbhide)&&postboxes.pbhide(e)),postboxes.save_state(c),postboxes._mark_area(),b.trigger("postbox-toggled",f)}),a('.columns-prefs input[type="radio"]').bind("click.postboxes",function(){var b=parseInt(a(this).val(),10);b&&(postboxes._pb_edit(b),postboxes.save_order(c))})},init:function(c,d){var e=a(document.body).hasClass("mobile"),f=a(".postbox .handlediv");a.extend(this,d||{}),a("#wpbody-content").css("overflow","hidden"),a(".meta-box-sortables").sortable({placeholder:"sortable-placeholder",connectWith:".meta-box-sortables",items:".postbox",handle:".hndle",cursor:"move",delay:e?200:0,distance:2,tolerance:"pointer",forcePlaceholderSize:!0,helper:function(a,b){return b.clone().find(":input").attr("name",function(a,b){return"sort_"+parseInt(1e5*Math.random(),10).toString()+"_"+b}).end()},opacity:.65,stop:function(){var b=a(this);return b.find("#dashboard_browser_nag").is(":visible")&&"dashboard_browser_nag"!=this.firstChild.id?void b.sortable("cancel"):void postboxes.save_order(c)},receive:function(c,d){"dashboard_browser_nag"==d.item[0].id&&a(d.sender).sortable("cancel"),postboxes._mark_area(),b.trigger("postbox-moved",d.item)}}),e&&(a(document.body).bind("orientationchange.postboxes",function(){postboxes._pb_change()}),this._pb_change()),this._mark_area(),f.each(function(){var b=a(this);b.attr("aria-expanded",!b.parent(".postbox").hasClass("closed"))})},save_state:function(b){var c,d;"nav-menus"!==b&&(c=a(".postbox").filter(".closed").map(function(){return this.id}).get().join(","),d=a(".postbox").filter(":hidden").map(function(){return this.id}).get().join(","),a.post(ajaxurl,{action:"closed-postboxes",closed:c,hidden:d,closedpostboxesnonce:jQuery("#closedpostboxesnonce").val(),page:b}))},save_order:function(b){var c,d=a(".columns-prefs input:checked").val()||0;c={action:"meta-box-order",_ajax_nonce:a("#meta-box-order-nonce").val(),page_columns:d,page:b},a(".meta-box-sortables").each(function(){c["order["+this.id.split("-")[0]+"]"]=a(this).sortable("toArray").join(",")}),a.post(ajaxurl,c)},_mark_area:function(){var b=a("div.postbox:visible").length,c=a("#post-body #side-sortables");a("#dashboard-widgets .meta-box-sortables:visible").each(function(){var c=a(this);1==b||c.children(".postbox:visible").length?c.removeClass("empty-container"):(c.addClass("empty-container"),c.attr("data-emptyString",postBoxL10n.postBoxEmptyString))}),c.length&&(c.children(".postbox:visible").length?c.removeClass("empty-container"):"280px"==a("#postbox-container-1").css("width")&&c.addClass("empty-container"))},_pb_edit:function(b){var c=a(".metabox-holder").get(0);c&&(c.className=c.className.replace(/columns-\d+/,"columns-"+b)),a(document).trigger("postboxes-columnchange")},_pb_change:function(){var b=a('label.columns-prefs-1 input[type="radio"]');switch(window.orientation){case 90:case-90:b.length&&b.is(":checked")||this._pb_edit(2);break;case 0:case 180:a("#poststuff").length?this._pb_edit(1):b.length&&b.is(":checked")||this._pb_edit(2)}},pbshow:!1,pbhide:!1}}(jQuery); \ No newline at end of file
diff --git a/www/crm/wp-admin/js/revisions.js b/www/crm/wp-admin/js/revisions.js
new file mode 100644
index 00000000..adf58651
--- /dev/null
+++ b/www/crm/wp-admin/js/revisions.js
@@ -0,0 +1,1171 @@
+/**
+ * @file Revisions interface functions, Backbone classes and
+ * the revisions.php document.ready bootstrap.
+ *
+ * @output wp-admin/js/revisions.js
+ */
+
+/* global isRtl */
+
+window.wp = window.wp || {};
+
+(function($) {
+ var revisions;
+ /**
+ * Expose the module in window.wp.revisions.
+ */
+ revisions = wp.revisions = { model: {}, view: {}, controller: {} };
+
+ // Link post revisions data served from the back end.
+ revisions.settings = window._wpRevisionsSettings || {};
+
+ // For debugging
+ revisions.debug = false;
+
+ /**
+ * wp.revisions.log
+ *
+ * A debugging utility for revisions. Works only when a
+ * debug flag is on and the browser supports it.
+ */
+ revisions.log = function() {
+ if ( window.console && revisions.debug ) {
+ window.console.log.apply( window.console, arguments );
+ }
+ };
+
+ // Handy functions to help with positioning
+ $.fn.allOffsets = function() {
+ var offset = this.offset() || {top: 0, left: 0}, win = $(window);
+ return _.extend( offset, {
+ right: win.width() - offset.left - this.outerWidth(),
+ bottom: win.height() - offset.top - this.outerHeight()
+ });
+ };
+
+ $.fn.allPositions = function() {
+ var position = this.position() || {top: 0, left: 0}, parent = this.parent();
+ return _.extend( position, {
+ right: parent.outerWidth() - position.left - this.outerWidth(),
+ bottom: parent.outerHeight() - position.top - this.outerHeight()
+ });
+ };
+
+ /**
+ * ========================================================================
+ * MODELS
+ * ========================================================================
+ */
+ revisions.model.Slider = Backbone.Model.extend({
+ defaults: {
+ value: null,
+ values: null,
+ min: 0,
+ max: 1,
+ step: 1,
+ range: false,
+ compareTwoMode: false
+ },
+
+ initialize: function( options ) {
+ this.frame = options.frame;
+ this.revisions = options.revisions;
+
+ // Listen for changes to the revisions or mode from outside
+ this.listenTo( this.frame, 'update:revisions', this.receiveRevisions );
+ this.listenTo( this.frame, 'change:compareTwoMode', this.updateMode );
+
+ // Listen for internal changes
+ this.on( 'change:from', this.handleLocalChanges );
+ this.on( 'change:to', this.handleLocalChanges );
+ this.on( 'change:compareTwoMode', this.updateSliderSettings );
+ this.on( 'update:revisions', this.updateSliderSettings );
+
+ // Listen for changes to the hovered revision
+ this.on( 'change:hoveredRevision', this.hoverRevision );
+
+ this.set({
+ max: this.revisions.length - 1,
+ compareTwoMode: this.frame.get('compareTwoMode'),
+ from: this.frame.get('from'),
+ to: this.frame.get('to')
+ });
+ this.updateSliderSettings();
+ },
+
+ getSliderValue: function( a, b ) {
+ return isRtl ? this.revisions.length - this.revisions.indexOf( this.get(a) ) - 1 : this.revisions.indexOf( this.get(b) );
+ },
+
+ updateSliderSettings: function() {
+ if ( this.get('compareTwoMode') ) {
+ this.set({
+ values: [
+ this.getSliderValue( 'to', 'from' ),
+ this.getSliderValue( 'from', 'to' )
+ ],
+ value: null,
+ range: true // ensures handles cannot cross
+ });
+ } else {
+ this.set({
+ value: this.getSliderValue( 'to', 'to' ),
+ values: null,
+ range: false
+ });
+ }
+ this.trigger( 'update:slider' );
+ },
+
+ // Called when a revision is hovered
+ hoverRevision: function( model, value ) {
+ this.trigger( 'hovered:revision', value );
+ },
+
+ // Called when `compareTwoMode` changes
+ updateMode: function( model, value ) {
+ this.set({ compareTwoMode: value });
+ },
+
+ // Called when `from` or `to` changes in the local model
+ handleLocalChanges: function() {
+ this.frame.set({
+ from: this.get('from'),
+ to: this.get('to')
+ });
+ },
+
+ // Receives revisions changes from outside the model
+ receiveRevisions: function( from, to ) {
+ // Bail if nothing changed
+ if ( this.get('from') === from && this.get('to') === to ) {
+ return;
+ }
+
+ this.set({ from: from, to: to }, { silent: true });
+ this.trigger( 'update:revisions', from, to );
+ }
+
+ });
+
+ revisions.model.Tooltip = Backbone.Model.extend({
+ defaults: {
+ revision: null,
+ offset: {},
+ hovering: false, // Whether the mouse is hovering
+ scrubbing: false // Whether the mouse is scrubbing
+ },
+
+ initialize: function( options ) {
+ this.frame = options.frame;
+ this.revisions = options.revisions;
+ this.slider = options.slider;
+
+ this.listenTo( this.slider, 'hovered:revision', this.updateRevision );
+ this.listenTo( this.slider, 'change:hovering', this.setHovering );
+ this.listenTo( this.slider, 'change:scrubbing', this.setScrubbing );
+ },
+
+
+ updateRevision: function( revision ) {
+ this.set({ revision: revision });
+ },
+
+ setHovering: function( model, value ) {
+ this.set({ hovering: value });
+ },
+
+ setScrubbing: function( model, value ) {
+ this.set({ scrubbing: value });
+ }
+ });
+
+ revisions.model.Revision = Backbone.Model.extend({});
+
+ /**
+ * wp.revisions.model.Revisions
+ *
+ * A collection of post revisions.
+ */
+ revisions.model.Revisions = Backbone.Collection.extend({
+ model: revisions.model.Revision,
+
+ initialize: function() {
+ _.bindAll( this, 'next', 'prev' );
+ },
+
+ next: function( revision ) {
+ var index = this.indexOf( revision );
+
+ if ( index !== -1 && index !== this.length - 1 ) {
+ return this.at( index + 1 );
+ }
+ },
+
+ prev: function( revision ) {
+ var index = this.indexOf( revision );
+
+ if ( index !== -1 && index !== 0 ) {
+ return this.at( index - 1 );
+ }
+ }
+ });
+
+ revisions.model.Field = Backbone.Model.extend({});
+
+ revisions.model.Fields = Backbone.Collection.extend({
+ model: revisions.model.Field
+ });
+
+ revisions.model.Diff = Backbone.Model.extend({
+ initialize: function() {
+ var fields = this.get('fields');
+ this.unset('fields');
+
+ this.fields = new revisions.model.Fields( fields );
+ }
+ });
+
+ revisions.model.Diffs = Backbone.Collection.extend({
+ initialize: function( models, options ) {
+ _.bindAll( this, 'getClosestUnloaded' );
+ this.loadAll = _.once( this._loadAll );
+ this.revisions = options.revisions;
+ this.postId = options.postId;
+ this.requests = {};
+ },
+
+ model: revisions.model.Diff,
+
+ ensure: function( id, context ) {
+ var diff = this.get( id ),
+ request = this.requests[ id ],
+ deferred = $.Deferred(),
+ ids = {},
+ from = id.split(':')[0],
+ to = id.split(':')[1];
+ ids[id] = true;
+
+ wp.revisions.log( 'ensure', id );
+
+ this.trigger( 'ensure', ids, from, to, deferred.promise() );
+
+ if ( diff ) {
+ deferred.resolveWith( context, [ diff ] );
+ } else {
+ this.trigger( 'ensure:load', ids, from, to, deferred.promise() );
+ _.each( ids, _.bind( function( id ) {
+ // Remove anything that has an ongoing request
+ if ( this.requests[ id ] ) {
+ delete ids[ id ];
+ }
+ // Remove anything we already have
+ if ( this.get( id ) ) {
+ delete ids[ id ];
+ }
+ }, this ) );
+ if ( ! request ) {
+ // Always include the ID that started this ensure
+ ids[ id ] = true;
+ request = this.load( _.keys( ids ) );
+ }
+
+ request.done( _.bind( function() {
+ deferred.resolveWith( context, [ this.get( id ) ] );
+ }, this ) ).fail( _.bind( function() {
+ deferred.reject();
+ }) );
+ }
+
+ return deferred.promise();
+ },
+
+ // Returns an array of proximal diffs
+ getClosestUnloaded: function( ids, centerId ) {
+ var self = this;
+ return _.chain([0].concat( ids )).initial().zip( ids ).sortBy( function( pair ) {
+ return Math.abs( centerId - pair[1] );
+ }).map( function( pair ) {
+ return pair.join(':');
+ }).filter( function( diffId ) {
+ return _.isUndefined( self.get( diffId ) ) && ! self.requests[ diffId ];
+ }).value();
+ },
+
+ _loadAll: function( allRevisionIds, centerId, num ) {
+ var self = this, deferred = $.Deferred(),
+ diffs = _.first( this.getClosestUnloaded( allRevisionIds, centerId ), num );
+ if ( _.size( diffs ) > 0 ) {
+ this.load( diffs ).done( function() {
+ self._loadAll( allRevisionIds, centerId, num ).done( function() {
+ deferred.resolve();
+ });
+ }).fail( function() {
+ if ( 1 === num ) { // Already tried 1. This just isn't working. Give up.
+ deferred.reject();
+ } else { // Request fewer diffs this time
+ self._loadAll( allRevisionIds, centerId, Math.ceil( num / 2 ) ).done( function() {
+ deferred.resolve();
+ });
+ }
+ });
+ } else {
+ deferred.resolve();
+ }
+ return deferred;
+ },
+
+ load: function( comparisons ) {
+ wp.revisions.log( 'load', comparisons );
+ // Our collection should only ever grow, never shrink, so remove: false
+ return this.fetch({ data: { compare: comparisons }, remove: false }).done( function() {
+ wp.revisions.log( 'load:complete', comparisons );
+ });
+ },
+
+ sync: function( method, model, options ) {
+ if ( 'read' === method ) {
+ options = options || {};
+ options.context = this;
+ options.data = _.extend( options.data || {}, {
+ action: 'get-revision-diffs',
+ post_id: this.postId
+ });
+
+ var deferred = wp.ajax.send( options ),
+ requests = this.requests;
+
+ // Record that we're requesting each diff.
+ if ( options.data.compare ) {
+ _.each( options.data.compare, function( id ) {
+ requests[ id ] = deferred;
+ });
+ }
+
+ // When the request completes, clear the stored request.
+ deferred.always( function() {
+ if ( options.data.compare ) {
+ _.each( options.data.compare, function( id ) {
+ delete requests[ id ];
+ });
+ }
+ });
+
+ return deferred;
+
+ // Otherwise, fall back to `Backbone.sync()`.
+ } else {
+ return Backbone.Model.prototype.sync.apply( this, arguments );
+ }
+ }
+ });
+
+
+ /**
+ * wp.revisions.model.FrameState
+ *
+ * The frame state.
+ *
+ * @see wp.revisions.view.Frame
+ *
+ * @param {object} attributes Model attributes - none are required.
+ * @param {object} options Options for the model.
+ * @param {revisions.model.Revisions} options.revisions A collection of revisions.
+ */
+ revisions.model.FrameState = Backbone.Model.extend({
+ defaults: {
+ loading: false,
+ error: false,
+ compareTwoMode: false
+ },
+
+ initialize: function( attributes, options ) {
+ var state = this.get( 'initialDiffState' );
+ _.bindAll( this, 'receiveDiff' );
+ this._debouncedEnsureDiff = _.debounce( this._ensureDiff, 200 );
+
+ this.revisions = options.revisions;
+
+ this.diffs = new revisions.model.Diffs( [], {
+ revisions: this.revisions,
+ postId: this.get( 'postId' )
+ } );
+
+ // Set the initial diffs collection.
+ this.diffs.set( this.get( 'diffData' ) );
+
+ // Set up internal listeners
+ this.listenTo( this, 'change:from', this.changeRevisionHandler );
+ this.listenTo( this, 'change:to', this.changeRevisionHandler );
+ this.listenTo( this, 'change:compareTwoMode', this.changeMode );
+ this.listenTo( this, 'update:revisions', this.updatedRevisions );
+ this.listenTo( this.diffs, 'ensure:load', this.updateLoadingStatus );
+ this.listenTo( this, 'update:diff', this.updateLoadingStatus );
+
+ // Set the initial revisions, baseUrl, and mode as provided through attributes.
+
+ this.set( {
+ to : this.revisions.get( state.to ),
+ from : this.revisions.get( state.from ),
+ compareTwoMode : state.compareTwoMode
+ } );
+
+ // Start the router if browser supports History API
+ if ( window.history && window.history.pushState ) {
+ this.router = new revisions.Router({ model: this });
+ if ( Backbone.History.started ) {
+ Backbone.history.stop();
+ }
+ Backbone.history.start({ pushState: true });
+ }
+ },
+
+ updateLoadingStatus: function() {
+ this.set( 'error', false );
+ this.set( 'loading', ! this.diff() );
+ },
+
+ changeMode: function( model, value ) {
+ var toIndex = this.revisions.indexOf( this.get( 'to' ) );
+
+ // If we were on the first revision before switching to two-handled mode,
+ // bump the 'to' position over one
+ if ( value && 0 === toIndex ) {
+ this.set({
+ from: this.revisions.at( toIndex ),
+ to: this.revisions.at( toIndex + 1 )
+ });
+ }
+
+ // When switching back to single-handled mode, reset 'from' model to
+ // one position before the 'to' model
+ if ( ! value && 0 !== toIndex ) { // '! value' means switching to single-handled mode
+ this.set({
+ from: this.revisions.at( toIndex - 1 ),
+ to: this.revisions.at( toIndex )
+ });
+ }
+ },
+
+ updatedRevisions: function( from, to ) {
+ if ( this.get( 'compareTwoMode' ) ) {
+ // TODO: compare-two loading strategy
+ } else {
+ this.diffs.loadAll( this.revisions.pluck('id'), to.id, 40 );
+ }
+ },
+
+ // Fetch the currently loaded diff.
+ diff: function() {
+ return this.diffs.get( this._diffId );
+ },
+
+ // So long as `from` and `to` are changed at the same time, the diff
+ // will only be updated once. This is because Backbone updates all of
+ // the changed attributes in `set`, and then fires the `change` events.
+ updateDiff: function( options ) {
+ var from, to, diffId, diff;
+
+ options = options || {};
+ from = this.get('from');
+ to = this.get('to');
+ diffId = ( from ? from.id : 0 ) + ':' + to.id;
+
+ // Check if we're actually changing the diff id.
+ if ( this._diffId === diffId ) {
+ return $.Deferred().reject().promise();
+ }
+
+ this._diffId = diffId;
+ this.trigger( 'update:revisions', from, to );
+
+ diff = this.diffs.get( diffId );
+
+ // If we already have the diff, then immediately trigger the update.
+ if ( diff ) {
+ this.receiveDiff( diff );
+ return $.Deferred().resolve().promise();
+ // Otherwise, fetch the diff.
+ } else {
+ if ( options.immediate ) {
+ return this._ensureDiff();
+ } else {
+ this._debouncedEnsureDiff();
+ return $.Deferred().reject().promise();
+ }
+ }
+ },
+
+ // A simple wrapper around `updateDiff` to prevent the change event's
+ // parameters from being passed through.
+ changeRevisionHandler: function() {
+ this.updateDiff();
+ },
+
+ receiveDiff: function( diff ) {
+ // Did we actually get a diff?
+ if ( _.isUndefined( diff ) || _.isUndefined( diff.id ) ) {
+ this.set({
+ loading: false,
+ error: true
+ });
+ } else if ( this._diffId === diff.id ) { // Make sure the current diff didn't change
+ this.trigger( 'update:diff', diff );
+ }
+ },
+
+ _ensureDiff: function() {
+ return this.diffs.ensure( this._diffId, this ).always( this.receiveDiff );
+ }
+ });
+
+
+ /**
+ * ========================================================================
+ * VIEWS
+ * ========================================================================
+ */
+
+ /**
+ * wp.revisions.view.Frame
+ *
+ * Top level frame that orchestrates the revisions experience.
+ *
+ * @param {object} options The options hash for the view.
+ * @param {revisions.model.FrameState} options.model The frame state model.
+ */
+ revisions.view.Frame = wp.Backbone.View.extend({
+ className: 'revisions',
+ template: wp.template('revisions-frame'),
+
+ initialize: function() {
+ this.listenTo( this.model, 'update:diff', this.renderDiff );
+ this.listenTo( this.model, 'change:compareTwoMode', this.updateCompareTwoMode );
+ this.listenTo( this.model, 'change:loading', this.updateLoadingStatus );
+ this.listenTo( this.model, 'change:error', this.updateErrorStatus );
+
+ this.views.set( '.revisions-control-frame', new revisions.view.Controls({
+ model: this.model
+ }) );
+ },
+
+ render: function() {
+ wp.Backbone.View.prototype.render.apply( this, arguments );
+
+ $('html').css( 'overflow-y', 'scroll' );
+ $('#wpbody-content .wrap').append( this.el );
+ this.updateCompareTwoMode();
+ this.renderDiff( this.model.diff() );
+ this.views.ready();
+
+ return this;
+ },
+
+ renderDiff: function( diff ) {
+ this.views.set( '.revisions-diff-frame', new revisions.view.Diff({
+ model: diff
+ }) );
+ },
+
+ updateLoadingStatus: function() {
+ this.$el.toggleClass( 'loading', this.model.get('loading') );
+ },
+
+ updateErrorStatus: function() {
+ this.$el.toggleClass( 'diff-error', this.model.get('error') );
+ },
+
+ updateCompareTwoMode: function() {
+ this.$el.toggleClass( 'comparing-two-revisions', this.model.get('compareTwoMode') );
+ }
+ });
+
+ /**
+ * wp.revisions.view.Controls
+ *
+ * The controls view.
+ *
+ * Contains the revision slider, previous/next buttons, the meta info and the compare checkbox.
+ */
+ revisions.view.Controls = wp.Backbone.View.extend({
+ className: 'revisions-controls',
+
+ initialize: function() {
+ _.bindAll( this, 'setWidth' );
+
+ // Add the button view
+ this.views.add( new revisions.view.Buttons({
+ model: this.model
+ }) );
+
+ // Add the checkbox view
+ this.views.add( new revisions.view.Checkbox({
+ model: this.model
+ }) );
+
+ // Prep the slider model
+ var slider = new revisions.model.Slider({
+ frame: this.model,
+ revisions: this.model.revisions
+ }),
+
+ // Prep the tooltip model
+ tooltip = new revisions.model.Tooltip({
+ frame: this.model,
+ revisions: this.model.revisions,
+ slider: slider
+ });
+
+ // Add the tooltip view
+ this.views.add( new revisions.view.Tooltip({
+ model: tooltip
+ }) );
+
+ // Add the tickmarks view
+ this.views.add( new revisions.view.Tickmarks({
+ model: tooltip
+ }) );
+
+ // Add the slider view
+ this.views.add( new revisions.view.Slider({
+ model: slider
+ }) );
+
+ // Add the Metabox view
+ this.views.add( new revisions.view.Metabox({
+ model: this.model
+ }) );
+ },
+
+ ready: function() {
+ this.top = this.$el.offset().top;
+ this.window = $(window);
+ this.window.on( 'scroll.wp.revisions', {controls: this}, function(e) {
+ var controls = e.data.controls,
+ container = controls.$el.parent(),
+ scrolled = controls.window.scrollTop(),
+ frame = controls.views.parent;
+
+ if ( scrolled >= controls.top ) {
+ if ( ! frame.$el.hasClass('pinned') ) {
+ controls.setWidth();
+ container.css('height', container.height() + 'px' );
+ controls.window.on('resize.wp.revisions.pinning click.wp.revisions.pinning', {controls: controls}, function(e) {
+ e.data.controls.setWidth();
+ });
+ }
+ frame.$el.addClass('pinned');
+ } else if ( frame.$el.hasClass('pinned') ) {
+ controls.window.off('.wp.revisions.pinning');
+ controls.$el.css('width', 'auto');
+ frame.$el.removeClass('pinned');
+ container.css('height', 'auto');
+ controls.top = controls.$el.offset().top;
+ } else {
+ controls.top = controls.$el.offset().top;
+ }
+ });
+ },
+
+ setWidth: function() {
+ this.$el.css('width', this.$el.parent().width() + 'px');
+ }
+ });
+
+ // The tickmarks view
+ revisions.view.Tickmarks = wp.Backbone.View.extend({
+ className: 'revisions-tickmarks',
+ direction: isRtl ? 'right' : 'left',
+
+ initialize: function() {
+ this.listenTo( this.model, 'change:revision', this.reportTickPosition );
+ },
+
+ reportTickPosition: function( model, revision ) {
+ var offset, thisOffset, parentOffset, tick, index = this.model.revisions.indexOf( revision );
+ thisOffset = this.$el.allOffsets();
+ parentOffset = this.$el.parent().allOffsets();
+ if ( index === this.model.revisions.length - 1 ) {
+ // Last one
+ offset = {
+ rightPlusWidth: thisOffset.left - parentOffset.left + 1,
+ leftPlusWidth: thisOffset.right - parentOffset.right + 1
+ };
+ } else {
+ // Normal tick
+ tick = this.$('div:nth-of-type(' + (index + 1) + ')');
+ offset = tick.allPositions();
+ _.extend( offset, {
+ left: offset.left + thisOffset.left - parentOffset.left,
+ right: offset.right + thisOffset.right - parentOffset.right
+ });
+ _.extend( offset, {
+ leftPlusWidth: offset.left + tick.outerWidth(),
+ rightPlusWidth: offset.right + tick.outerWidth()
+ });
+ }
+ this.model.set({ offset: offset });
+ },
+
+ ready: function() {
+ var tickCount, tickWidth;
+ tickCount = this.model.revisions.length - 1;
+ tickWidth = 1 / tickCount;
+ this.$el.css('width', ( this.model.revisions.length * 50 ) + 'px');
+
+ _(tickCount).times( function( index ){
+ this.$el.append( '<div style="' + this.direction + ': ' + ( 100 * tickWidth * index ) + '%"></div>' );
+ }, this );
+ }
+ });
+
+ // The metabox view
+ revisions.view.Metabox = wp.Backbone.View.extend({
+ className: 'revisions-meta',
+
+ initialize: function() {
+ // Add the 'from' view
+ this.views.add( new revisions.view.MetaFrom({
+ model: this.model,
+ className: 'diff-meta diff-meta-from'
+ }) );
+
+ // Add the 'to' view
+ this.views.add( new revisions.view.MetaTo({
+ model: this.model
+ }) );
+ }
+ });
+
+ // The revision meta view (to be extended)
+ revisions.view.Meta = wp.Backbone.View.extend({
+ template: wp.template('revisions-meta'),
+
+ events: {
+ 'click .restore-revision': 'restoreRevision'
+ },
+
+ initialize: function() {
+ this.listenTo( this.model, 'update:revisions', this.render );
+ },
+
+ prepare: function() {
+ return _.extend( this.model.toJSON()[this.type] || {}, {
+ type: this.type
+ });
+ },
+
+ restoreRevision: function() {
+ document.location = this.model.get('to').attributes.restoreUrl;
+ }
+ });
+
+ // The revision meta 'from' view
+ revisions.view.MetaFrom = revisions.view.Meta.extend({
+ className: 'diff-meta diff-meta-from',
+ type: 'from'
+ });
+
+ // The revision meta 'to' view
+ revisions.view.MetaTo = revisions.view.Meta.extend({
+ className: 'diff-meta diff-meta-to',
+ type: 'to'
+ });
+
+ // The checkbox view.
+ revisions.view.Checkbox = wp.Backbone.View.extend({
+ className: 'revisions-checkbox',
+ template: wp.template('revisions-checkbox'),
+
+ events: {
+ 'click .compare-two-revisions': 'compareTwoToggle'
+ },
+
+ initialize: function() {
+ this.listenTo( this.model, 'change:compareTwoMode', this.updateCompareTwoMode );
+ },
+
+ ready: function() {
+ if ( this.model.revisions.length < 3 ) {
+ $('.revision-toggle-compare-mode').hide();
+ }
+ },
+
+ updateCompareTwoMode: function() {
+ this.$('.compare-two-revisions').prop( 'checked', this.model.get('compareTwoMode') );
+ },
+
+ // Toggle the compare two mode feature when the compare two checkbox is checked.
+ compareTwoToggle: function() {
+ // Activate compare two mode?
+ this.model.set({ compareTwoMode: $('.compare-two-revisions').prop('checked') });
+ }
+ });
+
+ // The tooltip view.
+ // Encapsulates the tooltip.
+ revisions.view.Tooltip = wp.Backbone.View.extend({
+ className: 'revisions-tooltip',
+ template: wp.template('revisions-meta'),
+
+ initialize: function() {
+ this.listenTo( this.model, 'change:offset', this.render );
+ this.listenTo( this.model, 'change:hovering', this.toggleVisibility );
+ this.listenTo( this.model, 'change:scrubbing', this.toggleVisibility );
+ },
+
+ prepare: function() {
+ if ( _.isNull( this.model.get('revision') ) ) {
+ return;
+ } else {
+ return _.extend( { type: 'tooltip' }, {
+ attributes: this.model.get('revision').toJSON()
+ });
+ }
+ },
+
+ render: function() {
+ var otherDirection,
+ direction,
+ directionVal,
+ flipped,
+ css = {},
+ position = this.model.revisions.indexOf( this.model.get('revision') ) + 1;
+
+ flipped = ( position / this.model.revisions.length ) > 0.5;
+ if ( isRtl ) {
+ direction = flipped ? 'left' : 'right';
+ directionVal = flipped ? 'leftPlusWidth' : direction;
+ } else {
+ direction = flipped ? 'right' : 'left';
+ directionVal = flipped ? 'rightPlusWidth' : direction;
+ }
+ otherDirection = 'right' === direction ? 'left': 'right';
+ wp.Backbone.View.prototype.render.apply( this, arguments );
+ css[direction] = this.model.get('offset')[directionVal] + 'px';
+ css[otherDirection] = '';
+ this.$el.toggleClass( 'flipped', flipped ).css( css );
+ },
+
+ visible: function() {
+ return this.model.get( 'scrubbing' ) || this.model.get( 'hovering' );
+ },
+
+ toggleVisibility: function() {
+ if ( this.visible() ) {
+ this.$el.stop().show().fadeTo( 100 - this.el.style.opacity * 100, 1 );
+ } else {
+ this.$el.stop().fadeTo( this.el.style.opacity * 300, 0, function(){ $(this).hide(); } );
+ }
+ return;
+ }
+ });
+
+ // The buttons view.
+ // Encapsulates all of the configuration for the previous/next buttons.
+ revisions.view.Buttons = wp.Backbone.View.extend({
+ className: 'revisions-buttons',
+ template: wp.template('revisions-buttons'),
+
+ events: {
+ 'click .revisions-next .button': 'nextRevision',
+ 'click .revisions-previous .button': 'previousRevision'
+ },
+
+ initialize: function() {
+ this.listenTo( this.model, 'update:revisions', this.disabledButtonCheck );
+ },
+
+ ready: function() {
+ this.disabledButtonCheck();
+ },
+
+ // Go to a specific model index
+ gotoModel: function( toIndex ) {
+ var attributes = {
+ to: this.model.revisions.at( toIndex )
+ };
+ // If we're at the first revision, unset 'from'.
+ if ( toIndex ) {
+ attributes.from = this.model.revisions.at( toIndex - 1 );
+ } else {
+ this.model.unset('from', { silent: true });
+ }
+
+ this.model.set( attributes );
+ },
+
+ // Go to the 'next' revision
+ nextRevision: function() {
+ var toIndex = this.model.revisions.indexOf( this.model.get('to') ) + 1;
+ this.gotoModel( toIndex );
+ },
+
+ // Go to the 'previous' revision
+ previousRevision: function() {
+ var toIndex = this.model.revisions.indexOf( this.model.get('to') ) - 1;
+ this.gotoModel( toIndex );
+ },
+
+ // Check to see if the Previous or Next buttons need to be disabled or enabled.
+ disabledButtonCheck: function() {
+ var maxVal = this.model.revisions.length - 1,
+ minVal = 0,
+ next = $('.revisions-next .button'),
+ previous = $('.revisions-previous .button'),
+ val = this.model.revisions.indexOf( this.model.get('to') );
+
+ // Disable "Next" button if you're on the last node.
+ next.prop( 'disabled', ( maxVal === val ) );
+
+ // Disable "Previous" button if you're on the first node.
+ previous.prop( 'disabled', ( minVal === val ) );
+ }
+ });
+
+
+ // The slider view.
+ revisions.view.Slider = wp.Backbone.View.extend({
+ className: 'wp-slider',
+ direction: isRtl ? 'right' : 'left',
+
+ events: {
+ 'mousemove' : 'mouseMove'
+ },
+
+ initialize: function() {
+ _.bindAll( this, 'start', 'slide', 'stop', 'mouseMove', 'mouseEnter', 'mouseLeave' );
+ this.listenTo( this.model, 'update:slider', this.applySliderSettings );
+ },
+
+ ready: function() {
+ this.$el.css('width', ( this.model.revisions.length * 50 ) + 'px');
+ this.$el.slider( _.extend( this.model.toJSON(), {
+ start: this.start,
+ slide: this.slide,
+ stop: this.stop
+ }) );
+
+ this.$el.hoverIntent({
+ over: this.mouseEnter,
+ out: this.mouseLeave,
+ timeout: 800
+ });
+
+ this.applySliderSettings();
+ },
+
+ mouseMove: function( e ) {
+ var zoneCount = this.model.revisions.length - 1, // One fewer zone than models
+ sliderFrom = this.$el.allOffsets()[this.direction], // "From" edge of slider
+ sliderWidth = this.$el.width(), // Width of slider
+ tickWidth = sliderWidth / zoneCount, // Calculated width of zone
+ actualX = ( isRtl ? $(window).width() - e.pageX : e.pageX ) - sliderFrom, // Flipped for RTL - sliderFrom;
+ currentModelIndex = Math.floor( ( actualX + ( tickWidth / 2 ) ) / tickWidth ); // Calculate the model index
+
+ // Ensure sane value for currentModelIndex.
+ if ( currentModelIndex < 0 ) {
+ currentModelIndex = 0;
+ } else if ( currentModelIndex >= this.model.revisions.length ) {
+ currentModelIndex = this.model.revisions.length - 1;
+ }
+
+ // Update the tooltip mode
+ this.model.set({ hoveredRevision: this.model.revisions.at( currentModelIndex ) });
+ },
+
+ mouseLeave: function() {
+ this.model.set({ hovering: false });
+ },
+
+ mouseEnter: function() {
+ this.model.set({ hovering: true });
+ },
+
+ applySliderSettings: function() {
+ this.$el.slider( _.pick( this.model.toJSON(), 'value', 'values', 'range' ) );
+ var handles = this.$('a.ui-slider-handle');
+
+ if ( this.model.get('compareTwoMode') ) {
+ // in RTL mode the 'left handle' is the second in the slider, 'right' is first
+ handles.first()
+ .toggleClass( 'to-handle', !! isRtl )
+ .toggleClass( 'from-handle', ! isRtl );
+ handles.last()
+ .toggleClass( 'from-handle', !! isRtl )
+ .toggleClass( 'to-handle', ! isRtl );
+ } else {
+ handles.removeClass('from-handle to-handle');
+ }
+ },
+
+ start: function( event, ui ) {
+ this.model.set({ scrubbing: true });
+
+ // Track the mouse position to enable smooth dragging,
+ // overrides default jQuery UI step behavior.
+ $( window ).on( 'mousemove.wp.revisions', { view: this }, function( e ) {
+ var handles,
+ view = e.data.view,
+ leftDragBoundary = view.$el.offset().left,
+ sliderOffset = leftDragBoundary,
+ sliderRightEdge = leftDragBoundary + view.$el.width(),
+ rightDragBoundary = sliderRightEdge,
+ leftDragReset = '0',
+ rightDragReset = '100%',
+ handle = $( ui.handle );
+
+ // In two handle mode, ensure handles can't be dragged past each other.
+ // Adjust left/right boundaries and reset points.
+ if ( view.model.get('compareTwoMode') ) {
+ handles = handle.parent().find('.ui-slider-handle');
+ if ( handle.is( handles.first() ) ) { // We're the left handle
+ rightDragBoundary = handles.last().offset().left;
+ rightDragReset = rightDragBoundary - sliderOffset;
+ } else { // We're the right handle
+ leftDragBoundary = handles.first().offset().left + handles.first().width();
+ leftDragReset = leftDragBoundary - sliderOffset;
+ }
+ }
+
+ // Follow mouse movements, as long as handle remains inside slider.
+ if ( e.pageX < leftDragBoundary ) {
+ handle.css( 'left', leftDragReset ); // Mouse to left of slider.
+ } else if ( e.pageX > rightDragBoundary ) {
+ handle.css( 'left', rightDragReset ); // Mouse to right of slider.
+ } else {
+ handle.css( 'left', e.pageX - sliderOffset ); // Mouse in slider.
+ }
+ } );
+ },
+
+ getPosition: function( position ) {
+ return isRtl ? this.model.revisions.length - position - 1: position;
+ },
+
+ // Responds to slide events
+ slide: function( event, ui ) {
+ var attributes, movedRevision;
+ // Compare two revisions mode
+ if ( this.model.get('compareTwoMode') ) {
+ // Prevent sliders from occupying same spot
+ if ( ui.values[1] === ui.values[0] ) {
+ return false;
+ }
+ if ( isRtl ) {
+ ui.values.reverse();
+ }
+ attributes = {
+ from: this.model.revisions.at( this.getPosition( ui.values[0] ) ),
+ to: this.model.revisions.at( this.getPosition( ui.values[1] ) )
+ };
+ } else {
+ attributes = {
+ to: this.model.revisions.at( this.getPosition( ui.value ) )
+ };
+ // If we're at the first revision, unset 'from'.
+ if ( this.getPosition( ui.value ) > 0 ) {
+ attributes.from = this.model.revisions.at( this.getPosition( ui.value ) - 1 );
+ } else {
+ attributes.from = undefined;
+ }
+ }
+ movedRevision = this.model.revisions.at( this.getPosition( ui.value ) );
+
+ // If we are scrubbing, a scrub to a revision is considered a hover
+ if ( this.model.get('scrubbing') ) {
+ attributes.hoveredRevision = movedRevision;
+ }
+
+ this.model.set( attributes );
+ },
+
+ stop: function() {
+ $( window ).off('mousemove.wp.revisions');
+ this.model.updateSliderSettings(); // To snap us back to a tick mark
+ this.model.set({ scrubbing: false });
+ }
+ });
+
+ // The diff view.
+ // This is the view for the current active diff.
+ revisions.view.Diff = wp.Backbone.View.extend({
+ className: 'revisions-diff',
+ template: wp.template('revisions-diff'),
+
+ // Generate the options to be passed to the template.
+ prepare: function() {
+ return _.extend({ fields: this.model.fields.toJSON() }, this.options );
+ }
+ });
+
+ // The revisions router.
+ // Maintains the URL routes so browser URL matches state.
+ revisions.Router = Backbone.Router.extend({
+ initialize: function( options ) {
+ this.model = options.model;
+
+ // Maintain state and history when navigating
+ this.listenTo( this.model, 'update:diff', _.debounce( this.updateUrl, 250 ) );
+ this.listenTo( this.model, 'change:compareTwoMode', this.updateUrl );
+ },
+
+ baseUrl: function( url ) {
+ return this.model.get('baseUrl') + url;
+ },
+
+ updateUrl: function() {
+ var from = this.model.has('from') ? this.model.get('from').id : 0,
+ to = this.model.get('to').id;
+ if ( this.model.get('compareTwoMode' ) ) {
+ this.navigate( this.baseUrl( '?from=' + from + '&to=' + to ), { replace: true } );
+ } else {
+ this.navigate( this.baseUrl( '?revision=' + to ), { replace: true } );
+ }
+ },
+
+ handleRoute: function( a, b ) {
+ var compareTwo = _.isUndefined( b );
+
+ if ( ! compareTwo ) {
+ b = this.model.revisions.get( a );
+ a = this.model.revisions.prev( b );
+ b = b ? b.id : 0;
+ a = a ? a.id : 0;
+ }
+ }
+ });
+
+ /**
+ * Initialize the revisions UI for revision.php.
+ */
+ revisions.init = function() {
+ var state;
+
+ // Bail if the current page is not revision.php.
+ if ( ! window.adminpage || 'revision-php' !== window.adminpage ) {
+ return;
+ }
+
+ state = new revisions.model.FrameState({
+ initialDiffState: {
+ // wp_localize_script doesn't stringifies ints, so cast them.
+ to: parseInt( revisions.settings.to, 10 ),
+ from: parseInt( revisions.settings.from, 10 ),
+ // wp_localize_script does not allow for top-level booleans so do a comparator here.
+ compareTwoMode: ( revisions.settings.compareTwoMode === '1' )
+ },
+ diffData: revisions.settings.diffData,
+ baseUrl: revisions.settings.baseUrl,
+ postId: parseInt( revisions.settings.postId, 10 )
+ }, {
+ revisions: new revisions.model.Revisions( revisions.settings.revisionData )
+ });
+
+ revisions.view.frame = new revisions.view.Frame({
+ model: state
+ }).render();
+ };
+
+ $( revisions.init );
+}(jQuery));
diff --git a/www/crm/wp-admin/js/revisions.min.js b/www/crm/wp-admin/js/revisions.min.js
new file mode 100644
index 00000000..48a6110c
--- /dev/null
+++ b/www/crm/wp-admin/js/revisions.min.js
@@ -0,0 +1 @@
+window.wp=window.wp||{},function(a){var b;b=wp.revisions={model:{},view:{},controller:{}},b.settings=window._wpRevisionsSettings||{},b.debug=!1,b.log=function(){window.console&&b.debug&&window.console.log.apply(window.console,arguments)},a.fn.allOffsets=function(){var b=this.offset()||{top:0,left:0},c=a(window);return _.extend(b,{right:c.width()-b.left-this.outerWidth(),bottom:c.height()-b.top-this.outerHeight()})},a.fn.allPositions=function(){var a=this.position()||{top:0,left:0},b=this.parent();return _.extend(a,{right:b.outerWidth()-a.left-this.outerWidth(),bottom:b.outerHeight()-a.top-this.outerHeight()})},b.model.Slider=Backbone.Model.extend({defaults:{value:null,values:null,min:0,max:1,step:1,range:!1,compareTwoMode:!1},initialize:function(a){this.frame=a.frame,this.revisions=a.revisions,this.listenTo(this.frame,"update:revisions",this.receiveRevisions),this.listenTo(this.frame,"change:compareTwoMode",this.updateMode),this.on("change:from",this.handleLocalChanges),this.on("change:to",this.handleLocalChanges),this.on("change:compareTwoMode",this.updateSliderSettings),this.on("update:revisions",this.updateSliderSettings),this.on("change:hoveredRevision",this.hoverRevision),this.set({max:this.revisions.length-1,compareTwoMode:this.frame.get("compareTwoMode"),from:this.frame.get("from"),to:this.frame.get("to")}),this.updateSliderSettings()},getSliderValue:function(a,b){return isRtl?this.revisions.length-this.revisions.indexOf(this.get(a))-1:this.revisions.indexOf(this.get(b))},updateSliderSettings:function(){this.get("compareTwoMode")?this.set({values:[this.getSliderValue("to","from"),this.getSliderValue("from","to")],value:null,range:!0}):this.set({value:this.getSliderValue("to","to"),values:null,range:!1}),this.trigger("update:slider")},hoverRevision:function(a,b){this.trigger("hovered:revision",b)},updateMode:function(a,b){this.set({compareTwoMode:b})},handleLocalChanges:function(){this.frame.set({from:this.get("from"),to:this.get("to")})},receiveRevisions:function(a,b){this.get("from")===a&&this.get("to")===b||(this.set({from:a,to:b},{silent:!0}),this.trigger("update:revisions",a,b))}}),b.model.Tooltip=Backbone.Model.extend({defaults:{revision:null,offset:{},hovering:!1,scrubbing:!1},initialize:function(a){this.frame=a.frame,this.revisions=a.revisions,this.slider=a.slider,this.listenTo(this.slider,"hovered:revision",this.updateRevision),this.listenTo(this.slider,"change:hovering",this.setHovering),this.listenTo(this.slider,"change:scrubbing",this.setScrubbing)},updateRevision:function(a){this.set({revision:a})},setHovering:function(a,b){this.set({hovering:b})},setScrubbing:function(a,b){this.set({scrubbing:b})}}),b.model.Revision=Backbone.Model.extend({}),b.model.Revisions=Backbone.Collection.extend({model:b.model.Revision,initialize:function(){_.bindAll(this,"next","prev")},next:function(a){var b=this.indexOf(a);if(b!==-1&&b!==this.length-1)return this.at(b+1)},prev:function(a){var b=this.indexOf(a);if(b!==-1&&0!==b)return this.at(b-1)}}),b.model.Field=Backbone.Model.extend({}),b.model.Fields=Backbone.Collection.extend({model:b.model.Field}),b.model.Diff=Backbone.Model.extend({initialize:function(){var a=this.get("fields");this.unset("fields"),this.fields=new b.model.Fields(a)}}),b.model.Diffs=Backbone.Collection.extend({initialize:function(a,b){_.bindAll(this,"getClosestUnloaded"),this.loadAll=_.once(this._loadAll),this.revisions=b.revisions,this.postId=b.postId,this.requests={}},model:b.model.Diff,ensure:function(b,c){var d=this.get(b),e=this.requests[b],f=a.Deferred(),g={},h=b.split(":")[0],i=b.split(":")[1];return g[b]=!0,wp.revisions.log("ensure",b),this.trigger("ensure",g,h,i,f.promise()),d?f.resolveWith(c,[d]):(this.trigger("ensure:load",g,h,i,f.promise()),_.each(g,_.bind(function(a){this.requests[a]&&delete g[a],this.get(a)&&delete g[a]},this)),e||(g[b]=!0,e=this.load(_.keys(g))),e.done(_.bind(function(){f.resolveWith(c,[this.get(b)])},this)).fail(_.bind(function(){f.reject()}))),f.promise()},getClosestUnloaded:function(a,b){var c=this;return _.chain([0].concat(a)).initial().zip(a).sortBy(function(a){return Math.abs(b-a[1])}).map(function(a){return a.join(":")}).filter(function(a){return _.isUndefined(c.get(a))&&!c.requests[a]}).value()},_loadAll:function(b,c,d){var e=this,f=a.Deferred(),g=_.first(this.getClosestUnloaded(b,c),d);return _.size(g)>0?this.load(g).done(function(){e._loadAll(b,c,d).done(function(){f.resolve()})}).fail(function(){1===d?f.reject():e._loadAll(b,c,Math.ceil(d/2)).done(function(){f.resolve()})}):f.resolve(),f},load:function(a){return wp.revisions.log("load",a),this.fetch({data:{compare:a},remove:!1}).done(function(){wp.revisions.log("load:complete",a)})},sync:function(a,b,c){if("read"===a){c=c||{},c.context=this,c.data=_.extend(c.data||{},{action:"get-revision-diffs",post_id:this.postId});var d=wp.ajax.send(c),e=this.requests;return c.data.compare&&_.each(c.data.compare,function(a){e[a]=d}),d.always(function(){c.data.compare&&_.each(c.data.compare,function(a){delete e[a]})}),d}return Backbone.Model.prototype.sync.apply(this,arguments)}}),b.model.FrameState=Backbone.Model.extend({defaults:{loading:!1,error:!1,compareTwoMode:!1},initialize:function(a,c){var d=this.get("initialDiffState");_.bindAll(this,"receiveDiff"),this._debouncedEnsureDiff=_.debounce(this._ensureDiff,200),this.revisions=c.revisions,this.diffs=new b.model.Diffs([],{revisions:this.revisions,postId:this.get("postId")}),this.diffs.set(this.get("diffData")),this.listenTo(this,"change:from",this.changeRevisionHandler),this.listenTo(this,"change:to",this.changeRevisionHandler),this.listenTo(this,"change:compareTwoMode",this.changeMode),this.listenTo(this,"update:revisions",this.updatedRevisions),this.listenTo(this.diffs,"ensure:load",this.updateLoadingStatus),this.listenTo(this,"update:diff",this.updateLoadingStatus),this.set({to:this.revisions.get(d.to),from:this.revisions.get(d.from),compareTwoMode:d.compareTwoMode}),window.history&&window.history.pushState&&(this.router=new b.Router({model:this}),Backbone.History.started&&Backbone.history.stop(),Backbone.history.start({pushState:!0}))},updateLoadingStatus:function(){this.set("error",!1),this.set("loading",!this.diff())},changeMode:function(a,b){var c=this.revisions.indexOf(this.get("to"));b&&0===c&&this.set({from:this.revisions.at(c),to:this.revisions.at(c+1)}),b||0===c||this.set({from:this.revisions.at(c-1),to:this.revisions.at(c)})},updatedRevisions:function(a,b){this.get("compareTwoMode")||this.diffs.loadAll(this.revisions.pluck("id"),b.id,40)},diff:function(){return this.diffs.get(this._diffId)},updateDiff:function(b){var c,d,e,f;return b=b||{},c=this.get("from"),d=this.get("to"),e=(c?c.id:0)+":"+d.id,this._diffId===e?a.Deferred().reject().promise():(this._diffId=e,this.trigger("update:revisions",c,d),f=this.diffs.get(e),f?(this.receiveDiff(f),a.Deferred().resolve().promise()):b.immediate?this._ensureDiff():(this._debouncedEnsureDiff(),a.Deferred().reject().promise()))},changeRevisionHandler:function(){this.updateDiff()},receiveDiff:function(a){_.isUndefined(a)||_.isUndefined(a.id)?this.set({loading:!1,error:!0}):this._diffId===a.id&&this.trigger("update:diff",a)},_ensureDiff:function(){return this.diffs.ensure(this._diffId,this).always(this.receiveDiff)}}),b.view.Frame=wp.Backbone.View.extend({className:"revisions",template:wp.template("revisions-frame"),initialize:function(){this.listenTo(this.model,"update:diff",this.renderDiff),this.listenTo(this.model,"change:compareTwoMode",this.updateCompareTwoMode),this.listenTo(this.model,"change:loading",this.updateLoadingStatus),this.listenTo(this.model,"change:error",this.updateErrorStatus),this.views.set(".revisions-control-frame",new b.view.Controls({model:this.model}))},render:function(){return wp.Backbone.View.prototype.render.apply(this,arguments),a("html").css("overflow-y","scroll"),a("#wpbody-content .wrap").append(this.el),this.updateCompareTwoMode(),this.renderDiff(this.model.diff()),this.views.ready(),this},renderDiff:function(a){this.views.set(".revisions-diff-frame",new b.view.Diff({model:a}))},updateLoadingStatus:function(){this.$el.toggleClass("loading",this.model.get("loading"))},updateErrorStatus:function(){this.$el.toggleClass("diff-error",this.model.get("error"))},updateCompareTwoMode:function(){this.$el.toggleClass("comparing-two-revisions",this.model.get("compareTwoMode"))}}),b.view.Controls=wp.Backbone.View.extend({className:"revisions-controls",initialize:function(){_.bindAll(this,"setWidth"),this.views.add(new b.view.Buttons({model:this.model})),this.views.add(new b.view.Checkbox({model:this.model}));var a=new b.model.Slider({frame:this.model,revisions:this.model.revisions}),c=new b.model.Tooltip({frame:this.model,revisions:this.model.revisions,slider:a});this.views.add(new b.view.Tooltip({model:c})),this.views.add(new b.view.Tickmarks({model:c})),this.views.add(new b.view.Slider({model:a})),this.views.add(new b.view.Metabox({model:this.model}))},ready:function(){this.top=this.$el.offset().top,this.window=a(window),this.window.on("scroll.wp.revisions",{controls:this},function(a){var b=a.data.controls,c=b.$el.parent(),d=b.window.scrollTop(),e=b.views.parent;d>=b.top?(e.$el.hasClass("pinned")||(b.setWidth(),c.css("height",c.height()+"px"),b.window.on("resize.wp.revisions.pinning click.wp.revisions.pinning",{controls:b},function(a){a.data.controls.setWidth()})),e.$el.addClass("pinned")):e.$el.hasClass("pinned")?(b.window.off(".wp.revisions.pinning"),b.$el.css("width","auto"),e.$el.removeClass("pinned"),c.css("height","auto"),b.top=b.$el.offset().top):b.top=b.$el.offset().top})},setWidth:function(){this.$el.css("width",this.$el.parent().width()+"px")}}),b.view.Tickmarks=wp.Backbone.View.extend({className:"revisions-tickmarks",direction:isRtl?"right":"left",initialize:function(){this.listenTo(this.model,"change:revision",this.reportTickPosition)},reportTickPosition:function(a,b){var c,d,e,f,g=this.model.revisions.indexOf(b);d=this.$el.allOffsets(),e=this.$el.parent().allOffsets(),g===this.model.revisions.length-1?c={rightPlusWidth:d.left-e.left+1,leftPlusWidth:d.right-e.right+1}:(f=this.$("div:nth-of-type("+(g+1)+")"),c=f.allPositions(),_.extend(c,{left:c.left+d.left-e.left,right:c.right+d.right-e.right}),_.extend(c,{leftPlusWidth:c.left+f.outerWidth(),rightPlusWidth:c.right+f.outerWidth()})),this.model.set({offset:c})},ready:function(){var a,b;a=this.model.revisions.length-1,b=1/a,this.$el.css("width",50*this.model.revisions.length+"px"),_(a).times(function(a){this.$el.append('<div style="'+this.direction+": "+100*b*a+'%"></div>')},this)}}),b.view.Metabox=wp.Backbone.View.extend({className:"revisions-meta",initialize:function(){this.views.add(new b.view.MetaFrom({model:this.model,className:"diff-meta diff-meta-from"})),this.views.add(new b.view.MetaTo({model:this.model}))}}),b.view.Meta=wp.Backbone.View.extend({template:wp.template("revisions-meta"),events:{"click .restore-revision":"restoreRevision"},initialize:function(){this.listenTo(this.model,"update:revisions",this.render)},prepare:function(){return _.extend(this.model.toJSON()[this.type]||{},{type:this.type})},restoreRevision:function(){document.location=this.model.get("to").attributes.restoreUrl}}),b.view.MetaFrom=b.view.Meta.extend({className:"diff-meta diff-meta-from",type:"from"}),b.view.MetaTo=b.view.Meta.extend({className:"diff-meta diff-meta-to",type:"to"}),b.view.Checkbox=wp.Backbone.View.extend({className:"revisions-checkbox",template:wp.template("revisions-checkbox"),events:{"click .compare-two-revisions":"compareTwoToggle"},initialize:function(){this.listenTo(this.model,"change:compareTwoMode",this.updateCompareTwoMode)},ready:function(){this.model.revisions.length<3&&a(".revision-toggle-compare-mode").hide()},updateCompareTwoMode:function(){this.$(".compare-two-revisions").prop("checked",this.model.get("compareTwoMode"))},compareTwoToggle:function(){this.model.set({compareTwoMode:a(".compare-two-revisions").prop("checked")})}}),b.view.Tooltip=wp.Backbone.View.extend({className:"revisions-tooltip",template:wp.template("revisions-meta"),initialize:function(){this.listenTo(this.model,"change:offset",this.render),this.listenTo(this.model,"change:hovering",this.toggleVisibility),this.listenTo(this.model,"change:scrubbing",this.toggleVisibility)},prepare:function(){return _.isNull(this.model.get("revision"))?void 0:_.extend({type:"tooltip"},{attributes:this.model.get("revision").toJSON()})},render:function(){var a,b,c,d,e={},f=this.model.revisions.indexOf(this.model.get("revision"))+1;d=f/this.model.revisions.length>.5,isRtl?(b=d?"left":"right",c=d?"leftPlusWidth":b):(b=d?"right":"left",c=d?"rightPlusWidth":b),a="right"===b?"left":"right",wp.Backbone.View.prototype.render.apply(this,arguments),e[b]=this.model.get("offset")[c]+"px",e[a]="",this.$el.toggleClass("flipped",d).css(e)},visible:function(){return this.model.get("scrubbing")||this.model.get("hovering")},toggleVisibility:function(){this.visible()?this.$el.stop().show().fadeTo(100-100*this.el.style.opacity,1):this.$el.stop().fadeTo(300*this.el.style.opacity,0,function(){a(this).hide()})}}),b.view.Buttons=wp.Backbone.View.extend({className:"revisions-buttons",template:wp.template("revisions-buttons"),events:{"click .revisions-next .button":"nextRevision","click .revisions-previous .button":"previousRevision"},initialize:function(){this.listenTo(this.model,"update:revisions",this.disabledButtonCheck)},ready:function(){this.disabledButtonCheck()},gotoModel:function(a){var b={to:this.model.revisions.at(a)};a?b.from=this.model.revisions.at(a-1):this.model.unset("from",{silent:!0}),this.model.set(b)},nextRevision:function(){var a=this.model.revisions.indexOf(this.model.get("to"))+1;this.gotoModel(a)},previousRevision:function(){var a=this.model.revisions.indexOf(this.model.get("to"))-1;this.gotoModel(a)},disabledButtonCheck:function(){var b=this.model.revisions.length-1,c=0,d=a(".revisions-next .button"),e=a(".revisions-previous .button"),f=this.model.revisions.indexOf(this.model.get("to"));d.prop("disabled",b===f),e.prop("disabled",c===f)}}),b.view.Slider=wp.Backbone.View.extend({className:"wp-slider",direction:isRtl?"right":"left",events:{mousemove:"mouseMove"},initialize:function(){_.bindAll(this,"start","slide","stop","mouseMove","mouseEnter","mouseLeave"),this.listenTo(this.model,"update:slider",this.applySliderSettings)},ready:function(){this.$el.css("width",50*this.model.revisions.length+"px"),this.$el.slider(_.extend(this.model.toJSON(),{start:this.start,slide:this.slide,stop:this.stop})),this.$el.hoverIntent({over:this.mouseEnter,out:this.mouseLeave,timeout:800}),this.applySliderSettings()},mouseMove:function(b){var c=this.model.revisions.length-1,d=this.$el.allOffsets()[this.direction],e=this.$el.width(),f=e/c,g=(isRtl?a(window).width()-b.pageX:b.pageX)-d,h=Math.floor((g+f/2)/f);h<0?h=0:h>=this.model.revisions.length&&(h=this.model.revisions.length-1),this.model.set({hoveredRevision:this.model.revisions.at(h)})},mouseLeave:function(){this.model.set({hovering:!1})},mouseEnter:function(){this.model.set({hovering:!0})},applySliderSettings:function(){this.$el.slider(_.pick(this.model.toJSON(),"value","values","range"));var a=this.$("a.ui-slider-handle");this.model.get("compareTwoMode")?(a.first().toggleClass("to-handle",!!isRtl).toggleClass("from-handle",!isRtl),a.last().toggleClass("from-handle",!!isRtl).toggleClass("to-handle",!isRtl)):a.removeClass("from-handle to-handle")},start:function(b,c){this.model.set({scrubbing:!0}),a(window).on("mousemove.wp.revisions",{view:this},function(b){var d,e=b.data.view,f=e.$el.offset().left,g=f,h=f+e.$el.width(),i=h,j="0",k="100%",l=a(c.handle);e.model.get("compareTwoMode")&&(d=l.parent().find(".ui-slider-handle"),l.is(d.first())?(i=d.last().offset().left,k=i-g):(f=d.first().offset().left+d.first().width(),j=f-g)),b.pageX<f?l.css("left",j):b.pageX>i?l.css("left",k):l.css("left",b.pageX-g)})},getPosition:function(a){return isRtl?this.model.revisions.length-a-1:a},slide:function(a,b){var c,d;if(this.model.get("compareTwoMode")){if(b.values[1]===b.values[0])return!1;isRtl&&b.values.reverse(),c={from:this.model.revisions.at(this.getPosition(b.values[0])),to:this.model.revisions.at(this.getPosition(b.values[1]))}}else c={to:this.model.revisions.at(this.getPosition(b.value))},this.getPosition(b.value)>0?c.from=this.model.revisions.at(this.getPosition(b.value)-1):c.from=void 0;d=this.model.revisions.at(this.getPosition(b.value)),this.model.get("scrubbing")&&(c.hoveredRevision=d),this.model.set(c)},stop:function(){a(window).off("mousemove.wp.revisions"),this.model.updateSliderSettings(),this.model.set({scrubbing:!1})}}),b.view.Diff=wp.Backbone.View.extend({className:"revisions-diff",template:wp.template("revisions-diff"),prepare:function(){return _.extend({fields:this.model.fields.toJSON()},this.options)}}),b.Router=Backbone.Router.extend({initialize:function(a){this.model=a.model,this.listenTo(this.model,"update:diff",_.debounce(this.updateUrl,250)),this.listenTo(this.model,"change:compareTwoMode",this.updateUrl)},baseUrl:function(a){return this.model.get("baseUrl")+a},updateUrl:function(){var a=this.model.has("from")?this.model.get("from").id:0,b=this.model.get("to").id;this.model.get("compareTwoMode")?this.navigate(this.baseUrl("?from="+a+"&to="+b),{replace:!0}):this.navigate(this.baseUrl("?revision="+b),{replace:!0})},handleRoute:function(a,b){var c=_.isUndefined(b);c||(b=this.model.revisions.get(a),a=this.model.revisions.prev(b),b=b?b.id:0,a=a?a.id:0)}}),b.init=function(){var a;window.adminpage&&"revision-php"===window.adminpage&&(a=new b.model.FrameState({initialDiffState:{to:parseInt(b.settings.to,10),from:parseInt(b.settings.from,10),compareTwoMode:"1"===b.settings.compareTwoMode},diffData:b.settings.diffData,baseUrl:b.settings.baseUrl,postId:parseInt(b.settings.postId,10)},{revisions:new b.model.Revisions(b.settings.revisionData)}),b.view.frame=new b.view.Frame({model:a}).render())},a(b.init)}(jQuery); \ No newline at end of file
diff --git a/www/crm/wp-admin/js/set-post-thumbnail.js b/www/crm/wp-admin/js/set-post-thumbnail.js
new file mode 100644
index 00000000..0f3849af
--- /dev/null
+++ b/www/crm/wp-admin/js/set-post-thumbnail.js
@@ -0,0 +1,28 @@
+/**
+ * @output wp-admin/js/set-post-thumbnail.js
+ */
+
+/* global setPostThumbnailL10n, ajaxurl, post_id, alert */
+/* exported WPSetAsThumbnail */
+
+window.WPSetAsThumbnail = function( id, nonce ) {
+ var $link = jQuery('a#wp-post-thumbnail-' + id);
+
+ $link.text( setPostThumbnailL10n.saving );
+ jQuery.post(ajaxurl, {
+ action: 'set-post-thumbnail', post_id: post_id, thumbnail_id: id, _ajax_nonce: nonce, cookie: encodeURIComponent( document.cookie )
+ }, function(str){
+ var win = window.dialogArguments || opener || parent || top;
+ $link.text( setPostThumbnailL10n.setThumbnail );
+ if ( str == '0' ) {
+ alert( setPostThumbnailL10n.error );
+ } else {
+ jQuery('a.wp-post-thumbnail').show();
+ $link.text( setPostThumbnailL10n.done );
+ $link.fadeOut( 2000 );
+ win.WPSetThumbnailID(id);
+ win.WPSetThumbnailHTML(str);
+ }
+ }
+ );
+};
diff --git a/www/crm/wp-admin/js/set-post-thumbnail.min.js b/www/crm/wp-admin/js/set-post-thumbnail.min.js
new file mode 100644
index 00000000..a45957b2
--- /dev/null
+++ b/www/crm/wp-admin/js/set-post-thumbnail.min.js
@@ -0,0 +1 @@
+window.WPSetAsThumbnail=function(a,b){var c=jQuery("a#wp-post-thumbnail-"+a);c.text(setPostThumbnailL10n.saving),jQuery.post(ajaxurl,{action:"set-post-thumbnail",post_id:post_id,thumbnail_id:a,_ajax_nonce:b,cookie:encodeURIComponent(document.cookie)},function(b){var d=window.dialogArguments||opener||parent||top;c.text(setPostThumbnailL10n.setThumbnail),"0"==b?alert(setPostThumbnailL10n.error):(jQuery("a.wp-post-thumbnail").show(),c.text(setPostThumbnailL10n.done),c.fadeOut(2e3),d.WPSetThumbnailID(a),d.WPSetThumbnailHTML(b))})}; \ No newline at end of file
diff --git a/www/crm/wp-admin/js/site-health.js b/www/crm/wp-admin/js/site-health.js
new file mode 100644
index 00000000..a8aa67b7
--- /dev/null
+++ b/www/crm/wp-admin/js/site-health.js
@@ -0,0 +1,316 @@
+/**
+ * Interactions used by the Site Health modules in WordPress.
+ *
+ * @output wp-admin/js/site-health.js
+ */
+
+/* global ajaxurl, ClipboardJS, SiteHealth, wp */
+
+jQuery( document ).ready( function( $ ) {
+
+ var __ = wp.i18n.__,
+ _n = wp.i18n._n,
+ sprintf = wp.i18n.sprintf;
+
+ var data;
+ var clipboard = new ClipboardJS( '.site-health-copy-buttons .copy-button' );
+ var isDebugTab = $( '.health-check-body.health-check-debug-tab' ).length;
+ var pathsSizesSection = $( '#health-check-accordion-block-wp-paths-sizes' );
+
+ // Debug information copy section.
+ clipboard.on( 'success', function( e ) {
+ var $wrapper = $( e.trigger ).closest( 'div' );
+ $( '.success', $wrapper ).addClass( 'visible' );
+
+ wp.a11y.speak( __( 'Site information has been added to your clipboard.' ) );
+ } );
+
+ // Accordion handling in various areas.
+ $( '.health-check-accordion' ).on( 'click', '.health-check-accordion-trigger', function() {
+ var isExpanded = ( 'true' === $( this ).attr( 'aria-expanded' ) );
+
+ if ( isExpanded ) {
+ $( this ).attr( 'aria-expanded', 'false' );
+ $( '#' + $( this ).attr( 'aria-controls' ) ).attr( 'hidden', true );
+ } else {
+ $( this ).attr( 'aria-expanded', 'true' );
+ $( '#' + $( this ).attr( 'aria-controls' ) ).attr( 'hidden', false );
+ }
+ } );
+
+ // Site Health test handling.
+
+ $( '.site-health-view-passed' ).on( 'click', function() {
+ var goodIssuesWrapper = $( '#health-check-issues-good' );
+
+ goodIssuesWrapper.toggleClass( 'hidden' );
+ $( this ).attr( 'aria-expanded', ! goodIssuesWrapper.hasClass( 'hidden' ) );
+ } );
+
+ /**
+ * Append a new issue to the issue list.
+ *
+ * @since 5.2.0
+ *
+ * @param {Object} issue The issue data.
+ */
+ function AppendIssue( issue ) {
+ var template = wp.template( 'health-check-issue' ),
+ issueWrapper = $( '#health-check-issues-' + issue.status ),
+ heading,
+ count;
+
+ SiteHealth.site_status.issues[ issue.status ]++;
+
+ count = SiteHealth.site_status.issues[ issue.status ];
+
+ if ( 'critical' === issue.status ) {
+ heading = sprintf( _n( '%s Critical issue', '%s Critical issues', count ), '<span class="issue-count">' + count + '</span>' );
+ } else if ( 'recommended' === issue.status ) {
+ heading = sprintf( _n( '%s Recommended improvement', '%s Recommended improvements', count ), '<span class="issue-count">' + count + '</span>' );
+ } else if ( 'good' === issue.status ) {
+ heading = sprintf( _n( '%s Item with no issues detected', '%s Items with no issues detected', count ), '<span class="issue-count">' + count + '</span>' );
+ }
+
+ if ( heading ) {
+ $( '.site-health-issue-count-title', issueWrapper ).html( heading );
+ }
+
+ $( '.issues', '#health-check-issues-' + issue.status ).append( template( issue ) );
+ }
+
+ /**
+ * Update site health status indicator as asynchronous tests are run and returned.
+ *
+ * @since 5.2.0
+ */
+ function RecalculateProgression() {
+ var r, c, pct;
+ var $progress = $( '.site-health-progress' );
+ var $progressCount = $progress.find( '.site-health-progress-count' );
+ var $circle = $( '.site-health-progress svg #bar' );
+ var totalTests = parseInt( SiteHealth.site_status.issues.good, 0 ) + parseInt( SiteHealth.site_status.issues.recommended, 0 ) + ( parseInt( SiteHealth.site_status.issues.critical, 0 ) * 1.5 );
+ var failedTests = parseInt( SiteHealth.site_status.issues.recommended, 0 ) + ( parseInt( SiteHealth.site_status.issues.critical, 0 ) * 1.5 );
+ var val = 100 - Math.ceil( ( failedTests / totalTests ) * 100 );
+
+ if ( 0 === totalTests ) {
+ $progress.addClass( 'hidden' );
+ return;
+ }
+
+ $progress.removeClass( 'loading' );
+
+ r = $circle.attr( 'r' );
+ c = Math.PI * ( r * 2 );
+
+ if ( 0 > val ) {
+ val = 0;
+ }
+ if ( 100 < val ) {
+ val = 100;
+ }
+
+ pct = ( ( 100 - val ) / 100 ) * c;
+
+ $circle.css( { strokeDashoffset: pct } );
+
+ if ( 1 > parseInt( SiteHealth.site_status.issues.critical, 0 ) ) {
+ $( '#health-check-issues-critical' ).addClass( 'hidden' );
+ }
+
+ if ( 1 > parseInt( SiteHealth.site_status.issues.recommended, 0 ) ) {
+ $( '#health-check-issues-recommended' ).addClass( 'hidden' );
+ }
+
+ if ( 50 <= val ) {
+ $circle.addClass( 'orange' ).removeClass( 'red' );
+ }
+
+ if ( 90 <= val ) {
+ $circle.addClass( 'green' ).removeClass( 'orange' );
+ }
+
+ if ( 100 === val ) {
+ $( '.site-status-all-clear' ).removeClass( 'hide' );
+ $( '.site-status-has-issues' ).addClass( 'hide' );
+ }
+
+ $progressCount.text( val + '%' );
+
+ if ( ! isDebugTab ) {
+ $.post(
+ ajaxurl,
+ {
+ 'action': 'health-check-site-status-result',
+ '_wpnonce': SiteHealth.nonce.site_status_result,
+ 'counts': SiteHealth.site_status.issues
+ }
+ );
+
+ wp.a11y.speak( sprintf(
+ // translators: %s: The percentage score for the tests.
+ __( 'All site health tests have finished running. Your site scored %s, and the results are now available on the page.' ),
+ val + '%'
+ ) );
+ }
+ }
+
+ /**
+ * Queue the next asynchronous test when we're ready to run it.
+ *
+ * @since 5.2.0
+ */
+ function maybeRunNextAsyncTest() {
+ var doCalculation = true;
+
+ if ( 1 <= SiteHealth.site_status.async.length ) {
+ $.each( SiteHealth.site_status.async, function() {
+ var data = {
+ 'action': 'health-check-' + this.test.replace( '_', '-' ),
+ '_wpnonce': SiteHealth.nonce.site_status
+ };
+
+ if ( this.completed ) {
+ return true;
+ }
+
+ doCalculation = false;
+
+ this.completed = true;
+
+ $.post(
+ ajaxurl,
+ data,
+ function( response ) {
+ AppendIssue( response.data );
+ maybeRunNextAsyncTest();
+ }
+ );
+
+ return false;
+ } );
+ }
+
+ if ( doCalculation ) {
+ RecalculateProgression();
+ }
+ }
+
+ if ( 'undefined' !== typeof SiteHealth && ! isDebugTab ) {
+ if ( 0 === SiteHealth.site_status.direct.length && 0 === SiteHealth.site_status.async.length ) {
+ RecalculateProgression();
+ } else {
+ SiteHealth.site_status.issues = {
+ 'good': 0,
+ 'recommended': 0,
+ 'critical': 0
+ };
+ }
+
+ if ( 0 < SiteHealth.site_status.direct.length ) {
+ $.each( SiteHealth.site_status.direct, function() {
+ AppendIssue( this );
+ } );
+ }
+
+ if ( 0 < SiteHealth.site_status.async.length ) {
+ data = {
+ 'action': 'health-check-' + SiteHealth.site_status.async[0].test.replace( '_', '-' ),
+ '_wpnonce': SiteHealth.nonce.site_status
+ };
+
+ SiteHealth.site_status.async[0].completed = true;
+
+ $.post(
+ ajaxurl,
+ data,
+ function( response ) {
+ AppendIssue( response.data );
+ maybeRunNextAsyncTest();
+ }
+ );
+ } else {
+ RecalculateProgression();
+ }
+ }
+
+ function getDirectorySizes() {
+ var data = {
+ action: 'health-check-get-sizes',
+ _wpnonce: SiteHealth.nonce.site_status_result
+ };
+
+ var timestamp = ( new Date().getTime() );
+
+ // After 3 seconds announce that we're still waiting for directory sizes.
+ var timeout = window.setTimeout( function() {
+ wp.a11y.speak( __( 'Please wait...' ) );
+ }, 3000 );
+
+ $.post( {
+ type: 'POST',
+ url: ajaxurl,
+ data: data,
+ dataType: 'json'
+ } ).done( function( response ) {
+ updateDirSizes( response.data || {} );
+ } ).always( function() {
+ var delay = ( new Date().getTime() ) - timestamp;
+
+ $( '.health-check-wp-paths-sizes.spinner' ).css( 'visibility', 'hidden' );
+ RecalculateProgression();
+
+ if ( delay > 3000 ) {
+ // We have announced that we're waiting.
+ // Announce that we're ready after giving at least 3 seconds for the first announcement
+ // to be read out, or the two may collide.
+ if ( delay > 6000 ) {
+ delay = 0;
+ } else {
+ delay = 6500 - delay;
+ }
+
+ window.setTimeout( function() {
+ wp.a11y.speak( __( 'All site health tests have finished running.' ) );
+ }, delay );
+ } else {
+ // Cancel the announcement.
+ window.clearTimeout( timeout );
+ }
+
+ $( document ).trigger( 'site-health-info-dirsizes-done' );
+ } );
+ }
+
+ function updateDirSizes( data ) {
+ var copyButton = $( 'button.button.copy-button' );
+ var clipdoardText = copyButton.attr( 'data-clipboard-text' );
+
+ $.each( data, function( name, value ) {
+ var text = value.debug || value.size;
+
+ if ( typeof text !== 'undefined' ) {
+ clipdoardText = clipdoardText.replace( name + ': loading...', name + ': ' + text );
+ }
+ } );
+
+ copyButton.attr( 'data-clipboard-text', clipdoardText );
+
+ pathsSizesSection.find( 'td[class]' ).each( function( i, element ) {
+ var td = $( element );
+ var name = td.attr( 'class' );
+
+ if ( data.hasOwnProperty( name ) && data[ name ].size ) {
+ td.text( data[ name ].size );
+ }
+ } );
+ }
+
+ if ( isDebugTab ) {
+ if ( pathsSizesSection.length ) {
+ getDirectorySizes();
+ } else {
+ RecalculateProgression();
+ }
+ }
+} );
diff --git a/www/crm/wp-admin/js/site-health.min.js b/www/crm/wp-admin/js/site-health.min.js
new file mode 100644
index 00000000..4d3a2bb6
--- /dev/null
+++ b/www/crm/wp-admin/js/site-health.min.js
@@ -0,0 +1 @@
+jQuery(document).ready(function(a){function b(b){var c,d,e=wp.template("health-check-issue"),f=a("#health-check-issues-"+b.status);SiteHealth.site_status.issues[b.status]++,d=SiteHealth.site_status.issues[b.status],"critical"===b.status?c=j(i("%s Critical issue","%s Critical issues",d),'<span class="issue-count">'+d+"</span>"):"recommended"===b.status?c=j(i("%s Recommended improvement","%s Recommended improvements",d),'<span class="issue-count">'+d+"</span>"):"good"===b.status&&(c=j(i("%s Item with no issues detected","%s Items with no issues detected",d),'<span class="issue-count">'+d+"</span>")),c&&a(".site-health-issue-count-title",f).html(c),a(".issues","#health-check-issues-"+b.status).append(e(b))}function c(){var b,c,d,e=a(".site-health-progress"),f=e.find(".site-health-progress-count"),g=a(".site-health-progress svg #bar"),i=parseInt(SiteHealth.site_status.issues.good,0)+parseInt(SiteHealth.site_status.issues.recommended,0)+1.5*parseInt(SiteHealth.site_status.issues.critical,0),k=parseInt(SiteHealth.site_status.issues.recommended,0)+1.5*parseInt(SiteHealth.site_status.issues.critical,0),m=100-Math.ceil(k/i*100);return 0===i?void e.addClass("hidden"):(e.removeClass("loading"),b=g.attr("r"),c=Math.PI*(2*b),0>m&&(m=0),100<m&&(m=100),d=(100-m)/100*c,g.css({strokeDashoffset:d}),1>parseInt(SiteHealth.site_status.issues.critical,0)&&a("#health-check-issues-critical").addClass("hidden"),1>parseInt(SiteHealth.site_status.issues.recommended,0)&&a("#health-check-issues-recommended").addClass("hidden"),50<=m&&g.addClass("orange").removeClass("red"),90<=m&&g.addClass("green").removeClass("orange"),100===m&&(a(".site-status-all-clear").removeClass("hide"),a(".site-status-has-issues").addClass("hide")),f.text(m+"%"),void(l||(a.post(ajaxurl,{action:"health-check-site-status-result",_wpnonce:SiteHealth.nonce.site_status_result,counts:SiteHealth.site_status.issues}),wp.a11y.speak(j(h("All site health tests have finished running. Your site scored %s, and the results are now available on the page."),m+"%")))))}function d(){var e=!0;1<=SiteHealth.site_status.async.length&&a.each(SiteHealth.site_status.async,function(){var c={action:"health-check-"+this.test.replace("_","-"),_wpnonce:SiteHealth.nonce.site_status};return!!this.completed||(e=!1,this.completed=!0,a.post(ajaxurl,c,function(a){b(a.data),d()}),!1)}),e&&c()}function e(){var b={action:"health-check-get-sizes",_wpnonce:SiteHealth.nonce.site_status_result},d=(new Date).getTime(),e=window.setTimeout(function(){wp.a11y.speak(h("Please wait..."))},3e3);a.post({type:"POST",url:ajaxurl,data:b,dataType:"json"}).done(function(a){f(a.data||{})}).always(function(){var b=(new Date).getTime()-d;a(".health-check-wp-paths-sizes.spinner").css("visibility","hidden"),c(),b>3e3?(b=b>6e3?0:6500-b,window.setTimeout(function(){wp.a11y.speak(h("All site health tests have finished running."))},b)):window.clearTimeout(e),a(document).trigger("site-health-info-dirsizes-done")})}function f(b){var c=a("button.button.copy-button"),d=c.attr("data-clipboard-text");a.each(b,function(a,b){var c=b.debug||b.size;"undefined"!=typeof c&&(d=d.replace(a+": loading...",a+": "+c))}),c.attr("data-clipboard-text",d),m.find("td[class]").each(function(c,d){var e=a(d),f=e.attr("class");b.hasOwnProperty(f)&&b[f].size&&e.text(b[f].size)})}var g,h=wp.i18n.__,i=wp.i18n._n,j=wp.i18n.sprintf,k=new ClipboardJS(".site-health-copy-buttons .copy-button"),l=a(".health-check-body.health-check-debug-tab").length,m=a("#health-check-accordion-block-wp-paths-sizes");k.on("success",function(b){var c=a(b.trigger).closest("div");a(".success",c).addClass("visible"),wp.a11y.speak(h("Site information has been added to your clipboard."))}),a(".health-check-accordion").on("click",".health-check-accordion-trigger",function(){var b="true"===a(this).attr("aria-expanded");b?(a(this).attr("aria-expanded","false"),a("#"+a(this).attr("aria-controls")).attr("hidden",!0)):(a(this).attr("aria-expanded","true"),a("#"+a(this).attr("aria-controls")).attr("hidden",!1))}),a(".site-health-view-passed").on("click",function(){var b=a("#health-check-issues-good");b.toggleClass("hidden"),a(this).attr("aria-expanded",!b.hasClass("hidden"))}),"undefined"==typeof SiteHealth||l||(0===SiteHealth.site_status.direct.length&&0===SiteHealth.site_status.async.length?c():SiteHealth.site_status.issues={good:0,recommended:0,critical:0},0<SiteHealth.site_status.direct.length&&a.each(SiteHealth.site_status.direct,function(){b(this)}),0<SiteHealth.site_status.async.length?(g={action:"health-check-"+SiteHealth.site_status.async[0].test.replace("_","-"),_wpnonce:SiteHealth.nonce.site_status},SiteHealth.site_status.async[0].completed=!0,a.post(ajaxurl,g,function(a){b(a.data),d()})):c()),l&&(m.length?e():c())}); \ No newline at end of file
diff --git a/www/crm/wp-admin/js/svg-painter.js b/www/crm/wp-admin/js/svg-painter.js
new file mode 100644
index 00000000..e0277f13
--- /dev/null
+++ b/www/crm/wp-admin/js/svg-painter.js
@@ -0,0 +1,241 @@
+/**
+ * Attempt to re-color SVG icons used in the admin menu or the toolbar
+ *
+ * @output wp-admin/js/svg-painter.js
+ */
+
+window.wp = window.wp || {};
+
+wp.svgPainter = ( function( $, window, document, undefined ) {
+ 'use strict';
+ var selector, base64, painter,
+ colorscheme = {},
+ elements = [];
+
+ $(document).ready( function() {
+ // detection for browser SVG capability
+ if ( document.implementation.hasFeature( 'http://www.w3.org/TR/SVG11/feature#Image', '1.1' ) ) {
+ $( document.body ).removeClass( 'no-svg' ).addClass( 'svg' );
+ wp.svgPainter.init();
+ }
+ });
+
+ /**
+ * Needed only for IE9
+ *
+ * Based on jquery.base64.js 0.0.3 - https://github.com/yckart/jquery.base64.js
+ *
+ * Based on: https://gist.github.com/Yaffle/1284012
+ *
+ * Copyright (c) 2012 Yannick Albert (http://yckart.com)
+ * Licensed under the MIT license
+ * http://www.opensource.org/licenses/mit-license.php
+ */
+ base64 = ( function() {
+ var c,
+ b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
+ a256 = '',
+ r64 = [256],
+ r256 = [256],
+ i = 0;
+
+ function init() {
+ while( i < 256 ) {
+ c = String.fromCharCode(i);
+ a256 += c;
+ r256[i] = i;
+ r64[i] = b64.indexOf(c);
+ ++i;
+ }
+ }
+
+ function code( s, discard, alpha, beta, w1, w2 ) {
+ var tmp, length,
+ buffer = 0,
+ i = 0,
+ result = '',
+ bitsInBuffer = 0;
+
+ s = String(s);
+ length = s.length;
+
+ while( i < length ) {
+ c = s.charCodeAt(i);
+ c = c < 256 ? alpha[c] : -1;
+
+ buffer = ( buffer << w1 ) + c;
+ bitsInBuffer += w1;
+
+ while( bitsInBuffer >= w2 ) {
+ bitsInBuffer -= w2;
+ tmp = buffer >> bitsInBuffer;
+ result += beta.charAt(tmp);
+ buffer ^= tmp << bitsInBuffer;
+ }
+ ++i;
+ }
+
+ if ( ! discard && bitsInBuffer > 0 ) {
+ result += beta.charAt( buffer << ( w2 - bitsInBuffer ) );
+ }
+
+ return result;
+ }
+
+ function btoa( plain ) {
+ if ( ! c ) {
+ init();
+ }
+
+ plain = code( plain, false, r256, b64, 8, 6 );
+ return plain + '===='.slice( ( plain.length % 4 ) || 4 );
+ }
+
+ function atob( coded ) {
+ var i;
+
+ if ( ! c ) {
+ init();
+ }
+
+ coded = coded.replace( /[^A-Za-z0-9\+\/\=]/g, '' );
+ coded = String(coded).split('=');
+ i = coded.length;
+
+ do {
+ --i;
+ coded[i] = code( coded[i], true, r64, a256, 6, 8 );
+ } while ( i > 0 );
+
+ coded = coded.join('');
+ return coded;
+ }
+
+ return {
+ atob: atob,
+ btoa: btoa
+ };
+ })();
+
+ return {
+ init: function() {
+ painter = this;
+ selector = $( '#adminmenu .wp-menu-image, #wpadminbar .ab-item' );
+
+ this.setColors();
+ this.findElements();
+ this.paint();
+ },
+
+ setColors: function( colors ) {
+ if ( typeof colors === 'undefined' && typeof window._wpColorScheme !== 'undefined' ) {
+ colors = window._wpColorScheme;
+ }
+
+ if ( colors && colors.icons && colors.icons.base && colors.icons.current && colors.icons.focus ) {
+ colorscheme = colors.icons;
+ }
+ },
+
+ findElements: function() {
+ selector.each( function() {
+ var $this = $(this), bgImage = $this.css( 'background-image' );
+
+ if ( bgImage && bgImage.indexOf( 'data:image/svg+xml;base64' ) != -1 ) {
+ elements.push( $this );
+ }
+ });
+ },
+
+ paint: function() {
+ // loop through all elements
+ $.each( elements, function( index, $element ) {
+ var $menuitem = $element.parent().parent();
+
+ if ( $menuitem.hasClass( 'current' ) || $menuitem.hasClass( 'wp-has-current-submenu' ) ) {
+ // paint icon in 'current' color
+ painter.paintElement( $element, 'current' );
+ } else {
+ // paint icon in base color
+ painter.paintElement( $element, 'base' );
+
+ // set hover callbacks
+ $menuitem.hover(
+ function() {
+ painter.paintElement( $element, 'focus' );
+ },
+ function() {
+ // Match the delay from hoverIntent
+ window.setTimeout( function() {
+ painter.paintElement( $element, 'base' );
+ }, 100 );
+ }
+ );
+ }
+ });
+ },
+
+ paintElement: function( $element, colorType ) {
+ var xml, encoded, color;
+
+ if ( ! colorType || ! colorscheme.hasOwnProperty( colorType ) ) {
+ return;
+ }
+
+ color = colorscheme[ colorType ];
+
+ // only accept hex colors: #101 or #101010
+ if ( ! color.match( /^(#[0-9a-f]{3}|#[0-9a-f]{6})$/i ) ) {
+ return;
+ }
+
+ xml = $element.data( 'wp-ui-svg-' + color );
+
+ if ( xml === 'none' ) {
+ return;
+ }
+
+ if ( ! xml ) {
+ encoded = $element.css( 'background-image' ).match( /.+data:image\/svg\+xml;base64,([A-Za-z0-9\+\/\=]+)/ );
+
+ if ( ! encoded || ! encoded[1] ) {
+ $element.data( 'wp-ui-svg-' + color, 'none' );
+ return;
+ }
+
+ try {
+ if ( 'atob' in window ) {
+ xml = window.atob( encoded[1] );
+ } else {
+ xml = base64.atob( encoded[1] );
+ }
+ } catch ( error ) {}
+
+ if ( xml ) {
+ // replace `fill` attributes
+ xml = xml.replace( /fill="(.+?)"/g, 'fill="' + color + '"');
+
+ // replace `style` attributes
+ xml = xml.replace( /style="(.+?)"/g, 'style="fill:' + color + '"');
+
+ // replace `fill` properties in `<style>` tags
+ xml = xml.replace( /fill:.*?;/g, 'fill: ' + color + ';');
+
+ if ( 'btoa' in window ) {
+ xml = window.btoa( xml );
+ } else {
+ xml = base64.btoa( xml );
+ }
+
+ $element.data( 'wp-ui-svg-' + color, xml );
+ } else {
+ $element.data( 'wp-ui-svg-' + color, 'none' );
+ return;
+ }
+ }
+
+ $element.attr( 'style', 'background-image: url("data:image/svg+xml;base64,' + xml + '") !important;' );
+ }
+ };
+
+})( jQuery, window, document );
diff --git a/www/crm/wp-admin/js/svg-painter.min.js b/www/crm/wp-admin/js/svg-painter.min.js
new file mode 100644
index 00000000..51a467ec
--- /dev/null
+++ b/www/crm/wp-admin/js/svg-painter.min.js
@@ -0,0 +1 @@
+window.wp=window.wp||{},wp.svgPainter=function(a,b,c,d){"use strict";var e,f,g,h={},i=[];return a(c).ready(function(){c.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#Image","1.1")&&(a(c.body).removeClass("no-svg").addClass("svg"),wp.svgPainter.init())}),f=function(){function a(){for(;j<256;)e=String.fromCharCode(j),g+=e,i[j]=j,h[j]=f.indexOf(e),++j}function b(a,b,c,d,f,g){var h,i,j=0,k=0,l="",m=0;for(a=String(a),i=a.length;k<i;){for(e=a.charCodeAt(k),e=e<256?c[e]:-1,j=(j<<f)+e,m+=f;m>=g;)m-=g,h=j>>m,l+=d.charAt(h),j^=h<<m;++k}return!b&&m>0&&(l+=d.charAt(j<<g-m)),l}function c(c){return e||a(),c=b(c,!1,i,f,8,6),c+"====".slice(c.length%4||4)}function d(c){var d;e||a(),c=c.replace(/[^A-Za-z0-9\+\/\=]/g,""),c=String(c).split("="),d=c.length;do--d,c[d]=b(c[d],!0,h,g,6,8);while(d>0);return c=c.join("")}var e,f="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",g="",h=[256],i=[256],j=0;return{atob:d,btoa:c}}(),{init:function(){g=this,e=a("#adminmenu .wp-menu-image, #wpadminbar .ab-item"),this.setColors(),this.findElements(),this.paint()},setColors:function(a){"undefined"==typeof a&&"undefined"!=typeof b._wpColorScheme&&(a=b._wpColorScheme),a&&a.icons&&a.icons.base&&a.icons.current&&a.icons.focus&&(h=a.icons)},findElements:function(){e.each(function(){var b=a(this),c=b.css("background-image");c&&c.indexOf("data:image/svg+xml;base64")!=-1&&i.push(b)})},paint:function(){a.each(i,function(a,c){var d=c.parent().parent();d.hasClass("current")||d.hasClass("wp-has-current-submenu")?g.paintElement(c,"current"):(g.paintElement(c,"base"),d.hover(function(){g.paintElement(c,"focus")},function(){b.setTimeout(function(){g.paintElement(c,"base")},100)}))})},paintElement:function(a,c){var d,e,g;if(c&&h.hasOwnProperty(c)&&(g=h[c],g.match(/^(#[0-9a-f]{3}|#[0-9a-f]{6})$/i)&&(d=a.data("wp-ui-svg-"+g),"none"!==d))){if(!d){if(e=a.css("background-image").match(/.+data:image\/svg\+xml;base64,([A-Za-z0-9\+\/\=]+)/),!e||!e[1])return void a.data("wp-ui-svg-"+g,"none");try{d="atob"in b?b.atob(e[1]):f.atob(e[1])}catch(i){}if(!d)return void a.data("wp-ui-svg-"+g,"none");d=d.replace(/fill="(.+?)"/g,'fill="'+g+'"'),d=d.replace(/style="(.+?)"/g,'style="fill:'+g+'"'),d=d.replace(/fill:.*?;/g,"fill: "+g+";"),d="btoa"in b?b.btoa(d):f.btoa(d),a.data("wp-ui-svg-"+g,d)}a.attr("style",'background-image: url("data:image/svg+xml;base64,'+d+'") !important;')}}}}(jQuery,window,document); \ No newline at end of file
diff --git a/www/crm/wp-admin/js/tags-box.js b/www/crm/wp-admin/js/tags-box.js
new file mode 100644
index 00000000..c4676f26
--- /dev/null
+++ b/www/crm/wp-admin/js/tags-box.js
@@ -0,0 +1,430 @@
+/**
+ * @output wp-admin/js/tags-box.js
+ */
+
+/* jshint curly: false, eqeqeq: false */
+/* global ajaxurl, tagBox, array_unique_noempty */
+
+( function( $ ) {
+ var tagDelimiter = ( window.tagsSuggestL10n && window.tagsSuggestL10n.tagDelimiter ) || ',';
+
+ /**
+ * Filters unique items and returns a new array.
+ *
+ * Filters all items from an array into a new array containing only the unique
+ * items. This also excludes whitespace or empty values.
+ *
+ * @since 2.8.0
+ *
+ * @global
+ *
+ * @param {Array} array The array to filter through.
+ *
+ * @return {Array} A new array containing only the unique items.
+ */
+ window.array_unique_noempty = function( array ) {
+ var out = [];
+
+ // Trim the values and ensure they are unique.
+ $.each( array, function( key, val ) {
+ val = $.trim( val );
+
+ if ( val && $.inArray( val, out ) === -1 ) {
+ out.push( val );
+ }
+ } );
+
+ return out;
+ };
+
+ /**
+ * The TagBox object.
+ *
+ * Contains functions to create and manage tags that can be associated with a
+ * post.
+ *
+ * @since 2.9.0
+ *
+ * @global
+ */
+ window.tagBox = {
+ /**
+ * Cleans up tags by removing redundant characters.
+ *
+ * @since 2.9.0
+ * @memberOf tagBox
+ *
+ * @param {string} tags Comma separated tags that need to be cleaned up.
+ *
+ * @return {string} The cleaned up tags.
+ */
+ clean : function( tags ) {
+ if ( ',' !== tagDelimiter ) {
+ tags = tags.replace( new RegExp( tagDelimiter, 'g' ), ',' );
+ }
+
+ tags = tags.replace(/\s*,\s*/g, ',').replace(/,+/g, ',').replace(/[,\s]+$/, '').replace(/^[,\s]+/, '');
+
+ if ( ',' !== tagDelimiter ) {
+ tags = tags.replace( /,/g, tagDelimiter );
+ }
+
+ return tags;
+ },
+
+ /**
+ * Parses tags and makes them editable.
+ *
+ * @since 2.9.0
+ * @memberOf tagBox
+ *
+ * @param {Object} el The tag element to retrieve the ID from.
+ *
+ * @return {boolean} Always returns false.
+ */
+ parseTags : function(el) {
+ var id = el.id,
+ num = id.split('-check-num-')[1],
+ taxbox = $(el).closest('.tagsdiv'),
+ thetags = taxbox.find('.the-tags'),
+ current_tags = thetags.val().split( tagDelimiter ),
+ new_tags = [];
+
+ delete current_tags[num];
+
+ // Sanitize the current tags and push them as if they're new tags.
+ $.each( current_tags, function( key, val ) {
+ val = $.trim( val );
+ if ( val ) {
+ new_tags.push( val );
+ }
+ });
+
+ thetags.val( this.clean( new_tags.join( tagDelimiter ) ) );
+
+ this.quickClicks( taxbox );
+ return false;
+ },
+
+ /**
+ * Creates clickable links, buttons and fields for adding or editing tags.
+ *
+ * @since 2.9.0
+ * @memberOf tagBox
+ *
+ * @param {Object} el The container HTML element.
+ *
+ * @return {void}
+ */
+ quickClicks : function( el ) {
+ var thetags = $('.the-tags', el),
+ tagchecklist = $('.tagchecklist', el),
+ id = $(el).attr('id'),
+ current_tags, disabled;
+
+ if ( ! thetags.length )
+ return;
+
+ disabled = thetags.prop('disabled');
+
+ current_tags = thetags.val().split( tagDelimiter );
+ tagchecklist.empty();
+
+ /**
+ * Creates a delete button if tag editing is enabled, before adding it to the tag list.
+ *
+ * @since 2.5.0
+ * @memberOf tagBox
+ *
+ * @param {string} key The index of the current tag.
+ * @param {string} val The value of the current tag.
+ *
+ * @return {void}
+ */
+ $.each( current_tags, function( key, val ) {
+ var listItem, xbutton;
+
+ val = $.trim( val );
+
+ if ( ! val )
+ return;
+
+ // Create a new list item, and ensure the text is properly escaped.
+ listItem = $( '<li />' ).text( val );
+
+ // If tags editing isn't disabled, create the X button.
+ if ( ! disabled ) {
+ /*
+ * Build the X buttons, hide the X icon with aria-hidden and
+ * use visually hidden text for screen readers.
+ */
+ xbutton = $( '<button type="button" id="' + id + '-check-num-' + key + '" class="ntdelbutton">' +
+ '<span class="remove-tag-icon" aria-hidden="true"></span>' +
+ '<span class="screen-reader-text">' + window.tagsSuggestL10n.removeTerm + ' ' + listItem.html() + '</span>' +
+ '</button>' );
+
+ /**
+ * Handles the click and keypress event of the tag remove button.
+ *
+ * Makes sure the focus ends up in the tag input field when using
+ * the keyboard to delete the tag.
+ *
+ * @since 4.2.0
+ *
+ * @param {Event} e The click or keypress event to handle.
+ *
+ * @return {void}
+ */
+ xbutton.on( 'click keypress', function( e ) {
+ // On click or when using the Enter/Spacebar keys.
+ if ( 'click' === e.type || 13 === e.keyCode || 32 === e.keyCode ) {
+ /*
+ * When using the keyboard, move focus back to the
+ * add new tag field. Note: when releasing the pressed
+ * key this will fire the `keyup` event on the input.
+ */
+ if ( 13 === e.keyCode || 32 === e.keyCode ) {
+ $( this ).closest( '.tagsdiv' ).find( 'input.newtag' ).focus();
+ }
+
+ tagBox.userAction = 'remove';
+ tagBox.parseTags( this );
+ }
+ });
+
+ listItem.prepend( '&nbsp;' ).prepend( xbutton );
+ }
+
+ // Append the list item to the tag list.
+ tagchecklist.append( listItem );
+ });
+
+ // The buttons list is built now, give feedback to screen reader users.
+ tagBox.screenReadersMessage();
+ },
+
+ /**
+ * Adds a new tag.
+ *
+ * Also ensures that the quick links are properly generated.
+ *
+ * @since 2.9.0
+ * @memberOf tagBox
+ *
+ * @param {Object} el The container HTML element.
+ * @param {Object|boolean} a When this is an HTML element the text of that
+ * element will be used for the new tag.
+ * @param {number|boolean} f If this value is not passed then the tag input
+ * field is focused.
+ *
+ * @return {boolean} Always returns false.
+ */
+ flushTags : function( el, a, f ) {
+ var tagsval, newtags, text,
+ tags = $( '.the-tags', el ),
+ newtag = $( 'input.newtag', el );
+
+ a = a || false;
+
+ text = a ? $(a).text() : newtag.val();
+
+ /*
+ * Return if there's no new tag or if the input field is empty.
+ * Note: when using the keyboard to add tags, focus is moved back to
+ * the input field and the `keyup` event attached on this field will
+ * fire when releasing the pressed key. Checking also for the field
+ * emptiness avoids to set the tags and call quickClicks() again.
+ */
+ if ( 'undefined' == typeof( text ) || '' === text ) {
+ return false;
+ }
+
+ tagsval = tags.val();
+ newtags = tagsval ? tagsval + tagDelimiter + text : text;
+
+ newtags = this.clean( newtags );
+ newtags = array_unique_noempty( newtags.split( tagDelimiter ) ).join( tagDelimiter );
+ tags.val( newtags );
+ this.quickClicks( el );
+
+ if ( ! a )
+ newtag.val('');
+ if ( 'undefined' == typeof( f ) )
+ newtag.focus();
+
+ return false;
+ },
+
+ /**
+ * Retrieves the available tags and creates a tagcloud.
+ *
+ * Retrieves the available tags from the database and creates an interactive
+ * tagcloud. Clicking a tag will add it.
+ *
+ * @since 2.9.0
+ * @memberOf tagBox
+ *
+ * @param {string} id The ID to extract the taxonomy from.
+ *
+ * @return {void}
+ */
+ get : function( id ) {
+ var tax = id.substr( id.indexOf('-') + 1 );
+
+ /**
+ * Puts a received tag cloud into a DOM element.
+ *
+ * The tag cloud HTML is generated on the server.
+ *
+ * @since 2.9.0
+ *
+ * @param {number|string} r The response message from the AJAX call.
+ * @param {string} stat The status of the AJAX request.
+ *
+ * @return {void}
+ */
+ $.post( ajaxurl, { 'action': 'get-tagcloud', 'tax': tax }, function( r, stat ) {
+ if ( 0 === r || 'success' != stat ) {
+ return;
+ }
+
+ r = $( '<div id="tagcloud-' + tax + '" class="the-tagcloud">' + r + '</div>' );
+
+ /**
+ * Adds a new tag when a tag in the tagcloud is clicked.
+ *
+ * @since 2.9.0
+ *
+ * @return {boolean} Returns false to prevent the default action.
+ */
+ $( 'a', r ).click( function() {
+ tagBox.userAction = 'add';
+ tagBox.flushTags( $( '#' + tax ), this );
+ return false;
+ });
+
+ $( '#' + id ).after( r );
+ });
+ },
+
+ /**
+ * Track the user's last action.
+ *
+ * @since 4.7.0
+ */
+ userAction: '',
+
+ /**
+ * Dispatches an audible message to screen readers.
+ *
+ * This will inform the user when a tag has been added or removed.
+ *
+ * @since 4.7.0
+ *
+ * @return {void}
+ */
+ screenReadersMessage: function() {
+ var message;
+
+ switch ( this.userAction ) {
+ case 'remove':
+ message = window.tagsSuggestL10n.termRemoved;
+ break;
+
+ case 'add':
+ message = window.tagsSuggestL10n.termAdded;
+ break;
+
+ default:
+ return;
+ }
+
+ window.wp.a11y.speak( message, 'assertive' );
+ },
+
+ /**
+ * Initializes the tags box by setting up the links, buttons. Sets up event
+ * handling.
+ *
+ * This includes handling of pressing the enter key in the input field and the
+ * retrieval of tag suggestions.
+ *
+ * @since 2.9.0
+ * @memberOf tagBox
+ *
+ * @return {void}
+ */
+ init : function() {
+ var ajaxtag = $('div.ajaxtag');
+
+ $('.tagsdiv').each( function() {
+ tagBox.quickClicks( this );
+ });
+
+ $( '.tagadd', ajaxtag ).click( function() {
+ tagBox.userAction = 'add';
+ tagBox.flushTags( $( this ).closest( '.tagsdiv' ) );
+ });
+
+ /**
+ * Handles pressing enter on the new tag input field.
+ *
+ * Prevents submitting the post edit form. Uses `keypress` to take
+ * into account Input Method Editor (IME) converters.
+ *
+ * @since 2.9.0
+ *
+ * @param {Event} event The keypress event that occurred.
+ *
+ * @return {void}
+ */
+ $( 'input.newtag', ajaxtag ).keypress( function( event ) {
+ if ( 13 == event.which ) {
+ tagBox.userAction = 'add';
+ tagBox.flushTags( $( this ).closest( '.tagsdiv' ) );
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }).each( function( i, element ) {
+ $( element ).wpTagsSuggest();
+ });
+
+ /**
+ * Before a post is saved the value currently in the new tag input field will be
+ * added as a tag.
+ *
+ * @since 2.9.0
+ *
+ * @return {void}
+ */
+ $('#post').submit(function(){
+ $('div.tagsdiv').each( function() {
+ tagBox.flushTags(this, false, 1);
+ });
+ });
+
+ /**
+ * Handles clicking on the tag cloud link.
+ *
+ * Makes sure the ARIA attributes are set correctly.
+ *
+ * @since 2.9.0
+ *
+ * @return {void}
+ */
+ $('.tagcloud-link').click(function(){
+ // On the first click, fetch the tag cloud and insert it in the DOM.
+ tagBox.get( $( this ).attr( 'id' ) );
+ // Update button state, remove previous click event and attach a new one to toggle the cloud.
+ $( this )
+ .attr( 'aria-expanded', 'true' )
+ .unbind()
+ .click( function() {
+ $( this )
+ .attr( 'aria-expanded', 'false' === $( this ).attr( 'aria-expanded' ) ? 'true' : 'false' )
+ .siblings( '.the-tagcloud' ).toggle();
+ });
+ });
+ }
+ };
+}( jQuery ));
diff --git a/www/crm/wp-admin/js/tags-box.min.js b/www/crm/wp-admin/js/tags-box.min.js
new file mode 100644
index 00000000..1e468334
--- /dev/null
+++ b/www/crm/wp-admin/js/tags-box.min.js
@@ -0,0 +1 @@
+!function(a){var b=window.tagsSuggestL10n&&window.tagsSuggestL10n.tagDelimiter||",";window.array_unique_noempty=function(b){var c=[];return a.each(b,function(b,d){d=a.trim(d),d&&a.inArray(d,c)===-1&&c.push(d)}),c},window.tagBox={clean:function(a){return","!==b&&(a=a.replace(new RegExp(b,"g"),",")),a=a.replace(/\s*,\s*/g,",").replace(/,+/g,",").replace(/[,\s]+$/,"").replace(/^[,\s]+/,""),","!==b&&(a=a.replace(/,/g,b)),a},parseTags:function(c){var d=c.id,e=d.split("-check-num-")[1],f=a(c).closest(".tagsdiv"),g=f.find(".the-tags"),h=g.val().split(b),i=[];return delete h[e],a.each(h,function(b,c){c=a.trim(c),c&&i.push(c)}),g.val(this.clean(i.join(b))),this.quickClicks(f),!1},quickClicks:function(c){var d,e,f=a(".the-tags",c),g=a(".tagchecklist",c),h=a(c).attr("id");f.length&&(e=f.prop("disabled"),d=f.val().split(b),g.empty(),a.each(d,function(b,c){var d,f;c=a.trim(c),c&&(d=a("<li />").text(c),e||(f=a('<button type="button" id="'+h+"-check-num-"+b+'" class="ntdelbutton"><span class="remove-tag-icon" aria-hidden="true"></span><span class="screen-reader-text">'+window.tagsSuggestL10n.removeTerm+" "+d.html()+"</span></button>"),f.on("click keypress",function(b){"click"!==b.type&&13!==b.keyCode&&32!==b.keyCode||(13!==b.keyCode&&32!==b.keyCode||a(this).closest(".tagsdiv").find("input.newtag").focus(),tagBox.userAction="remove",tagBox.parseTags(this))}),d.prepend("&nbsp;").prepend(f)),g.append(d))}),tagBox.screenReadersMessage())},flushTags:function(c,d,e){var f,g,h,i=a(".the-tags",c),j=a("input.newtag",c);return d=d||!1,h=d?a(d).text():j.val(),"undefined"!=typeof h&&""!==h&&(f=i.val(),g=f?f+b+h:h,g=this.clean(g),g=array_unique_noempty(g.split(b)).join(b),i.val(g),this.quickClicks(c),d||j.val(""),"undefined"==typeof e&&j.focus(),!1)},get:function(b){var c=b.substr(b.indexOf("-")+1);a.post(ajaxurl,{action:"get-tagcloud",tax:c},function(d,e){0!==d&&"success"==e&&(d=a('<div id="tagcloud-'+c+'" class="the-tagcloud">'+d+"</div>"),a("a",d).click(function(){return tagBox.userAction="add",tagBox.flushTags(a("#"+c),this),!1}),a("#"+b).after(d))})},userAction:"",screenReadersMessage:function(){var a;switch(this.userAction){case"remove":a=window.tagsSuggestL10n.termRemoved;break;case"add":a=window.tagsSuggestL10n.termAdded;break;default:return}window.wp.a11y.speak(a,"assertive")},init:function(){var b=a("div.ajaxtag");a(".tagsdiv").each(function(){tagBox.quickClicks(this)}),a(".tagadd",b).click(function(){tagBox.userAction="add",tagBox.flushTags(a(this).closest(".tagsdiv"))}),a("input.newtag",b).keypress(function(b){13==b.which&&(tagBox.userAction="add",tagBox.flushTags(a(this).closest(".tagsdiv")),b.preventDefault(),b.stopPropagation())}).each(function(b,c){a(c).wpTagsSuggest()}),a("#post").submit(function(){a("div.tagsdiv").each(function(){tagBox.flushTags(this,!1,1)})}),a(".tagcloud-link").click(function(){tagBox.get(a(this).attr("id")),a(this).attr("aria-expanded","true").unbind().click(function(){a(this).attr("aria-expanded","false"===a(this).attr("aria-expanded")?"true":"false").siblings(".the-tagcloud").toggle()})})}}}(jQuery); \ No newline at end of file
diff --git a/www/crm/wp-admin/js/tags-suggest.js b/www/crm/wp-admin/js/tags-suggest.js
new file mode 100644
index 00000000..866d7d06
--- /dev/null
+++ b/www/crm/wp-admin/js/tags-suggest.js
@@ -0,0 +1,193 @@
+/**
+ * Default settings for jQuery UI Autocomplete for use with non-hierarchical taxonomies.
+ *
+ * @output wp-admin/js/tags-suggest.js
+ */
+( function( $ ) {
+ if ( typeof window.tagsSuggestL10n === 'undefined' || typeof window.uiAutocompleteL10n === 'undefined' ) {
+ return;
+ }
+
+ var tempID = 0;
+ var separator = window.tagsSuggestL10n.tagDelimiter || ',';
+
+ function split( val ) {
+ return val.split( new RegExp( separator + '\\s*' ) );
+ }
+
+ function getLast( term ) {
+ return split( term ).pop();
+ }
+
+ /**
+ * Add UI Autocomplete to an input or textarea element with presets for use
+ * with non-hierarchical taxonomies.
+ *
+ * Example: `$( element ).wpTagsSuggest( options )`.
+ *
+ * The taxonomy can be passed in a `data-wp-taxonomy` attribute on the element or
+ * can be in `options.taxonomy`.
+ *
+ * @since 4.7.0
+ *
+ * @param {object} options Options that are passed to UI Autocomplete. Can be used to override the default settings.
+ * @returns {object} jQuery instance.
+ */
+ $.fn.wpTagsSuggest = function( options ) {
+ var cache;
+ var last;
+ var $element = $( this );
+
+ options = options || {};
+
+ var taxonomy = options.taxonomy || $element.attr( 'data-wp-taxonomy' ) || 'post_tag';
+
+ delete( options.taxonomy );
+
+ options = $.extend( {
+ source: function( request, response ) {
+ var term;
+
+ if ( last === request.term ) {
+ response( cache );
+ return;
+ }
+
+ term = getLast( request.term );
+
+ $.get( window.ajaxurl, {
+ action: 'ajax-tag-search',
+ tax: taxonomy,
+ q: term
+ } ).always( function() {
+ $element.removeClass( 'ui-autocomplete-loading' ); // UI fails to remove this sometimes?
+ } ).done( function( data ) {
+ var tagName;
+ var tags = [];
+
+ if ( data ) {
+ data = data.split( '\n' );
+
+ for ( tagName in data ) {
+ var id = ++tempID;
+
+ tags.push({
+ id: id,
+ name: data[tagName]
+ });
+ }
+
+ cache = tags;
+ response( tags );
+ } else {
+ response( tags );
+ }
+ } );
+
+ last = request.term;
+ },
+ focus: function( event, ui ) {
+ $element.attr( 'aria-activedescendant', 'wp-tags-autocomplete-' + ui.item.id );
+
+ // Don't empty the input field when using the arrow keys to
+ // highlight items. See api.jqueryui.com/autocomplete/#event-focus
+ event.preventDefault();
+ },
+ select: function( event, ui ) {
+ var tags = split( $element.val() );
+ // Remove the last user input.
+ tags.pop();
+ // Append the new tag and an empty element to get one more separator at the end.
+ tags.push( ui.item.name, '' );
+
+ $element.val( tags.join( separator + ' ' ) );
+
+ if ( $.ui.keyCode.TAB === event.keyCode ) {
+ // Audible confirmation message when a tag has been selected.
+ window.wp.a11y.speak( window.tagsSuggestL10n.termSelected, 'assertive' );
+ event.preventDefault();
+ } else if ( $.ui.keyCode.ENTER === event.keyCode ) {
+ // If we're in the edit post Tags meta box, add the tag.
+ if ( window.tagBox ) {
+ window.tagBox.userAction = 'add';
+ window.tagBox.flushTags( $( this ).closest( '.tagsdiv' ) );
+ }
+
+ // Do not close Quick Edit / Bulk Edit
+ event.preventDefault();
+ event.stopPropagation();
+ }
+
+ return false;
+ },
+ open: function() {
+ $element.attr( 'aria-expanded', 'true' );
+ },
+ close: function() {
+ $element.attr( 'aria-expanded', 'false' );
+ },
+ minLength: 2,
+ position: {
+ my: 'left top+2',
+ at: 'left bottom',
+ collision: 'none'
+ },
+ messages: {
+ noResults: window.uiAutocompleteL10n.noResults,
+ results: function( number ) {
+ if ( number > 1 ) {
+ return window.uiAutocompleteL10n.manyResults.replace( '%d', number );
+ }
+
+ return window.uiAutocompleteL10n.oneResult;
+ }
+ }
+ }, options );
+
+ $element.on( 'keydown', function() {
+ $element.removeAttr( 'aria-activedescendant' );
+ } )
+ .autocomplete( options )
+ .autocomplete( 'instance' )._renderItem = function( ul, item ) {
+ return $( '<li role="option" id="wp-tags-autocomplete-' + item.id + '">' )
+ .text( item.name )
+ .appendTo( ul );
+ };
+
+ $element.attr( {
+ 'role': 'combobox',
+ 'aria-autocomplete': 'list',
+ 'aria-expanded': 'false',
+ 'aria-owns': $element.autocomplete( 'widget' ).attr( 'id' )
+ } )
+ .on( 'focus', function() {
+ var inputValue = split( $element.val() ).pop();
+
+ // Don't trigger a search if the field is empty.
+ // Also, avoids screen readers announce `No search results`.
+ if ( inputValue ) {
+ $element.autocomplete( 'search' );
+ }
+ } )
+ // Returns a jQuery object containing the menu element.
+ .autocomplete( 'widget' )
+ .addClass( 'wp-tags-autocomplete' )
+ .attr( 'role', 'listbox' )
+ .removeAttr( 'tabindex' ) // Remove the `tabindex=0` attribute added by jQuery UI.
+
+ // Looks like Safari and VoiceOver need an `aria-selected` attribute. See ticket #33301.
+ // The `menufocus` and `menublur` events are the same events used to add and remove
+ // the `ui-state-focus` CSS class on the menu items. See jQuery UI Menu Widget.
+ .on( 'menufocus', function( event, ui ) {
+ ui.item.attr( 'aria-selected', 'true' );
+ })
+ .on( 'menublur', function() {
+ // The `menublur` event returns an object where the item is `null`
+ // so we need to find the active item with other means.
+ $( this ).find( '[aria-selected="true"]' ).removeAttr( 'aria-selected' );
+ });
+
+ return this;
+ };
+
+}( jQuery ) );
diff --git a/www/crm/wp-admin/js/tags-suggest.min.js b/www/crm/wp-admin/js/tags-suggest.min.js
new file mode 100644
index 00000000..a4f07eb9
--- /dev/null
+++ b/www/crm/wp-admin/js/tags-suggest.min.js
@@ -0,0 +1 @@
+!function(a){function b(a){return a.split(new RegExp(e+"\\s*"))}function c(a){return b(a).pop()}if("undefined"!=typeof window.tagsSuggestL10n&&"undefined"!=typeof window.uiAutocompleteL10n){var d=0,e=window.tagsSuggestL10n.tagDelimiter||",";a.fn.wpTagsSuggest=function(f){var g,h,i=a(this);f=f||{};var j=f.taxonomy||i.attr("data-wp-taxonomy")||"post_tag";return delete f.taxonomy,f=a.extend({source:function(b,e){var f;return h===b.term?void e(g):(f=c(b.term),a.get(window.ajaxurl,{action:"ajax-tag-search",tax:j,q:f}).always(function(){i.removeClass("ui-autocomplete-loading")}).done(function(a){var b,c=[];if(a){a=a.split("\n");for(b in a){var f=++d;c.push({id:f,name:a[b]})}g=c,e(c)}else e(c)}),void(h=b.term))},focus:function(a,b){i.attr("aria-activedescendant","wp-tags-autocomplete-"+b.item.id),a.preventDefault()},select:function(c,d){var f=b(i.val());return f.pop(),f.push(d.item.name,""),i.val(f.join(e+" ")),a.ui.keyCode.TAB===c.keyCode?(window.wp.a11y.speak(window.tagsSuggestL10n.termSelected,"assertive"),c.preventDefault()):a.ui.keyCode.ENTER===c.keyCode&&(window.tagBox&&(window.tagBox.userAction="add",window.tagBox.flushTags(a(this).closest(".tagsdiv"))),c.preventDefault(),c.stopPropagation()),!1},open:function(){i.attr("aria-expanded","true")},close:function(){i.attr("aria-expanded","false")},minLength:2,position:{my:"left top+2",at:"left bottom",collision:"none"},messages:{noResults:window.uiAutocompleteL10n.noResults,results:function(a){return a>1?window.uiAutocompleteL10n.manyResults.replace("%d",a):window.uiAutocompleteL10n.oneResult}}},f),i.on("keydown",function(){i.removeAttr("aria-activedescendant")}).autocomplete(f).autocomplete("instance")._renderItem=function(b,c){return a('<li role="option" id="wp-tags-autocomplete-'+c.id+'">').text(c.name).appendTo(b)},i.attr({role:"combobox","aria-autocomplete":"list","aria-expanded":"false","aria-owns":i.autocomplete("widget").attr("id")}).on("focus",function(){var a=b(i.val()).pop();a&&i.autocomplete("search")}).autocomplete("widget").addClass("wp-tags-autocomplete").attr("role","listbox").removeAttr("tabindex").on("menufocus",function(a,b){b.item.attr("aria-selected","true")}).on("menublur",function(){a(this).find('[aria-selected="true"]').removeAttr("aria-selected")}),this}}}(jQuery); \ No newline at end of file
diff --git a/www/crm/wp-admin/js/tags.js b/www/crm/wp-admin/js/tags.js
new file mode 100644
index 00000000..0f195c77
--- /dev/null
+++ b/www/crm/wp-admin/js/tags.js
@@ -0,0 +1,147 @@
+/**
+ * Contains logic for deleting and adding tags.
+ *
+ * For deleting tags it makes a request to the server to delete the tag.
+ * For adding tags it makes a request to the server to add the tag.
+ *
+ * @output wp-admin/js/tags.js
+ */
+
+ /* global ajaxurl, wpAjax, tagsl10n, showNotice, validateForm */
+
+jQuery(document).ready(function($) {
+
+ /**
+ * Adds an event handler to the delete term link on the term overview page.
+ *
+ * Cancels default event handling and event bubbling.
+ *
+ * @since 2.8.0
+ *
+ * @returns boolean Always returns false to cancel the default event handling.
+ */
+ $( '#the-list' ).on( 'click', '.delete-tag', function() {
+ var t = $(this), tr = t.parents('tr'), r = true, data;
+
+ if ( 'undefined' != showNotice )
+ r = showNotice.warn();
+
+ if ( r ) {
+ data = t.attr('href').replace(/[^?]*\?/, '').replace(/action=delete/, 'action=delete-tag');
+
+ /**
+ * Makes a request to the server to delete the term that corresponds to the
+ * delete term button.
+ *
+ * @param {string} r The response from the server.
+ *
+ * @returns {void}
+ */
+ $.post(ajaxurl, data, function(r){
+ if ( '1' == r ) {
+ $('#ajax-response').empty();
+ tr.fadeOut('normal', function(){ tr.remove(); });
+
+ /**
+ * Removes the term from the parent box and the tag cloud.
+ *
+ * `data.match(/tag_ID=(\d+)/)[1]` matches the term id from the data variable.
+ * This term id is then used to select the relevant HTML elements:
+ * The parent box and the tag cloud.
+ */
+ $('select#parent option[value="' + data.match(/tag_ID=(\d+)/)[1] + '"]').remove();
+ $('a.tag-link-' + data.match(/tag_ID=(\d+)/)[1]).remove();
+
+ } else if ( '-1' == r ) {
+ $('#ajax-response').empty().append('<div class="error"><p>' + tagsl10n.noPerm + '</p></div>');
+ tr.children().css('backgroundColor', '');
+
+ } else {
+ $('#ajax-response').empty().append('<div class="error"><p>' + tagsl10n.broken + '</p></div>');
+ tr.children().css('backgroundColor', '');
+ }
+ });
+
+ tr.children().css('backgroundColor', '#f33');
+ }
+
+ return false;
+ });
+
+ /**
+ * Adds a deletion confirmation when removing a tag.
+ *
+ * @since 4.8.0
+ *
+ * @returns {void}
+ */
+ $( '#edittag' ).on( 'click', '.delete', function( e ) {
+ if ( 'undefined' === typeof showNotice ) {
+ return true;
+ }
+
+ // Confirms the deletion, a negative response means the deletion must not be executed.
+ var response = showNotice.warn();
+ if ( ! response ) {
+ e.preventDefault();
+ }
+ });
+
+ /**
+ * Adds an event handler to the form submit on the term overview page.
+ *
+ * Cancels default event handling and event bubbling.
+ *
+ * @since 2.8.0
+ *
+ * @returns boolean Always returns false to cancel the default event handling.
+ */
+ $('#submit').click(function(){
+ var form = $(this).parents('form');
+
+ if ( ! validateForm( form ) )
+ return false;
+
+ /**
+ * Does a request to the server to add a new term to the database
+ *
+ * @param {string} r The response from the server.
+ *
+ * @returns {void}
+ */
+ $.post(ajaxurl, $('#addtag').serialize(), function(r){
+ var res, parent, term, indent, i;
+
+ $('#ajax-response').empty();
+ res = wpAjax.parseAjaxResponse( r, 'ajax-response' );
+ if ( ! res || res.errors )
+ return;
+
+ parent = form.find( 'select#parent' ).val();
+
+ if ( parent > 0 && $('#tag-' + parent ).length > 0 ) // If the parent exists on this page, insert it below. Else insert it at the top of the list.
+ $( '.tags #tag-' + parent ).after( res.responses[0].supplemental.noparents ); // As the parent exists, Insert the version with - - - prefixed
+ else
+ $( '.tags' ).prepend( res.responses[0].supplemental.parents ); // As the parent is not visible, Insert the version with Parent - Child - ThisTerm
+
+ $('.tags .no-items').remove();
+
+ if ( form.find('select#parent') ) {
+ // Parents field exists, Add new term to the list.
+ term = res.responses[1].supplemental;
+
+ // Create an indent for the Parent field
+ indent = '';
+ for ( i = 0; i < res.responses[1].position; i++ )
+ indent += '&nbsp;&nbsp;&nbsp;';
+
+ form.find( 'select#parent option:selected' ).after( '<option value="' + term.term_id + '">' + indent + term.name + '</option>' );
+ }
+
+ $('input[type="text"]:visible, textarea:visible', form).val('');
+ });
+
+ return false;
+ });
+
+});
diff --git a/www/crm/wp-admin/js/tags.min.js b/www/crm/wp-admin/js/tags.min.js
new file mode 100644
index 00000000..2f7db48d
--- /dev/null
+++ b/www/crm/wp-admin/js/tags.min.js
@@ -0,0 +1 @@
+jQuery(document).ready(function(a){a("#the-list").on("click",".delete-tag",function(){var b,c=a(this),d=c.parents("tr"),e=!0;return"undefined"!=showNotice&&(e=showNotice.warn()),e&&(b=c.attr("href").replace(/[^?]*\?/,"").replace(/action=delete/,"action=delete-tag"),a.post(ajaxurl,b,function(c){"1"==c?(a("#ajax-response").empty(),d.fadeOut("normal",function(){d.remove()}),a('select#parent option[value="'+b.match(/tag_ID=(\d+)/)[1]+'"]').remove(),a("a.tag-link-"+b.match(/tag_ID=(\d+)/)[1]).remove()):"-1"==c?(a("#ajax-response").empty().append('<div class="error"><p>'+tagsl10n.noPerm+"</p></div>"),d.children().css("backgroundColor","")):(a("#ajax-response").empty().append('<div class="error"><p>'+tagsl10n.broken+"</p></div>"),d.children().css("backgroundColor",""))}),d.children().css("backgroundColor","#f33")),!1}),a("#edittag").on("click",".delete",function(a){if("undefined"==typeof showNotice)return!0;var b=showNotice.warn();b||a.preventDefault()}),a("#submit").click(function(){var b=a(this).parents("form");return!!validateForm(b)&&(a.post(ajaxurl,a("#addtag").serialize(),function(c){var d,e,f,g,h;if(a("#ajax-response").empty(),d=wpAjax.parseAjaxResponse(c,"ajax-response"),d&&!d.errors){if(e=b.find("select#parent").val(),e>0&&a("#tag-"+e).length>0?a(".tags #tag-"+e).after(d.responses[0].supplemental.noparents):a(".tags").prepend(d.responses[0].supplemental.parents),a(".tags .no-items").remove(),b.find("select#parent")){for(f=d.responses[1].supplemental,g="",h=0;h<d.responses[1].position;h++)g+="&nbsp;&nbsp;&nbsp;";b.find("select#parent option:selected").after('<option value="'+f.term_id+'">'+g+f.name+"</option>")}a('input[type="text"]:visible, textarea:visible',b).val("")}}),!1)})}); \ No newline at end of file
diff --git a/www/crm/wp-admin/js/theme-plugin-editor.js b/www/crm/wp-admin/js/theme-plugin-editor.js
new file mode 100644
index 00000000..6127b2cf
--- /dev/null
+++ b/www/crm/wp-admin/js/theme-plugin-editor.js
@@ -0,0 +1,1006 @@
+/**
+ * @output wp-admin/js/theme-plugin-editor.js
+ */
+
+/* eslint no-magic-numbers: ["error", { "ignore": [-1, 0, 1] }] */
+
+if ( ! window.wp ) {
+ window.wp = {};
+}
+
+wp.themePluginEditor = (function( $ ) {
+ 'use strict';
+ var component, TreeLinks;
+
+ component = {
+ l10n: {
+ lintError: {
+ singular: '',
+ plural: ''
+ },
+ saveAlert: '',
+ saveError: ''
+ },
+ codeEditor: {},
+ instance: null,
+ noticeElements: {},
+ dirty: false,
+ lintErrors: []
+ };
+
+ /**
+ * Initialize component.
+ *
+ * @since 4.9.0
+ *
+ * @param {jQuery} form - Form element.
+ * @param {object} settings - Settings.
+ * @param {object|boolean} settings.codeEditor - Code editor settings (or `false` if syntax highlighting is disabled).
+ * @returns {void}
+ */
+ component.init = function init( form, settings ) {
+
+ component.form = form;
+ if ( settings ) {
+ $.extend( component, settings );
+ }
+
+ component.noticeTemplate = wp.template( 'wp-file-editor-notice' );
+ component.noticesContainer = component.form.find( '.editor-notices' );
+ component.submitButton = component.form.find( ':input[name=submit]' );
+ component.spinner = component.form.find( '.submit .spinner' );
+ component.form.on( 'submit', component.submit );
+ component.textarea = component.form.find( '#newcontent' );
+ component.textarea.on( 'change', component.onChange );
+ component.warning = $( '.file-editor-warning' );
+ component.docsLookUpButton = component.form.find( '#docs-lookup' );
+ component.docsLookUpList = component.form.find( '#docs-list' );
+
+ if ( component.warning.length > 0 ) {
+ component.showWarning();
+ }
+
+ if ( false !== component.codeEditor ) {
+ /*
+ * Defer adding notices until after DOM ready as workaround for WP Admin injecting
+ * its own managed dismiss buttons and also to prevent the editor from showing a notice
+ * when the file had linting errors to begin with.
+ */
+ _.defer( function() {
+ component.initCodeEditor();
+ } );
+ }
+
+ $( component.initFileBrowser );
+
+ $( window ).on( 'beforeunload', function() {
+ if ( component.dirty ) {
+ return component.l10n.saveAlert;
+ }
+ return undefined;
+ } );
+
+ component.docsLookUpList.on( 'change', function() {
+ var option = $( this ).val();
+ if ( '' === option ) {
+ component.docsLookUpButton.prop( 'disabled', true );
+ } else {
+ component.docsLookUpButton.prop( 'disabled', false );
+ }
+ } );
+ };
+
+ /**
+ * Set up and display the warning modal.
+ *
+ * @since 4.9.0
+ * @returns {void}
+ */
+ component.showWarning = function() {
+ // Get the text within the modal.
+ var rawMessage = component.warning.find( '.file-editor-warning-message' ).text();
+ // Hide all the #wpwrap content from assistive technologies.
+ $( '#wpwrap' ).attr( 'aria-hidden', 'true' );
+ // Detach the warning modal from its position and append it to the body.
+ $( document.body )
+ .addClass( 'modal-open' )
+ .append( component.warning.detach() );
+ // Reveal the modal and set focus on the go back button.
+ component.warning
+ .removeClass( 'hidden' )
+ .find( '.file-editor-warning-go-back' ).focus();
+ // Get the links and buttons within the modal.
+ component.warningTabbables = component.warning.find( 'a, button' );
+ // Attach event handlers.
+ component.warningTabbables.on( 'keydown', component.constrainTabbing );
+ component.warning.on( 'click', '.file-editor-warning-dismiss', component.dismissWarning );
+ // Make screen readers announce the warning message after a short delay (necessary for some screen readers).
+ setTimeout( function() {
+ wp.a11y.speak( wp.sanitize.stripTags( rawMessage.replace( /\s+/g, ' ' ) ), 'assertive' );
+ }, 1000 );
+ };
+
+ /**
+ * Constrain tabbing within the warning modal.
+ *
+ * @since 4.9.0
+ * @param {object} event jQuery event object.
+ * @returns {void}
+ */
+ component.constrainTabbing = function( event ) {
+ var firstTabbable, lastTabbable;
+
+ if ( 9 !== event.which ) {
+ return;
+ }
+
+ firstTabbable = component.warningTabbables.first()[0];
+ lastTabbable = component.warningTabbables.last()[0];
+
+ if ( lastTabbable === event.target && ! event.shiftKey ) {
+ firstTabbable.focus();
+ event.preventDefault();
+ } else if ( firstTabbable === event.target && event.shiftKey ) {
+ lastTabbable.focus();
+ event.preventDefault();
+ }
+ };
+
+ /**
+ * Dismiss the warning modal.
+ *
+ * @since 4.9.0
+ * @returns {void}
+ */
+ component.dismissWarning = function() {
+
+ wp.ajax.post( 'dismiss-wp-pointer', {
+ pointer: component.themeOrPlugin + '_editor_notice'
+ });
+
+ // Hide modal.
+ component.warning.remove();
+ $( '#wpwrap' ).removeAttr( 'aria-hidden' );
+ $( 'body' ).removeClass( 'modal-open' );
+ };
+
+ /**
+ * Callback for when a change happens.
+ *
+ * @since 4.9.0
+ * @returns {void}
+ */
+ component.onChange = function() {
+ component.dirty = true;
+ component.removeNotice( 'file_saved' );
+ };
+
+ /**
+ * Submit file via Ajax.
+ *
+ * @since 4.9.0
+ * @param {jQuery.Event} event - Event.
+ * @returns {void}
+ */
+ component.submit = function( event ) {
+ var data = {}, request;
+ event.preventDefault(); // Prevent form submission in favor of Ajax below.
+ $.each( component.form.serializeArray(), function() {
+ data[ this.name ] = this.value;
+ } );
+
+ // Use value from codemirror if present.
+ if ( component.instance ) {
+ data.newcontent = component.instance.codemirror.getValue();
+ }
+
+ if ( component.isSaving ) {
+ return;
+ }
+
+ // Scroll ot the line that has the error.
+ if ( component.lintErrors.length ) {
+ component.instance.codemirror.setCursor( component.lintErrors[0].from.line );
+ return;
+ }
+
+ component.isSaving = true;
+ component.textarea.prop( 'readonly', true );
+ if ( component.instance ) {
+ component.instance.codemirror.setOption( 'readOnly', true );
+ }
+
+ component.spinner.addClass( 'is-active' );
+ request = wp.ajax.post( 'edit-theme-plugin-file', data );
+
+ // Remove previous save notice before saving.
+ if ( component.lastSaveNoticeCode ) {
+ component.removeNotice( component.lastSaveNoticeCode );
+ }
+
+ request.done( function( response ) {
+ component.lastSaveNoticeCode = 'file_saved';
+ component.addNotice({
+ code: component.lastSaveNoticeCode,
+ type: 'success',
+ message: response.message,
+ dismissible: true
+ });
+ component.dirty = false;
+ } );
+
+ request.fail( function( response ) {
+ var notice = $.extend(
+ {
+ code: 'save_error',
+ message: component.l10n.saveError
+ },
+ response,
+ {
+ type: 'error',
+ dismissible: true
+ }
+ );
+ component.lastSaveNoticeCode = notice.code;
+ component.addNotice( notice );
+ } );
+
+ request.always( function() {
+ component.spinner.removeClass( 'is-active' );
+ component.isSaving = false;
+
+ component.textarea.prop( 'readonly', false );
+ if ( component.instance ) {
+ component.instance.codemirror.setOption( 'readOnly', false );
+ }
+ } );
+ };
+
+ /**
+ * Add notice.
+ *
+ * @since 4.9.0
+ *
+ * @param {object} notice - Notice.
+ * @param {string} notice.code - Code.
+ * @param {string} notice.type - Type.
+ * @param {string} notice.message - Message.
+ * @param {boolean} [notice.dismissible=false] - Dismissible.
+ * @param {Function} [notice.onDismiss] - Callback for when a user dismisses the notice.
+ * @returns {jQuery} Notice element.
+ */
+ component.addNotice = function( notice ) {
+ var noticeElement;
+
+ if ( ! notice.code ) {
+ throw new Error( 'Missing code.' );
+ }
+
+ // Only let one notice of a given type be displayed at a time.
+ component.removeNotice( notice.code );
+
+ noticeElement = $( component.noticeTemplate( notice ) );
+ noticeElement.hide();
+
+ noticeElement.find( '.notice-dismiss' ).on( 'click', function() {
+ component.removeNotice( notice.code );
+ if ( notice.onDismiss ) {
+ notice.onDismiss( notice );
+ }
+ } );
+
+ wp.a11y.speak( notice.message );
+
+ component.noticesContainer.append( noticeElement );
+ noticeElement.slideDown( 'fast' );
+ component.noticeElements[ notice.code ] = noticeElement;
+ return noticeElement;
+ };
+
+ /**
+ * Remove notice.
+ *
+ * @since 4.9.0
+ *
+ * @param {string} code - Notice code.
+ * @returns {boolean} Whether a notice was removed.
+ */
+ component.removeNotice = function( code ) {
+ if ( component.noticeElements[ code ] ) {
+ component.noticeElements[ code ].slideUp( 'fast', function() {
+ $( this ).remove();
+ } );
+ delete component.noticeElements[ code ];
+ return true;
+ }
+ return false;
+ };
+
+ /**
+ * Initialize code editor.
+ *
+ * @since 4.9.0
+ * @returns {void}
+ */
+ component.initCodeEditor = function initCodeEditor() {
+ var codeEditorSettings, editor;
+
+ codeEditorSettings = $.extend( {}, component.codeEditor );
+
+ /**
+ * Handle tabbing to the field before the editor.
+ *
+ * @since 4.9.0
+ *
+ * @returns {void}
+ */
+ codeEditorSettings.onTabPrevious = function() {
+ $( '#templateside' ).find( ':tabbable' ).last().focus();
+ };
+
+ /**
+ * Handle tabbing to the field after the editor.
+ *
+ * @since 4.9.0
+ *
+ * @returns {void}
+ */
+ codeEditorSettings.onTabNext = function() {
+ $( '#template' ).find( ':tabbable:not(.CodeMirror-code)' ).first().focus();
+ };
+
+ /**
+ * Handle change to the linting errors.
+ *
+ * @since 4.9.0
+ *
+ * @param {Array} errors - List of linting errors.
+ * @returns {void}
+ */
+ codeEditorSettings.onChangeLintingErrors = function( errors ) {
+ component.lintErrors = errors;
+
+ // Only disable the button in onUpdateErrorNotice when there are errors so users can still feel they can click the button.
+ if ( 0 === errors.length ) {
+ component.submitButton.toggleClass( 'disabled', false );
+ }
+ };
+
+ /**
+ * Update error notice.
+ *
+ * @since 4.9.0
+ *
+ * @param {Array} errorAnnotations - Error annotations.
+ * @returns {void}
+ */
+ codeEditorSettings.onUpdateErrorNotice = function onUpdateErrorNotice( errorAnnotations ) {
+ var message, noticeElement;
+
+ component.submitButton.toggleClass( 'disabled', errorAnnotations.length > 0 );
+
+ if ( 0 !== errorAnnotations.length ) {
+ if ( 1 === errorAnnotations.length ) {
+ message = component.l10n.lintError.singular.replace( '%d', '1' );
+ } else {
+ message = component.l10n.lintError.plural.replace( '%d', String( errorAnnotations.length ) );
+ }
+ noticeElement = component.addNotice({
+ code: 'lint_errors',
+ type: 'error',
+ message: message,
+ dismissible: false
+ });
+ noticeElement.find( 'input[type=checkbox]' ).on( 'click', function() {
+ codeEditorSettings.onChangeLintingErrors( [] );
+ component.removeNotice( 'lint_errors' );
+ } );
+ } else {
+ component.removeNotice( 'lint_errors' );
+ }
+ };
+
+ editor = wp.codeEditor.initialize( $( '#newcontent' ), codeEditorSettings );
+ editor.codemirror.on( 'change', component.onChange );
+
+ // Improve the editor accessibility.
+ $( editor.codemirror.display.lineDiv )
+ .attr({
+ role: 'textbox',
+ 'aria-multiline': 'true',
+ 'aria-labelledby': 'theme-plugin-editor-label',
+ 'aria-describedby': 'editor-keyboard-trap-help-1 editor-keyboard-trap-help-2 editor-keyboard-trap-help-3 editor-keyboard-trap-help-4'
+ });
+
+ // Focus the editor when clicking on its label.
+ $( '#theme-plugin-editor-label' ).on( 'click', function() {
+ editor.codemirror.focus();
+ });
+
+ component.instance = editor;
+ };
+
+ /**
+ * Initialization of the file browser's folder states.
+ *
+ * @since 4.9.0
+ * @returns {void}
+ */
+ component.initFileBrowser = function initFileBrowser() {
+
+ var $templateside = $( '#templateside' );
+
+ // Collapse all folders.
+ $templateside.find( '[role="group"]' ).parent().attr( 'aria-expanded', false );
+
+ // Expand ancestors to the current file.
+ $templateside.find( '.notice' ).parents( '[aria-expanded]' ).attr( 'aria-expanded', true );
+
+ // Find Tree elements and enhance them.
+ $templateside.find( '[role="tree"]' ).each( function() {
+ var treeLinks = new TreeLinks( this );
+ treeLinks.init();
+ } );
+
+ // Scroll the current file into view.
+ $templateside.find( '.current-file:first' ).each( function() {
+ if ( this.scrollIntoViewIfNeeded ) {
+ this.scrollIntoViewIfNeeded();
+ } else {
+ this.scrollIntoView( false );
+ }
+ } );
+ };
+
+ /* jshint ignore:start */
+ /* jscs:disable */
+ /* eslint-disable */
+
+ /**
+ * Creates a new TreeitemLink.
+ *
+ * @since 4.9.0
+ * @class
+ * @private
+ * @see {@link https://www.w3.org/TR/wai-aria-practices-1.1/examples/treeview/treeview-2/treeview-2b.html|W3C Treeview Example}
+ * @license W3C-20150513
+ */
+ var TreeitemLink = (function () {
+ /**
+ * This content is licensed according to the W3C Software License at
+ * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
+ *
+ * File: TreeitemLink.js
+ *
+ * Desc: Treeitem widget that implements ARIA Authoring Practices
+ * for a tree being used as a file viewer
+ *
+ * Author: Jon Gunderson, Ku Ja Eun and Nicholas Hoyt
+ */
+
+ /**
+ * @constructor
+ *
+ * @desc
+ * Treeitem object for representing the state and user interactions for a
+ * treeItem widget
+ *
+ * @param node
+ * An element with the role=tree attribute
+ */
+
+ var TreeitemLink = function (node, treeObj, group) {
+
+ // Check whether node is a DOM element
+ if (typeof node !== 'object') {
+ return;
+ }
+
+ node.tabIndex = -1;
+ this.tree = treeObj;
+ this.groupTreeitem = group;
+ this.domNode = node;
+ this.label = node.textContent.trim();
+ this.stopDefaultClick = false;
+
+ if (node.getAttribute('aria-label')) {
+ this.label = node.getAttribute('aria-label').trim();
+ }
+
+ this.isExpandable = false;
+ this.isVisible = false;
+ this.inGroup = false;
+
+ if (group) {
+ this.inGroup = true;
+ }
+
+ var elem = node.firstElementChild;
+
+ while (elem) {
+
+ if (elem.tagName.toLowerCase() == 'ul') {
+ elem.setAttribute('role', 'group');
+ this.isExpandable = true;
+ break;
+ }
+
+ elem = elem.nextElementSibling;
+ }
+
+ this.keyCode = Object.freeze({
+ RETURN: 13,
+ SPACE: 32,
+ PAGEUP: 33,
+ PAGEDOWN: 34,
+ END: 35,
+ HOME: 36,
+ LEFT: 37,
+ UP: 38,
+ RIGHT: 39,
+ DOWN: 40
+ });
+ };
+
+ TreeitemLink.prototype.init = function () {
+ this.domNode.tabIndex = -1;
+
+ if (!this.domNode.getAttribute('role')) {
+ this.domNode.setAttribute('role', 'treeitem');
+ }
+
+ this.domNode.addEventListener('keydown', this.handleKeydown.bind(this));
+ this.domNode.addEventListener('click', this.handleClick.bind(this));
+ this.domNode.addEventListener('focus', this.handleFocus.bind(this));
+ this.domNode.addEventListener('blur', this.handleBlur.bind(this));
+
+ if (this.isExpandable) {
+ this.domNode.firstElementChild.addEventListener('mouseover', this.handleMouseOver.bind(this));
+ this.domNode.firstElementChild.addEventListener('mouseout', this.handleMouseOut.bind(this));
+ }
+ else {
+ this.domNode.addEventListener('mouseover', this.handleMouseOver.bind(this));
+ this.domNode.addEventListener('mouseout', this.handleMouseOut.bind(this));
+ }
+ };
+
+ TreeitemLink.prototype.isExpanded = function () {
+
+ if (this.isExpandable) {
+ return this.domNode.getAttribute('aria-expanded') === 'true';
+ }
+
+ return false;
+
+ };
+
+ /* EVENT HANDLERS */
+
+ TreeitemLink.prototype.handleKeydown = function (event) {
+ var tgt = event.currentTarget,
+ flag = false,
+ _char = event.key,
+ clickEvent;
+
+ function isPrintableCharacter(str) {
+ return str.length === 1 && str.match(/\S/);
+ }
+
+ function printableCharacter(item) {
+ if (_char == '*') {
+ item.tree.expandAllSiblingItems(item);
+ flag = true;
+ }
+ else {
+ if (isPrintableCharacter(_char)) {
+ item.tree.setFocusByFirstCharacter(item, _char);
+ flag = true;
+ }
+ }
+ }
+
+ this.stopDefaultClick = false;
+
+ if (event.altKey || event.ctrlKey || event.metaKey) {
+ return;
+ }
+
+ if (event.shift) {
+ if (event.keyCode == this.keyCode.SPACE || event.keyCode == this.keyCode.RETURN) {
+ event.stopPropagation();
+ this.stopDefaultClick = true;
+ }
+ else {
+ if (isPrintableCharacter(_char)) {
+ printableCharacter(this);
+ }
+ }
+ }
+ else {
+ switch (event.keyCode) {
+ case this.keyCode.SPACE:
+ case this.keyCode.RETURN:
+ if (this.isExpandable) {
+ if (this.isExpanded()) {
+ this.tree.collapseTreeitem(this);
+ }
+ else {
+ this.tree.expandTreeitem(this);
+ }
+ flag = true;
+ }
+ else {
+ event.stopPropagation();
+ this.stopDefaultClick = true;
+ }
+ break;
+
+ case this.keyCode.UP:
+ this.tree.setFocusToPreviousItem(this);
+ flag = true;
+ break;
+
+ case this.keyCode.DOWN:
+ this.tree.setFocusToNextItem(this);
+ flag = true;
+ break;
+
+ case this.keyCode.RIGHT:
+ if (this.isExpandable) {
+ if (this.isExpanded()) {
+ this.tree.setFocusToNextItem(this);
+ }
+ else {
+ this.tree.expandTreeitem(this);
+ }
+ }
+ flag = true;
+ break;
+
+ case this.keyCode.LEFT:
+ if (this.isExpandable && this.isExpanded()) {
+ this.tree.collapseTreeitem(this);
+ flag = true;
+ }
+ else {
+ if (this.inGroup) {
+ this.tree.setFocusToParentItem(this);
+ flag = true;
+ }
+ }
+ break;
+
+ case this.keyCode.HOME:
+ this.tree.setFocusToFirstItem();
+ flag = true;
+ break;
+
+ case this.keyCode.END:
+ this.tree.setFocusToLastItem();
+ flag = true;
+ break;
+
+ default:
+ if (isPrintableCharacter(_char)) {
+ printableCharacter(this);
+ }
+ break;
+ }
+ }
+
+ if (flag) {
+ event.stopPropagation();
+ event.preventDefault();
+ }
+ };
+
+ TreeitemLink.prototype.handleClick = function (event) {
+
+ // only process click events that directly happened on this treeitem
+ if (event.target !== this.domNode && event.target !== this.domNode.firstElementChild) {
+ return;
+ }
+
+ if (this.isExpandable) {
+ if (this.isExpanded()) {
+ this.tree.collapseTreeitem(this);
+ }
+ else {
+ this.tree.expandTreeitem(this);
+ }
+ event.stopPropagation();
+ }
+ };
+
+ TreeitemLink.prototype.handleFocus = function (event) {
+ var node = this.domNode;
+ if (this.isExpandable) {
+ node = node.firstElementChild;
+ }
+ node.classList.add('focus');
+ };
+
+ TreeitemLink.prototype.handleBlur = function (event) {
+ var node = this.domNode;
+ if (this.isExpandable) {
+ node = node.firstElementChild;
+ }
+ node.classList.remove('focus');
+ };
+
+ TreeitemLink.prototype.handleMouseOver = function (event) {
+ event.currentTarget.classList.add('hover');
+ };
+
+ TreeitemLink.prototype.handleMouseOut = function (event) {
+ event.currentTarget.classList.remove('hover');
+ };
+
+ return TreeitemLink;
+ })();
+
+ /**
+ * Creates a new TreeLinks.
+ *
+ * @since 4.9.0
+ * @class
+ * @private
+ * @see {@link https://www.w3.org/TR/wai-aria-practices-1.1/examples/treeview/treeview-2/treeview-2b.html|W3C Treeview Example}
+ * @license W3C-20150513
+ */
+ TreeLinks = (function () {
+ /*
+ * This content is licensed according to the W3C Software License at
+ * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
+ *
+ * File: TreeLinks.js
+ *
+ * Desc: Tree widget that implements ARIA Authoring Practices
+ * for a tree being used as a file viewer
+ *
+ * Author: Jon Gunderson, Ku Ja Eun and Nicholas Hoyt
+ */
+
+ /*
+ * @constructor
+ *
+ * @desc
+ * Tree item object for representing the state and user interactions for a
+ * tree widget
+ *
+ * @param node
+ * An element with the role=tree attribute
+ */
+
+ var TreeLinks = function (node) {
+ // Check whether node is a DOM element
+ if (typeof node !== 'object') {
+ return;
+ }
+
+ this.domNode = node;
+
+ this.treeitems = [];
+ this.firstChars = [];
+
+ this.firstTreeitem = null;
+ this.lastTreeitem = null;
+
+ };
+
+ TreeLinks.prototype.init = function () {
+
+ function findTreeitems(node, tree, group) {
+
+ var elem = node.firstElementChild;
+ var ti = group;
+
+ while (elem) {
+
+ if ((elem.tagName.toLowerCase() === 'li' && elem.firstElementChild.tagName.toLowerCase() === 'span') || elem.tagName.toLowerCase() === 'a') {
+ ti = new TreeitemLink(elem, tree, group);
+ ti.init();
+ tree.treeitems.push(ti);
+ tree.firstChars.push(ti.label.substring(0, 1).toLowerCase());
+ }
+
+ if (elem.firstElementChild) {
+ findTreeitems(elem, tree, ti);
+ }
+
+ elem = elem.nextElementSibling;
+ }
+ }
+
+ // initialize pop up menus
+ if (!this.domNode.getAttribute('role')) {
+ this.domNode.setAttribute('role', 'tree');
+ }
+
+ findTreeitems(this.domNode, this, false);
+
+ this.updateVisibleTreeitems();
+
+ this.firstTreeitem.domNode.tabIndex = 0;
+
+ };
+
+ TreeLinks.prototype.setFocusToItem = function (treeitem) {
+
+ for (var i = 0; i < this.treeitems.length; i++) {
+ var ti = this.treeitems[i];
+
+ if (ti === treeitem) {
+ ti.domNode.tabIndex = 0;
+ ti.domNode.focus();
+ }
+ else {
+ ti.domNode.tabIndex = -1;
+ }
+ }
+
+ };
+
+ TreeLinks.prototype.setFocusToNextItem = function (currentItem) {
+
+ var nextItem = false;
+
+ for (var i = (this.treeitems.length - 1); i >= 0; i--) {
+ var ti = this.treeitems[i];
+ if (ti === currentItem) {
+ break;
+ }
+ if (ti.isVisible) {
+ nextItem = ti;
+ }
+ }
+
+ if (nextItem) {
+ this.setFocusToItem(nextItem);
+ }
+
+ };
+
+ TreeLinks.prototype.setFocusToPreviousItem = function (currentItem) {
+
+ var prevItem = false;
+
+ for (var i = 0; i < this.treeitems.length; i++) {
+ var ti = this.treeitems[i];
+ if (ti === currentItem) {
+ break;
+ }
+ if (ti.isVisible) {
+ prevItem = ti;
+ }
+ }
+
+ if (prevItem) {
+ this.setFocusToItem(prevItem);
+ }
+ };
+
+ TreeLinks.prototype.setFocusToParentItem = function (currentItem) {
+
+ if (currentItem.groupTreeitem) {
+ this.setFocusToItem(currentItem.groupTreeitem);
+ }
+ };
+
+ TreeLinks.prototype.setFocusToFirstItem = function () {
+ this.setFocusToItem(this.firstTreeitem);
+ };
+
+ TreeLinks.prototype.setFocusToLastItem = function () {
+ this.setFocusToItem(this.lastTreeitem);
+ };
+
+ TreeLinks.prototype.expandTreeitem = function (currentItem) {
+
+ if (currentItem.isExpandable) {
+ currentItem.domNode.setAttribute('aria-expanded', true);
+ this.updateVisibleTreeitems();
+ }
+
+ };
+
+ TreeLinks.prototype.expandAllSiblingItems = function (currentItem) {
+ for (var i = 0; i < this.treeitems.length; i++) {
+ var ti = this.treeitems[i];
+
+ if ((ti.groupTreeitem === currentItem.groupTreeitem) && ti.isExpandable) {
+ this.expandTreeitem(ti);
+ }
+ }
+
+ };
+
+ TreeLinks.prototype.collapseTreeitem = function (currentItem) {
+
+ var groupTreeitem = false;
+
+ if (currentItem.isExpanded()) {
+ groupTreeitem = currentItem;
+ }
+ else {
+ groupTreeitem = currentItem.groupTreeitem;
+ }
+
+ if (groupTreeitem) {
+ groupTreeitem.domNode.setAttribute('aria-expanded', false);
+ this.updateVisibleTreeitems();
+ this.setFocusToItem(groupTreeitem);
+ }
+
+ };
+
+ TreeLinks.prototype.updateVisibleTreeitems = function () {
+
+ this.firstTreeitem = this.treeitems[0];
+
+ for (var i = 0; i < this.treeitems.length; i++) {
+ var ti = this.treeitems[i];
+
+ var parent = ti.domNode.parentNode;
+
+ ti.isVisible = true;
+
+ while (parent && (parent !== this.domNode)) {
+
+ if (parent.getAttribute('aria-expanded') == 'false') {
+ ti.isVisible = false;
+ }
+ parent = parent.parentNode;
+ }
+
+ if (ti.isVisible) {
+ this.lastTreeitem = ti;
+ }
+ }
+
+ };
+
+ TreeLinks.prototype.setFocusByFirstCharacter = function (currentItem, _char) {
+ var start, index;
+ _char = _char.toLowerCase();
+
+ // Get start index for search based on position of currentItem
+ start = this.treeitems.indexOf(currentItem) + 1;
+ if (start === this.treeitems.length) {
+ start = 0;
+ }
+
+ // Check remaining slots in the menu
+ index = this.getIndexFirstChars(start, _char);
+
+ // If not found in remaining slots, check from beginning
+ if (index === -1) {
+ index = this.getIndexFirstChars(0, _char);
+ }
+
+ // If match was found...
+ if (index > -1) {
+ this.setFocusToItem(this.treeitems[index]);
+ }
+ };
+
+ TreeLinks.prototype.getIndexFirstChars = function (startIndex, _char) {
+ for (var i = startIndex; i < this.firstChars.length; i++) {
+ if (this.treeitems[i].isVisible) {
+ if (_char === this.firstChars[i]) {
+ return i;
+ }
+ }
+ }
+ return -1;
+ };
+
+ return TreeLinks;
+ })();
+
+ /* jshint ignore:end */
+ /* jscs:enable */
+ /* eslint-enable */
+
+ return component;
+})( jQuery );
diff --git a/www/crm/wp-admin/js/theme-plugin-editor.min.js b/www/crm/wp-admin/js/theme-plugin-editor.min.js
new file mode 100644
index 00000000..7c1195ef
--- /dev/null
+++ b/www/crm/wp-admin/js/theme-plugin-editor.min.js
@@ -0,0 +1 @@
+window.wp||(window.wp={}),wp.themePluginEditor=function(a){"use strict";var b,c;b={l10n:{lintError:{singular:"",plural:""},saveAlert:"",saveError:""},codeEditor:{},instance:null,noticeElements:{},dirty:!1,lintErrors:[]},b.init=function(c,d){b.form=c,d&&a.extend(b,d),b.noticeTemplate=wp.template("wp-file-editor-notice"),b.noticesContainer=b.form.find(".editor-notices"),b.submitButton=b.form.find(":input[name=submit]"),b.spinner=b.form.find(".submit .spinner"),b.form.on("submit",b.submit),b.textarea=b.form.find("#newcontent"),b.textarea.on("change",b.onChange),b.warning=a(".file-editor-warning"),b.docsLookUpButton=b.form.find("#docs-lookup"),b.docsLookUpList=b.form.find("#docs-list"),b.warning.length>0&&b.showWarning(),!1!==b.codeEditor&&_.defer(function(){b.initCodeEditor()}),a(b.initFileBrowser),a(window).on("beforeunload",function(){if(b.dirty)return b.l10n.saveAlert}),b.docsLookUpList.on("change",function(){var c=a(this).val();""===c?b.docsLookUpButton.prop("disabled",!0):b.docsLookUpButton.prop("disabled",!1)})},b.showWarning=function(){var c=b.warning.find(".file-editor-warning-message").text();a("#wpwrap").attr("aria-hidden","true"),a(document.body).addClass("modal-open").append(b.warning.detach()),b.warning.removeClass("hidden").find(".file-editor-warning-go-back").focus(),b.warningTabbables=b.warning.find("a, button"),b.warningTabbables.on("keydown",b.constrainTabbing),b.warning.on("click",".file-editor-warning-dismiss",b.dismissWarning),setTimeout(function(){wp.a11y.speak(wp.sanitize.stripTags(c.replace(/\s+/g," ")),"assertive")},1e3)},b.constrainTabbing=function(a){var c,d;9===a.which&&(c=b.warningTabbables.first()[0],d=b.warningTabbables.last()[0],d!==a.target||a.shiftKey?c===a.target&&a.shiftKey&&(d.focus(),a.preventDefault()):(c.focus(),a.preventDefault()))},b.dismissWarning=function(){wp.ajax.post("dismiss-wp-pointer",{pointer:b.themeOrPlugin+"_editor_notice"}),b.warning.remove(),a("#wpwrap").removeAttr("aria-hidden"),a("body").removeClass("modal-open")},b.onChange=function(){b.dirty=!0,b.removeNotice("file_saved")},b.submit=function(c){var d,e={};if(c.preventDefault(),a.each(b.form.serializeArray(),function(){e[this.name]=this.value}),b.instance&&(e.newcontent=b.instance.codemirror.getValue()),!b.isSaving){if(b.lintErrors.length)return void b.instance.codemirror.setCursor(b.lintErrors[0].from.line);b.isSaving=!0,b.textarea.prop("readonly",!0),b.instance&&b.instance.codemirror.setOption("readOnly",!0),b.spinner.addClass("is-active"),d=wp.ajax.post("edit-theme-plugin-file",e),b.lastSaveNoticeCode&&b.removeNotice(b.lastSaveNoticeCode),d.done(function(a){b.lastSaveNoticeCode="file_saved",b.addNotice({code:b.lastSaveNoticeCode,type:"success",message:a.message,dismissible:!0}),b.dirty=!1}),d.fail(function(c){var d=a.extend({code:"save_error",message:b.l10n.saveError},c,{type:"error",dismissible:!0});b.lastSaveNoticeCode=d.code,b.addNotice(d)}),d.always(function(){b.spinner.removeClass("is-active"),b.isSaving=!1,b.textarea.prop("readonly",!1),b.instance&&b.instance.codemirror.setOption("readOnly",!1)})}},b.addNotice=function(c){var d;if(!c.code)throw new Error("Missing code.");return b.removeNotice(c.code),d=a(b.noticeTemplate(c)),d.hide(),d.find(".notice-dismiss").on("click",function(){b.removeNotice(c.code),c.onDismiss&&c.onDismiss(c)}),wp.a11y.speak(c.message),b.noticesContainer.append(d),d.slideDown("fast"),b.noticeElements[c.code]=d,d},b.removeNotice=function(c){return!!b.noticeElements[c]&&(b.noticeElements[c].slideUp("fast",function(){a(this).remove()}),delete b.noticeElements[c],!0)},b.initCodeEditor=function(){var c,d;c=a.extend({},b.codeEditor),c.onTabPrevious=function(){a("#templateside").find(":tabbable").last().focus()},c.onTabNext=function(){a("#template").find(":tabbable:not(.CodeMirror-code)").first().focus()},c.onChangeLintingErrors=function(a){b.lintErrors=a,0===a.length&&b.submitButton.toggleClass("disabled",!1)},c.onUpdateErrorNotice=function(a){var d,e;b.submitButton.toggleClass("disabled",a.length>0),0!==a.length?(d=1===a.length?b.l10n.lintError.singular.replace("%d","1"):b.l10n.lintError.plural.replace("%d",String(a.length)),e=b.addNotice({code:"lint_errors",type:"error",message:d,dismissible:!1}),e.find("input[type=checkbox]").on("click",function(){c.onChangeLintingErrors([]),b.removeNotice("lint_errors")})):b.removeNotice("lint_errors")},d=wp.codeEditor.initialize(a("#newcontent"),c),d.codemirror.on("change",b.onChange),a(d.codemirror.display.lineDiv).attr({role:"textbox","aria-multiline":"true","aria-labelledby":"theme-plugin-editor-label","aria-describedby":"editor-keyboard-trap-help-1 editor-keyboard-trap-help-2 editor-keyboard-trap-help-3 editor-keyboard-trap-help-4"}),a("#theme-plugin-editor-label").on("click",function(){d.codemirror.focus()}),b.instance=d},b.initFileBrowser=function(){var b=a("#templateside");b.find('[role="group"]').parent().attr("aria-expanded",!1),b.find(".notice").parents("[aria-expanded]").attr("aria-expanded",!0),b.find('[role="tree"]').each(function(){var a=new c(this);a.init()}),b.find(".current-file:first").each(function(){this.scrollIntoViewIfNeeded?this.scrollIntoViewIfNeeded():this.scrollIntoView(!1)})};var d=function(){var a=function(a,b,c){if("object"==typeof a){a.tabIndex=-1,this.tree=b,this.groupTreeitem=c,this.domNode=a,this.label=a.textContent.trim(),this.stopDefaultClick=!1,a.getAttribute("aria-label")&&(this.label=a.getAttribute("aria-label").trim()),this.isExpandable=!1,this.isVisible=!1,this.inGroup=!1,c&&(this.inGroup=!0);for(var d=a.firstElementChild;d;){if("ul"==d.tagName.toLowerCase()){d.setAttribute("role","group"),this.isExpandable=!0;break}d=d.nextElementSibling}this.keyCode=Object.freeze({RETURN:13,SPACE:32,PAGEUP:33,PAGEDOWN:34,END:35,HOME:36,LEFT:37,UP:38,RIGHT:39,DOWN:40})}};return a.prototype.init=function(){this.domNode.tabIndex=-1,this.domNode.getAttribute("role")||this.domNode.setAttribute("role","treeitem"),this.domNode.addEventListener("keydown",this.handleKeydown.bind(this)),this.domNode.addEventListener("click",this.handleClick.bind(this)),this.domNode.addEventListener("focus",this.handleFocus.bind(this)),this.domNode.addEventListener("blur",this.handleBlur.bind(this)),this.isExpandable?(this.domNode.firstElementChild.addEventListener("mouseover",this.handleMouseOver.bind(this)),this.domNode.firstElementChild.addEventListener("mouseout",this.handleMouseOut.bind(this))):(this.domNode.addEventListener("mouseover",this.handleMouseOver.bind(this)),this.domNode.addEventListener("mouseout",this.handleMouseOut.bind(this)))},a.prototype.isExpanded=function(){return!!this.isExpandable&&"true"===this.domNode.getAttribute("aria-expanded")},a.prototype.handleKeydown=function(a){function b(a){return 1===a.length&&a.match(/\S/)}function c(a){"*"==e?(a.tree.expandAllSiblingItems(a),d=!0):b(e)&&(a.tree.setFocusByFirstCharacter(a,e),d=!0)}var d=(a.currentTarget,!1),e=a.key;if(this.stopDefaultClick=!1,!(a.altKey||a.ctrlKey||a.metaKey)){if(a.shift)a.keyCode==this.keyCode.SPACE||a.keyCode==this.keyCode.RETURN?(a.stopPropagation(),this.stopDefaultClick=!0):b(e)&&c(this);else switch(a.keyCode){case this.keyCode.SPACE:case this.keyCode.RETURN:this.isExpandable?(this.isExpanded()?this.tree.collapseTreeitem(this):this.tree.expandTreeitem(this),d=!0):(a.stopPropagation(),this.stopDefaultClick=!0);break;case this.keyCode.UP:this.tree.setFocusToPreviousItem(this),d=!0;break;case this.keyCode.DOWN:this.tree.setFocusToNextItem(this),d=!0;break;case this.keyCode.RIGHT:this.isExpandable&&(this.isExpanded()?this.tree.setFocusToNextItem(this):this.tree.expandTreeitem(this)),d=!0;break;case this.keyCode.LEFT:this.isExpandable&&this.isExpanded()?(this.tree.collapseTreeitem(this),d=!0):this.inGroup&&(this.tree.setFocusToParentItem(this),d=!0);break;case this.keyCode.HOME:this.tree.setFocusToFirstItem(),d=!0;break;case this.keyCode.END:this.tree.setFocusToLastItem(),d=!0;break;default:b(e)&&c(this)}d&&(a.stopPropagation(),a.preventDefault())}},a.prototype.handleClick=function(a){a.target!==this.domNode&&a.target!==this.domNode.firstElementChild||this.isExpandable&&(this.isExpanded()?this.tree.collapseTreeitem(this):this.tree.expandTreeitem(this),a.stopPropagation())},a.prototype.handleFocus=function(a){var b=this.domNode;this.isExpandable&&(b=b.firstElementChild),b.classList.add("focus")},a.prototype.handleBlur=function(a){var b=this.domNode;this.isExpandable&&(b=b.firstElementChild),b.classList.remove("focus")},a.prototype.handleMouseOver=function(a){a.currentTarget.classList.add("hover")},a.prototype.handleMouseOut=function(a){a.currentTarget.classList.remove("hover")},a}();return c=function(){var a=function(a){"object"==typeof a&&(this.domNode=a,this.treeitems=[],this.firstChars=[],this.firstTreeitem=null,this.lastTreeitem=null)};return a.prototype.init=function(){function a(b,c,e){for(var f=b.firstElementChild,g=e;f;)("li"===f.tagName.toLowerCase()&&"span"===f.firstElementChild.tagName.toLowerCase()||"a"===f.tagName.toLowerCase())&&(g=new d(f,c,e),g.init(),c.treeitems.push(g),c.firstChars.push(g.label.substring(0,1).toLowerCase())),f.firstElementChild&&a(f,c,g),f=f.nextElementSibling}this.domNode.getAttribute("role")||this.domNode.setAttribute("role","tree"),a(this.domNode,this,!1),this.updateVisibleTreeitems(),this.firstTreeitem.domNode.tabIndex=0},a.prototype.setFocusToItem=function(a){for(var b=0;b<this.treeitems.length;b++){var c=this.treeitems[b];c===a?(c.domNode.tabIndex=0,c.domNode.focus()):c.domNode.tabIndex=-1}},a.prototype.setFocusToNextItem=function(a){for(var b=!1,c=this.treeitems.length-1;c>=0;c--){var d=this.treeitems[c];if(d===a)break;d.isVisible&&(b=d)}b&&this.setFocusToItem(b)},a.prototype.setFocusToPreviousItem=function(a){for(var b=!1,c=0;c<this.treeitems.length;c++){var d=this.treeitems[c];if(d===a)break;d.isVisible&&(b=d)}b&&this.setFocusToItem(b)},a.prototype.setFocusToParentItem=function(a){a.groupTreeitem&&this.setFocusToItem(a.groupTreeitem)},a.prototype.setFocusToFirstItem=function(){this.setFocusToItem(this.firstTreeitem)},a.prototype.setFocusToLastItem=function(){this.setFocusToItem(this.lastTreeitem)},a.prototype.expandTreeitem=function(a){a.isExpandable&&(a.domNode.setAttribute("aria-expanded",!0),this.updateVisibleTreeitems())},a.prototype.expandAllSiblingItems=function(a){for(var b=0;b<this.treeitems.length;b++){var c=this.treeitems[b];c.groupTreeitem===a.groupTreeitem&&c.isExpandable&&this.expandTreeitem(c)}},a.prototype.collapseTreeitem=function(a){var b=!1;b=a.isExpanded()?a:a.groupTreeitem,b&&(b.domNode.setAttribute("aria-expanded",!1),this.updateVisibleTreeitems(),this.setFocusToItem(b))},a.prototype.updateVisibleTreeitems=function(){this.firstTreeitem=this.treeitems[0];for(var a=0;a<this.treeitems.length;a++){var b=this.treeitems[a],c=b.domNode.parentNode;for(b.isVisible=!0;c&&c!==this.domNode;)"false"==c.getAttribute("aria-expanded")&&(b.isVisible=!1),c=c.parentNode;b.isVisible&&(this.lastTreeitem=b)}},a.prototype.setFocusByFirstCharacter=function(a,b){var c,d;b=b.toLowerCase(),c=this.treeitems.indexOf(a)+1,c===this.treeitems.length&&(c=0),d=this.getIndexFirstChars(c,b),d===-1&&(d=this.getIndexFirstChars(0,b)),d>-1&&this.setFocusToItem(this.treeitems[d])},a.prototype.getIndexFirstChars=function(a,b){for(var c=a;c<this.firstChars.length;c++)if(this.treeitems[c].isVisible&&b===this.firstChars[c])return c;return-1},a}(),b}(jQuery); \ No newline at end of file
diff --git a/www/crm/wp-admin/js/theme.js b/www/crm/wp-admin/js/theme.js
new file mode 100644
index 00000000..596b097f
--- /dev/null
+++ b/www/crm/wp-admin/js/theme.js
@@ -0,0 +1,2067 @@
+/**
+ * @output wp-admin/js/theme.js
+ */
+
+/* global _wpThemeSettings, confirm, tb_position */
+window.wp = window.wp || {};
+
+( function($) {
+
+// Set up our namespace...
+var themes, l10n;
+themes = wp.themes = wp.themes || {};
+
+// Store the theme data and settings for organized and quick access
+// themes.data.settings, themes.data.themes, themes.data.l10n
+themes.data = _wpThemeSettings;
+l10n = themes.data.l10n;
+
+// Shortcut for isInstall check
+themes.isInstall = !! themes.data.settings.isInstall;
+
+// Setup app structure
+_.extend( themes, { model: {}, view: {}, routes: {}, router: {}, template: wp.template });
+
+themes.Model = Backbone.Model.extend({
+ // Adds attributes to the default data coming through the .org themes api
+ // Map `id` to `slug` for shared code
+ initialize: function() {
+ var description;
+
+ // If theme is already installed, set an attribute.
+ if ( _.indexOf( themes.data.installedThemes, this.get( 'slug' ) ) !== -1 ) {
+ this.set({ installed: true });
+ }
+
+ // Set the attributes
+ this.set({
+ // slug is for installation, id is for existing.
+ id: this.get( 'slug' ) || this.get( 'id' )
+ });
+
+ // Map `section.description` to `description`
+ // as the API sometimes returns it differently
+ if ( this.has( 'sections' ) ) {
+ description = this.get( 'sections' ).description;
+ this.set({ description: description });
+ }
+ }
+});
+
+// Main view controller for themes.php
+// Unifies and renders all available views
+themes.view.Appearance = wp.Backbone.View.extend({
+
+ el: '#wpbody-content .wrap .theme-browser',
+
+ window: $( window ),
+ // Pagination instance
+ page: 0,
+
+ // Sets up a throttler for binding to 'scroll'
+ initialize: function( options ) {
+ // Scroller checks how far the scroll position is
+ _.bindAll( this, 'scroller' );
+
+ this.SearchView = options.SearchView ? options.SearchView : themes.view.Search;
+ // Bind to the scroll event and throttle
+ // the results from this.scroller
+ this.window.bind( 'scroll', _.throttle( this.scroller, 300 ) );
+ },
+
+ // Main render control
+ render: function() {
+ // Setup the main theme view
+ // with the current theme collection
+ this.view = new themes.view.Themes({
+ collection: this.collection,
+ parent: this
+ });
+
+ // Render search form.
+ this.search();
+
+ this.$el.removeClass( 'search-loading' );
+
+ // Render and append
+ this.view.render();
+ this.$el.empty().append( this.view.el ).addClass( 'rendered' );
+ },
+
+ // Defines search element container
+ searchContainer: $( '.search-form' ),
+
+ // Search input and view
+ // for current theme collection
+ search: function() {
+ var view,
+ self = this;
+
+ // Don't render the search if there is only one theme
+ if ( themes.data.themes.length === 1 ) {
+ return;
+ }
+
+ view = new this.SearchView({
+ collection: self.collection,
+ parent: this
+ });
+ self.SearchView = view;
+
+ // Render and append after screen title
+ view.render();
+ this.searchContainer
+ .append( $.parseHTML( '<label class="screen-reader-text" for="wp-filter-search-input">' + l10n.search + '</label>' ) )
+ .append( view.el )
+ .on( 'submit', function( event ) {
+ event.preventDefault();
+ });
+ },
+
+ // Checks when the user gets close to the bottom
+ // of the mage and triggers a theme:scroll event
+ scroller: function() {
+ var self = this,
+ bottom, threshold;
+
+ bottom = this.window.scrollTop() + self.window.height();
+ threshold = self.$el.offset().top + self.$el.outerHeight( false ) - self.window.height();
+ threshold = Math.round( threshold * 0.9 );
+
+ if ( bottom > threshold ) {
+ this.trigger( 'theme:scroll' );
+ }
+ }
+});
+
+// Set up the Collection for our theme data
+// @has 'id' 'name' 'screenshot' 'author' 'authorURI' 'version' 'active' ...
+themes.Collection = Backbone.Collection.extend({
+
+ model: themes.Model,
+
+ // Search terms
+ terms: '',
+
+ // Controls searching on the current theme collection
+ // and triggers an update event
+ doSearch: function( value ) {
+
+ // Don't do anything if we've already done this search
+ // Useful because the Search handler fires multiple times per keystroke
+ if ( this.terms === value ) {
+ return;
+ }
+
+ // Updates terms with the value passed
+ this.terms = value;
+
+ // If we have terms, run a search...
+ if ( this.terms.length > 0 ) {
+ this.search( this.terms );
+ }
+
+ // If search is blank, show all themes
+ // Useful for resetting the views when you clean the input
+ if ( this.terms === '' ) {
+ this.reset( themes.data.themes );
+ $( 'body' ).removeClass( 'no-results' );
+ }
+
+ // Trigger a 'themes:update' event
+ this.trigger( 'themes:update' );
+ },
+
+ // Performs a search within the collection
+ // @uses RegExp
+ search: function( term ) {
+ var match, results, haystack, name, description, author;
+
+ // Start with a full collection
+ this.reset( themes.data.themes, { silent: true } );
+
+ // Escape the term string for RegExp meta characters
+ term = term.replace( /[-\/\\^$*+?.()|[\]{}]/g, '\\$&' );
+
+ // Consider spaces as word delimiters and match the whole string
+ // so matching terms can be combined
+ term = term.replace( / /g, ')(?=.*' );
+ match = new RegExp( '^(?=.*' + term + ').+', 'i' );
+
+ // Find results
+ // _.filter and .test
+ results = this.filter( function( data ) {
+ name = data.get( 'name' ).replace( /(<([^>]+)>)/ig, '' );
+ description = data.get( 'description' ).replace( /(<([^>]+)>)/ig, '' );
+ author = data.get( 'author' ).replace( /(<([^>]+)>)/ig, '' );
+
+ haystack = _.union( [ name, data.get( 'id' ), description, author, data.get( 'tags' ) ] );
+
+ if ( match.test( data.get( 'author' ) ) && term.length > 2 ) {
+ data.set( 'displayAuthor', true );
+ }
+
+ return match.test( haystack );
+ });
+
+ if ( results.length === 0 ) {
+ this.trigger( 'query:empty' );
+ } else {
+ $( 'body' ).removeClass( 'no-results' );
+ }
+
+ this.reset( results );
+ },
+
+ // Paginates the collection with a helper method
+ // that slices the collection
+ paginate: function( instance ) {
+ var collection = this;
+ instance = instance || 0;
+
+ // Themes per instance are set at 20
+ collection = _( collection.rest( 20 * instance ) );
+ collection = _( collection.first( 20 ) );
+
+ return collection;
+ },
+
+ count: false,
+
+ // Handles requests for more themes
+ // and caches results
+ //
+ // When we are missing a cache object we fire an apiCall()
+ // which triggers events of `query:success` or `query:fail`
+ query: function( request ) {
+ /**
+ * @static
+ * @type Array
+ */
+ var queries = this.queries,
+ self = this,
+ query, isPaginated, count;
+
+ // Store current query request args
+ // for later use with the event `theme:end`
+ this.currentQuery.request = request;
+
+ // Search the query cache for matches.
+ query = _.find( queries, function( query ) {
+ return _.isEqual( query.request, request );
+ });
+
+ // If the request matches the stored currentQuery.request
+ // it means we have a paginated request.
+ isPaginated = _.has( request, 'page' );
+
+ // Reset the internal api page counter for non paginated queries.
+ if ( ! isPaginated ) {
+ this.currentQuery.page = 1;
+ }
+
+ // Otherwise, send a new API call and add it to the cache.
+ if ( ! query && ! isPaginated ) {
+ query = this.apiCall( request ).done( function( data ) {
+
+ // Update the collection with the queried data.
+ if ( data.themes ) {
+ self.reset( data.themes );
+ count = data.info.results;
+ // Store the results and the query request
+ queries.push( { themes: data.themes, request: request, total: count } );
+ }
+
+ // Trigger a collection refresh event
+ // and a `query:success` event with a `count` argument.
+ self.trigger( 'themes:update' );
+ self.trigger( 'query:success', count );
+
+ if ( data.themes && data.themes.length === 0 ) {
+ self.trigger( 'query:empty' );
+ }
+
+ }).fail( function() {
+ self.trigger( 'query:fail' );
+ });
+ } else {
+ // If it's a paginated request we need to fetch more themes...
+ if ( isPaginated ) {
+ return this.apiCall( request, isPaginated ).done( function( data ) {
+ // Add the new themes to the current collection
+ // @todo update counter
+ self.add( data.themes );
+ self.trigger( 'query:success' );
+
+ // We are done loading themes for now.
+ self.loadingThemes = false;
+
+ }).fail( function() {
+ self.trigger( 'query:fail' );
+ });
+ }
+
+ if ( query.themes.length === 0 ) {
+ self.trigger( 'query:empty' );
+ } else {
+ $( 'body' ).removeClass( 'no-results' );
+ }
+
+ // Only trigger an update event since we already have the themes
+ // on our cached object
+ if ( _.isNumber( query.total ) ) {
+ this.count = query.total;
+ }
+
+ this.reset( query.themes );
+ if ( ! query.total ) {
+ this.count = this.length;
+ }
+
+ this.trigger( 'themes:update' );
+ this.trigger( 'query:success', this.count );
+ }
+ },
+
+ // Local cache array for API queries
+ queries: [],
+
+ // Keep track of current query so we can handle pagination
+ currentQuery: {
+ page: 1,
+ request: {}
+ },
+
+ // Send request to api.wordpress.org/themes
+ apiCall: function( request, paginated ) {
+ return wp.ajax.send( 'query-themes', {
+ data: {
+ // Request data
+ request: _.extend({
+ per_page: 100
+ }, request)
+ },
+
+ beforeSend: function() {
+ if ( ! paginated ) {
+ // Spin it
+ $( 'body' ).addClass( 'loading-content' ).removeClass( 'no-results' );
+ }
+ }
+ });
+ },
+
+ // Static status controller for when we are loading themes.
+ loadingThemes: false
+});
+
+// This is the view that controls each theme item
+// that will be displayed on the screen
+themes.view.Theme = wp.Backbone.View.extend({
+
+ // Wrap theme data on a div.theme element
+ className: 'theme',
+
+ // Reflects which theme view we have
+ // 'grid' (default) or 'detail'
+ state: 'grid',
+
+ // The HTML template for each element to be rendered
+ html: themes.template( 'theme' ),
+
+ events: {
+ 'click': themes.isInstall ? 'preview': 'expand',
+ 'keydown': themes.isInstall ? 'preview': 'expand',
+ 'touchend': themes.isInstall ? 'preview': 'expand',
+ 'keyup': 'addFocus',
+ 'touchmove': 'preventExpand',
+ 'click .theme-install': 'installTheme',
+ 'click .update-message': 'updateTheme'
+ },
+
+ touchDrag: false,
+
+ initialize: function() {
+ this.model.on( 'change', this.render, this );
+ },
+
+ render: function() {
+ var data = this.model.toJSON();
+
+ // Render themes using the html template
+ this.$el.html( this.html( data ) ).attr({
+ tabindex: 0,
+ 'aria-describedby' : data.id + '-action ' + data.id + '-name',
+ 'data-slug': data.id
+ });
+
+ // Renders active theme styles
+ this.activeTheme();
+
+ if ( this.model.get( 'displayAuthor' ) ) {
+ this.$el.addClass( 'display-author' );
+ }
+ },
+
+ // Adds a class to the currently active theme
+ // and to the overlay in detailed view mode
+ activeTheme: function() {
+ if ( this.model.get( 'active' ) ) {
+ this.$el.addClass( 'active' );
+ }
+ },
+
+ // Add class of focus to the theme we are focused on.
+ addFocus: function() {
+ var $themeToFocus = ( $( ':focus' ).hasClass( 'theme' ) ) ? $( ':focus' ) : $(':focus').parents('.theme');
+
+ $('.theme.focus').removeClass('focus');
+ $themeToFocus.addClass('focus');
+ },
+
+ // Single theme overlay screen
+ // It's shown when clicking a theme
+ expand: function( event ) {
+ var self = this;
+
+ event = event || window.event;
+
+ // 'enter' and 'space' keys expand the details view when a theme is :focused
+ if ( event.type === 'keydown' && ( event.which !== 13 && event.which !== 32 ) ) {
+ return;
+ }
+
+ // Bail if the user scrolled on a touch device
+ if ( this.touchDrag === true ) {
+ return this.touchDrag = false;
+ }
+
+ // Prevent the modal from showing when the user clicks
+ // one of the direct action buttons
+ if ( $( event.target ).is( '.theme-actions a' ) ) {
+ return;
+ }
+
+ // Prevent the modal from showing when the user clicks one of the direct action buttons.
+ if ( $( event.target ).is( '.theme-actions a, .update-message, .button-link, .notice-dismiss' ) ) {
+ return;
+ }
+
+ // Set focused theme to current element
+ themes.focusedTheme = this.$el;
+
+ this.trigger( 'theme:expand', self.model.cid );
+ },
+
+ preventExpand: function() {
+ this.touchDrag = true;
+ },
+
+ preview: function( event ) {
+ var self = this,
+ current, preview;
+
+ event = event || window.event;
+
+ // Bail if the user scrolled on a touch device
+ if ( this.touchDrag === true ) {
+ return this.touchDrag = false;
+ }
+
+ // Allow direct link path to installing a theme.
+ if ( $( event.target ).not( '.install-theme-preview' ).parents( '.theme-actions' ).length ) {
+ return;
+ }
+
+ // 'enter' and 'space' keys expand the details view when a theme is :focused
+ if ( event.type === 'keydown' && ( event.which !== 13 && event.which !== 32 ) ) {
+ return;
+ }
+
+ // pressing enter while focused on the buttons shouldn't open the preview
+ if ( event.type === 'keydown' && event.which !== 13 && $( ':focus' ).hasClass( 'button' ) ) {
+ return;
+ }
+
+ event.preventDefault();
+
+ event = event || window.event;
+
+ // Set focus to current theme.
+ themes.focusedTheme = this.$el;
+
+ // Construct a new Preview view.
+ themes.preview = preview = new themes.view.Preview({
+ model: this.model
+ });
+
+ // Render the view and append it.
+ preview.render();
+ this.setNavButtonsState();
+
+ // Hide previous/next navigation if there is only one theme
+ if ( this.model.collection.length === 1 ) {
+ preview.$el.addClass( 'no-navigation' );
+ } else {
+ preview.$el.removeClass( 'no-navigation' );
+ }
+
+ // Append preview
+ $( 'div.wrap' ).append( preview.el );
+
+ // Listen to our preview object
+ // for `theme:next` and `theme:previous` events.
+ this.listenTo( preview, 'theme:next', function() {
+
+ // Keep local track of current theme model.
+ current = self.model;
+
+ // If we have ventured away from current model update the current model position.
+ if ( ! _.isUndefined( self.current ) ) {
+ current = self.current;
+ }
+
+ // Get next theme model.
+ self.current = self.model.collection.at( self.model.collection.indexOf( current ) + 1 );
+
+ // If we have no more themes, bail.
+ if ( _.isUndefined( self.current ) ) {
+ self.options.parent.parent.trigger( 'theme:end' );
+ return self.current = current;
+ }
+
+ preview.model = self.current;
+
+ // Render and append.
+ preview.render();
+ this.setNavButtonsState();
+ $( '.next-theme' ).focus();
+ })
+ .listenTo( preview, 'theme:previous', function() {
+
+ // Keep track of current theme model.
+ current = self.model;
+
+ // Bail early if we are at the beginning of the collection
+ if ( self.model.collection.indexOf( self.current ) === 0 ) {
+ return;
+ }
+
+ // If we have ventured away from current model update the current model position.
+ if ( ! _.isUndefined( self.current ) ) {
+ current = self.current;
+ }
+
+ // Get previous theme model.
+ self.current = self.model.collection.at( self.model.collection.indexOf( current ) - 1 );
+
+ // If we have no more themes, bail.
+ if ( _.isUndefined( self.current ) ) {
+ return;
+ }
+
+ preview.model = self.current;
+
+ // Render and append.
+ preview.render();
+ this.setNavButtonsState();
+ $( '.previous-theme' ).focus();
+ });
+
+ this.listenTo( preview, 'preview:close', function() {
+ self.current = self.model;
+ });
+
+ },
+
+ // Handles .disabled classes for previous/next buttons in theme installer preview
+ setNavButtonsState: function() {
+ var $themeInstaller = $( '.theme-install-overlay' ),
+ current = _.isUndefined( this.current ) ? this.model : this.current,
+ previousThemeButton = $themeInstaller.find( '.previous-theme' ),
+ nextThemeButton = $themeInstaller.find( '.next-theme' );
+
+ // Disable previous at the zero position
+ if ( 0 === this.model.collection.indexOf( current ) ) {
+ previousThemeButton
+ .addClass( 'disabled' )
+ .prop( 'disabled', true );
+
+ nextThemeButton.focus();
+ }
+
+ // Disable next if the next model is undefined
+ if ( _.isUndefined( this.model.collection.at( this.model.collection.indexOf( current ) + 1 ) ) ) {
+ nextThemeButton
+ .addClass( 'disabled' )
+ .prop( 'disabled', true );
+
+ previousThemeButton.focus();
+ }
+ },
+
+ installTheme: function( event ) {
+ var _this = this;
+
+ event.preventDefault();
+
+ wp.updates.maybeRequestFilesystemCredentials( event );
+
+ $( document ).on( 'wp-theme-install-success', function( event, response ) {
+ if ( _this.model.get( 'id' ) === response.slug ) {
+ _this.model.set( { 'installed': true } );
+ }
+ } );
+
+ wp.updates.installTheme( {
+ slug: $( event.target ).data( 'slug' )
+ } );
+ },
+
+ updateTheme: function( event ) {
+ var _this = this;
+
+ if ( ! this.model.get( 'hasPackage' ) ) {
+ return;
+ }
+
+ event.preventDefault();
+
+ wp.updates.maybeRequestFilesystemCredentials( event );
+
+ $( document ).on( 'wp-theme-update-success', function( event, response ) {
+ _this.model.off( 'change', _this.render, _this );
+ if ( _this.model.get( 'id' ) === response.slug ) {
+ _this.model.set( {
+ hasUpdate: false,
+ version: response.newVersion
+ } );
+ }
+ _this.model.on( 'change', _this.render, _this );
+ } );
+
+ wp.updates.updateTheme( {
+ slug: $( event.target ).parents( 'div.theme' ).first().data( 'slug' )
+ } );
+ }
+});
+
+// Theme Details view
+// Set ups a modal overlay with the expanded theme data
+themes.view.Details = wp.Backbone.View.extend({
+
+ // Wrap theme data on a div.theme element
+ className: 'theme-overlay',
+
+ events: {
+ 'click': 'collapse',
+ 'click .delete-theme': 'deleteTheme',
+ 'click .left': 'previousTheme',
+ 'click .right': 'nextTheme',
+ 'click #update-theme': 'updateTheme'
+ },
+
+ // The HTML template for the theme overlay
+ html: themes.template( 'theme-single' ),
+
+ render: function() {
+ var data = this.model.toJSON();
+ this.$el.html( this.html( data ) );
+ // Renders active theme styles
+ this.activeTheme();
+ // Set up navigation events
+ this.navigation();
+ // Checks screenshot size
+ this.screenshotCheck( this.$el );
+ // Contain "tabbing" inside the overlay
+ this.containFocus( this.$el );
+ },
+
+ // Adds a class to the currently active theme
+ // and to the overlay in detailed view mode
+ activeTheme: function() {
+ // Check the model has the active property
+ this.$el.toggleClass( 'active', this.model.get( 'active' ) );
+ },
+
+ // Set initial focus and constrain tabbing within the theme browser modal.
+ containFocus: function( $el ) {
+
+ // Set initial focus on the primary action control.
+ _.delay( function() {
+ $( '.theme-overlay' ).focus();
+ }, 100 );
+
+ // Constrain tabbing within the modal.
+ $el.on( 'keydown.wp-themes', function( event ) {
+ var $firstFocusable = $el.find( '.theme-header button:not(.disabled)' ).first(),
+ $lastFocusable = $el.find( '.theme-actions a:visible' ).last();
+
+ // Check for the Tab key.
+ if ( 9 === event.which ) {
+ if ( $firstFocusable[0] === event.target && event.shiftKey ) {
+ $lastFocusable.focus();
+ event.preventDefault();
+ } else if ( $lastFocusable[0] === event.target && ! event.shiftKey ) {
+ $firstFocusable.focus();
+ event.preventDefault();
+ }
+ }
+ });
+ },
+
+ // Single theme overlay screen
+ // It's shown when clicking a theme
+ collapse: function( event ) {
+ var self = this,
+ scroll;
+
+ event = event || window.event;
+
+ // Prevent collapsing detailed view when there is only one theme available
+ if ( themes.data.themes.length === 1 ) {
+ return;
+ }
+
+ // Detect if the click is inside the overlay
+ // and don't close it unless the target was
+ // the div.back button
+ if ( $( event.target ).is( '.theme-backdrop' ) || $( event.target ).is( '.close' ) || event.keyCode === 27 ) {
+
+ // Add a temporary closing class while overlay fades out
+ $( 'body' ).addClass( 'closing-overlay' );
+
+ // With a quick fade out animation
+ this.$el.fadeOut( 130, function() {
+ // Clicking outside the modal box closes the overlay
+ $( 'body' ).removeClass( 'closing-overlay' );
+ // Handle event cleanup
+ self.closeOverlay();
+
+ // Get scroll position to avoid jumping to the top
+ scroll = document.body.scrollTop;
+
+ // Clean the url structure
+ themes.router.navigate( themes.router.baseUrl( '' ) );
+
+ // Restore scroll position
+ document.body.scrollTop = scroll;
+
+ // Return focus to the theme div
+ if ( themes.focusedTheme ) {
+ themes.focusedTheme.focus();
+ }
+ });
+ }
+ },
+
+ // Handles .disabled classes for next/previous buttons
+ navigation: function() {
+
+ // Disable Left/Right when at the start or end of the collection
+ if ( this.model.cid === this.model.collection.at(0).cid ) {
+ this.$el.find( '.left' )
+ .addClass( 'disabled' )
+ .prop( 'disabled', true );
+ }
+ if ( this.model.cid === this.model.collection.at( this.model.collection.length - 1 ).cid ) {
+ this.$el.find( '.right' )
+ .addClass( 'disabled' )
+ .prop( 'disabled', true );
+ }
+ },
+
+ // Performs the actions to effectively close
+ // the theme details overlay
+ closeOverlay: function() {
+ $( 'body' ).removeClass( 'modal-open' );
+ this.remove();
+ this.unbind();
+ this.trigger( 'theme:collapse' );
+ },
+
+ updateTheme: function( event ) {
+ var _this = this;
+ event.preventDefault();
+
+ wp.updates.maybeRequestFilesystemCredentials( event );
+
+ $( document ).on( 'wp-theme-update-success', function( event, response ) {
+ if ( _this.model.get( 'id' ) === response.slug ) {
+ _this.model.set( {
+ hasUpdate: false,
+ version: response.newVersion
+ } );
+ }
+ _this.render();
+ } );
+
+ wp.updates.updateTheme( {
+ slug: $( event.target ).data( 'slug' )
+ } );
+ },
+
+ deleteTheme: function( event ) {
+ var _this = this,
+ _collection = _this.model.collection,
+ _themes = themes;
+ event.preventDefault();
+
+ // Confirmation dialog for deleting a theme.
+ if ( ! window.confirm( wp.themes.data.settings.confirmDelete ) ) {
+ return;
+ }
+
+ wp.updates.maybeRequestFilesystemCredentials( event );
+
+ $( document ).one( 'wp-theme-delete-success', function( event, response ) {
+ _this.$el.find( '.close' ).trigger( 'click' );
+ $( '[data-slug="' + response.slug + '"]' ).css( { backgroundColor:'#faafaa' } ).fadeOut( 350, function() {
+ $( this ).remove();
+ _themes.data.themes = _.without( _themes.data.themes, _.findWhere( _themes.data.themes, { id: response.slug } ) );
+
+ $( '.wp-filter-search' ).val( '' );
+ _collection.doSearch( '' );
+ _collection.remove( _this.model );
+ _collection.trigger( 'themes:update' );
+ } );
+ } );
+
+ wp.updates.deleteTheme( {
+ slug: this.model.get( 'id' )
+ } );
+ },
+
+ nextTheme: function() {
+ var self = this;
+ self.trigger( 'theme:next', self.model.cid );
+ return false;
+ },
+
+ previousTheme: function() {
+ var self = this;
+ self.trigger( 'theme:previous', self.model.cid );
+ return false;
+ },
+
+ // Checks if the theme screenshot is the old 300px width version
+ // and adds a corresponding class if it's true
+ screenshotCheck: function( el ) {
+ var screenshot, image;
+
+ screenshot = el.find( '.screenshot img' );
+ image = new Image();
+ image.src = screenshot.attr( 'src' );
+
+ // Width check
+ if ( image.width && image.width <= 300 ) {
+ el.addClass( 'small-screenshot' );
+ }
+ }
+});
+
+// Theme Preview view
+// Set ups a modal overlay with the expanded theme data
+themes.view.Preview = themes.view.Details.extend({
+
+ className: 'wp-full-overlay expanded',
+ el: '.theme-install-overlay',
+
+ events: {
+ 'click .close-full-overlay': 'close',
+ 'click .collapse-sidebar': 'collapse',
+ 'click .devices button': 'previewDevice',
+ 'click .previous-theme': 'previousTheme',
+ 'click .next-theme': 'nextTheme',
+ 'keyup': 'keyEvent',
+ 'click .theme-install': 'installTheme'
+ },
+
+ // The HTML template for the theme preview
+ html: themes.template( 'theme-preview' ),
+
+ render: function() {
+ var self = this,
+ currentPreviewDevice,
+ data = this.model.toJSON(),
+ $body = $( document.body );
+
+ $body.attr( 'aria-busy', 'true' );
+
+ this.$el.removeClass( 'iframe-ready' ).html( this.html( data ) );
+
+ currentPreviewDevice = this.$el.data( 'current-preview-device' );
+ if ( currentPreviewDevice ) {
+ self.tooglePreviewDeviceButtons( currentPreviewDevice );
+ }
+
+ themes.router.navigate( themes.router.baseUrl( themes.router.themePath + this.model.get( 'id' ) ), { replace: false } );
+
+ this.$el.fadeIn( 200, function() {
+ $body.addClass( 'theme-installer-active full-overlay-active' );
+ });
+
+ this.$el.find( 'iframe' ).one( 'load', function() {
+ self.iframeLoaded();
+ });
+ },
+
+ iframeLoaded: function() {
+ this.$el.addClass( 'iframe-ready' );
+ $( document.body ).attr( 'aria-busy', 'false' );
+ },
+
+ close: function() {
+ this.$el.fadeOut( 200, function() {
+ $( 'body' ).removeClass( 'theme-installer-active full-overlay-active' );
+
+ // Return focus to the theme div
+ if ( themes.focusedTheme ) {
+ themes.focusedTheme.focus();
+ }
+ }).removeClass( 'iframe-ready' );
+
+ // Restore the previous browse tab if available.
+ if ( themes.router.selectedTab ) {
+ themes.router.navigate( themes.router.baseUrl( '?browse=' + themes.router.selectedTab ) );
+ themes.router.selectedTab = false;
+ } else {
+ themes.router.navigate( themes.router.baseUrl( '' ) );
+ }
+ this.trigger( 'preview:close' );
+ this.undelegateEvents();
+ this.unbind();
+ return false;
+ },
+
+ collapse: function( event ) {
+ var $button = $( event.currentTarget );
+ if ( 'true' === $button.attr( 'aria-expanded' ) ) {
+ $button.attr({ 'aria-expanded': 'false', 'aria-label': l10n.expandSidebar });
+ } else {
+ $button.attr({ 'aria-expanded': 'true', 'aria-label': l10n.collapseSidebar });
+ }
+
+ this.$el.toggleClass( 'collapsed' ).toggleClass( 'expanded' );
+ return false;
+ },
+
+ previewDevice: function( event ) {
+ var device = $( event.currentTarget ).data( 'device' );
+
+ this.$el
+ .removeClass( 'preview-desktop preview-tablet preview-mobile' )
+ .addClass( 'preview-' + device )
+ .data( 'current-preview-device', device );
+
+ this.tooglePreviewDeviceButtons( device );
+ },
+
+ tooglePreviewDeviceButtons: function( newDevice ) {
+ var $devices = $( '.wp-full-overlay-footer .devices' );
+
+ $devices.find( 'button' )
+ .removeClass( 'active' )
+ .attr( 'aria-pressed', false );
+
+ $devices.find( 'button.preview-' + newDevice )
+ .addClass( 'active' )
+ .attr( 'aria-pressed', true );
+ },
+
+ keyEvent: function( event ) {
+ // The escape key closes the preview
+ if ( event.keyCode === 27 ) {
+ this.undelegateEvents();
+ this.close();
+ }
+ // The right arrow key, next theme
+ if ( event.keyCode === 39 ) {
+ _.once( this.nextTheme() );
+ }
+
+ // The left arrow key, previous theme
+ if ( event.keyCode === 37 ) {
+ this.previousTheme();
+ }
+ },
+
+ installTheme: function( event ) {
+ var _this = this,
+ $target = $( event.target );
+ event.preventDefault();
+
+ if ( $target.hasClass( 'disabled' ) ) {
+ return;
+ }
+
+ wp.updates.maybeRequestFilesystemCredentials( event );
+
+ $( document ).on( 'wp-theme-install-success', function() {
+ _this.model.set( { 'installed': true } );
+ } );
+
+ wp.updates.installTheme( {
+ slug: $target.data( 'slug' )
+ } );
+ }
+});
+
+// Controls the rendering of div.themes,
+// a wrapper that will hold all the theme elements
+themes.view.Themes = wp.Backbone.View.extend({
+
+ className: 'themes wp-clearfix',
+ $overlay: $( 'div.theme-overlay' ),
+
+ // Number to keep track of scroll position
+ // while in theme-overlay mode
+ index: 0,
+
+ // The theme count element
+ count: $( '.wrap .theme-count' ),
+
+ // The live themes count
+ liveThemeCount: 0,
+
+ initialize: function( options ) {
+ var self = this;
+
+ // Set up parent
+ this.parent = options.parent;
+
+ // Set current view to [grid]
+ this.setView( 'grid' );
+
+ // Move the active theme to the beginning of the collection
+ self.currentTheme();
+
+ // When the collection is updated by user input...
+ this.listenTo( self.collection, 'themes:update', function() {
+ self.parent.page = 0;
+ self.currentTheme();
+ self.render( this );
+ } );
+
+ // Update theme count to full result set when available.
+ this.listenTo( self.collection, 'query:success', function( count ) {
+ if ( _.isNumber( count ) ) {
+ self.count.text( count );
+ self.announceSearchResults( count );
+ } else {
+ self.count.text( self.collection.length );
+ self.announceSearchResults( self.collection.length );
+ }
+ });
+
+ this.listenTo( self.collection, 'query:empty', function() {
+ $( 'body' ).addClass( 'no-results' );
+ });
+
+ this.listenTo( this.parent, 'theme:scroll', function() {
+ self.renderThemes( self.parent.page );
+ });
+
+ this.listenTo( this.parent, 'theme:close', function() {
+ if ( self.overlay ) {
+ self.overlay.closeOverlay();
+ }
+ } );
+
+ // Bind keyboard events.
+ $( 'body' ).on( 'keyup', function( event ) {
+ if ( ! self.overlay ) {
+ return;
+ }
+
+ // Bail if the filesystem credentials dialog is shown.
+ if ( $( '#request-filesystem-credentials-dialog' ).is( ':visible' ) ) {
+ return;
+ }
+
+ // Pressing the right arrow key fires a theme:next event
+ if ( event.keyCode === 39 ) {
+ self.overlay.nextTheme();
+ }
+
+ // Pressing the left arrow key fires a theme:previous event
+ if ( event.keyCode === 37 ) {
+ self.overlay.previousTheme();
+ }
+
+ // Pressing the escape key fires a theme:collapse event
+ if ( event.keyCode === 27 ) {
+ self.overlay.collapse( event );
+ }
+ });
+ },
+
+ // Manages rendering of theme pages
+ // and keeping theme count in sync
+ render: function() {
+ // Clear the DOM, please
+ this.$el.empty();
+
+ // If the user doesn't have switch capabilities
+ // or there is only one theme in the collection
+ // render the detailed view of the active theme
+ if ( themes.data.themes.length === 1 ) {
+
+ // Constructs the view
+ this.singleTheme = new themes.view.Details({
+ model: this.collection.models[0]
+ });
+
+ // Render and apply a 'single-theme' class to our container
+ this.singleTheme.render();
+ this.$el.addClass( 'single-theme' );
+ this.$el.append( this.singleTheme.el );
+ }
+
+ // Generate the themes
+ // Using page instance
+ // While checking the collection has items
+ if ( this.options.collection.size() > 0 ) {
+ this.renderThemes( this.parent.page );
+ }
+
+ // Display a live theme count for the collection
+ this.liveThemeCount = this.collection.count ? this.collection.count : this.collection.length;
+ this.count.text( this.liveThemeCount );
+
+ /*
+ * In the theme installer the themes count is already announced
+ * because `announceSearchResults` is called on `query:success`.
+ */
+ if ( ! themes.isInstall ) {
+ this.announceSearchResults( this.liveThemeCount );
+ }
+ },
+
+ // Iterates through each instance of the collection
+ // and renders each theme module
+ renderThemes: function( page ) {
+ var self = this;
+
+ self.instance = self.collection.paginate( page );
+
+ // If we have no more themes bail
+ if ( self.instance.size() === 0 ) {
+ // Fire a no-more-themes event.
+ this.parent.trigger( 'theme:end' );
+ return;
+ }
+
+ // Make sure the add-new stays at the end
+ if ( ! themes.isInstall && page >= 1 ) {
+ $( '.add-new-theme' ).remove();
+ }
+
+ // Loop through the themes and setup each theme view
+ self.instance.each( function( theme ) {
+ self.theme = new themes.view.Theme({
+ model: theme,
+ parent: self
+ });
+
+ // Render the views...
+ self.theme.render();
+ // and append them to div.themes
+ self.$el.append( self.theme.el );
+
+ // Binds to theme:expand to show the modal box
+ // with the theme details
+ self.listenTo( self.theme, 'theme:expand', self.expand, self );
+ });
+
+ // 'Add new theme' element shown at the end of the grid
+ if ( ! themes.isInstall && themes.data.settings.canInstall ) {
+ this.$el.append( '<div class="theme add-new-theme"><a href="' + themes.data.settings.installURI + '"><div class="theme-screenshot"><span></span></div><h2 class="theme-name">' + l10n.addNew + '</h2></a></div>' );
+ }
+
+ this.parent.page++;
+ },
+
+ // Grabs current theme and puts it at the beginning of the collection
+ currentTheme: function() {
+ var self = this,
+ current;
+
+ current = self.collection.findWhere({ active: true });
+
+ // Move the active theme to the beginning of the collection
+ if ( current ) {
+ self.collection.remove( current );
+ self.collection.add( current, { at:0 } );
+ }
+ },
+
+ // Sets current view
+ setView: function( view ) {
+ return view;
+ },
+
+ // Renders the overlay with the ThemeDetails view
+ // Uses the current model data
+ expand: function( id ) {
+ var self = this, $card, $modal;
+
+ // Set the current theme model
+ this.model = self.collection.get( id );
+
+ // Trigger a route update for the current model
+ themes.router.navigate( themes.router.baseUrl( themes.router.themePath + this.model.id ) );
+
+ // Sets this.view to 'detail'
+ this.setView( 'detail' );
+ $( 'body' ).addClass( 'modal-open' );
+
+ // Set up the theme details view
+ this.overlay = new themes.view.Details({
+ model: self.model
+ });
+
+ this.overlay.render();
+
+ if ( this.model.get( 'hasUpdate' ) ) {
+ $card = $( '[data-slug="' + this.model.id + '"]' );
+ $modal = $( this.overlay.el );
+
+ if ( $card.find( '.updating-message' ).length ) {
+ $modal.find( '.notice-warning h3' ).remove();
+ $modal.find( '.notice-warning' )
+ .removeClass( 'notice-large' )
+ .addClass( 'updating-message' )
+ .find( 'p' ).text( wp.updates.l10n.updating );
+ } else if ( $card.find( '.notice-error' ).length ) {
+ $modal.find( '.notice-warning' ).remove();
+ }
+ }
+
+ this.$overlay.html( this.overlay.el );
+
+ // Bind to theme:next and theme:previous
+ // triggered by the arrow keys
+ //
+ // Keep track of the current model so we
+ // can infer an index position
+ this.listenTo( this.overlay, 'theme:next', function() {
+ // Renders the next theme on the overlay
+ self.next( [ self.model.cid ] );
+
+ })
+ .listenTo( this.overlay, 'theme:previous', function() {
+ // Renders the previous theme on the overlay
+ self.previous( [ self.model.cid ] );
+ });
+ },
+
+ // This method renders the next theme on the overlay modal
+ // based on the current position in the collection
+ // @params [model cid]
+ next: function( args ) {
+ var self = this,
+ model, nextModel;
+
+ // Get the current theme
+ model = self.collection.get( args[0] );
+ // Find the next model within the collection
+ nextModel = self.collection.at( self.collection.indexOf( model ) + 1 );
+
+ // Sanity check which also serves as a boundary test
+ if ( nextModel !== undefined ) {
+
+ // We have a new theme...
+ // Close the overlay
+ this.overlay.closeOverlay();
+
+ // Trigger a route update for the current model
+ self.theme.trigger( 'theme:expand', nextModel.cid );
+
+ }
+ },
+
+ // This method renders the previous theme on the overlay modal
+ // based on the current position in the collection
+ // @params [model cid]
+ previous: function( args ) {
+ var self = this,
+ model, previousModel;
+
+ // Get the current theme
+ model = self.collection.get( args[0] );
+ // Find the previous model within the collection
+ previousModel = self.collection.at( self.collection.indexOf( model ) - 1 );
+
+ if ( previousModel !== undefined ) {
+
+ // We have a new theme...
+ // Close the overlay
+ this.overlay.closeOverlay();
+
+ // Trigger a route update for the current model
+ self.theme.trigger( 'theme:expand', previousModel.cid );
+
+ }
+ },
+
+ // Dispatch audible search results feedback message
+ announceSearchResults: function( count ) {
+ if ( 0 === count ) {
+ wp.a11y.speak( l10n.noThemesFound );
+ } else {
+ wp.a11y.speak( l10n.themesFound.replace( '%d', count ) );
+ }
+ }
+});
+
+// Search input view controller.
+themes.view.Search = wp.Backbone.View.extend({
+
+ tagName: 'input',
+ className: 'wp-filter-search',
+ id: 'wp-filter-search-input',
+ searching: false,
+
+ attributes: {
+ placeholder: l10n.searchPlaceholder,
+ type: 'search',
+ 'aria-describedby': 'live-search-desc'
+ },
+
+ events: {
+ 'input': 'search',
+ 'keyup': 'search',
+ 'blur': 'pushState'
+ },
+
+ initialize: function( options ) {
+
+ this.parent = options.parent;
+
+ this.listenTo( this.parent, 'theme:close', function() {
+ this.searching = false;
+ } );
+
+ },
+
+ search: function( event ) {
+ // Clear on escape.
+ if ( event.type === 'keyup' && event.which === 27 ) {
+ event.target.value = '';
+ }
+
+ // Since doSearch is debounced, it will only run when user input comes to a rest.
+ this.doSearch( event );
+ },
+
+ // Runs a search on the theme collection.
+ doSearch: function( event ) {
+ var options = {};
+
+ this.collection.doSearch( event.target.value.replace( /\+/g, ' ' ) );
+
+ // if search is initiated and key is not return
+ if ( this.searching && event.which !== 13 ) {
+ options.replace = true;
+ } else {
+ this.searching = true;
+ }
+
+ // Update the URL hash
+ if ( event.target.value ) {
+ themes.router.navigate( themes.router.baseUrl( themes.router.searchPath + event.target.value ), options );
+ } else {
+ themes.router.navigate( themes.router.baseUrl( '' ) );
+ }
+ },
+
+ pushState: function( event ) {
+ var url = themes.router.baseUrl( '' );
+
+ if ( event.target.value ) {
+ url = themes.router.baseUrl( themes.router.searchPath + encodeURIComponent( event.target.value ) );
+ }
+
+ this.searching = false;
+ themes.router.navigate( url );
+
+ }
+});
+
+/**
+ * Navigate router.
+ *
+ * @since 4.9.0
+ *
+ * @param {string} url - URL to navigate to.
+ * @param {object} state - State.
+ * @returns {void}
+ */
+function navigateRouter( url, state ) {
+ var router = this;
+ if ( Backbone.history._hasPushState ) {
+ Backbone.Router.prototype.navigate.call( router, url, state );
+ }
+}
+
+// Sets up the routes events for relevant url queries
+// Listens to [theme] and [search] params
+themes.Router = Backbone.Router.extend({
+
+ routes: {
+ 'themes.php?theme=:slug': 'theme',
+ 'themes.php?search=:query': 'search',
+ 'themes.php?s=:query': 'search',
+ 'themes.php': 'themes',
+ '': 'themes'
+ },
+
+ baseUrl: function( url ) {
+ return 'themes.php' + url;
+ },
+
+ themePath: '?theme=',
+ searchPath: '?search=',
+
+ search: function( query ) {
+ $( '.wp-filter-search' ).val( query.replace( /\+/g, ' ' ) );
+ },
+
+ themes: function() {
+ $( '.wp-filter-search' ).val( '' );
+ },
+
+ navigate: navigateRouter
+
+});
+
+// Execute and setup the application
+themes.Run = {
+ init: function() {
+ // Initializes the blog's theme library view
+ // Create a new collection with data
+ this.themes = new themes.Collection( themes.data.themes );
+
+ // Set up the view
+ this.view = new themes.view.Appearance({
+ collection: this.themes
+ });
+
+ this.render();
+
+ // Start debouncing user searches after Backbone.history.start().
+ this.view.SearchView.doSearch = _.debounce( this.view.SearchView.doSearch, 500 );
+ },
+
+ render: function() {
+
+ // Render results
+ this.view.render();
+ this.routes();
+
+ if ( Backbone.History.started ) {
+ Backbone.history.stop();
+ }
+ Backbone.history.start({
+ root: themes.data.settings.adminUrl,
+ pushState: true,
+ hashChange: false
+ });
+ },
+
+ routes: function() {
+ var self = this;
+ // Bind to our global thx object
+ // so that the object is available to sub-views
+ themes.router = new themes.Router();
+
+ // Handles theme details route event
+ themes.router.on( 'route:theme', function( slug ) {
+ self.view.view.expand( slug );
+ });
+
+ themes.router.on( 'route:themes', function() {
+ self.themes.doSearch( '' );
+ self.view.trigger( 'theme:close' );
+ });
+
+ // Handles search route event
+ themes.router.on( 'route:search', function() {
+ $( '.wp-filter-search' ).trigger( 'keyup' );
+ });
+
+ this.extraRoutes();
+ },
+
+ extraRoutes: function() {
+ return false;
+ }
+};
+
+// Extend the main Search view
+themes.view.InstallerSearch = themes.view.Search.extend({
+
+ events: {
+ 'input': 'search',
+ 'keyup': 'search'
+ },
+
+ terms: '',
+
+ // Handles Ajax request for searching through themes in public repo
+ search: function( event ) {
+
+ // Tabbing or reverse tabbing into the search input shouldn't trigger a search
+ if ( event.type === 'keyup' && ( event.which === 9 || event.which === 16 ) ) {
+ return;
+ }
+
+ this.collection = this.options.parent.view.collection;
+
+ // Clear on escape.
+ if ( event.type === 'keyup' && event.which === 27 ) {
+ event.target.value = '';
+ }
+
+ this.doSearch( event.target.value );
+ },
+
+ doSearch: function( value ) {
+ var request = {};
+
+ // Don't do anything if the search terms haven't changed.
+ if ( this.terms === value ) {
+ return;
+ }
+
+ // Updates terms with the value passed.
+ this.terms = value;
+
+ request.search = value;
+
+ // Intercept an [author] search.
+ //
+ // If input value starts with `author:` send a request
+ // for `author` instead of a regular `search`
+ if ( value.substring( 0, 7 ) === 'author:' ) {
+ request.search = '';
+ request.author = value.slice( 7 );
+ }
+
+ // Intercept a [tag] search.
+ //
+ // If input value starts with `tag:` send a request
+ // for `tag` instead of a regular `search`
+ if ( value.substring( 0, 4 ) === 'tag:' ) {
+ request.search = '';
+ request.tag = [ value.slice( 4 ) ];
+ }
+
+ $( '.filter-links li > a.current' )
+ .removeClass( 'current' )
+ .removeAttr( 'aria-current' );
+
+ $( 'body' ).removeClass( 'show-filters filters-applied show-favorites-form' );
+ $( '.drawer-toggle' ).attr( 'aria-expanded', 'false' );
+
+ // Get the themes by sending Ajax POST request to api.wordpress.org/themes
+ // or searching the local cache
+ this.collection.query( request );
+
+ // Set route
+ themes.router.navigate( themes.router.baseUrl( themes.router.searchPath + encodeURIComponent( value ) ), { replace: true } );
+ }
+});
+
+themes.view.Installer = themes.view.Appearance.extend({
+
+ el: '#wpbody-content .wrap',
+
+ // Register events for sorting and filters in theme-navigation
+ events: {
+ 'click .filter-links li > a': 'onSort',
+ 'click .theme-filter': 'onFilter',
+ 'click .drawer-toggle': 'moreFilters',
+ 'click .filter-drawer .apply-filters': 'applyFilters',
+ 'click .filter-group [type="checkbox"]': 'addFilter',
+ 'click .filter-drawer .clear-filters': 'clearFilters',
+ 'click .edit-filters': 'backToFilters',
+ 'click .favorites-form-submit' : 'saveUsername',
+ 'keyup #wporg-username-input': 'saveUsername'
+ },
+
+ // Initial render method
+ render: function() {
+ var self = this;
+
+ this.search();
+ this.uploader();
+
+ this.collection = new themes.Collection();
+
+ // Bump `collection.currentQuery.page` and request more themes if we hit the end of the page.
+ this.listenTo( this, 'theme:end', function() {
+
+ // Make sure we are not already loading
+ if ( self.collection.loadingThemes ) {
+ return;
+ }
+
+ // Set loadingThemes to true and bump page instance of currentQuery.
+ self.collection.loadingThemes = true;
+ self.collection.currentQuery.page++;
+
+ // Use currentQuery.page to build the themes request.
+ _.extend( self.collection.currentQuery.request, { page: self.collection.currentQuery.page } );
+ self.collection.query( self.collection.currentQuery.request );
+ });
+
+ this.listenTo( this.collection, 'query:success', function() {
+ $( 'body' ).removeClass( 'loading-content' );
+ $( '.theme-browser' ).find( 'div.error' ).remove();
+ });
+
+ this.listenTo( this.collection, 'query:fail', function() {
+ $( 'body' ).removeClass( 'loading-content' );
+ $( '.theme-browser' ).find( 'div.error' ).remove();
+ $( '.theme-browser' ).find( 'div.themes' ).before( '<div class="error"><p>' + l10n.error + '</p><p><button class="button try-again">' + l10n.tryAgain + '</button></p></div>' );
+ $( '.theme-browser .error .try-again' ).on( 'click', function( e ) {
+ e.preventDefault();
+ $( 'input.wp-filter-search' ).trigger( 'input' );
+ } );
+ });
+
+ if ( this.view ) {
+ this.view.remove();
+ }
+
+ // Set ups the view and passes the section argument
+ this.view = new themes.view.Themes({
+ collection: this.collection,
+ parent: this
+ });
+
+ // Reset pagination every time the install view handler is run
+ this.page = 0;
+
+ // Render and append
+ this.$el.find( '.themes' ).remove();
+ this.view.render();
+ this.$el.find( '.theme-browser' ).append( this.view.el ).addClass( 'rendered' );
+ },
+
+ // Handles all the rendering of the public theme directory
+ browse: function( section ) {
+ // Create a new collection with the proper theme data
+ // for each section
+ this.collection.query( { browse: section } );
+ },
+
+ // Sorting navigation
+ onSort: function( event ) {
+ var $el = $( event.target ),
+ sort = $el.data( 'sort' );
+
+ event.preventDefault();
+
+ $( 'body' ).removeClass( 'filters-applied show-filters' );
+ $( '.drawer-toggle' ).attr( 'aria-expanded', 'false' );
+
+ // Bail if this is already active
+ if ( $el.hasClass( this.activeClass ) ) {
+ return;
+ }
+
+ this.sort( sort );
+
+ // Trigger a router.naviagte update
+ themes.router.navigate( themes.router.baseUrl( themes.router.browsePath + sort ) );
+ },
+
+ sort: function( sort ) {
+ this.clearSearch();
+
+ // Track sorting so we can restore the correct tab when closing preview.
+ themes.router.selectedTab = sort;
+
+ $( '.filter-links li > a, .theme-filter' )
+ .removeClass( this.activeClass )
+ .removeAttr( 'aria-current' );
+
+ $( '[data-sort="' + sort + '"]' )
+ .addClass( this.activeClass )
+ .attr( 'aria-current', 'page' );
+
+ if ( 'favorites' === sort ) {
+ $( 'body' ).addClass( 'show-favorites-form' );
+ } else {
+ $( 'body' ).removeClass( 'show-favorites-form' );
+ }
+
+ this.browse( sort );
+ },
+
+ // Filters and Tags
+ onFilter: function( event ) {
+ var request,
+ $el = $( event.target ),
+ filter = $el.data( 'filter' );
+
+ // Bail if this is already active
+ if ( $el.hasClass( this.activeClass ) ) {
+ return;
+ }
+
+ $( '.filter-links li > a, .theme-section' )
+ .removeClass( this.activeClass )
+ .removeAttr( 'aria-current' );
+ $el
+ .addClass( this.activeClass )
+ .attr( 'aria-current', 'page' );
+
+ if ( ! filter ) {
+ return;
+ }
+
+ // Construct the filter request
+ // using the default values
+ filter = _.union( [ filter, this.filtersChecked() ] );
+ request = { tag: [ filter ] };
+
+ // Get the themes by sending Ajax POST request to api.wordpress.org/themes
+ // or searching the local cache
+ this.collection.query( request );
+ },
+
+ // Clicking on a checkbox to add another filter to the request
+ addFilter: function() {
+ this.filtersChecked();
+ },
+
+ // Applying filters triggers a tag request
+ applyFilters: function( event ) {
+ var name,
+ tags = this.filtersChecked(),
+ request = { tag: tags },
+ filteringBy = $( '.filtered-by .tags' );
+
+ if ( event ) {
+ event.preventDefault();
+ }
+
+ if ( ! tags ) {
+ wp.a11y.speak( l10n.selectFeatureFilter );
+ return;
+ }
+
+ $( 'body' ).addClass( 'filters-applied' );
+ $( '.filter-links li > a.current' )
+ .removeClass( 'current' )
+ .removeAttr( 'aria-current' );
+
+ filteringBy.empty();
+
+ _.each( tags, function( tag ) {
+ name = $( 'label[for="filter-id-' + tag + '"]' ).text();
+ filteringBy.append( '<span class="tag">' + name + '</span>' );
+ });
+
+ // Get the themes by sending Ajax POST request to api.wordpress.org/themes
+ // or searching the local cache
+ this.collection.query( request );
+ },
+
+ // Save the user's WordPress.org username and get his favorite themes.
+ saveUsername: function ( event ) {
+ var username = $( '#wporg-username-input' ).val(),
+ nonce = $( '#wporg-username-nonce' ).val(),
+ request = { browse: 'favorites', user: username },
+ that = this;
+
+ if ( event ) {
+ event.preventDefault();
+ }
+
+ // save username on enter
+ if ( event.type === 'keyup' && event.which !== 13 ) {
+ return;
+ }
+
+ return wp.ajax.send( 'save-wporg-username', {
+ data: {
+ _wpnonce: nonce,
+ username: username
+ },
+ success: function () {
+ // Get the themes by sending Ajax POST request to api.wordpress.org/themes
+ // or searching the local cache
+ that.collection.query( request );
+ }
+ } );
+ },
+
+ // Get the checked filters
+ // @return {array} of tags or false
+ filtersChecked: function() {
+ var items = $( '.filter-group' ).find( ':checkbox' ),
+ tags = [];
+
+ _.each( items.filter( ':checked' ), function( item ) {
+ tags.push( $( item ).prop( 'value' ) );
+ });
+
+ // When no filters are checked, restore initial state and return
+ if ( tags.length === 0 ) {
+ $( '.filter-drawer .apply-filters' ).find( 'span' ).text( '' );
+ $( '.filter-drawer .clear-filters' ).hide();
+ $( 'body' ).removeClass( 'filters-applied' );
+ return false;
+ }
+
+ $( '.filter-drawer .apply-filters' ).find( 'span' ).text( tags.length );
+ $( '.filter-drawer .clear-filters' ).css( 'display', 'inline-block' );
+
+ return tags;
+ },
+
+ activeClass: 'current',
+
+ /*
+ * When users press the "Upload Theme" button, show the upload form in place.
+ */
+ uploader: function() {
+ var uploadViewToggle = $( '.upload-view-toggle' ),
+ $body = $( document.body );
+
+ uploadViewToggle.on( 'click', function() {
+ // Toggle the upload view.
+ $body.toggleClass( 'show-upload-view' );
+ // Toggle the `aria-expanded` button attribute.
+ uploadViewToggle.attr( 'aria-expanded', $body.hasClass( 'show-upload-view' ) );
+ });
+ },
+
+ // Toggle the full filters navigation
+ moreFilters: function( event ) {
+ var $body = $( 'body' ),
+ $toggleButton = $( '.drawer-toggle' );
+
+ event.preventDefault();
+
+ if ( $body.hasClass( 'filters-applied' ) ) {
+ return this.backToFilters();
+ }
+
+ this.clearSearch();
+
+ themes.router.navigate( themes.router.baseUrl( '' ) );
+ // Toggle the feature filters view.
+ $body.toggleClass( 'show-filters' );
+ // Toggle the `aria-expanded` button attribute.
+ $toggleButton.attr( 'aria-expanded', $body.hasClass( 'show-filters' ) );
+ },
+
+ // Clears all the checked filters
+ // @uses filtersChecked()
+ clearFilters: function( event ) {
+ var items = $( '.filter-group' ).find( ':checkbox' ),
+ self = this;
+
+ event.preventDefault();
+
+ _.each( items.filter( ':checked' ), function( item ) {
+ $( item ).prop( 'checked', false );
+ return self.filtersChecked();
+ });
+ },
+
+ backToFilters: function( event ) {
+ if ( event ) {
+ event.preventDefault();
+ }
+
+ $( 'body' ).removeClass( 'filters-applied' );
+ },
+
+ clearSearch: function() {
+ $( '#wp-filter-search-input').val( '' );
+ }
+});
+
+themes.InstallerRouter = Backbone.Router.extend({
+ routes: {
+ 'theme-install.php?theme=:slug': 'preview',
+ 'theme-install.php?browse=:sort': 'sort',
+ 'theme-install.php?search=:query': 'search',
+ 'theme-install.php': 'sort'
+ },
+
+ baseUrl: function( url ) {
+ return 'theme-install.php' + url;
+ },
+
+ themePath: '?theme=',
+ browsePath: '?browse=',
+ searchPath: '?search=',
+
+ search: function( query ) {
+ $( '.wp-filter-search' ).val( query.replace( /\+/g, ' ' ) );
+ },
+
+ navigate: navigateRouter
+});
+
+
+themes.RunInstaller = {
+
+ init: function() {
+ // Set up the view
+ // Passes the default 'section' as an option
+ this.view = new themes.view.Installer({
+ section: 'featured',
+ SearchView: themes.view.InstallerSearch
+ });
+
+ // Render results
+ this.render();
+
+ // Start debouncing user searches after Backbone.history.start().
+ this.view.SearchView.doSearch = _.debounce( this.view.SearchView.doSearch, 500 );
+ },
+
+ render: function() {
+
+ // Render results
+ this.view.render();
+ this.routes();
+
+ if ( Backbone.History.started ) {
+ Backbone.history.stop();
+ }
+ Backbone.history.start({
+ root: themes.data.settings.adminUrl,
+ pushState: true,
+ hashChange: false
+ });
+ },
+
+ routes: function() {
+ var self = this,
+ request = {};
+
+ // Bind to our global `wp.themes` object
+ // so that the router is available to sub-views
+ themes.router = new themes.InstallerRouter();
+
+ // Handles `theme` route event
+ // Queries the API for the passed theme slug
+ themes.router.on( 'route:preview', function( slug ) {
+
+ // Remove existing handlers.
+ if ( themes.preview ) {
+ themes.preview.undelegateEvents();
+ themes.preview.unbind();
+ }
+
+ // If the theme preview is active, set the current theme.
+ if ( self.view.view.theme && self.view.view.theme.preview ) {
+ self.view.view.theme.model = self.view.collection.findWhere( { 'slug': slug } );
+ self.view.view.theme.preview();
+ } else {
+
+ // Select the theme by slug.
+ request.theme = slug;
+ self.view.collection.query( request );
+ self.view.collection.trigger( 'update' );
+
+ // Open the theme preview.
+ self.view.collection.once( 'query:success', function() {
+ $( 'div[data-slug="' + slug + '"]' ).trigger( 'click' );
+ });
+
+ }
+ });
+
+ // Handles sorting / browsing routes
+ // Also handles the root URL triggering a sort request
+ // for `featured`, the default view
+ themes.router.on( 'route:sort', function( sort ) {
+ if ( ! sort ) {
+ sort = 'featured';
+ themes.router.navigate( themes.router.baseUrl( '?browse=featured' ), { replace: true } );
+ }
+ self.view.sort( sort );
+
+ // Close the preview if open.
+ if ( themes.preview ) {
+ themes.preview.close();
+ }
+ });
+
+ // The `search` route event. The router populates the input field.
+ themes.router.on( 'route:search', function() {
+ $( '.wp-filter-search' ).focus().trigger( 'keyup' );
+ });
+
+ this.extraRoutes();
+ },
+
+ extraRoutes: function() {
+ return false;
+ }
+};
+
+// Ready...
+$( document ).ready(function() {
+ if ( themes.isInstall ) {
+ themes.RunInstaller.init();
+ } else {
+ themes.Run.init();
+ }
+
+ // Update the return param just in time.
+ $( document.body ).on( 'click', '.load-customize', function() {
+ var link = $( this ), urlParser = document.createElement( 'a' );
+ urlParser.href = link.prop( 'href' );
+ urlParser.search = $.param( _.extend(
+ wp.customize.utils.parseQueryString( urlParser.search.substr( 1 ) ),
+ {
+ 'return': window.location.href
+ }
+ ) );
+ link.prop( 'href', urlParser.href );
+ });
+
+ $( '.broken-themes .delete-theme' ).on( 'click', function() {
+ return confirm( _wpThemeSettings.settings.confirmDelete );
+ });
+});
+
+})( jQuery );
+
+// Align theme browser thickbox
+jQuery(document).ready( function($) {
+ window.tb_position = function() {
+ var tbWindow = $('#TB_window'),
+ width = $(window).width(),
+ H = $(window).height(),
+ W = ( 1040 < width ) ? 1040 : width,
+ adminbar_height = 0;
+
+ if ( $('#wpadminbar').length ) {
+ adminbar_height = parseInt( $('#wpadminbar').css('height'), 10 );
+ }
+
+ if ( tbWindow.size() ) {
+ tbWindow.width( W - 50 ).height( H - 45 - adminbar_height );
+ $('#TB_iframeContent').width( W - 50 ).height( H - 75 - adminbar_height );
+ tbWindow.css({'margin-left': '-' + parseInt( ( ( W - 50 ) / 2 ), 10 ) + 'px'});
+ if ( typeof document.body.style.maxWidth !== 'undefined' ) {
+ tbWindow.css({'top': 20 + adminbar_height + 'px', 'margin-top': '0'});
+ }
+ }
+ };
+
+ $(window).resize(function(){ tb_position(); });
+});
diff --git a/www/crm/wp-admin/js/theme.min.js b/www/crm/wp-admin/js/theme.min.js
new file mode 100644
index 00000000..dc9e7566
--- /dev/null
+++ b/www/crm/wp-admin/js/theme.min.js
@@ -0,0 +1 @@
+window.wp=window.wp||{},function(a){function b(a,b){var c=this;Backbone.history._hasPushState&&Backbone.Router.prototype.navigate.call(c,a,b)}var c,d;c=wp.themes=wp.themes||{},c.data=_wpThemeSettings,d=c.data.l10n,c.isInstall=!!c.data.settings.isInstall,_.extend(c,{model:{},view:{},routes:{},router:{},template:wp.template}),c.Model=Backbone.Model.extend({initialize:function(){var a;_.indexOf(c.data.installedThemes,this.get("slug"))!==-1&&this.set({installed:!0}),this.set({id:this.get("slug")||this.get("id")}),this.has("sections")&&(a=this.get("sections").description,this.set({description:a}))}}),c.view.Appearance=wp.Backbone.View.extend({el:"#wpbody-content .wrap .theme-browser",window:a(window),page:0,initialize:function(a){_.bindAll(this,"scroller"),this.SearchView=a.SearchView?a.SearchView:c.view.Search,this.window.bind("scroll",_.throttle(this.scroller,300))},render:function(){this.view=new c.view.Themes({collection:this.collection,parent:this}),this.search(),this.$el.removeClass("search-loading"),this.view.render(),this.$el.empty().append(this.view.el).addClass("rendered")},searchContainer:a(".search-form"),search:function(){var b,e=this;1!==c.data.themes.length&&(b=new this.SearchView({collection:e.collection,parent:this}),e.SearchView=b,b.render(),this.searchContainer.append(a.parseHTML('<label class="screen-reader-text" for="wp-filter-search-input">'+d.search+"</label>")).append(b.el).on("submit",function(a){a.preventDefault()}))},scroller:function(){var a,b,c=this;a=this.window.scrollTop()+c.window.height(),b=c.$el.offset().top+c.$el.outerHeight(!1)-c.window.height(),b=Math.round(.9*b),a>b&&this.trigger("theme:scroll")}}),c.Collection=Backbone.Collection.extend({model:c.Model,terms:"",doSearch:function(b){this.terms!==b&&(this.terms=b,this.terms.length>0&&this.search(this.terms),""===this.terms&&(this.reset(c.data.themes),a("body").removeClass("no-results")),this.trigger("themes:update"))},search:function(b){var d,e,f,g,h,i;this.reset(c.data.themes,{silent:!0}),b=b.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&"),b=b.replace(/ /g,")(?=.*"),d=new RegExp("^(?=.*"+b+").+","i"),e=this.filter(function(a){return g=a.get("name").replace(/(<([^>]+)>)/gi,""),h=a.get("description").replace(/(<([^>]+)>)/gi,""),i=a.get("author").replace(/(<([^>]+)>)/gi,""),f=_.union([g,a.get("id"),h,i,a.get("tags")]),d.test(a.get("author"))&&b.length>2&&a.set("displayAuthor",!0),d.test(f)}),0===e.length?this.trigger("query:empty"):a("body").removeClass("no-results"),this.reset(e)},paginate:function(a){var b=this;return a=a||0,b=_(b.rest(20*a)),b=_(b.first(20))},count:!1,query:function(b){var c,d,e,f=this.queries,g=this;if(this.currentQuery.request=b,c=_.find(f,function(a){return _.isEqual(a.request,b)}),d=_.has(b,"page"),d||(this.currentQuery.page=1),c||d){if(d)return this.apiCall(b,d).done(function(a){g.add(a.themes),g.trigger("query:success"),g.loadingThemes=!1}).fail(function(){g.trigger("query:fail")});0===c.themes.length?g.trigger("query:empty"):a("body").removeClass("no-results"),_.isNumber(c.total)&&(this.count=c.total),this.reset(c.themes),c.total||(this.count=this.length),this.trigger("themes:update"),this.trigger("query:success",this.count)}else c=this.apiCall(b).done(function(a){a.themes&&(g.reset(a.themes),e=a.info.results,f.push({themes:a.themes,request:b,total:e})),g.trigger("themes:update"),g.trigger("query:success",e),a.themes&&0===a.themes.length&&g.trigger("query:empty")}).fail(function(){g.trigger("query:fail")})},queries:[],currentQuery:{page:1,request:{}},apiCall:function(b,c){return wp.ajax.send("query-themes",{data:{request:_.extend({per_page:100},b)},beforeSend:function(){c||a("body").addClass("loading-content").removeClass("no-results")}})},loadingThemes:!1}),c.view.Theme=wp.Backbone.View.extend({className:"theme",state:"grid",html:c.template("theme"),events:{click:c.isInstall?"preview":"expand",keydown:c.isInstall?"preview":"expand",touchend:c.isInstall?"preview":"expand",keyup:"addFocus",touchmove:"preventExpand","click .theme-install":"installTheme","click .update-message":"updateTheme"},touchDrag:!1,initialize:function(){this.model.on("change",this.render,this)},render:function(){var a=this.model.toJSON();this.$el.html(this.html(a)).attr({tabindex:0,"aria-describedby":a.id+"-action "+a.id+"-name","data-slug":a.id}),this.activeTheme(),this.model.get("displayAuthor")&&this.$el.addClass("display-author")},activeTheme:function(){this.model.get("active")&&this.$el.addClass("active")},addFocus:function(){var b=a(":focus").hasClass("theme")?a(":focus"):a(":focus").parents(".theme");a(".theme.focus").removeClass("focus"),b.addClass("focus")},expand:function(b){var d=this;if(b=b||window.event,"keydown"!==b.type||13===b.which||32===b.which)return this.touchDrag===!0?this.touchDrag=!1:void(a(b.target).is(".theme-actions a")||a(b.target).is(".theme-actions a, .update-message, .button-link, .notice-dismiss")||(c.focusedTheme=this.$el,this.trigger("theme:expand",d.model.cid)))},preventExpand:function(){this.touchDrag=!0},preview:function(b){var d,e,f=this;return b=b||window.event,this.touchDrag===!0?this.touchDrag=!1:void(a(b.target).not(".install-theme-preview").parents(".theme-actions").length||"keydown"===b.type&&13!==b.which&&32!==b.which||"keydown"===b.type&&13!==b.which&&a(":focus").hasClass("button")||(b.preventDefault(),b=b||window.event,c.focusedTheme=this.$el,c.preview=e=new c.view.Preview({model:this.model}),e.render(),this.setNavButtonsState(),1===this.model.collection.length?e.$el.addClass("no-navigation"):e.$el.removeClass("no-navigation"),a("div.wrap").append(e.el),this.listenTo(e,"theme:next",function(){return d=f.model,_.isUndefined(f.current)||(d=f.current),f.current=f.model.collection.at(f.model.collection.indexOf(d)+1),_.isUndefined(f.current)?(f.options.parent.parent.trigger("theme:end"),f.current=d):(e.model=f.current,e.render(),this.setNavButtonsState(),void a(".next-theme").focus())}).listenTo(e,"theme:previous",function(){d=f.model,0!==f.model.collection.indexOf(f.current)&&(_.isUndefined(f.current)||(d=f.current),f.current=f.model.collection.at(f.model.collection.indexOf(d)-1),_.isUndefined(f.current)||(e.model=f.current,e.render(),this.setNavButtonsState(),a(".previous-theme").focus()))}),this.listenTo(e,"preview:close",function(){f.current=f.model})))},setNavButtonsState:function(){var b=a(".theme-install-overlay"),c=_.isUndefined(this.current)?this.model:this.current,d=b.find(".previous-theme"),e=b.find(".next-theme");0===this.model.collection.indexOf(c)&&(d.addClass("disabled").prop("disabled",!0),e.focus()),_.isUndefined(this.model.collection.at(this.model.collection.indexOf(c)+1))&&(e.addClass("disabled").prop("disabled",!0),d.focus())},installTheme:function(b){var c=this;b.preventDefault(),wp.updates.maybeRequestFilesystemCredentials(b),a(document).on("wp-theme-install-success",function(a,b){c.model.get("id")===b.slug&&c.model.set({installed:!0})}),wp.updates.installTheme({slug:a(b.target).data("slug")})},updateTheme:function(b){var c=this;this.model.get("hasPackage")&&(b.preventDefault(),wp.updates.maybeRequestFilesystemCredentials(b),a(document).on("wp-theme-update-success",function(a,b){c.model.off("change",c.render,c),c.model.get("id")===b.slug&&c.model.set({hasUpdate:!1,version:b.newVersion}),c.model.on("change",c.render,c)}),wp.updates.updateTheme({slug:a(b.target).parents("div.theme").first().data("slug")}))}}),c.view.Details=wp.Backbone.View.extend({className:"theme-overlay",events:{click:"collapse","click .delete-theme":"deleteTheme","click .left":"previousTheme","click .right":"nextTheme","click #update-theme":"updateTheme"},html:c.template("theme-single"),render:function(){var a=this.model.toJSON();this.$el.html(this.html(a)),this.activeTheme(),this.navigation(),this.screenshotCheck(this.$el),this.containFocus(this.$el)},activeTheme:function(){this.$el.toggleClass("active",this.model.get("active"))},containFocus:function(b){_.delay(function(){a(".theme-overlay").focus()},100),b.on("keydown.wp-themes",function(a){var c=b.find(".theme-header button:not(.disabled)").first(),d=b.find(".theme-actions a:visible").last();9===a.which&&(c[0]===a.target&&a.shiftKey?(d.focus(),a.preventDefault()):d[0]!==a.target||a.shiftKey||(c.focus(),a.preventDefault()))})},collapse:function(b){var d,e=this;b=b||window.event,1!==c.data.themes.length&&(a(b.target).is(".theme-backdrop")||a(b.target).is(".close")||27===b.keyCode)&&(a("body").addClass("closing-overlay"),this.$el.fadeOut(130,function(){a("body").removeClass("closing-overlay"),e.closeOverlay(),d=document.body.scrollTop,c.router.navigate(c.router.baseUrl("")),document.body.scrollTop=d,c.focusedTheme&&c.focusedTheme.focus()}))},navigation:function(){this.model.cid===this.model.collection.at(0).cid&&this.$el.find(".left").addClass("disabled").prop("disabled",!0),this.model.cid===this.model.collection.at(this.model.collection.length-1).cid&&this.$el.find(".right").addClass("disabled").prop("disabled",!0)},closeOverlay:function(){a("body").removeClass("modal-open"),this.remove(),this.unbind(),this.trigger("theme:collapse")},updateTheme:function(b){var c=this;b.preventDefault(),wp.updates.maybeRequestFilesystemCredentials(b),a(document).on("wp-theme-update-success",function(a,b){c.model.get("id")===b.slug&&c.model.set({hasUpdate:!1,version:b.newVersion}),c.render()}),wp.updates.updateTheme({slug:a(b.target).data("slug")})},deleteTheme:function(b){var d=this,e=d.model.collection,f=c;b.preventDefault(),window.confirm(wp.themes.data.settings.confirmDelete)&&(wp.updates.maybeRequestFilesystemCredentials(b),a(document).one("wp-theme-delete-success",function(b,c){d.$el.find(".close").trigger("click"),a('[data-slug="'+c.slug+'"]').css({backgroundColor:"#faafaa"}).fadeOut(350,function(){a(this).remove(),f.data.themes=_.without(f.data.themes,_.findWhere(f.data.themes,{id:c.slug})),a(".wp-filter-search").val(""),e.doSearch(""),e.remove(d.model),e.trigger("themes:update")})}),wp.updates.deleteTheme({slug:this.model.get("id")}))},nextTheme:function(){var a=this;return a.trigger("theme:next",a.model.cid),!1},previousTheme:function(){var a=this;return a.trigger("theme:previous",a.model.cid),!1},screenshotCheck:function(a){var b,c;b=a.find(".screenshot img"),c=new Image,c.src=b.attr("src"),c.width&&c.width<=300&&a.addClass("small-screenshot")}}),c.view.Preview=c.view.Details.extend({className:"wp-full-overlay expanded",el:".theme-install-overlay",events:{"click .close-full-overlay":"close","click .collapse-sidebar":"collapse","click .devices button":"previewDevice","click .previous-theme":"previousTheme","click .next-theme":"nextTheme",keyup:"keyEvent","click .theme-install":"installTheme"},html:c.template("theme-preview"),render:function(){var b,d=this,e=this.model.toJSON(),f=a(document.body);f.attr("aria-busy","true"),this.$el.removeClass("iframe-ready").html(this.html(e)),b=this.$el.data("current-preview-device"),b&&d.tooglePreviewDeviceButtons(b),c.router.navigate(c.router.baseUrl(c.router.themePath+this.model.get("id")),{replace:!1}),this.$el.fadeIn(200,function(){f.addClass("theme-installer-active full-overlay-active")}),this.$el.find("iframe").one("load",function(){d.iframeLoaded()})},iframeLoaded:function(){this.$el.addClass("iframe-ready"),a(document.body).attr("aria-busy","false")},close:function(){return this.$el.fadeOut(200,function(){a("body").removeClass("theme-installer-active full-overlay-active"),c.focusedTheme&&c.focusedTheme.focus()}).removeClass("iframe-ready"),c.router.selectedTab?(c.router.navigate(c.router.baseUrl("?browse="+c.router.selectedTab)),c.router.selectedTab=!1):c.router.navigate(c.router.baseUrl("")),this.trigger("preview:close"),this.undelegateEvents(),this.unbind(),!1},collapse:function(b){var c=a(b.currentTarget);return"true"===c.attr("aria-expanded")?c.attr({"aria-expanded":"false","aria-label":d.expandSidebar}):c.attr({"aria-expanded":"true","aria-label":d.collapseSidebar}),this.$el.toggleClass("collapsed").toggleClass("expanded"),!1},previewDevice:function(b){var c=a(b.currentTarget).data("device");this.$el.removeClass("preview-desktop preview-tablet preview-mobile").addClass("preview-"+c).data("current-preview-device",c),this.tooglePreviewDeviceButtons(c)},tooglePreviewDeviceButtons:function(b){var c=a(".wp-full-overlay-footer .devices");c.find("button").removeClass("active").attr("aria-pressed",!1),c.find("button.preview-"+b).addClass("active").attr("aria-pressed",!0)},keyEvent:function(a){27===a.keyCode&&(this.undelegateEvents(),this.close()),39===a.keyCode&&_.once(this.nextTheme()),37===a.keyCode&&this.previousTheme()},installTheme:function(b){var c=this,d=a(b.target);b.preventDefault(),d.hasClass("disabled")||(wp.updates.maybeRequestFilesystemCredentials(b),a(document).on("wp-theme-install-success",function(){c.model.set({installed:!0})}),wp.updates.installTheme({slug:d.data("slug")}))}}),c.view.Themes=wp.Backbone.View.extend({className:"themes wp-clearfix",$overlay:a("div.theme-overlay"),index:0,count:a(".wrap .theme-count"),liveThemeCount:0,initialize:function(b){var c=this;this.parent=b.parent,this.setView("grid"),c.currentTheme(),this.listenTo(c.collection,"themes:update",function(){c.parent.page=0,c.currentTheme(),c.render(this)}),this.listenTo(c.collection,"query:success",function(a){_.isNumber(a)?(c.count.text(a),c.announceSearchResults(a)):(c.count.text(c.collection.length),c.announceSearchResults(c.collection.length))}),this.listenTo(c.collection,"query:empty",function(){a("body").addClass("no-results")}),this.listenTo(this.parent,"theme:scroll",function(){c.renderThemes(c.parent.page)}),this.listenTo(this.parent,"theme:close",function(){c.overlay&&c.overlay.closeOverlay()}),a("body").on("keyup",function(b){c.overlay&&(a("#request-filesystem-credentials-dialog").is(":visible")||(39===b.keyCode&&c.overlay.nextTheme(),37===b.keyCode&&c.overlay.previousTheme(),27===b.keyCode&&c.overlay.collapse(b)))})},render:function(){this.$el.empty(),1===c.data.themes.length&&(this.singleTheme=new c.view.Details({model:this.collection.models[0]}),this.singleTheme.render(),this.$el.addClass("single-theme"),this.$el.append(this.singleTheme.el)),this.options.collection.size()>0&&this.renderThemes(this.parent.page),this.liveThemeCount=this.collection.count?this.collection.count:this.collection.length,this.count.text(this.liveThemeCount),c.isInstall||this.announceSearchResults(this.liveThemeCount)},renderThemes:function(b){var e=this;return e.instance=e.collection.paginate(b),0===e.instance.size()?void this.parent.trigger("theme:end"):(!c.isInstall&&b>=1&&a(".add-new-theme").remove(),e.instance.each(function(a){e.theme=new c.view.Theme({model:a,parent:e}),e.theme.render(),e.$el.append(e.theme.el),e.listenTo(e.theme,"theme:expand",e.expand,e)}),!c.isInstall&&c.data.settings.canInstall&&this.$el.append('<div class="theme add-new-theme"><a href="'+c.data.settings.installURI+'"><div class="theme-screenshot"><span></span></div><h2 class="theme-name">'+d.addNew+"</h2></a></div>"),void this.parent.page++)},currentTheme:function(){var a,b=this;a=b.collection.findWhere({active:!0}),a&&(b.collection.remove(a),b.collection.add(a,{at:0}))},setView:function(a){return a},expand:function(b){var d,e,f=this;this.model=f.collection.get(b),c.router.navigate(c.router.baseUrl(c.router.themePath+this.model.id)),this.setView("detail"),a("body").addClass("modal-open"),this.overlay=new c.view.Details({model:f.model}),this.overlay.render(),this.model.get("hasUpdate")&&(d=a('[data-slug="'+this.model.id+'"]'),e=a(this.overlay.el),d.find(".updating-message").length?(e.find(".notice-warning h3").remove(),e.find(".notice-warning").removeClass("notice-large").addClass("updating-message").find("p").text(wp.updates.l10n.updating)):d.find(".notice-error").length&&e.find(".notice-warning").remove()),this.$overlay.html(this.overlay.el),this.listenTo(this.overlay,"theme:next",function(){f.next([f.model.cid])}).listenTo(this.overlay,"theme:previous",function(){f.previous([f.model.cid])})},next:function(a){var b,c,d=this;b=d.collection.get(a[0]),c=d.collection.at(d.collection.indexOf(b)+1),void 0!==c&&(this.overlay.closeOverlay(),d.theme.trigger("theme:expand",c.cid))},previous:function(a){var b,c,d=this;b=d.collection.get(a[0]),c=d.collection.at(d.collection.indexOf(b)-1),void 0!==c&&(this.overlay.closeOverlay(),d.theme.trigger("theme:expand",c.cid))},announceSearchResults:function(a){0===a?wp.a11y.speak(d.noThemesFound):wp.a11y.speak(d.themesFound.replace("%d",a))}}),c.view.Search=wp.Backbone.View.extend({tagName:"input",className:"wp-filter-search",id:"wp-filter-search-input",searching:!1,attributes:{placeholder:d.searchPlaceholder,type:"search","aria-describedby":"live-search-desc"},events:{input:"search",keyup:"search",blur:"pushState"},initialize:function(a){this.parent=a.parent,this.listenTo(this.parent,"theme:close",function(){this.searching=!1})},search:function(a){"keyup"===a.type&&27===a.which&&(a.target.value=""),this.doSearch(a)},doSearch:function(a){var b={};this.collection.doSearch(a.target.value.replace(/\+/g," ")),this.searching&&13!==a.which?b.replace=!0:this.searching=!0,a.target.value?c.router.navigate(c.router.baseUrl(c.router.searchPath+a.target.value),b):c.router.navigate(c.router.baseUrl(""))},pushState:function(a){var b=c.router.baseUrl("");a.target.value&&(b=c.router.baseUrl(c.router.searchPath+encodeURIComponent(a.target.value))),this.searching=!1,c.router.navigate(b)}}),c.Router=Backbone.Router.extend({routes:{"themes.php?theme=:slug":"theme","themes.php?search=:query":"search","themes.php?s=:query":"search","themes.php":"themes","":"themes"},baseUrl:function(a){return"themes.php"+a},themePath:"?theme=",searchPath:"?search=",search:function(b){a(".wp-filter-search").val(b.replace(/\+/g," "))},themes:function(){a(".wp-filter-search").val("")},navigate:b}),c.Run={init:function(){this.themes=new c.Collection(c.data.themes),this.view=new c.view.Appearance({collection:this.themes}),this.render(),this.view.SearchView.doSearch=_.debounce(this.view.SearchView.doSearch,500)},render:function(){this.view.render(),this.routes(),Backbone.History.started&&Backbone.history.stop(),Backbone.history.start({root:c.data.settings.adminUrl,pushState:!0,hashChange:!1})},routes:function(){var b=this;c.router=new c.Router,c.router.on("route:theme",function(a){b.view.view.expand(a)}),c.router.on("route:themes",function(){b.themes.doSearch(""),b.view.trigger("theme:close")}),c.router.on("route:search",function(){a(".wp-filter-search").trigger("keyup")}),this.extraRoutes()},extraRoutes:function(){return!1}},c.view.InstallerSearch=c.view.Search.extend({events:{input:"search",keyup:"search"},terms:"",search:function(a){("keyup"!==a.type||9!==a.which&&16!==a.which)&&(this.collection=this.options.parent.view.collection,"keyup"===a.type&&27===a.which&&(a.target.value=""),this.doSearch(a.target.value))},doSearch:function(b){var d={};this.terms!==b&&(this.terms=b,d.search=b,"author:"===b.substring(0,7)&&(d.search="",d.author=b.slice(7)),"tag:"===b.substring(0,4)&&(d.search="",d.tag=[b.slice(4)]),a(".filter-links li > a.current").removeClass("current").removeAttr("aria-current"),a("body").removeClass("show-filters filters-applied show-favorites-form"),a(".drawer-toggle").attr("aria-expanded","false"),this.collection.query(d),c.router.navigate(c.router.baseUrl(c.router.searchPath+encodeURIComponent(b)),{replace:!0}))}}),c.view.Installer=c.view.Appearance.extend({el:"#wpbody-content .wrap",events:{"click .filter-links li > a":"onSort","click .theme-filter":"onFilter","click .drawer-toggle":"moreFilters","click .filter-drawer .apply-filters":"applyFilters",'click .filter-group [type="checkbox"]':"addFilter","click .filter-drawer .clear-filters":"clearFilters","click .edit-filters":"backToFilters","click .favorites-form-submit":"saveUsername","keyup #wporg-username-input":"saveUsername"},render:function(){var b=this;this.search(),this.uploader(),this.collection=new c.Collection,this.listenTo(this,"theme:end",function(){b.collection.loadingThemes||(b.collection.loadingThemes=!0,b.collection.currentQuery.page++,_.extend(b.collection.currentQuery.request,{page:b.collection.currentQuery.page}),b.collection.query(b.collection.currentQuery.request))}),this.listenTo(this.collection,"query:success",function(){a("body").removeClass("loading-content"),a(".theme-browser").find("div.error").remove()}),this.listenTo(this.collection,"query:fail",function(){a("body").removeClass("loading-content"),a(".theme-browser").find("div.error").remove(),a(".theme-browser").find("div.themes").before('<div class="error"><p>'+d.error+'</p><p><button class="button try-again">'+d.tryAgain+"</button></p></div>"),a(".theme-browser .error .try-again").on("click",function(b){b.preventDefault(),a("input.wp-filter-search").trigger("input")})}),this.view&&this.view.remove(),this.view=new c.view.Themes({collection:this.collection,parent:this}),this.page=0,this.$el.find(".themes").remove(),this.view.render(),this.$el.find(".theme-browser").append(this.view.el).addClass("rendered")},browse:function(a){this.collection.query({browse:a})},onSort:function(b){var d=a(b.target),e=d.data("sort");b.preventDefault(),a("body").removeClass("filters-applied show-filters"),a(".drawer-toggle").attr("aria-expanded","false"),d.hasClass(this.activeClass)||(this.sort(e),c.router.navigate(c.router.baseUrl(c.router.browsePath+e)))},sort:function(b){this.clearSearch(),c.router.selectedTab=b,a(".filter-links li > a, .theme-filter").removeClass(this.activeClass).removeAttr("aria-current"),a('[data-sort="'+b+'"]').addClass(this.activeClass).attr("aria-current","page"),"favorites"===b?a("body").addClass("show-favorites-form"):a("body").removeClass("show-favorites-form"),this.browse(b)},onFilter:function(b){var c,d=a(b.target),e=d.data("filter");d.hasClass(this.activeClass)||(a(".filter-links li > a, .theme-section").removeClass(this.activeClass).removeAttr("aria-current"),d.addClass(this.activeClass).attr("aria-current","page"),e&&(e=_.union([e,this.filtersChecked()]),c={tag:[e]},this.collection.query(c)))},addFilter:function(){this.filtersChecked()},applyFilters:function(b){var c,e=this.filtersChecked(),f={tag:e},g=a(".filtered-by .tags");return b&&b.preventDefault(),e?(a("body").addClass("filters-applied"),a(".filter-links li > a.current").removeClass("current").removeAttr("aria-current"),g.empty(),_.each(e,function(b){c=a('label[for="filter-id-'+b+'"]').text(),g.append('<span class="tag">'+c+"</span>")}),void this.collection.query(f)):void wp.a11y.speak(d.selectFeatureFilter)},saveUsername:function(b){var c=a("#wporg-username-input").val(),d=a("#wporg-username-nonce").val(),e={browse:"favorites",user:c},f=this;if(b&&b.preventDefault(),"keyup"!==b.type||13===b.which)return wp.ajax.send("save-wporg-username",{data:{_wpnonce:d,username:c},success:function(){f.collection.query(e)}})},filtersChecked:function(){var b=a(".filter-group").find(":checkbox"),c=[];return _.each(b.filter(":checked"),function(b){c.push(a(b).prop("value"))}),0===c.length?(a(".filter-drawer .apply-filters").find("span").text(""),a(".filter-drawer .clear-filters").hide(),a("body").removeClass("filters-applied"),!1):(a(".filter-drawer .apply-filters").find("span").text(c.length),a(".filter-drawer .clear-filters").css("display","inline-block"),c)},activeClass:"current",uploader:function(){var b=a(".upload-view-toggle"),c=a(document.body);b.on("click",function(){c.toggleClass("show-upload-view"),b.attr("aria-expanded",c.hasClass("show-upload-view"))})},moreFilters:function(b){var d=a("body"),e=a(".drawer-toggle");return b.preventDefault(),d.hasClass("filters-applied")?this.backToFilters():(this.clearSearch(),c.router.navigate(c.router.baseUrl("")),d.toggleClass("show-filters"),void e.attr("aria-expanded",d.hasClass("show-filters")))},clearFilters:function(b){var c=a(".filter-group").find(":checkbox"),d=this;b.preventDefault(),_.each(c.filter(":checked"),function(b){return a(b).prop("checked",!1),d.filtersChecked()})},backToFilters:function(b){b&&b.preventDefault(),a("body").removeClass("filters-applied")},clearSearch:function(){a("#wp-filter-search-input").val("")}}),c.InstallerRouter=Backbone.Router.extend({routes:{"theme-install.php?theme=:slug":"preview","theme-install.php?browse=:sort":"sort","theme-install.php?search=:query":"search","theme-install.php":"sort"},baseUrl:function(a){return"theme-install.php"+a},themePath:"?theme=",browsePath:"?browse=",searchPath:"?search=",search:function(b){a(".wp-filter-search").val(b.replace(/\+/g," "))},navigate:b}),c.RunInstaller={init:function(){this.view=new c.view.Installer({section:"featured",SearchView:c.view.InstallerSearch}),this.render(),this.view.SearchView.doSearch=_.debounce(this.view.SearchView.doSearch,500)},render:function(){this.view.render(),this.routes(),Backbone.History.started&&Backbone.history.stop(),Backbone.history.start({root:c.data.settings.adminUrl,pushState:!0,hashChange:!1})},routes:function(){var b=this,d={};c.router=new c.InstallerRouter,c.router.on("route:preview",function(e){c.preview&&(c.preview.undelegateEvents(),c.preview.unbind()),b.view.view.theme&&b.view.view.theme.preview?(b.view.view.theme.model=b.view.collection.findWhere({slug:e}),b.view.view.theme.preview()):(d.theme=e,b.view.collection.query(d),b.view.collection.trigger("update"),b.view.collection.once("query:success",function(){a('div[data-slug="'+e+'"]').trigger("click")}))}),c.router.on("route:sort",function(a){a||(a="featured",c.router.navigate(c.router.baseUrl("?browse=featured"),{replace:!0})),b.view.sort(a),c.preview&&c.preview.close()}),c.router.on("route:search",function(){a(".wp-filter-search").focus().trigger("keyup")}),this.extraRoutes()},extraRoutes:function(){return!1}},a(document).ready(function(){c.isInstall?c.RunInstaller.init():c.Run.init(),a(document.body).on("click",".load-customize",function(){var b=a(this),c=document.createElement("a");c.href=b.prop("href"),c.search=a.param(_.extend(wp.customize.utils.parseQueryString(c.search.substr(1)),{"return":window.location.href})),b.prop("href",c.href)}),a(".broken-themes .delete-theme").on("click",function(){return confirm(_wpThemeSettings.settings.confirmDelete)})})}(jQuery),jQuery(document).ready(function(a){window.tb_position=function(){var b=a("#TB_window"),c=a(window).width(),d=a(window).height(),e=1040<c?1040:c,f=0;a("#wpadminbar").length&&(f=parseInt(a("#wpadminbar").css("height"),10)),b.size()&&(b.width(e-50).height(d-45-f),a("#TB_iframeContent").width(e-50).height(d-75-f),b.css({"margin-left":"-"+parseInt((e-50)/2,10)+"px"}),"undefined"!=typeof document.body.style.maxWidth&&b.css({top:20+f+"px","margin-top":"0"}))},a(window).resize(function(){tb_position()})}); \ No newline at end of file
diff --git a/www/crm/wp-admin/js/updates.js b/www/crm/wp-admin/js/updates.js
new file mode 100644
index 00000000..9afa4957
--- /dev/null
+++ b/www/crm/wp-admin/js/updates.js
@@ -0,0 +1,2465 @@
+/**
+ * Functions for ajaxified updates, deletions and installs inside the WordPress admin.
+ *
+ * @version 4.2.0
+ * @output wp-admin/js/updates.js
+ */
+
+/* global pagenow */
+
+/**
+ * @param {jQuery} $ jQuery object.
+ * @param {object} wp WP object.
+ * @param {object} settings WP Updates settings.
+ * @param {string} settings.ajax_nonce AJAX nonce.
+ * @param {object} settings.l10n Translation strings.
+ * @param {object=} settings.plugins Base names of plugins in their different states.
+ * @param {Array} settings.plugins.all Base names of all plugins.
+ * @param {Array} settings.plugins.active Base names of active plugins.
+ * @param {Array} settings.plugins.inactive Base names of inactive plugins.
+ * @param {Array} settings.plugins.upgrade Base names of plugins with updates available.
+ * @param {Array} settings.plugins.recently_activated Base names of recently activated plugins.
+ * @param {object=} settings.themes Plugin/theme status information or null.
+ * @param {number} settings.themes.all Amount of all themes.
+ * @param {number} settings.themes.upgrade Amount of themes with updates available.
+ * @param {number} settings.themes.disabled Amount of disabled themes.
+ * @param {object=} settings.totals Combined information for available update counts.
+ * @param {number} settings.totals.count Holds the amount of available updates.
+ */
+(function( $, wp, settings ) {
+ var $document = $( document );
+
+ wp = wp || {};
+
+ /**
+ * The WP Updates object.
+ *
+ * @since 4.2.0
+ *
+ * @namespace wp.updates
+ */
+ wp.updates = {};
+
+ /**
+ * User nonce for ajax calls.
+ *
+ * @since 4.2.0
+ *
+ * @type {string}
+ */
+ wp.updates.ajaxNonce = settings.ajax_nonce;
+
+ /**
+ * Localized strings.
+ *
+ * @since 4.2.0
+ *
+ * @type {object}
+ */
+ wp.updates.l10n = settings.l10n;
+
+ /**
+ * Current search term.
+ *
+ * @since 4.6.0
+ *
+ * @type {string}
+ */
+ wp.updates.searchTerm = '';
+
+ /**
+ * Whether filesystem credentials need to be requested from the user.
+ *
+ * @since 4.2.0
+ *
+ * @type {bool}
+ */
+ wp.updates.shouldRequestFilesystemCredentials = false;
+
+ /**
+ * Filesystem credentials to be packaged along with the request.
+ *
+ * @since 4.2.0
+ * @since 4.6.0 Added `available` property to indicate whether credentials have been provided.
+ *
+ * @type {Object}
+ * @property {Object} filesystemCredentials.ftp Holds FTP credentials.
+ * @property {string} filesystemCredentials.ftp.host FTP host. Default empty string.
+ * @property {string} filesystemCredentials.ftp.username FTP user name. Default empty string.
+ * @property {string} filesystemCredentials.ftp.password FTP password. Default empty string.
+ * @property {string} filesystemCredentials.ftp.connectionType Type of FTP connection. 'ssh', 'ftp', or 'ftps'.
+ * Default empty string.
+ * @property {Object} filesystemCredentials.ssh Holds SSH credentials.
+ * @property {string} filesystemCredentials.ssh.publicKey The public key. Default empty string.
+ * @property {string} filesystemCredentials.ssh.privateKey The private key. Default empty string.
+ * @property {string} filesystemCredentials.fsNonce Filesystem credentials form nonce.
+ * @property {bool} filesystemCredentials.available Whether filesystem credentials have been provided.
+ * Default 'false'.
+ */
+ wp.updates.filesystemCredentials = {
+ ftp: {
+ host: '',
+ username: '',
+ password: '',
+ connectionType: ''
+ },
+ ssh: {
+ publicKey: '',
+ privateKey: ''
+ },
+ fsNonce: '',
+ available: false
+ };
+
+ /**
+ * Whether we're waiting for an Ajax request to complete.
+ *
+ * @since 4.2.0
+ * @since 4.6.0 More accurately named `ajaxLocked`.
+ *
+ * @type {bool}
+ */
+ wp.updates.ajaxLocked = false;
+
+ /**
+ * Admin notice template.
+ *
+ * @since 4.6.0
+ *
+ * @type {function}
+ */
+ wp.updates.adminNotice = wp.template( 'wp-updates-admin-notice' );
+
+ /**
+ * Update queue.
+ *
+ * If the user tries to update a plugin while an update is
+ * already happening, it can be placed in this queue to perform later.
+ *
+ * @since 4.2.0
+ * @since 4.6.0 More accurately named `queue`.
+ *
+ * @type {Array.object}
+ */
+ wp.updates.queue = [];
+
+ /**
+ * Holds a jQuery reference to return focus to when exiting the request credentials modal.
+ *
+ * @since 4.2.0
+ *
+ * @type {jQuery}
+ */
+ wp.updates.$elToReturnFocusToFromCredentialsModal = undefined;
+
+ /**
+ * Adds or updates an admin notice.
+ *
+ * @since 4.6.0
+ *
+ * @param {object} data
+ * @param {*=} data.selector Optional. Selector of an element to be replaced with the admin notice.
+ * @param {string=} data.id Optional. Unique id that will be used as the notice's id attribute.
+ * @param {string=} data.className Optional. Class names that will be used in the admin notice.
+ * @param {string=} data.message Optional. The message displayed in the notice.
+ * @param {number=} data.successes Optional. The amount of successful operations.
+ * @param {number=} data.errors Optional. The amount of failed operations.
+ * @param {Array=} data.errorMessages Optional. Error messages of failed operations.
+ *
+ */
+ wp.updates.addAdminNotice = function( data ) {
+ var $notice = $( data.selector ),
+ $headerEnd = $( '.wp-header-end' ),
+ $adminNotice;
+
+ delete data.selector;
+ $adminNotice = wp.updates.adminNotice( data );
+
+ // Check if this admin notice already exists.
+ if ( ! $notice.length ) {
+ $notice = $( '#' + data.id );
+ }
+
+ if ( $notice.length ) {
+ $notice.replaceWith( $adminNotice );
+ } else if ( $headerEnd.length ) {
+ $headerEnd.after( $adminNotice );
+ } else {
+ if ( 'customize' === pagenow ) {
+ $( '.customize-themes-notifications' ).append( $adminNotice );
+ } else {
+ $( '.wrap' ).find( '> h1' ).after( $adminNotice );
+ }
+ }
+
+ $document.trigger( 'wp-updates-notice-added' );
+ };
+
+ /**
+ * Handles Ajax requests to WordPress.
+ *
+ * @since 4.6.0
+ *
+ * @param {string} action The type of Ajax request ('update-plugin', 'install-theme', etc).
+ * @param {object} data Data that needs to be passed to the ajax callback.
+ * @return {$.promise} A jQuery promise that represents the request,
+ * decorated with an abort() method.
+ */
+ wp.updates.ajax = function( action, data ) {
+ var options = {};
+
+ if ( wp.updates.ajaxLocked ) {
+ wp.updates.queue.push( {
+ action: action,
+ data: data
+ } );
+
+ // Return a Deferred object so callbacks can always be registered.
+ return $.Deferred();
+ }
+
+ wp.updates.ajaxLocked = true;
+
+ if ( data.success ) {
+ options.success = data.success;
+ delete data.success;
+ }
+
+ if ( data.error ) {
+ options.error = data.error;
+ delete data.error;
+ }
+
+ options.data = _.extend( data, {
+ action: action,
+ _ajax_nonce: wp.updates.ajaxNonce,
+ _fs_nonce: wp.updates.filesystemCredentials.fsNonce,
+ username: wp.updates.filesystemCredentials.ftp.username,
+ password: wp.updates.filesystemCredentials.ftp.password,
+ hostname: wp.updates.filesystemCredentials.ftp.hostname,
+ connection_type: wp.updates.filesystemCredentials.ftp.connectionType,
+ public_key: wp.updates.filesystemCredentials.ssh.publicKey,
+ private_key: wp.updates.filesystemCredentials.ssh.privateKey
+ } );
+
+ return wp.ajax.send( options ).always( wp.updates.ajaxAlways );
+ };
+
+ /**
+ * Actions performed after every Ajax request.
+ *
+ * @since 4.6.0
+ *
+ * @param {object} response
+ * @param {array=} response.debug Optional. Debug information.
+ * @param {string=} response.errorCode Optional. Error code for an error that occurred.
+ */
+ wp.updates.ajaxAlways = function( response ) {
+ if ( ! response.errorCode || 'unable_to_connect_to_filesystem' !== response.errorCode ) {
+ wp.updates.ajaxLocked = false;
+ wp.updates.queueChecker();
+ }
+
+ if ( 'undefined' !== typeof response.debug && window.console && window.console.log ) {
+ _.map( response.debug, function( message ) {
+ // Remove all HTML tags and write a message to the console.
+ window.console.log( wp.sanitize.stripTagsAndEncodeText( message ) );
+ } );
+ }
+ };
+
+ /**
+ * Refreshes update counts everywhere on the screen.
+ *
+ * @since 4.7.0
+ */
+ wp.updates.refreshCount = function() {
+ var $adminBarUpdates = $( '#wp-admin-bar-updates' ),
+ $dashboardNavMenuUpdateCount = $( 'a[href="update-core.php"] .update-plugins' ),
+ $pluginsNavMenuUpdateCount = $( 'a[href="plugins.php"] .update-plugins' ),
+ $appearanceNavMenuUpdateCount = $( 'a[href="themes.php"] .update-plugins' ),
+ itemCount;
+
+ $adminBarUpdates.find( '.ab-item' ).removeAttr( 'title' );
+ $adminBarUpdates.find( '.ab-label' ).text( settings.totals.counts.total );
+
+ // Remove the update count from the toolbar if it's zero.
+ if ( 0 === settings.totals.counts.total ) {
+ $adminBarUpdates.find( '.ab-label' ).parents( 'li' ).remove();
+ }
+
+ // Update the "Updates" menu item.
+ $dashboardNavMenuUpdateCount.each( function( index, element ) {
+ element.className = element.className.replace( /count-\d+/, 'count-' + settings.totals.counts.total );
+ } );
+ if ( settings.totals.counts.total > 0 ) {
+ $dashboardNavMenuUpdateCount.find( '.update-count' ).text( settings.totals.counts.total );
+ } else {
+ $dashboardNavMenuUpdateCount.remove();
+ }
+
+ // Update the "Plugins" menu item.
+ $pluginsNavMenuUpdateCount.each( function( index, element ) {
+ element.className = element.className.replace( /count-\d+/, 'count-' + settings.totals.counts.plugins );
+ } );
+ if ( settings.totals.counts.total > 0 ) {
+ $pluginsNavMenuUpdateCount.find( '.plugin-count' ).text( settings.totals.counts.plugins );
+ } else {
+ $pluginsNavMenuUpdateCount.remove();
+ }
+
+ // Update the "Appearance" menu item.
+ $appearanceNavMenuUpdateCount.each( function( index, element ) {
+ element.className = element.className.replace( /count-\d+/, 'count-' + settings.totals.counts.themes );
+ } );
+ if ( settings.totals.counts.total > 0 ) {
+ $appearanceNavMenuUpdateCount.find( '.theme-count' ).text( settings.totals.counts.themes );
+ } else {
+ $appearanceNavMenuUpdateCount.remove();
+ }
+
+ // Update list table filter navigation.
+ if ( 'plugins' === pagenow || 'plugins-network' === pagenow ) {
+ itemCount = settings.totals.counts.plugins;
+ } else if ( 'themes' === pagenow || 'themes-network' === pagenow ) {
+ itemCount = settings.totals.counts.themes;
+ }
+
+ if ( itemCount > 0 ) {
+ $( '.subsubsub .upgrade .count' ).text( '(' + itemCount + ')' );
+ } else {
+ $( '.subsubsub .upgrade' ).remove();
+ $( '.subsubsub li:last' ).html( function() { return $( this ).children(); } );
+ }
+ };
+
+ /**
+ * Decrements the update counts throughout the various menus.
+ *
+ * This includes the toolbar, the "Updates" menu item and the menu items
+ * for plugins and themes.
+ *
+ * @since 3.9.0
+ *
+ * @param {string} type The type of item that was updated or deleted.
+ * Can be 'plugin', 'theme'.
+ */
+ wp.updates.decrementCount = function( type ) {
+ settings.totals.counts.total = Math.max( --settings.totals.counts.total, 0 );
+
+ if ( 'plugin' === type ) {
+ settings.totals.counts.plugins = Math.max( --settings.totals.counts.plugins, 0 );
+ } else if ( 'theme' === type ) {
+ settings.totals.counts.themes = Math.max( --settings.totals.counts.themes, 0 );
+ }
+
+ wp.updates.refreshCount( type );
+ };
+
+ /**
+ * Sends an Ajax request to the server to update a plugin.
+ *
+ * @since 4.2.0
+ * @since 4.6.0 More accurately named `updatePlugin`.
+ *
+ * @param {object} args Arguments.
+ * @param {string} args.plugin Plugin basename.
+ * @param {string} args.slug Plugin slug.
+ * @param {updatePluginSuccess=} args.success Optional. Success callback. Default: wp.updates.updatePluginSuccess
+ * @param {updatePluginError=} args.error Optional. Error callback. Default: wp.updates.updatePluginError
+ * @return {$.promise} A jQuery promise that represents the request,
+ * decorated with an abort() method.
+ */
+ wp.updates.updatePlugin = function( args ) {
+ var $updateRow, $card, $message, message;
+
+ args = _.extend( {
+ success: wp.updates.updatePluginSuccess,
+ error: wp.updates.updatePluginError
+ }, args );
+
+ if ( 'plugins' === pagenow || 'plugins-network' === pagenow ) {
+ $updateRow = $( 'tr[data-plugin="' + args.plugin + '"]' );
+ $message = $updateRow.find( '.update-message' ).removeClass( 'notice-error' ).addClass( 'updating-message notice-warning' ).find( 'p' );
+ message = wp.updates.l10n.pluginUpdatingLabel.replace( '%s', $updateRow.find( '.plugin-title strong' ).text() );
+ } else if ( 'plugin-install' === pagenow || 'plugin-install-network' === pagenow ) {
+ $card = $( '.plugin-card-' + args.slug );
+ $message = $card.find( '.update-now' ).addClass( 'updating-message' );
+ message = wp.updates.l10n.pluginUpdatingLabel.replace( '%s', $message.data( 'name' ) );
+
+ // Remove previous error messages, if any.
+ $card.removeClass( 'plugin-card-update-failed' ).find( '.notice.notice-error' ).remove();
+ }
+
+ if ( $message.html() !== wp.updates.l10n.updating ) {
+ $message.data( 'originaltext', $message.html() );
+ }
+
+ $message
+ .attr( 'aria-label', message )
+ .text( wp.updates.l10n.updating );
+
+ $document.trigger( 'wp-plugin-updating', args );
+
+ return wp.updates.ajax( 'update-plugin', args );
+ };
+
+ /**
+ * Updates the UI appropriately after a successful plugin update.
+ *
+ * @since 4.2.0
+ * @since 4.6.0 More accurately named `updatePluginSuccess`.
+ *
+ * @param {object} response Response from the server.
+ * @param {string} response.slug Slug of the plugin to be updated.
+ * @param {string} response.plugin Basename of the plugin to be updated.
+ * @param {string} response.pluginName Name of the plugin to be updated.
+ * @param {string} response.oldVersion Old version of the plugin.
+ * @param {string} response.newVersion New version of the plugin.
+ */
+ wp.updates.updatePluginSuccess = function( response ) {
+ var $pluginRow, $updateMessage, newText;
+
+ if ( 'plugins' === pagenow || 'plugins-network' === pagenow ) {
+ $pluginRow = $( 'tr[data-plugin="' + response.plugin + '"]' )
+ .removeClass( 'update' )
+ .addClass( 'updated' );
+ $updateMessage = $pluginRow.find( '.update-message' )
+ .removeClass( 'updating-message notice-warning' )
+ .addClass( 'updated-message notice-success' ).find( 'p' );
+
+ // Update the version number in the row.
+ newText = $pluginRow.find( '.plugin-version-author-uri' ).html().replace( response.oldVersion, response.newVersion );
+ $pluginRow.find( '.plugin-version-author-uri' ).html( newText );
+ } else if ( 'plugin-install' === pagenow || 'plugin-install-network' === pagenow ) {
+ $updateMessage = $( '.plugin-card-' + response.slug ).find( '.update-now' )
+ .removeClass( 'updating-message' )
+ .addClass( 'button-disabled updated-message' );
+ }
+
+ $updateMessage
+ .attr( 'aria-label', wp.updates.l10n.pluginUpdatedLabel.replace( '%s', response.pluginName ) )
+ .text( wp.updates.l10n.pluginUpdated );
+
+ wp.a11y.speak( wp.updates.l10n.updatedMsg, 'polite' );
+
+ wp.updates.decrementCount( 'plugin' );
+
+ $document.trigger( 'wp-plugin-update-success', response );
+ };
+
+ /**
+ * Updates the UI appropriately after a failed plugin update.
+ *
+ * @since 4.2.0
+ * @since 4.6.0 More accurately named `updatePluginError`.
+ *
+ * @param {object} response Response from the server.
+ * @param {string} response.slug Slug of the plugin to be updated.
+ * @param {string} response.plugin Basename of the plugin to be updated.
+ * @param {string=} response.pluginName Optional. Name of the plugin to be updated.
+ * @param {string} response.errorCode Error code for the error that occurred.
+ * @param {string} response.errorMessage The error that occurred.
+ */
+ wp.updates.updatePluginError = function( response ) {
+ var $card, $message, errorMessage;
+
+ if ( ! wp.updates.isValidResponse( response, 'update' ) ) {
+ return;
+ }
+
+ if ( wp.updates.maybeHandleCredentialError( response, 'update-plugin' ) ) {
+ return;
+ }
+
+ errorMessage = wp.updates.l10n.updateFailed.replace( '%s', response.errorMessage );
+
+ if ( 'plugins' === pagenow || 'plugins-network' === pagenow ) {
+ if ( response.plugin ) {
+ $message = $( 'tr[data-plugin="' + response.plugin + '"]' ).find( '.update-message' );
+ } else {
+ $message = $( 'tr[data-slug="' + response.slug + '"]' ).find( '.update-message' );
+ }
+ $message.removeClass( 'updating-message notice-warning' ).addClass( 'notice-error' ).find( 'p' ).html( errorMessage );
+
+ if ( response.pluginName ) {
+ $message.find( 'p' )
+ .attr( 'aria-label', wp.updates.l10n.pluginUpdateFailedLabel.replace( '%s', response.pluginName ) );
+ } else {
+ $message.find( 'p' ).removeAttr( 'aria-label' );
+ }
+ } else if ( 'plugin-install' === pagenow || 'plugin-install-network' === pagenow ) {
+ $card = $( '.plugin-card-' + response.slug )
+ .addClass( 'plugin-card-update-failed' )
+ .append( wp.updates.adminNotice( {
+ className: 'update-message notice-error notice-alt is-dismissible',
+ message: errorMessage
+ } ) );
+
+ $card.find( '.update-now' )
+ .text( wp.updates.l10n.updateFailedShort ).removeClass( 'updating-message' );
+
+ if ( response.pluginName ) {
+ $card.find( '.update-now' )
+ .attr( 'aria-label', wp.updates.l10n.pluginUpdateFailedLabel.replace( '%s', response.pluginName ) );
+ } else {
+ $card.find( '.update-now' ).removeAttr( 'aria-label' );
+ }
+
+ $card.on( 'click', '.notice.is-dismissible .notice-dismiss', function() {
+
+ // Use same delay as the total duration of the notice fadeTo + slideUp animation.
+ setTimeout( function() {
+ $card
+ .removeClass( 'plugin-card-update-failed' )
+ .find( '.column-name a' ).focus();
+
+ $card.find( '.update-now' )
+ .attr( 'aria-label', false )
+ .text( wp.updates.l10n.updateNow );
+ }, 200 );
+ } );
+ }
+
+ wp.a11y.speak( errorMessage, 'assertive' );
+
+ $document.trigger( 'wp-plugin-update-error', response );
+ };
+
+ /**
+ * Sends an Ajax request to the server to install a plugin.
+ *
+ * @since 4.6.0
+ *
+ * @param {object} args Arguments.
+ * @param {string} args.slug Plugin identifier in the WordPress.org Plugin repository.
+ * @param {installPluginSuccess=} args.success Optional. Success callback. Default: wp.updates.installPluginSuccess
+ * @param {installPluginError=} args.error Optional. Error callback. Default: wp.updates.installPluginError
+ * @return {$.promise} A jQuery promise that represents the request,
+ * decorated with an abort() method.
+ */
+ wp.updates.installPlugin = function( args ) {
+ var $card = $( '.plugin-card-' + args.slug ),
+ $message = $card.find( '.install-now' );
+
+ args = _.extend( {
+ success: wp.updates.installPluginSuccess,
+ error: wp.updates.installPluginError
+ }, args );
+
+ if ( 'import' === pagenow ) {
+ $message = $( '[data-slug="' + args.slug + '"]' );
+ }
+
+ if ( $message.html() !== wp.updates.l10n.installing ) {
+ $message.data( 'originaltext', $message.html() );
+ }
+
+ $message
+ .addClass( 'updating-message' )
+ .attr( 'aria-label', wp.updates.l10n.pluginInstallingLabel.replace( '%s', $message.data( 'name' ) ) )
+ .text( wp.updates.l10n.installing );
+
+ wp.a11y.speak( wp.updates.l10n.installingMsg, 'polite' );
+
+ // Remove previous error messages, if any.
+ $card.removeClass( 'plugin-card-install-failed' ).find( '.notice.notice-error' ).remove();
+
+ $document.trigger( 'wp-plugin-installing', args );
+
+ return wp.updates.ajax( 'install-plugin', args );
+ };
+
+ /**
+ * Updates the UI appropriately after a successful plugin install.
+ *
+ * @since 4.6.0
+ *
+ * @param {object} response Response from the server.
+ * @param {string} response.slug Slug of the installed plugin.
+ * @param {string} response.pluginName Name of the installed plugin.
+ * @param {string} response.activateUrl URL to activate the just installed plugin.
+ */
+ wp.updates.installPluginSuccess = function( response ) {
+ var $message = $( '.plugin-card-' + response.slug ).find( '.install-now' );
+
+ $message
+ .removeClass( 'updating-message' )
+ .addClass( 'updated-message installed button-disabled' )
+ .attr( 'aria-label', wp.updates.l10n.pluginInstalledLabel.replace( '%s', response.pluginName ) )
+ .text( wp.updates.l10n.pluginInstalled );
+
+ wp.a11y.speak( wp.updates.l10n.installedMsg, 'polite' );
+
+ $document.trigger( 'wp-plugin-install-success', response );
+
+ if ( response.activateUrl ) {
+ setTimeout( function() {
+
+ // Transform the 'Install' button into an 'Activate' button.
+ $message.removeClass( 'install-now installed button-disabled updated-message' ).addClass( 'activate-now button-primary' )
+ .attr( 'href', response.activateUrl )
+ .attr( 'aria-label', wp.updates.l10n.activatePluginLabel.replace( '%s', response.pluginName ) )
+ .text( wp.updates.l10n.activatePlugin );
+ }, 1000 );
+ }
+ };
+
+ /**
+ * Updates the UI appropriately after a failed plugin install.
+ *
+ * @since 4.6.0
+ *
+ * @param {object} response Response from the server.
+ * @param {string} response.slug Slug of the plugin to be installed.
+ * @param {string=} response.pluginName Optional. Name of the plugin to be installed.
+ * @param {string} response.errorCode Error code for the error that occurred.
+ * @param {string} response.errorMessage The error that occurred.
+ */
+ wp.updates.installPluginError = function( response ) {
+ var $card = $( '.plugin-card-' + response.slug ),
+ $button = $card.find( '.install-now' ),
+ errorMessage;
+
+ if ( ! wp.updates.isValidResponse( response, 'install' ) ) {
+ return;
+ }
+
+ if ( wp.updates.maybeHandleCredentialError( response, 'install-plugin' ) ) {
+ return;
+ }
+
+ errorMessage = wp.updates.l10n.installFailed.replace( '%s', response.errorMessage );
+
+ $card
+ .addClass( 'plugin-card-update-failed' )
+ .append( '<div class="notice notice-error notice-alt is-dismissible"><p>' + errorMessage + '</p></div>' );
+
+ $card.on( 'click', '.notice.is-dismissible .notice-dismiss', function() {
+
+ // Use same delay as the total duration of the notice fadeTo + slideUp animation.
+ setTimeout( function() {
+ $card
+ .removeClass( 'plugin-card-update-failed' )
+ .find( '.column-name a' ).focus();
+ }, 200 );
+ } );
+
+ $button
+ .removeClass( 'updating-message' ).addClass( 'button-disabled' )
+ .attr( 'aria-label', wp.updates.l10n.pluginInstallFailedLabel.replace( '%s', $button.data( 'name' ) ) )
+ .text( wp.updates.l10n.installFailedShort );
+
+ wp.a11y.speak( errorMessage, 'assertive' );
+
+ $document.trigger( 'wp-plugin-install-error', response );
+ };
+
+ /**
+ * Updates the UI appropriately after a successful importer install.
+ *
+ * @since 4.6.0
+ *
+ * @param {object} response Response from the server.
+ * @param {string} response.slug Slug of the installed plugin.
+ * @param {string} response.pluginName Name of the installed plugin.
+ * @param {string} response.activateUrl URL to activate the just installed plugin.
+ */
+ wp.updates.installImporterSuccess = function( response ) {
+ wp.updates.addAdminNotice( {
+ id: 'install-success',
+ className: 'notice-success is-dismissible',
+ message: wp.updates.l10n.importerInstalledMsg.replace( '%s', response.activateUrl + '&from=import' )
+ } );
+
+ $( '[data-slug="' + response.slug + '"]' )
+ .removeClass( 'install-now updating-message' )
+ .addClass( 'activate-now' )
+ .attr({
+ 'href': response.activateUrl + '&from=import',
+ 'aria-label': wp.updates.l10n.activateImporterLabel.replace( '%s', response.pluginName )
+ })
+ .text( wp.updates.l10n.activateImporter );
+
+ wp.a11y.speak( wp.updates.l10n.installedMsg, 'polite' );
+
+ $document.trigger( 'wp-importer-install-success', response );
+ };
+
+ /**
+ * Updates the UI appropriately after a failed importer install.
+ *
+ * @since 4.6.0
+ *
+ * @param {object} response Response from the server.
+ * @param {string} response.slug Slug of the plugin to be installed.
+ * @param {string=} response.pluginName Optional. Name of the plugin to be installed.
+ * @param {string} response.errorCode Error code for the error that occurred.
+ * @param {string} response.errorMessage The error that occurred.
+ */
+ wp.updates.installImporterError = function( response ) {
+ var errorMessage = wp.updates.l10n.installFailed.replace( '%s', response.errorMessage ),
+ $installLink = $( '[data-slug="' + response.slug + '"]' ),
+ pluginName = $installLink.data( 'name' );
+
+ if ( ! wp.updates.isValidResponse( response, 'install' ) ) {
+ return;
+ }
+
+ if ( wp.updates.maybeHandleCredentialError( response, 'install-plugin' ) ) {
+ return;
+ }
+
+ wp.updates.addAdminNotice( {
+ id: response.errorCode,
+ className: 'notice-error is-dismissible',
+ message: errorMessage
+ } );
+
+ $installLink
+ .removeClass( 'updating-message' )
+ .text( wp.updates.l10n.installNow )
+ .attr( 'aria-label', wp.updates.l10n.pluginInstallNowLabel.replace( '%s', pluginName ) );
+
+ wp.a11y.speak( errorMessage, 'assertive' );
+
+ $document.trigger( 'wp-importer-install-error', response );
+ };
+
+ /**
+ * Sends an Ajax request to the server to delete a plugin.
+ *
+ * @since 4.6.0
+ *
+ * @param {object} args Arguments.
+ * @param {string} args.plugin Basename of the plugin to be deleted.
+ * @param {string} args.slug Slug of the plugin to be deleted.
+ * @param {deletePluginSuccess=} args.success Optional. Success callback. Default: wp.updates.deletePluginSuccess
+ * @param {deletePluginError=} args.error Optional. Error callback. Default: wp.updates.deletePluginError
+ * @return {$.promise} A jQuery promise that represents the request,
+ * decorated with an abort() method.
+ */
+ wp.updates.deletePlugin = function( args ) {
+ var $link = $( '[data-plugin="' + args.plugin + '"]' ).find( '.row-actions a.delete' );
+
+ args = _.extend( {
+ success: wp.updates.deletePluginSuccess,
+ error: wp.updates.deletePluginError
+ }, args );
+
+ if ( $link.html() !== wp.updates.l10n.deleting ) {
+ $link
+ .data( 'originaltext', $link.html() )
+ .text( wp.updates.l10n.deleting );
+ }
+
+ wp.a11y.speak( wp.updates.l10n.deleting, 'polite' );
+
+ $document.trigger( 'wp-plugin-deleting', args );
+
+ return wp.updates.ajax( 'delete-plugin', args );
+ };
+
+ /**
+ * Updates the UI appropriately after a successful plugin deletion.
+ *
+ * @since 4.6.0
+ *
+ * @param {Object} response Response from the server.
+ * @param {string} response.slug Slug of the plugin that was deleted.
+ * @param {string} response.plugin Base name of the plugin that was deleted.
+ * @param {string} response.pluginName Name of the plugin that was deleted.
+ */
+ wp.updates.deletePluginSuccess = function( response ) {
+
+ // Removes the plugin and updates rows.
+ $( '[data-plugin="' + response.plugin + '"]' ).css( { backgroundColor: '#faafaa' } ).fadeOut( 350, function() {
+ var $form = $( '#bulk-action-form' ),
+ $views = $( '.subsubsub' ),
+ $pluginRow = $( this ),
+ columnCount = $form.find( 'thead th:not(.hidden), thead td' ).length,
+ pluginDeletedRow = wp.template( 'item-deleted-row' ),
+ /**
+ * Plugins Base names of plugins in their different states.
+ *
+ * @type {Object}
+ */
+ plugins = settings.plugins;
+
+ // Add a success message after deleting a plugin.
+ if ( ! $pluginRow.hasClass( 'plugin-update-tr' ) ) {
+ $pluginRow.after(
+ pluginDeletedRow( {
+ slug: response.slug,
+ plugin: response.plugin,
+ colspan: columnCount,
+ name: response.pluginName
+ } )
+ );
+ }
+
+ $pluginRow.remove();
+
+ // Remove plugin from update count.
+ if ( -1 !== _.indexOf( plugins.upgrade, response.plugin ) ) {
+ plugins.upgrade = _.without( plugins.upgrade, response.plugin );
+ wp.updates.decrementCount( 'plugin' );
+ }
+
+ // Remove from views.
+ if ( -1 !== _.indexOf( plugins.inactive, response.plugin ) ) {
+ plugins.inactive = _.without( plugins.inactive, response.plugin );
+ if ( plugins.inactive.length ) {
+ $views.find( '.inactive .count' ).text( '(' + plugins.inactive.length + ')' );
+ } else {
+ $views.find( '.inactive' ).remove();
+ }
+ }
+
+ if ( -1 !== _.indexOf( plugins.active, response.plugin ) ) {
+ plugins.active = _.without( plugins.active, response.plugin );
+ if ( plugins.active.length ) {
+ $views.find( '.active .count' ).text( '(' + plugins.active.length + ')' );
+ } else {
+ $views.find( '.active' ).remove();
+ }
+ }
+
+ if ( -1 !== _.indexOf( plugins.recently_activated, response.plugin ) ) {
+ plugins.recently_activated = _.without( plugins.recently_activated, response.plugin );
+ if ( plugins.recently_activated.length ) {
+ $views.find( '.recently_activated .count' ).text( '(' + plugins.recently_activated.length + ')' );
+ } else {
+ $views.find( '.recently_activated' ).remove();
+ }
+ }
+
+ plugins.all = _.without( plugins.all, response.plugin );
+
+ if ( plugins.all.length ) {
+ $views.find( '.all .count' ).text( '(' + plugins.all.length + ')' );
+ } else {
+ $form.find( '.tablenav' ).css( { visibility: 'hidden' } );
+ $views.find( '.all' ).remove();
+
+ if ( ! $form.find( 'tr.no-items' ).length ) {
+ $form.find( '#the-list' ).append( '<tr class="no-items"><td class="colspanchange" colspan="' + columnCount + '">' + wp.updates.l10n.noPlugins + '</td></tr>' );
+ }
+ }
+ } );
+
+ wp.a11y.speak( wp.updates.l10n.pluginDeleted, 'polite' );
+
+ $document.trigger( 'wp-plugin-delete-success', response );
+ };
+
+ /**
+ * Updates the UI appropriately after a failed plugin deletion.
+ *
+ * @since 4.6.0
+ *
+ * @param {object} response Response from the server.
+ * @param {string} response.slug Slug of the plugin to be deleted.
+ * @param {string} response.plugin Base name of the plugin to be deleted
+ * @param {string=} response.pluginName Optional. Name of the plugin to be deleted.
+ * @param {string} response.errorCode Error code for the error that occurred.
+ * @param {string} response.errorMessage The error that occurred.
+ */
+ wp.updates.deletePluginError = function( response ) {
+ var $plugin, $pluginUpdateRow,
+ pluginUpdateRow = wp.template( 'item-update-row' ),
+ noticeContent = wp.updates.adminNotice( {
+ className: 'update-message notice-error notice-alt',
+ message: response.errorMessage
+ } );
+
+ if ( response.plugin ) {
+ $plugin = $( 'tr.inactive[data-plugin="' + response.plugin + '"]' );
+ $pluginUpdateRow = $plugin.siblings( '[data-plugin="' + response.plugin + '"]' );
+ } else {
+ $plugin = $( 'tr.inactive[data-slug="' + response.slug + '"]' );
+ $pluginUpdateRow = $plugin.siblings( '[data-slug="' + response.slug + '"]' );
+ }
+
+ if ( ! wp.updates.isValidResponse( response, 'delete' ) ) {
+ return;
+ }
+
+ if ( wp.updates.maybeHandleCredentialError( response, 'delete-plugin' ) ) {
+ return;
+ }
+
+ // Add a plugin update row if it doesn't exist yet.
+ if ( ! $pluginUpdateRow.length ) {
+ $plugin.addClass( 'update' ).after(
+ pluginUpdateRow( {
+ slug: response.slug,
+ plugin: response.plugin || response.slug,
+ colspan: $( '#bulk-action-form' ).find( 'thead th:not(.hidden), thead td' ).length,
+ content: noticeContent
+ } )
+ );
+ } else {
+
+ // Remove previous error messages, if any.
+ $pluginUpdateRow.find( '.notice-error' ).remove();
+
+ $pluginUpdateRow.find( '.plugin-update' ).append( noticeContent );
+ }
+
+ $document.trigger( 'wp-plugin-delete-error', response );
+ };
+
+ /**
+ * Sends an Ajax request to the server to update a theme.
+ *
+ * @since 4.6.0
+ *
+ * @param {object} args Arguments.
+ * @param {string} args.slug Theme stylesheet.
+ * @param {updateThemeSuccess=} args.success Optional. Success callback. Default: wp.updates.updateThemeSuccess
+ * @param {updateThemeError=} args.error Optional. Error callback. Default: wp.updates.updateThemeError
+ * @return {$.promise} A jQuery promise that represents the request,
+ * decorated with an abort() method.
+ */
+ wp.updates.updateTheme = function( args ) {
+ var $notice;
+
+ args = _.extend( {
+ success: wp.updates.updateThemeSuccess,
+ error: wp.updates.updateThemeError
+ }, args );
+
+ if ( 'themes-network' === pagenow ) {
+ $notice = $( '[data-slug="' + args.slug + '"]' ).find( '.update-message' ).removeClass( 'notice-error' ).addClass( 'updating-message notice-warning' ).find( 'p' );
+
+ } else if ( 'customize' === pagenow ) {
+
+ // Update the theme details UI.
+ $notice = $( '[data-slug="' + args.slug + '"].notice' ).removeClass( 'notice-large' );
+
+ $notice.find( 'h3' ).remove();
+
+ // Add the top-level UI, and update both.
+ $notice = $notice.add( $( '#customize-control-installed_theme_' + args.slug ).find( '.update-message' ) );
+ $notice = $notice.addClass( 'updating-message' ).find( 'p' );
+
+ } else {
+ $notice = $( '#update-theme' ).closest( '.notice' ).removeClass( 'notice-large' );
+
+ $notice.find( 'h3' ).remove();
+
+ $notice = $notice.add( $( '[data-slug="' + args.slug + '"]' ).find( '.update-message' ) );
+ $notice = $notice.addClass( 'updating-message' ).find( 'p' );
+ }
+
+ if ( $notice.html() !== wp.updates.l10n.updating ) {
+ $notice.data( 'originaltext', $notice.html() );
+ }
+
+ wp.a11y.speak( wp.updates.l10n.updatingMsg, 'polite' );
+ $notice.text( wp.updates.l10n.updating );
+
+ $document.trigger( 'wp-theme-updating', args );
+
+ return wp.updates.ajax( 'update-theme', args );
+ };
+
+ /**
+ * Updates the UI appropriately after a successful theme update.
+ *
+ * @since 4.6.0
+ *
+ * @param {object} response
+ * @param {string} response.slug Slug of the theme to be updated.
+ * @param {object} response.theme Updated theme.
+ * @param {string} response.oldVersion Old version of the theme.
+ * @param {string} response.newVersion New version of the theme.
+ */
+ wp.updates.updateThemeSuccess = function( response ) {
+ var isModalOpen = $( 'body.modal-open' ).length,
+ $theme = $( '[data-slug="' + response.slug + '"]' ),
+ updatedMessage = {
+ className: 'updated-message notice-success notice-alt',
+ message: wp.updates.l10n.themeUpdated
+ },
+ $notice, newText;
+
+ if ( 'customize' === pagenow ) {
+ $theme = $( '.updating-message' ).siblings( '.theme-name' );
+
+ if ( $theme.length ) {
+
+ // Update the version number in the row.
+ newText = $theme.html().replace( response.oldVersion, response.newVersion );
+ $theme.html( newText );
+ }
+
+ $notice = $( '.theme-info .notice' ).add( wp.customize.control( 'installed_theme_' + response.slug ).container.find( '.theme' ).find( '.update-message' ) );
+ } else if ( 'themes-network' === pagenow ) {
+ $notice = $theme.find( '.update-message' );
+
+ // Update the version number in the row.
+ newText = $theme.find( '.theme-version-author-uri' ).html().replace( response.oldVersion, response.newVersion );
+ $theme.find( '.theme-version-author-uri' ).html( newText );
+ } else {
+ $notice = $( '.theme-info .notice' ).add( $theme.find( '.update-message' ) );
+
+ // Focus on Customize button after updating.
+ if ( isModalOpen ) {
+ $( '.load-customize:visible' ).focus();
+ } else {
+ $theme.find( '.load-customize' ).focus();
+ }
+ }
+
+ wp.updates.addAdminNotice( _.extend( { selector: $notice }, updatedMessage ) );
+ wp.a11y.speak( wp.updates.l10n.updatedMsg, 'polite' );
+
+ wp.updates.decrementCount( 'theme' );
+
+ $document.trigger( 'wp-theme-update-success', response );
+
+ // Show updated message after modal re-rendered.
+ if ( isModalOpen && 'customize' !== pagenow ) {
+ $( '.theme-info .theme-author' ).after( wp.updates.adminNotice( updatedMessage ) );
+ }
+ };
+
+ /**
+ * Updates the UI appropriately after a failed theme update.
+ *
+ * @since 4.6.0
+ *
+ * @param {object} response Response from the server.
+ * @param {string} response.slug Slug of the theme to be updated.
+ * @param {string} response.errorCode Error code for the error that occurred.
+ * @param {string} response.errorMessage The error that occurred.
+ */
+ wp.updates.updateThemeError = function( response ) {
+ var $theme = $( '[data-slug="' + response.slug + '"]' ),
+ errorMessage = wp.updates.l10n.updateFailed.replace( '%s', response.errorMessage ),
+ $notice;
+
+ if ( ! wp.updates.isValidResponse( response, 'update' ) ) {
+ return;
+ }
+
+ if ( wp.updates.maybeHandleCredentialError( response, 'update-theme' ) ) {
+ return;
+ }
+
+ if ( 'customize' === pagenow ) {
+ $theme = wp.customize.control( 'installed_theme_' + response.slug ).container.find( '.theme' );
+ }
+
+ if ( 'themes-network' === pagenow ) {
+ $notice = $theme.find( '.update-message ' );
+ } else {
+ $notice = $( '.theme-info .notice' ).add( $theme.find( '.notice' ) );
+
+ $( 'body.modal-open' ).length ? $( '.load-customize:visible' ).focus() : $theme.find( '.load-customize' ).focus();
+ }
+
+ wp.updates.addAdminNotice( {
+ selector: $notice,
+ className: 'update-message notice-error notice-alt is-dismissible',
+ message: errorMessage
+ } );
+
+ wp.a11y.speak( errorMessage, 'polite' );
+
+ $document.trigger( 'wp-theme-update-error', response );
+ };
+
+ /**
+ * Sends an Ajax request to the server to install a theme.
+ *
+ * @since 4.6.0
+ *
+ * @param {object} args
+ * @param {string} args.slug Theme stylesheet.
+ * @param {installThemeSuccess=} args.success Optional. Success callback. Default: wp.updates.installThemeSuccess
+ * @param {installThemeError=} args.error Optional. Error callback. Default: wp.updates.installThemeError
+ * @return {$.promise} A jQuery promise that represents the request,
+ * decorated with an abort() method.
+ */
+ wp.updates.installTheme = function( args ) {
+ var $message = $( '.theme-install[data-slug="' + args.slug + '"]' );
+
+ args = _.extend( {
+ success: wp.updates.installThemeSuccess,
+ error: wp.updates.installThemeError
+ }, args );
+
+ $message.addClass( 'updating-message' );
+ $message.parents( '.theme' ).addClass( 'focus' );
+ if ( $message.html() !== wp.updates.l10n.installing ) {
+ $message.data( 'originaltext', $message.html() );
+ }
+
+ $message
+ .text( wp.updates.l10n.installing )
+ .attr( 'aria-label', wp.updates.l10n.themeInstallingLabel.replace( '%s', $message.data( 'name' ) ) );
+ wp.a11y.speak( wp.updates.l10n.installingMsg, 'polite' );
+
+ // Remove previous error messages, if any.
+ $( '.install-theme-info, [data-slug="' + args.slug + '"]' ).removeClass( 'theme-install-failed' ).find( '.notice.notice-error' ).remove();
+
+ $document.trigger( 'wp-theme-installing', args );
+
+ return wp.updates.ajax( 'install-theme', args );
+ };
+
+ /**
+ * Updates the UI appropriately after a successful theme install.
+ *
+ * @since 4.6.0
+ *
+ * @param {object} response Response from the server.
+ * @param {string} response.slug Slug of the theme to be installed.
+ * @param {string} response.customizeUrl URL to the Customizer for the just installed theme.
+ * @param {string} response.activateUrl URL to activate the just installed theme.
+ */
+ wp.updates.installThemeSuccess = function( response ) {
+ var $card = $( '.wp-full-overlay-header, [data-slug=' + response.slug + ']' ),
+ $message;
+
+ $document.trigger( 'wp-theme-install-success', response );
+
+ $message = $card.find( '.button-primary' )
+ .removeClass( 'updating-message' )
+ .addClass( 'updated-message disabled' )
+ .attr( 'aria-label', wp.updates.l10n.themeInstalledLabel.replace( '%s', response.themeName ) )
+ .text( wp.updates.l10n.themeInstalled );
+
+ wp.a11y.speak( wp.updates.l10n.installedMsg, 'polite' );
+
+ setTimeout( function() {
+
+ if ( response.activateUrl ) {
+
+ // Transform the 'Install' button into an 'Activate' button.
+ $message
+ .attr( 'href', response.activateUrl )
+ .removeClass( 'theme-install updated-message disabled' )
+ .addClass( 'activate' )
+ .attr( 'aria-label', wp.updates.l10n.activateThemeLabel.replace( '%s', response.themeName ) )
+ .text( wp.updates.l10n.activateTheme );
+ }
+
+ if ( response.customizeUrl ) {
+
+ // Transform the 'Preview' button into a 'Live Preview' button.
+ $message.siblings( '.preview' ).replaceWith( function () {
+ return $( '<a>' )
+ .attr( 'href', response.customizeUrl )
+ .addClass( 'button load-customize' )
+ .text( wp.updates.l10n.livePreview );
+ } );
+ }
+ }, 1000 );
+ };
+
+ /**
+ * Updates the UI appropriately after a failed theme install.
+ *
+ * @since 4.6.0
+ *
+ * @param {object} response Response from the server.
+ * @param {string} response.slug Slug of the theme to be installed.
+ * @param {string} response.errorCode Error code for the error that occurred.
+ * @param {string} response.errorMessage The error that occurred.
+ */
+ wp.updates.installThemeError = function( response ) {
+ var $card, $button,
+ errorMessage = wp.updates.l10n.installFailed.replace( '%s', response.errorMessage ),
+ $message = wp.updates.adminNotice( {
+ className: 'update-message notice-error notice-alt',
+ message: errorMessage
+ } );
+
+ if ( ! wp.updates.isValidResponse( response, 'install' ) ) {
+ return;
+ }
+
+ if ( wp.updates.maybeHandleCredentialError( response, 'install-theme' ) ) {
+ return;
+ }
+
+ if ( 'customize' === pagenow ) {
+ if ( $document.find( 'body' ).hasClass( 'modal-open' ) ) {
+ $button = $( '.theme-install[data-slug="' + response.slug + '"]' );
+ $card = $( '.theme-overlay .theme-info' ).prepend( $message );
+ } else {
+ $button = $( '.theme-install[data-slug="' + response.slug + '"]' );
+ $card = $button.closest( '.theme' ).addClass( 'theme-install-failed' ).append( $message );
+ }
+ wp.customize.notifications.remove( 'theme_installing' );
+ } else {
+ if ( $document.find( 'body' ).hasClass( 'full-overlay-active' ) ) {
+ $button = $( '.theme-install[data-slug="' + response.slug + '"]' );
+ $card = $( '.install-theme-info' ).prepend( $message );
+ } else {
+ $card = $( '[data-slug="' + response.slug + '"]' ).removeClass( 'focus' ).addClass( 'theme-install-failed' ).append( $message );
+ $button = $card.find( '.theme-install' );
+ }
+ }
+
+ $button
+ .removeClass( 'updating-message' )
+ .attr( 'aria-label', wp.updates.l10n.themeInstallFailedLabel.replace( '%s', $button.data( 'name' ) ) )
+ .text( wp.updates.l10n.installFailedShort );
+
+ wp.a11y.speak( errorMessage, 'assertive' );
+
+ $document.trigger( 'wp-theme-install-error', response );
+ };
+
+ /**
+ * Sends an Ajax request to the server to delete a theme.
+ *
+ * @since 4.6.0
+ *
+ * @param {object} args
+ * @param {string} args.slug Theme stylesheet.
+ * @param {deleteThemeSuccess=} args.success Optional. Success callback. Default: wp.updates.deleteThemeSuccess
+ * @param {deleteThemeError=} args.error Optional. Error callback. Default: wp.updates.deleteThemeError
+ * @return {$.promise} A jQuery promise that represents the request,
+ * decorated with an abort() method.
+ */
+ wp.updates.deleteTheme = function( args ) {
+ var $button;
+
+ if ( 'themes' === pagenow ) {
+ $button = $( '.theme-actions .delete-theme' );
+ } else if ( 'themes-network' === pagenow ) {
+ $button = $( '[data-slug="' + args.slug + '"]' ).find( '.row-actions a.delete' );
+ }
+
+ args = _.extend( {
+ success: wp.updates.deleteThemeSuccess,
+ error: wp.updates.deleteThemeError
+ }, args );
+
+ if ( $button && $button.html() !== wp.updates.l10n.deleting ) {
+ $button
+ .data( 'originaltext', $button.html() )
+ .text( wp.updates.l10n.deleting );
+ }
+
+ wp.a11y.speak( wp.updates.l10n.deleting, 'polite' );
+
+ // Remove previous error messages, if any.
+ $( '.theme-info .update-message' ).remove();
+
+ $document.trigger( 'wp-theme-deleting', args );
+
+ return wp.updates.ajax( 'delete-theme', args );
+ };
+
+ /**
+ * Updates the UI appropriately after a successful theme deletion.
+ *
+ * @since 4.6.0
+ *
+ * @param {object} response Response from the server.
+ * @param {string} response.slug Slug of the theme that was deleted.
+ */
+ wp.updates.deleteThemeSuccess = function( response ) {
+ var $themeRows = $( '[data-slug="' + response.slug + '"]' );
+
+ if ( 'themes-network' === pagenow ) {
+
+ // Removes the theme and updates rows.
+ $themeRows.css( { backgroundColor: '#faafaa' } ).fadeOut( 350, function() {
+ var $views = $( '.subsubsub' ),
+ $themeRow = $( this ),
+ totals = settings.themes,
+ deletedRow = wp.template( 'item-deleted-row' );
+
+ if ( ! $themeRow.hasClass( 'plugin-update-tr' ) ) {
+ $themeRow.after(
+ deletedRow( {
+ slug: response.slug,
+ colspan: $( '#bulk-action-form' ).find( 'thead th:not(.hidden), thead td' ).length,
+ name: $themeRow.find( '.theme-title strong' ).text()
+ } )
+ );
+ }
+
+ $themeRow.remove();
+
+ // Remove theme from update count.
+ if ( $themeRow.hasClass( 'update' ) ) {
+ totals.upgrade--;
+ wp.updates.decrementCount( 'theme' );
+ }
+
+ // Remove from views.
+ if ( $themeRow.hasClass( 'inactive' ) ) {
+ totals.disabled--;
+ if ( totals.disabled ) {
+ $views.find( '.disabled .count' ).text( '(' + totals.disabled + ')' );
+ } else {
+ $views.find( '.disabled' ).remove();
+ }
+ }
+
+ // There is always at least one theme available.
+ $views.find( '.all .count' ).text( '(' + --totals.all + ')' );
+ } );
+ }
+
+ wp.a11y.speak( wp.updates.l10n.themeDeleted, 'polite' );
+
+ $document.trigger( 'wp-theme-delete-success', response );
+ };
+
+ /**
+ * Updates the UI appropriately after a failed theme deletion.
+ *
+ * @since 4.6.0
+ *
+ * @param {object} response Response from the server.
+ * @param {string} response.slug Slug of the theme to be deleted.
+ * @param {string} response.errorCode Error code for the error that occurred.
+ * @param {string} response.errorMessage The error that occurred.
+ */
+ wp.updates.deleteThemeError = function( response ) {
+ var $themeRow = $( 'tr.inactive[data-slug="' + response.slug + '"]' ),
+ $button = $( '.theme-actions .delete-theme' ),
+ updateRow = wp.template( 'item-update-row' ),
+ $updateRow = $themeRow.siblings( '#' + response.slug + '-update' ),
+ errorMessage = wp.updates.l10n.deleteFailed.replace( '%s', response.errorMessage ),
+ $message = wp.updates.adminNotice( {
+ className: 'update-message notice-error notice-alt',
+ message: errorMessage
+ } );
+
+ if ( wp.updates.maybeHandleCredentialError( response, 'delete-theme' ) ) {
+ return;
+ }
+
+ if ( 'themes-network' === pagenow ) {
+ if ( ! $updateRow.length ) {
+ $themeRow.addClass( 'update' ).after(
+ updateRow( {
+ slug: response.slug,
+ colspan: $( '#bulk-action-form' ).find( 'thead th:not(.hidden), thead td' ).length,
+ content: $message
+ } )
+ );
+ } else {
+ // Remove previous error messages, if any.
+ $updateRow.find( '.notice-error' ).remove();
+ $updateRow.find( '.plugin-update' ).append( $message );
+ }
+ } else {
+ $( '.theme-info .theme-description' ).before( $message );
+ }
+
+ $button.html( $button.data( 'originaltext' ) );
+
+ wp.a11y.speak( errorMessage, 'assertive' );
+
+ $document.trigger( 'wp-theme-delete-error', response );
+ };
+
+ /**
+ * Adds the appropriate callback based on the type of action and the current page.
+ *
+ * @since 4.6.0
+ * @private
+ *
+ * @param {object} data AJAX payload.
+ * @param {string} action The type of request to perform.
+ * @return {object} The AJAX payload with the appropriate callbacks.
+ */
+ wp.updates._addCallbacks = function( data, action ) {
+ if ( 'import' === pagenow && 'install-plugin' === action ) {
+ data.success = wp.updates.installImporterSuccess;
+ data.error = wp.updates.installImporterError;
+ }
+
+ return data;
+ };
+
+ /**
+ * Pulls available jobs from the queue and runs them.
+ *
+ * @since 4.2.0
+ * @since 4.6.0 Can handle multiple job types.
+ */
+ wp.updates.queueChecker = function() {
+ var job;
+
+ if ( wp.updates.ajaxLocked || ! wp.updates.queue.length ) {
+ return;
+ }
+
+ job = wp.updates.queue.shift();
+
+ // Handle a queue job.
+ switch ( job.action ) {
+ case 'install-plugin':
+ wp.updates.installPlugin( job.data );
+ break;
+
+ case 'update-plugin':
+ wp.updates.updatePlugin( job.data );
+ break;
+
+ case 'delete-plugin':
+ wp.updates.deletePlugin( job.data );
+ break;
+
+ case 'install-theme':
+ wp.updates.installTheme( job.data );
+ break;
+
+ case 'update-theme':
+ wp.updates.updateTheme( job.data );
+ break;
+
+ case 'delete-theme':
+ wp.updates.deleteTheme( job.data );
+ break;
+
+ default:
+ break;
+ }
+ };
+
+ /**
+ * Requests the users filesystem credentials if they aren't already known.
+ *
+ * @since 4.2.0
+ *
+ * @param {Event=} event Optional. Event interface.
+ */
+ wp.updates.requestFilesystemCredentials = function( event ) {
+ if ( false === wp.updates.filesystemCredentials.available ) {
+ /*
+ * After exiting the credentials request modal,
+ * return the focus to the element triggering the request.
+ */
+ if ( event && ! wp.updates.$elToReturnFocusToFromCredentialsModal ) {
+ wp.updates.$elToReturnFocusToFromCredentialsModal = $( event.target );
+ }
+
+ wp.updates.ajaxLocked = true;
+ wp.updates.requestForCredentialsModalOpen();
+ }
+ };
+
+ /**
+ * Requests the users filesystem credentials if needed and there is no lock.
+ *
+ * @since 4.6.0
+ *
+ * @param {Event=} event Optional. Event interface.
+ */
+ wp.updates.maybeRequestFilesystemCredentials = function( event ) {
+ if ( wp.updates.shouldRequestFilesystemCredentials && ! wp.updates.ajaxLocked ) {
+ wp.updates.requestFilesystemCredentials( event );
+ }
+ };
+
+ /**
+ * Keydown handler for the request for credentials modal.
+ *
+ * Closes the modal when the escape key is pressed and
+ * constrains keyboard navigation to inside the modal.
+ *
+ * @since 4.2.0
+ *
+ * @param {Event} event Event interface.
+ */
+ wp.updates.keydown = function( event ) {
+ if ( 27 === event.keyCode ) {
+ wp.updates.requestForCredentialsModalCancel();
+ } else if ( 9 === event.keyCode ) {
+
+ // #upgrade button must always be the last focus-able element in the dialog.
+ if ( 'upgrade' === event.target.id && ! event.shiftKey ) {
+ $( '#hostname' ).focus();
+
+ event.preventDefault();
+ } else if ( 'hostname' === event.target.id && event.shiftKey ) {
+ $( '#upgrade' ).focus();
+
+ event.preventDefault();
+ }
+ }
+ };
+
+ /**
+ * Opens the request for credentials modal.
+ *
+ * @since 4.2.0
+ */
+ wp.updates.requestForCredentialsModalOpen = function() {
+ var $modal = $( '#request-filesystem-credentials-dialog' );
+
+ $( 'body' ).addClass( 'modal-open' );
+ $modal.show();
+ $modal.find( 'input:enabled:first' ).focus();
+ $modal.on( 'keydown', wp.updates.keydown );
+ };
+
+ /**
+ * Closes the request for credentials modal.
+ *
+ * @since 4.2.0
+ */
+ wp.updates.requestForCredentialsModalClose = function() {
+ $( '#request-filesystem-credentials-dialog' ).hide();
+ $( 'body' ).removeClass( 'modal-open' );
+
+ if ( wp.updates.$elToReturnFocusToFromCredentialsModal ) {
+ wp.updates.$elToReturnFocusToFromCredentialsModal.focus();
+ }
+ };
+
+ /**
+ * Takes care of the steps that need to happen when the modal is canceled out.
+ *
+ * @since 4.2.0
+ * @since 4.6.0 Triggers an event for callbacks to listen to and add their actions.
+ */
+ wp.updates.requestForCredentialsModalCancel = function() {
+
+ // Not ajaxLocked and no queue means we already have cleared things up.
+ if ( ! wp.updates.ajaxLocked && ! wp.updates.queue.length ) {
+ return;
+ }
+
+ _.each( wp.updates.queue, function( job ) {
+ $document.trigger( 'credential-modal-cancel', job );
+ } );
+
+ // Remove the lock, and clear the queue.
+ wp.updates.ajaxLocked = false;
+ wp.updates.queue = [];
+
+ wp.updates.requestForCredentialsModalClose();
+ };
+
+ /**
+ * Displays an error message in the request for credentials form.
+ *
+ * @since 4.2.0
+ *
+ * @param {string} message Error message.
+ */
+ wp.updates.showErrorInCredentialsForm = function( message ) {
+ var $filesystemForm = $( '#request-filesystem-credentials-form' );
+
+ // Remove any existing error.
+ $filesystemForm.find( '.notice' ).remove();
+ $filesystemForm.find( '#request-filesystem-credentials-title' ).after( '<div class="notice notice-alt notice-error"><p>' + message + '</p></div>' );
+ };
+
+ /**
+ * Handles credential errors and runs events that need to happen in that case.
+ *
+ * @since 4.2.0
+ *
+ * @param {object} response Ajax response.
+ * @param {string} action The type of request to perform.
+ */
+ wp.updates.credentialError = function( response, action ) {
+
+ // Restore callbacks.
+ response = wp.updates._addCallbacks( response, action );
+
+ wp.updates.queue.unshift( {
+ action: action,
+
+ /*
+ * Not cool that we're depending on response for this data.
+ * This would feel more whole in a view all tied together.
+ */
+ data: response
+ } );
+
+ wp.updates.filesystemCredentials.available = false;
+ wp.updates.showErrorInCredentialsForm( response.errorMessage );
+ wp.updates.requestFilesystemCredentials();
+ };
+
+ /**
+ * Handles credentials errors if it could not connect to the filesystem.
+ *
+ * @since 4.6.0
+ *
+ * @param {object} response Response from the server.
+ * @param {string} response.errorCode Error code for the error that occurred.
+ * @param {string} response.errorMessage The error that occurred.
+ * @param {string} action The type of request to perform.
+ * @returns {boolean} Whether there is an error that needs to be handled or not.
+ */
+ wp.updates.maybeHandleCredentialError = function( response, action ) {
+ if ( wp.updates.shouldRequestFilesystemCredentials && response.errorCode && 'unable_to_connect_to_filesystem' === response.errorCode ) {
+ wp.updates.credentialError( response, action );
+ return true;
+ }
+
+ return false;
+ };
+
+ /**
+ * Validates an AJAX response to ensure it's a proper object.
+ *
+ * If the response deems to be invalid, an admin notice is being displayed.
+ *
+ * @param {(object|string)} response Response from the server.
+ * @param {function=} response.always Optional. Callback for when the Deferred is resolved or rejected.
+ * @param {string=} response.statusText Optional. Status message corresponding to the status code.
+ * @param {string=} response.responseText Optional. Request response as text.
+ * @param {string} action Type of action the response is referring to. Can be 'delete',
+ * 'update' or 'install'.
+ */
+ wp.updates.isValidResponse = function( response, action ) {
+ var error = wp.updates.l10n.unknownError,
+ errorMessage;
+
+ // Make sure the response is a valid data object and not a Promise object.
+ if ( _.isObject( response ) && ! _.isFunction( response.always ) ) {
+ return true;
+ }
+
+ if ( _.isString( response ) && '-1' === response ) {
+ error = wp.updates.l10n.nonceError;
+ } else if ( _.isString( response ) ) {
+ error = response;
+ } else if ( 'undefined' !== typeof response.readyState && 0 === response.readyState ) {
+ error = wp.updates.l10n.connectionError;
+ } else if ( _.isString( response.responseText ) && '' !== response.responseText ) {
+ error = response.responseText;
+ } else if ( _.isString( response.statusText ) ) {
+ error = response.statusText;
+ }
+
+ switch ( action ) {
+ case 'update':
+ errorMessage = wp.updates.l10n.updateFailed;
+ break;
+
+ case 'install':
+ errorMessage = wp.updates.l10n.installFailed;
+ break;
+
+ case 'delete':
+ errorMessage = wp.updates.l10n.deleteFailed;
+ break;
+ }
+
+ // Messages are escaped, remove HTML tags to make them more readable.
+ error = error.replace( /<[\/a-z][^<>]*>/gi, '' );
+ errorMessage = errorMessage.replace( '%s', error );
+
+ // Add admin notice.
+ wp.updates.addAdminNotice( {
+ id: 'unknown_error',
+ className: 'notice-error is-dismissible',
+ message: _.escape( errorMessage )
+ } );
+
+ // Remove the lock, and clear the queue.
+ wp.updates.ajaxLocked = false;
+ wp.updates.queue = [];
+
+ // Change buttons of all running updates.
+ $( '.button.updating-message' )
+ .removeClass( 'updating-message' )
+ .removeAttr( 'aria-label' )
+ .prop( 'disabled', true )
+ .text( wp.updates.l10n.updateFailedShort );
+
+ $( '.updating-message:not(.button):not(.thickbox)' )
+ .removeClass( 'updating-message notice-warning' )
+ .addClass( 'notice-error' )
+ .find( 'p' )
+ .removeAttr( 'aria-label' )
+ .text( errorMessage );
+
+ wp.a11y.speak( errorMessage, 'assertive' );
+
+ return false;
+ };
+
+ /**
+ * Potentially adds an AYS to a user attempting to leave the page.
+ *
+ * If an update is on-going and a user attempts to leave the page,
+ * opens an "Are you sure?" alert.
+ *
+ * @since 4.2.0
+ */
+ wp.updates.beforeunload = function() {
+ if ( wp.updates.ajaxLocked ) {
+ return wp.updates.l10n.beforeunload;
+ }
+ };
+
+ $( function() {
+ var $pluginFilter = $( '#plugin-filter' ),
+ $bulkActionForm = $( '#bulk-action-form' ),
+ $filesystemForm = $( '#request-filesystem-credentials-form' ),
+ $filesystemModal = $( '#request-filesystem-credentials-dialog' ),
+ $pluginSearch = $( '.plugins-php .wp-filter-search' ),
+ $pluginInstallSearch = $( '.plugin-install-php .wp-filter-search' );
+
+ settings = _.extend( settings, window._wpUpdatesItemCounts || {} );
+
+ if ( settings.totals ) {
+ wp.updates.refreshCount();
+ }
+
+ /*
+ * Whether a user needs to submit filesystem credentials.
+ *
+ * This is based on whether the form was output on the page server-side.
+ *
+ * @see {wp_print_request_filesystem_credentials_modal() in PHP}
+ */
+ wp.updates.shouldRequestFilesystemCredentials = $filesystemModal.length > 0;
+
+ /**
+ * File system credentials form submit noop-er / handler.
+ *
+ * @since 4.2.0
+ */
+ $filesystemModal.on( 'submit', 'form', function( event ) {
+ event.preventDefault();
+
+ // Persist the credentials input by the user for the duration of the page load.
+ wp.updates.filesystemCredentials.ftp.hostname = $( '#hostname' ).val();
+ wp.updates.filesystemCredentials.ftp.username = $( '#username' ).val();
+ wp.updates.filesystemCredentials.ftp.password = $( '#password' ).val();
+ wp.updates.filesystemCredentials.ftp.connectionType = $( 'input[name="connection_type"]:checked' ).val();
+ wp.updates.filesystemCredentials.ssh.publicKey = $( '#public_key' ).val();
+ wp.updates.filesystemCredentials.ssh.privateKey = $( '#private_key' ).val();
+ wp.updates.filesystemCredentials.fsNonce = $( '#_fs_nonce' ).val();
+ wp.updates.filesystemCredentials.available = true;
+
+ // Unlock and invoke the queue.
+ wp.updates.ajaxLocked = false;
+ wp.updates.queueChecker();
+
+ wp.updates.requestForCredentialsModalClose();
+ } );
+
+ /**
+ * Closes the request credentials modal when clicking the 'Cancel' button or outside of the modal.
+ *
+ * @since 4.2.0
+ */
+ $filesystemModal.on( 'click', '[data-js-action="close"], .notification-dialog-background', wp.updates.requestForCredentialsModalCancel );
+
+ /**
+ * Hide SSH fields when not selected.
+ *
+ * @since 4.2.0
+ */
+ $filesystemForm.on( 'change', 'input[name="connection_type"]', function() {
+ $( '#ssh-keys' ).toggleClass( 'hidden', ( 'ssh' !== $( this ).val() ) );
+ } ).change();
+
+ /**
+ * Handles events after the credential modal was closed.
+ *
+ * @since 4.6.0
+ *
+ * @param {Event} event Event interface.
+ * @param {string} job The install/update.delete request.
+ */
+ $document.on( 'credential-modal-cancel', function( event, job ) {
+ var $updatingMessage = $( '.updating-message' ),
+ $message, originalText;
+
+ if ( 'import' === pagenow ) {
+ $updatingMessage.removeClass( 'updating-message' );
+ } else if ( 'plugins' === pagenow || 'plugins-network' === pagenow ) {
+ if ( 'update-plugin' === job.action ) {
+ $message = $( 'tr[data-plugin="' + job.data.plugin + '"]' ).find( '.update-message' );
+ } else if ( 'delete-plugin' === job.action ) {
+ $message = $( '[data-plugin="' + job.data.plugin + '"]' ).find( '.row-actions a.delete' );
+ }
+ } else if ( 'themes' === pagenow || 'themes-network' === pagenow ) {
+ if ( 'update-theme' === job.action ) {
+ $message = $( '[data-slug="' + job.data.slug + '"]' ).find( '.update-message' );
+ } else if ( 'delete-theme' === job.action && 'themes-network' === pagenow ) {
+ $message = $( '[data-slug="' + job.data.slug + '"]' ).find( '.row-actions a.delete' );
+ } else if ( 'delete-theme' === job.action && 'themes' === pagenow ) {
+ $message = $( '.theme-actions .delete-theme' );
+ }
+ } else {
+ $message = $updatingMessage;
+ }
+
+ if ( $message && $message.hasClass( 'updating-message' ) ) {
+ originalText = $message.data( 'originaltext' );
+
+ if ( 'undefined' === typeof originalText ) {
+ originalText = $( '<p>' ).html( $message.find( 'p' ).data( 'originaltext' ) );
+ }
+
+ $message
+ .removeClass( 'updating-message' )
+ .html( originalText );
+
+ if ( 'plugin-install' === pagenow || 'plugin-install-network' === pagenow ) {
+ if ( 'update-plugin' === job.action ) {
+ $message.attr( 'aria-label', wp.updates.l10n.pluginUpdateNowLabel.replace( '%s', $message.data( 'name' ) ) );
+ } else if ( 'install-plugin' === job.action ) {
+ $message.attr( 'aria-label', wp.updates.l10n.pluginInstallNowLabel.replace( '%s', $message.data( 'name' ) ) );
+ }
+ }
+ }
+
+ wp.a11y.speak( wp.updates.l10n.updateCancel, 'polite' );
+ } );
+
+ /**
+ * Click handler for plugin updates in List Table view.
+ *
+ * @since 4.2.0
+ *
+ * @param {Event} event Event interface.
+ */
+ $bulkActionForm.on( 'click', '[data-plugin] .update-link', function( event ) {
+ var $message = $( event.target ),
+ $pluginRow = $message.parents( 'tr' );
+
+ event.preventDefault();
+
+ if ( $message.hasClass( 'updating-message' ) || $message.hasClass( 'button-disabled' ) ) {
+ return;
+ }
+
+ wp.updates.maybeRequestFilesystemCredentials( event );
+
+ // Return the user to the input box of the plugin's table row after closing the modal.
+ wp.updates.$elToReturnFocusToFromCredentialsModal = $pluginRow.find( '.check-column input' );
+ wp.updates.updatePlugin( {
+ plugin: $pluginRow.data( 'plugin' ),
+ slug: $pluginRow.data( 'slug' )
+ } );
+ } );
+
+ /**
+ * Click handler for plugin updates in plugin install view.
+ *
+ * @since 4.2.0
+ *
+ * @param {Event} event Event interface.
+ */
+ $pluginFilter.on( 'click', '.update-now', function( event ) {
+ var $button = $( event.target );
+ event.preventDefault();
+
+ if ( $button.hasClass( 'updating-message' ) || $button.hasClass( 'button-disabled' ) ) {
+ return;
+ }
+
+ wp.updates.maybeRequestFilesystemCredentials( event );
+
+ wp.updates.updatePlugin( {
+ plugin: $button.data( 'plugin' ),
+ slug: $button.data( 'slug' )
+ } );
+ } );
+
+ /**
+ * Click handler for plugin installs in plugin install view.
+ *
+ * @since 4.6.0
+ *
+ * @param {Event} event Event interface.
+ */
+ $pluginFilter.on( 'click', '.install-now', function( event ) {
+ var $button = $( event.target );
+ event.preventDefault();
+
+ if ( $button.hasClass( 'updating-message' ) || $button.hasClass( 'button-disabled' ) ) {
+ return;
+ }
+
+ if ( wp.updates.shouldRequestFilesystemCredentials && ! wp.updates.ajaxLocked ) {
+ wp.updates.requestFilesystemCredentials( event );
+
+ $document.on( 'credential-modal-cancel', function() {
+ var $message = $( '.install-now.updating-message' );
+
+ $message
+ .removeClass( 'updating-message' )
+ .text( wp.updates.l10n.installNow );
+
+ wp.a11y.speak( wp.updates.l10n.updateCancel, 'polite' );
+ } );
+ }
+
+ wp.updates.installPlugin( {
+ slug: $button.data( 'slug' )
+ } );
+ } );
+
+ /**
+ * Click handler for importer plugins installs in the Import screen.
+ *
+ * @since 4.6.0
+ *
+ * @param {Event} event Event interface.
+ */
+ $document.on( 'click', '.importer-item .install-now', function( event ) {
+ var $button = $( event.target ),
+ pluginName = $( this ).data( 'name' );
+
+ event.preventDefault();
+
+ if ( $button.hasClass( 'updating-message' ) ) {
+ return;
+ }
+
+ if ( wp.updates.shouldRequestFilesystemCredentials && ! wp.updates.ajaxLocked ) {
+ wp.updates.requestFilesystemCredentials( event );
+
+ $document.on( 'credential-modal-cancel', function() {
+
+ $button
+ .removeClass( 'updating-message' )
+ .text( wp.updates.l10n.installNow )
+ .attr( 'aria-label', wp.updates.l10n.pluginInstallNowLabel.replace( '%s', pluginName ) );
+
+ wp.a11y.speak( wp.updates.l10n.updateCancel, 'polite' );
+ } );
+ }
+
+ wp.updates.installPlugin( {
+ slug: $button.data( 'slug' ),
+ pagenow: pagenow,
+ success: wp.updates.installImporterSuccess,
+ error: wp.updates.installImporterError
+ } );
+ } );
+
+ /**
+ * Click handler for plugin deletions.
+ *
+ * @since 4.6.0
+ *
+ * @param {Event} event Event interface.
+ */
+ $bulkActionForm.on( 'click', '[data-plugin] a.delete', function( event ) {
+ var $pluginRow = $( event.target ).parents( 'tr' );
+
+ event.preventDefault();
+
+ if ( ! window.confirm( wp.updates.l10n.aysDeleteUninstall.replace( '%s', $pluginRow.find( '.plugin-title strong' ).text() ) ) ) {
+ return;
+ }
+
+ wp.updates.maybeRequestFilesystemCredentials( event );
+
+ wp.updates.deletePlugin( {
+ plugin: $pluginRow.data( 'plugin' ),
+ slug: $pluginRow.data( 'slug' )
+ } );
+
+ } );
+
+ /**
+ * Click handler for theme updates.
+ *
+ * @since 4.6.0
+ *
+ * @param {Event} event Event interface.
+ */
+ $document.on( 'click', '.themes-php.network-admin .update-link', function( event ) {
+ var $message = $( event.target ),
+ $themeRow = $message.parents( 'tr' );
+
+ event.preventDefault();
+
+ if ( $message.hasClass( 'updating-message' ) || $message.hasClass( 'button-disabled' ) ) {
+ return;
+ }
+
+ wp.updates.maybeRequestFilesystemCredentials( event );
+
+ // Return the user to the input box of the theme's table row after closing the modal.
+ wp.updates.$elToReturnFocusToFromCredentialsModal = $themeRow.find( '.check-column input' );
+ wp.updates.updateTheme( {
+ slug: $themeRow.data( 'slug' )
+ } );
+ } );
+
+ /**
+ * Click handler for theme deletions.
+ *
+ * @since 4.6.0
+ *
+ * @param {Event} event Event interface.
+ */
+ $document.on( 'click', '.themes-php.network-admin a.delete', function( event ) {
+ var $themeRow = $( event.target ).parents( 'tr' );
+
+ event.preventDefault();
+
+ if ( ! window.confirm( wp.updates.l10n.aysDelete.replace( '%s', $themeRow.find( '.theme-title strong' ).text() ) ) ) {
+ return;
+ }
+
+ wp.updates.maybeRequestFilesystemCredentials( event );
+
+ wp.updates.deleteTheme( {
+ slug: $themeRow.data( 'slug' )
+ } );
+ } );
+
+ /**
+ * Bulk action handler for plugins and themes.
+ *
+ * Handles both deletions and updates.
+ *
+ * @since 4.6.0
+ *
+ * @param {Event} event Event interface.
+ */
+ $bulkActionForm.on( 'click', '[type="submit"]:not([name="clear-recent-list"])', function( event ) {
+ var bulkAction = $( event.target ).siblings( 'select' ).val(),
+ itemsSelected = $bulkActionForm.find( 'input[name="checked[]"]:checked' ),
+ success = 0,
+ error = 0,
+ errorMessages = [],
+ type, action;
+
+ // Determine which type of item we're dealing with.
+ switch ( pagenow ) {
+ case 'plugins':
+ case 'plugins-network':
+ type = 'plugin';
+ break;
+
+ case 'themes-network':
+ type = 'theme';
+ break;
+
+ default:
+ return;
+ }
+
+ // Bail if there were no items selected.
+ if ( ! itemsSelected.length ) {
+ event.preventDefault();
+ $( 'html, body' ).animate( { scrollTop: 0 } );
+
+ return wp.updates.addAdminNotice( {
+ id: 'no-items-selected',
+ className: 'notice-error is-dismissible',
+ message: wp.updates.l10n.noItemsSelected
+ } );
+ }
+
+ // Determine the type of request we're dealing with.
+ switch ( bulkAction ) {
+ case 'update-selected':
+ action = bulkAction.replace( 'selected', type );
+ break;
+
+ case 'delete-selected':
+ if ( ! window.confirm( 'plugin' === type ? wp.updates.l10n.aysBulkDelete : wp.updates.l10n.aysBulkDeleteThemes ) ) {
+ event.preventDefault();
+ return;
+ }
+
+ action = bulkAction.replace( 'selected', type );
+ break;
+
+ default:
+ return;
+ }
+
+ wp.updates.maybeRequestFilesystemCredentials( event );
+
+ event.preventDefault();
+
+ // Un-check the bulk checkboxes.
+ $bulkActionForm.find( '.manage-column [type="checkbox"]' ).prop( 'checked', false );
+
+ $document.trigger( 'wp-' + type + '-bulk-' + bulkAction, itemsSelected );
+
+ // Find all the checkboxes which have been checked.
+ itemsSelected.each( function( index, element ) {
+ var $checkbox = $( element ),
+ $itemRow = $checkbox.parents( 'tr' );
+
+ // Only add update-able items to the update queue.
+ if ( 'update-selected' === bulkAction && ( ! $itemRow.hasClass( 'update' ) || $itemRow.find( 'notice-error' ).length ) ) {
+
+ // Un-check the box.
+ $checkbox.prop( 'checked', false );
+ return;
+ }
+
+ // Add it to the queue.
+ wp.updates.queue.push( {
+ action: action,
+ data: {
+ plugin: $itemRow.data( 'plugin' ),
+ slug: $itemRow.data( 'slug' )
+ }
+ } );
+ } );
+
+ // Display bulk notification for updates of any kind.
+ $document.on( 'wp-plugin-update-success wp-plugin-update-error wp-theme-update-success wp-theme-update-error', function( event, response ) {
+ var $itemRow = $( '[data-slug="' + response.slug + '"]' ),
+ $bulkActionNotice, itemName;
+
+ if ( 'wp-' + response.update + '-update-success' === event.type ) {
+ success++;
+ } else {
+ itemName = response.pluginName ? response.pluginName : $itemRow.find( '.column-primary strong' ).text();
+
+ error++;
+ errorMessages.push( itemName + ': ' + response.errorMessage );
+ }
+
+ $itemRow.find( 'input[name="checked[]"]:checked' ).prop( 'checked', false );
+
+ wp.updates.adminNotice = wp.template( 'wp-bulk-updates-admin-notice' );
+
+ wp.updates.addAdminNotice( {
+ id: 'bulk-action-notice',
+ className: 'bulk-action-notice',
+ successes: success,
+ errors: error,
+ errorMessages: errorMessages,
+ type: response.update
+ } );
+
+ $bulkActionNotice = $( '#bulk-action-notice' ).on( 'click', 'button', function() {
+ // $( this ) is the clicked button, no need to get it again.
+ $( this )
+ .toggleClass( 'bulk-action-errors-collapsed' )
+ .attr( 'aria-expanded', ! $( this ).hasClass( 'bulk-action-errors-collapsed' ) );
+ // Show the errors list.
+ $bulkActionNotice.find( '.bulk-action-errors' ).toggleClass( 'hidden' );
+ } );
+
+ if ( error > 0 && ! wp.updates.queue.length ) {
+ $( 'html, body' ).animate( { scrollTop: 0 } );
+ }
+ } );
+
+ // Reset admin notice template after #bulk-action-notice was added.
+ $document.on( 'wp-updates-notice-added', function() {
+ wp.updates.adminNotice = wp.template( 'wp-updates-admin-notice' );
+ } );
+
+ // Check the queue, now that the event handlers have been added.
+ wp.updates.queueChecker();
+ } );
+
+ if ( $pluginInstallSearch.length ) {
+ $pluginInstallSearch.attr( 'aria-describedby', 'live-search-desc' );
+ }
+
+ /**
+ * Handles changes to the plugin search box on the new-plugin page,
+ * searching the repository dynamically.
+ *
+ * @since 4.6.0
+ */
+ $pluginInstallSearch.on( 'keyup input', _.debounce( function( event, eventtype ) {
+ var $searchTab = $( '.plugin-install-search' ), data, searchLocation;
+
+ data = {
+ _ajax_nonce: wp.updates.ajaxNonce,
+ s: event.target.value,
+ tab: 'search',
+ type: $( '#typeselector' ).val(),
+ pagenow: pagenow
+ };
+ searchLocation = location.href.split( '?' )[ 0 ] + '?' + $.param( _.omit( data, [ '_ajax_nonce', 'pagenow' ] ) );
+
+ // Clear on escape.
+ if ( 'keyup' === event.type && 27 === event.which ) {
+ event.target.value = '';
+ }
+
+ if ( wp.updates.searchTerm === data.s && 'typechange' !== eventtype ) {
+ return;
+ } else {
+ $pluginFilter.empty();
+ wp.updates.searchTerm = data.s;
+ }
+
+ if ( window.history && window.history.replaceState ) {
+ window.history.replaceState( null, '', searchLocation );
+ }
+
+ if ( ! $searchTab.length ) {
+ $searchTab = $( '<li class="plugin-install-search" />' )
+ .append( $( '<a />', {
+ 'class': 'current',
+ 'href': searchLocation,
+ 'text': wp.updates.l10n.searchResultsLabel
+ } ) );
+
+ $( '.wp-filter .filter-links .current' )
+ .removeClass( 'current' )
+ .parents( '.filter-links' )
+ .prepend( $searchTab );
+
+ $pluginFilter.prev( 'p' ).remove();
+ $( '.plugins-popular-tags-wrapper' ).remove();
+ }
+
+ if ( 'undefined' !== typeof wp.updates.searchRequest ) {
+ wp.updates.searchRequest.abort();
+ }
+ $( 'body' ).addClass( 'loading-content' );
+
+ wp.updates.searchRequest = wp.ajax.post( 'search-install-plugins', data ).done( function( response ) {
+ $( 'body' ).removeClass( 'loading-content' );
+ $pluginFilter.append( response.items );
+ delete wp.updates.searchRequest;
+
+ if ( 0 === response.count ) {
+ wp.a11y.speak( wp.updates.l10n.noPluginsFound );
+ } else {
+ wp.a11y.speak( wp.updates.l10n.pluginsFound.replace( '%d', response.count ) );
+ }
+ } );
+ }, 500 ) );
+
+ if ( $pluginSearch.length ) {
+ $pluginSearch.attr( 'aria-describedby', 'live-search-desc' );
+ }
+
+ /**
+ * Handles changes to the plugin search box on the Installed Plugins screen,
+ * searching the plugin list dynamically.
+ *
+ * @since 4.6.0
+ */
+ $pluginSearch.on( 'keyup input', _.debounce( function( event ) {
+ var data = {
+ _ajax_nonce: wp.updates.ajaxNonce,
+ s: event.target.value,
+ pagenow: pagenow,
+ plugin_status: 'all'
+ },
+ queryArgs;
+
+ // Clear on escape.
+ if ( 'keyup' === event.type && 27 === event.which ) {
+ event.target.value = '';
+ }
+
+ if ( wp.updates.searchTerm === data.s ) {
+ return;
+ } else {
+ wp.updates.searchTerm = data.s;
+ }
+
+ queryArgs = _.object( _.compact( _.map( location.search.slice( 1 ).split( '&' ), function( item ) {
+ if ( item ) return item.split( '=' );
+ } ) ) );
+
+ data.plugin_status = queryArgs.plugin_status || 'all';
+
+ if ( window.history && window.history.replaceState ) {
+ window.history.replaceState( null, '', location.href.split( '?' )[ 0 ] + '?s=' + data.s + '&plugin_status=' + data.plugin_status );
+ }
+
+ if ( 'undefined' !== typeof wp.updates.searchRequest ) {
+ wp.updates.searchRequest.abort();
+ }
+
+ $bulkActionForm.empty();
+ $( 'body' ).addClass( 'loading-content' );
+ $( '.subsubsub .current' ).removeClass( 'current' );
+
+ wp.updates.searchRequest = wp.ajax.post( 'search-plugins', data ).done( function( response ) {
+
+ // Can we just ditch this whole subtitle business?
+ var $subTitle = $( '<span />' ).addClass( 'subtitle' ).html( wp.updates.l10n.searchResults.replace( '%s', _.escape( data.s ) ) ),
+ $oldSubTitle = $( '.wrap .subtitle' );
+
+ if ( ! data.s.length ) {
+ $oldSubTitle.remove();
+ $( '.subsubsub .' + data.plugin_status + ' a' ).addClass( 'current' );
+ } else if ( $oldSubTitle.length ) {
+ $oldSubTitle.replaceWith( $subTitle );
+ } else {
+ $( '.wp-header-end' ).before( $subTitle );
+ }
+
+ $( 'body' ).removeClass( 'loading-content' );
+ $bulkActionForm.append( response.items );
+ delete wp.updates.searchRequest;
+
+ if ( 0 === response.count ) {
+ wp.a11y.speak( wp.updates.l10n.noPluginsFound );
+ } else {
+ wp.a11y.speak( wp.updates.l10n.pluginsFound.replace( '%d', response.count ) );
+ }
+ } );
+ }, 500 ) );
+
+ /**
+ * Trigger a search event when the search form gets submitted.
+ *
+ * @since 4.6.0
+ */
+ $document.on( 'submit', '.search-plugins', function( event ) {
+ event.preventDefault();
+
+ $( 'input.wp-filter-search' ).trigger( 'input' );
+ } );
+
+ /**
+ * Trigger a search event when the "Try Again" button is clicked.
+ *
+ * @since 4.9.0
+ */
+ $document.on( 'click', '.try-again', function( event ) {
+ event.preventDefault();
+ $pluginInstallSearch.trigger( 'input' );
+ } );
+
+ /**
+ * Trigger a search event when the search type gets changed.
+ *
+ * @since 4.6.0
+ */
+ $( '#typeselector' ).on( 'change', function() {
+ var $search = $( 'input[name="s"]' );
+
+ if ( $search.val().length ) {
+ $search.trigger( 'input', 'typechange' );
+ }
+ } );
+
+ /**
+ * Click handler for updating a plugin from the details modal on `plugin-install.php`.
+ *
+ * @since 4.2.0
+ *
+ * @param {Event} event Event interface.
+ */
+ $( '#plugin_update_from_iframe' ).on( 'click', function( event ) {
+ var target = window.parent === window ? null : window.parent,
+ update;
+
+ $.support.postMessage = !! window.postMessage;
+
+ if ( false === $.support.postMessage || null === target || -1 !== window.parent.location.pathname.indexOf( 'update-core.php' ) ) {
+ return;
+ }
+
+ event.preventDefault();
+
+ update = {
+ action: 'update-plugin',
+ data: {
+ plugin: $( this ).data( 'plugin' ),
+ slug: $( this ).data( 'slug' )
+ }
+ };
+
+ target.postMessage( JSON.stringify( update ), window.location.origin );
+ } );
+
+ /**
+ * Click handler for installing a plugin from the details modal on `plugin-install.php`.
+ *
+ * @since 4.6.0
+ *
+ * @param {Event} event Event interface.
+ */
+ $( '#plugin_install_from_iframe' ).on( 'click', function( event ) {
+ var target = window.parent === window ? null : window.parent,
+ install;
+
+ $.support.postMessage = !! window.postMessage;
+
+ if ( false === $.support.postMessage || null === target || -1 !== window.parent.location.pathname.indexOf( 'index.php' ) ) {
+ return;
+ }
+
+ event.preventDefault();
+
+ install = {
+ action: 'install-plugin',
+ data: {
+ slug: $( this ).data( 'slug' )
+ }
+ };
+
+ target.postMessage( JSON.stringify( install ), window.location.origin );
+ } );
+
+ /**
+ * Handles postMessage events.
+ *
+ * @since 4.2.0
+ * @since 4.6.0 Switched `update-plugin` action to use the queue.
+ *
+ * @param {Event} event Event interface.
+ */
+ $( window ).on( 'message', function( event ) {
+ var originalEvent = event.originalEvent,
+ expectedOrigin = document.location.protocol + '//' + document.location.hostname,
+ message;
+
+ if ( originalEvent.origin !== expectedOrigin ) {
+ return;
+ }
+
+ try {
+ message = $.parseJSON( originalEvent.data );
+ } catch ( e ) {
+ return;
+ }
+
+ if ( ! message || 'undefined' === typeof message.action ) {
+ return;
+ }
+
+ switch ( message.action ) {
+
+ // Called from `wp-admin/includes/class-wp-upgrader-skins.php`.
+ case 'decrementUpdateCount':
+ /** @property {string} message.upgradeType */
+ wp.updates.decrementCount( message.upgradeType );
+ break;
+
+ case 'install-plugin':
+ case 'update-plugin':
+ /* jscs:disable requireCamelCaseOrUpperCaseIdentifiers */
+ window.tb_remove();
+ /* jscs:enable */
+
+ message.data = wp.updates._addCallbacks( message.data, message.action );
+
+ wp.updates.queue.push( message );
+ wp.updates.queueChecker();
+ break;
+ }
+ } );
+
+ /**
+ * Adds a callback to display a warning before leaving the page.
+ *
+ * @since 4.2.0
+ */
+ $( window ).on( 'beforeunload', wp.updates.beforeunload );
+ } );
+})( jQuery, window.wp, window._wpUpdatesSettings );
diff --git a/www/crm/wp-admin/js/updates.min.js b/www/crm/wp-admin/js/updates.min.js
new file mode 100644
index 00000000..a6bb6e6b
--- /dev/null
+++ b/www/crm/wp-admin/js/updates.min.js
@@ -0,0 +1,2 @@
+!function(a,b,c){var d=a(document);b=b||{},b.updates={},b.updates.ajaxNonce=c.ajax_nonce,b.updates.l10n=c.l10n,b.updates.searchTerm="",b.updates.shouldRequestFilesystemCredentials=!1,b.updates.filesystemCredentials={ftp:{host:"",username:"",password:"",connectionType:""},ssh:{publicKey:"",privateKey:""},fsNonce:"",available:!1},b.updates.ajaxLocked=!1,b.updates.adminNotice=b.template("wp-updates-admin-notice"),b.updates.queue=[],b.updates.$elToReturnFocusToFromCredentialsModal=void 0,b.updates.addAdminNotice=function(c){var e,f=a(c.selector),g=a(".wp-header-end");delete c.selector,e=b.updates.adminNotice(c),f.length||(f=a("#"+c.id)),f.length?f.replaceWith(e):g.length?g.after(e):"customize"===pagenow?a(".customize-themes-notifications").append(e):a(".wrap").find("> h1").after(e),d.trigger("wp-updates-notice-added")},b.updates.ajax=function(c,d){var e={};return b.updates.ajaxLocked?(b.updates.queue.push({action:c,data:d}),a.Deferred()):(b.updates.ajaxLocked=!0,d.success&&(e.success=d.success,delete d.success),d.error&&(e.error=d.error,delete d.error),e.data=_.extend(d,{action:c,_ajax_nonce:b.updates.ajaxNonce,_fs_nonce:b.updates.filesystemCredentials.fsNonce,username:b.updates.filesystemCredentials.ftp.username,password:b.updates.filesystemCredentials.ftp.password,hostname:b.updates.filesystemCredentials.ftp.hostname,connection_type:b.updates.filesystemCredentials.ftp.connectionType,public_key:b.updates.filesystemCredentials.ssh.publicKey,private_key:b.updates.filesystemCredentials.ssh.privateKey}),b.ajax.send(e).always(b.updates.ajaxAlways))},b.updates.ajaxAlways=function(a){a.errorCode&&"unable_to_connect_to_filesystem"===a.errorCode||(b.updates.ajaxLocked=!1,b.updates.queueChecker()),"undefined"!=typeof a.debug&&window.console&&window.console.log&&_.map(a.debug,function(a){window.console.log(b.sanitize.stripTagsAndEncodeText(a))})},b.updates.refreshCount=function(){var b,d=a("#wp-admin-bar-updates"),e=a('a[href="update-core.php"] .update-plugins'),f=a('a[href="plugins.php"] .update-plugins'),g=a('a[href="themes.php"] .update-plugins');d.find(".ab-item").removeAttr("title"),d.find(".ab-label").text(c.totals.counts.total),0===c.totals.counts.total&&d.find(".ab-label").parents("li").remove(),e.each(function(a,b){b.className=b.className.replace(/count-\d+/,"count-"+c.totals.counts.total)}),c.totals.counts.total>0?e.find(".update-count").text(c.totals.counts.total):e.remove(),f.each(function(a,b){b.className=b.className.replace(/count-\d+/,"count-"+c.totals.counts.plugins)}),c.totals.counts.total>0?f.find(".plugin-count").text(c.totals.counts.plugins):f.remove(),g.each(function(a,b){b.className=b.className.replace(/count-\d+/,"count-"+c.totals.counts.themes)}),c.totals.counts.total>0?g.find(".theme-count").text(c.totals.counts.themes):g.remove(),"plugins"===pagenow||"plugins-network"===pagenow?b=c.totals.counts.plugins:"themes"!==pagenow&&"themes-network"!==pagenow||(b=c.totals.counts.themes),b>0?a(".subsubsub .upgrade .count").text("("+b+")"):(a(".subsubsub .upgrade").remove(),a(".subsubsub li:last").html(function(){return a(this).children()}))},b.updates.decrementCount=function(a){c.totals.counts.total=Math.max(--c.totals.counts.total,0),"plugin"===a?c.totals.counts.plugins=Math.max(--c.totals.counts.plugins,0):"theme"===a&&(c.totals.counts.themes=Math.max(--c.totals.counts.themes,0)),b.updates.refreshCount(a)},b.updates.updatePlugin=function(c){var e,f,g,h;return c=_.extend({success:b.updates.updatePluginSuccess,error:b.updates.updatePluginError},c),"plugins"===pagenow||"plugins-network"===pagenow?(e=a('tr[data-plugin="'+c.plugin+'"]'),g=e.find(".update-message").removeClass("notice-error").addClass("updating-message notice-warning").find("p"),h=b.updates.l10n.pluginUpdatingLabel.replace("%s",e.find(".plugin-title strong").text())):"plugin-install"!==pagenow&&"plugin-install-network"!==pagenow||(f=a(".plugin-card-"+c.slug),g=f.find(".update-now").addClass("updating-message"),h=b.updates.l10n.pluginUpdatingLabel.replace("%s",g.data("name")),f.removeClass("plugin-card-update-failed").find(".notice.notice-error").remove()),g.html()!==b.updates.l10n.updating&&g.data("originaltext",g.html()),g.attr("aria-label",h).text(b.updates.l10n.updating),d.trigger("wp-plugin-updating",c),b.updates.ajax("update-plugin",c)},b.updates.updatePluginSuccess=function(c){var e,f,g;"plugins"===pagenow||"plugins-network"===pagenow?(e=a('tr[data-plugin="'+c.plugin+'"]').removeClass("update").addClass("updated"),f=e.find(".update-message").removeClass("updating-message notice-warning").addClass("updated-message notice-success").find("p"),g=e.find(".plugin-version-author-uri").html().replace(c.oldVersion,c.newVersion),e.find(".plugin-version-author-uri").html(g)):"plugin-install"!==pagenow&&"plugin-install-network"!==pagenow||(f=a(".plugin-card-"+c.slug).find(".update-now").removeClass("updating-message").addClass("button-disabled updated-message")),f.attr("aria-label",b.updates.l10n.pluginUpdatedLabel.replace("%s",c.pluginName)).text(b.updates.l10n.pluginUpdated),b.a11y.speak(b.updates.l10n.updatedMsg,"polite"),b.updates.decrementCount("plugin"),d.trigger("wp-plugin-update-success",c)},b.updates.updatePluginError=function(c){var e,f,g;b.updates.isValidResponse(c,"update")&&(b.updates.maybeHandleCredentialError(c,"update-plugin")||(g=b.updates.l10n.updateFailed.replace("%s",c.errorMessage),"plugins"===pagenow||"plugins-network"===pagenow?(f=c.plugin?a('tr[data-plugin="'+c.plugin+'"]').find(".update-message"):a('tr[data-slug="'+c.slug+'"]').find(".update-message"),f.removeClass("updating-message notice-warning").addClass("notice-error").find("p").html(g),c.pluginName?f.find("p").attr("aria-label",b.updates.l10n.pluginUpdateFailedLabel.replace("%s",c.pluginName)):f.find("p").removeAttr("aria-label")):"plugin-install"!==pagenow&&"plugin-install-network"!==pagenow||(e=a(".plugin-card-"+c.slug).addClass("plugin-card-update-failed").append(b.updates.adminNotice({className:"update-message notice-error notice-alt is-dismissible",message:g})),e.find(".update-now").text(b.updates.l10n.updateFailedShort).removeClass("updating-message"),c.pluginName?e.find(".update-now").attr("aria-label",b.updates.l10n.pluginUpdateFailedLabel.replace("%s",c.pluginName)):e.find(".update-now").removeAttr("aria-label"),e.on("click",".notice.is-dismissible .notice-dismiss",function(){setTimeout(function(){e.removeClass("plugin-card-update-failed").find(".column-name a").focus(),e.find(".update-now").attr("aria-label",!1).text(b.updates.l10n.updateNow)},200)})),b.a11y.speak(g,"assertive"),d.trigger("wp-plugin-update-error",c)))},b.updates.installPlugin=function(c){var e=a(".plugin-card-"+c.slug),f=e.find(".install-now");return c=_.extend({success:b.updates.installPluginSuccess,error:b.updates.installPluginError},c),"import"===pagenow&&(f=a('[data-slug="'+c.slug+'"]')),f.html()!==b.updates.l10n.installing&&f.data("originaltext",f.html()),f.addClass("updating-message").attr("aria-label",b.updates.l10n.pluginInstallingLabel.replace("%s",f.data("name"))).text(b.updates.l10n.installing),b.a11y.speak(b.updates.l10n.installingMsg,"polite"),e.removeClass("plugin-card-install-failed").find(".notice.notice-error").remove(),d.trigger("wp-plugin-installing",c),b.updates.ajax("install-plugin",c)},b.updates.installPluginSuccess=function(c){var e=a(".plugin-card-"+c.slug).find(".install-now");e.removeClass("updating-message").addClass("updated-message installed button-disabled").attr("aria-label",b.updates.l10n.pluginInstalledLabel.replace("%s",c.pluginName)).text(b.updates.l10n.pluginInstalled),b.a11y.speak(b.updates.l10n.installedMsg,"polite"),d.trigger("wp-plugin-install-success",c),c.activateUrl&&setTimeout(function(){e.removeClass("install-now installed button-disabled updated-message").addClass("activate-now button-primary").attr("href",c.activateUrl).attr("aria-label",b.updates.l10n.activatePluginLabel.replace("%s",c.pluginName)).text(b.updates.l10n.activatePlugin)},1e3)},b.updates.installPluginError=function(c){var e,f=a(".plugin-card-"+c.slug),g=f.find(".install-now");b.updates.isValidResponse(c,"install")&&(b.updates.maybeHandleCredentialError(c,"install-plugin")||(e=b.updates.l10n.installFailed.replace("%s",c.errorMessage),f.addClass("plugin-card-update-failed").append('<div class="notice notice-error notice-alt is-dismissible"><p>'+e+"</p></div>"),f.on("click",".notice.is-dismissible .notice-dismiss",function(){setTimeout(function(){f.removeClass("plugin-card-update-failed").find(".column-name a").focus()},200)}),g.removeClass("updating-message").addClass("button-disabled").attr("aria-label",b.updates.l10n.pluginInstallFailedLabel.replace("%s",g.data("name"))).text(b.updates.l10n.installFailedShort),b.a11y.speak(e,"assertive"),d.trigger("wp-plugin-install-error",c)))},b.updates.installImporterSuccess=function(c){b.updates.addAdminNotice({id:"install-success",className:"notice-success is-dismissible",message:b.updates.l10n.importerInstalledMsg.replace("%s",c.activateUrl+"&from=import")}),a('[data-slug="'+c.slug+'"]').removeClass("install-now updating-message").addClass("activate-now").attr({href:c.activateUrl+"&from=import","aria-label":b.updates.l10n.activateImporterLabel.replace("%s",c.pluginName)}).text(b.updates.l10n.activateImporter),b.a11y.speak(b.updates.l10n.installedMsg,"polite"),d.trigger("wp-importer-install-success",c)},b.updates.installImporterError=function(c){var e=b.updates.l10n.installFailed.replace("%s",c.errorMessage),f=a('[data-slug="'+c.slug+'"]'),g=f.data("name");b.updates.isValidResponse(c,"install")&&(b.updates.maybeHandleCredentialError(c,"install-plugin")||(b.updates.addAdminNotice({id:c.errorCode,className:"notice-error is-dismissible",message:e}),f.removeClass("updating-message").text(b.updates.l10n.installNow).attr("aria-label",b.updates.l10n.pluginInstallNowLabel.replace("%s",g)),b.a11y.speak(e,"assertive"),d.trigger("wp-importer-install-error",c)))},b.updates.deletePlugin=function(c){var e=a('[data-plugin="'+c.plugin+'"]').find(".row-actions a.delete");return c=_.extend({success:b.updates.deletePluginSuccess,error:b.updates.deletePluginError},c),e.html()!==b.updates.l10n.deleting&&e.data("originaltext",e.html()).text(b.updates.l10n.deleting),b.a11y.speak(b.updates.l10n.deleting,"polite"),d.trigger("wp-plugin-deleting",c),b.updates.ajax("delete-plugin",c)},b.updates.deletePluginSuccess=function(e){a('[data-plugin="'+e.plugin+'"]').css({backgroundColor:"#faafaa"}).fadeOut(350,function(){var d=a("#bulk-action-form"),f=a(".subsubsub"),g=a(this),h=d.find("thead th:not(.hidden), thead td").length,i=b.template("item-deleted-row"),j=c.plugins;g.hasClass("plugin-update-tr")||g.after(i({slug:e.slug,plugin:e.plugin,colspan:h,name:e.pluginName})),g.remove(),-1!==_.indexOf(j.upgrade,e.plugin)&&(j.upgrade=_.without(j.upgrade,e.plugin),b.updates.decrementCount("plugin")),-1!==_.indexOf(j.inactive,e.plugin)&&(j.inactive=_.without(j.inactive,e.plugin),j.inactive.length?f.find(".inactive .count").text("("+j.inactive.length+")"):f.find(".inactive").remove()),-1!==_.indexOf(j.active,e.plugin)&&(j.active=_.without(j.active,e.plugin),j.active.length?f.find(".active .count").text("("+j.active.length+")"):f.find(".active").remove()),-1!==_.indexOf(j.recently_activated,e.plugin)&&(j.recently_activated=_.without(j.recently_activated,e.plugin),j.recently_activated.length?f.find(".recently_activated .count").text("("+j.recently_activated.length+")"):f.find(".recently_activated").remove()),j.all=_.without(j.all,e.plugin),j.all.length?f.find(".all .count").text("("+j.all.length+")"):(d.find(".tablenav").css({visibility:"hidden"}),f.find(".all").remove(),d.find("tr.no-items").length||d.find("#the-list").append('<tr class="no-items"><td class="colspanchange" colspan="'+h+'">'+b.updates.l10n.noPlugins+"</td></tr>"))}),b.a11y.speak(b.updates.l10n.pluginDeleted,"polite"),d.trigger("wp-plugin-delete-success",e)},b.updates.deletePluginError=function(c){var e,f,g=b.template("item-update-row"),h=b.updates.adminNotice({className:"update-message notice-error notice-alt",message:c.errorMessage});c.plugin?(e=a('tr.inactive[data-plugin="'+c.plugin+'"]'),f=e.siblings('[data-plugin="'+c.plugin+'"]')):(e=a('tr.inactive[data-slug="'+c.slug+'"]'),f=e.siblings('[data-slug="'+c.slug+'"]')),b.updates.isValidResponse(c,"delete")&&(b.updates.maybeHandleCredentialError(c,"delete-plugin")||(f.length?(f.find(".notice-error").remove(),f.find(".plugin-update").append(h)):e.addClass("update").after(g({slug:c.slug,plugin:c.plugin||c.slug,colspan:a("#bulk-action-form").find("thead th:not(.hidden), thead td").length,content:h})),d.trigger("wp-plugin-delete-error",c)))},b.updates.updateTheme=function(c){var e;return c=_.extend({success:b.updates.updateThemeSuccess,error:b.updates.updateThemeError},c),"themes-network"===pagenow?e=a('[data-slug="'+c.slug+'"]').find(".update-message").removeClass("notice-error").addClass("updating-message notice-warning").find("p"):"customize"===pagenow?(e=a('[data-slug="'+c.slug+'"].notice').removeClass("notice-large"),e.find("h3").remove(),e=e.add(a("#customize-control-installed_theme_"+c.slug).find(".update-message")),e=e.addClass("updating-message").find("p")):(e=a("#update-theme").closest(".notice").removeClass("notice-large"),e.find("h3").remove(),e=e.add(a('[data-slug="'+c.slug+'"]').find(".update-message")),e=e.addClass("updating-message").find("p")),e.html()!==b.updates.l10n.updating&&e.data("originaltext",e.html()),b.a11y.speak(b.updates.l10n.updatingMsg,"polite"),e.text(b.updates.l10n.updating),d.trigger("wp-theme-updating",c),b.updates.ajax("update-theme",c)},b.updates.updateThemeSuccess=function(c){var e,f,g=a("body.modal-open").length,h=a('[data-slug="'+c.slug+'"]'),i={className:"updated-message notice-success notice-alt",message:b.updates.l10n.themeUpdated};"customize"===pagenow?(h=a(".updating-message").siblings(".theme-name"),h.length&&(f=h.html().replace(c.oldVersion,c.newVersion),h.html(f)),e=a(".theme-info .notice").add(b.customize.control("installed_theme_"+c.slug).container.find(".theme").find(".update-message"))):"themes-network"===pagenow?(e=h.find(".update-message"),f=h.find(".theme-version-author-uri").html().replace(c.oldVersion,c.newVersion),h.find(".theme-version-author-uri").html(f)):(e=a(".theme-info .notice").add(h.find(".update-message")),g?a(".load-customize:visible").focus():h.find(".load-customize").focus()),b.updates.addAdminNotice(_.extend({selector:e},i)),b.a11y.speak(b.updates.l10n.updatedMsg,"polite"),b.updates.decrementCount("theme"),d.trigger("wp-theme-update-success",c),g&&"customize"!==pagenow&&a(".theme-info .theme-author").after(b.updates.adminNotice(i))},b.updates.updateThemeError=function(c){var e,f=a('[data-slug="'+c.slug+'"]'),g=b.updates.l10n.updateFailed.replace("%s",c.errorMessage);b.updates.isValidResponse(c,"update")&&(b.updates.maybeHandleCredentialError(c,"update-theme")||("customize"===pagenow&&(f=b.customize.control("installed_theme_"+c.slug).container.find(".theme")),"themes-network"===pagenow?e=f.find(".update-message "):(e=a(".theme-info .notice").add(f.find(".notice")),a("body.modal-open").length?a(".load-customize:visible").focus():f.find(".load-customize").focus()),b.updates.addAdminNotice({selector:e,className:"update-message notice-error notice-alt is-dismissible",message:g}),b.a11y.speak(g,"polite"),d.trigger("wp-theme-update-error",c)))},b.updates.installTheme=function(c){var e=a('.theme-install[data-slug="'+c.slug+'"]');return c=_.extend({success:b.updates.installThemeSuccess,error:b.updates.installThemeError},c),e.addClass("updating-message"),e.parents(".theme").addClass("focus"),e.html()!==b.updates.l10n.installing&&e.data("originaltext",e.html()),e.text(b.updates.l10n.installing).attr("aria-label",b.updates.l10n.themeInstallingLabel.replace("%s",e.data("name"))),b.a11y.speak(b.updates.l10n.installingMsg,"polite"),a('.install-theme-info, [data-slug="'+c.slug+'"]').removeClass("theme-install-failed").find(".notice.notice-error").remove(),d.trigger("wp-theme-installing",c),b.updates.ajax("install-theme",c)},b.updates.installThemeSuccess=function(c){var e,f=a(".wp-full-overlay-header, [data-slug="+c.slug+"]");d.trigger("wp-theme-install-success",c),e=f.find(".button-primary").removeClass("updating-message").addClass("updated-message disabled").attr("aria-label",b.updates.l10n.themeInstalledLabel.replace("%s",c.themeName)).text(b.updates.l10n.themeInstalled),b.a11y.speak(b.updates.l10n.installedMsg,"polite"),setTimeout(function(){c.activateUrl&&e.attr("href",c.activateUrl).removeClass("theme-install updated-message disabled").addClass("activate").attr("aria-label",b.updates.l10n.activateThemeLabel.replace("%s",c.themeName)).text(b.updates.l10n.activateTheme),c.customizeUrl&&e.siblings(".preview").replaceWith(function(){return a("<a>").attr("href",c.customizeUrl).addClass("button load-customize").text(b.updates.l10n.livePreview)})},1e3)},b.updates.installThemeError=function(c){var e,f,g=b.updates.l10n.installFailed.replace("%s",c.errorMessage),h=b.updates.adminNotice({className:"update-message notice-error notice-alt",message:g});b.updates.isValidResponse(c,"install")&&(b.updates.maybeHandleCredentialError(c,"install-theme")||("customize"===pagenow?(d.find("body").hasClass("modal-open")?(f=a('.theme-install[data-slug="'+c.slug+'"]'),e=a(".theme-overlay .theme-info").prepend(h)):(f=a('.theme-install[data-slug="'+c.slug+'"]'),e=f.closest(".theme").addClass("theme-install-failed").append(h)),b.customize.notifications.remove("theme_installing")):d.find("body").hasClass("full-overlay-active")?(f=a('.theme-install[data-slug="'+c.slug+'"]'),e=a(".install-theme-info").prepend(h)):(e=a('[data-slug="'+c.slug+'"]').removeClass("focus").addClass("theme-install-failed").append(h),f=e.find(".theme-install")),f.removeClass("updating-message").attr("aria-label",b.updates.l10n.themeInstallFailedLabel.replace("%s",f.data("name"))).text(b.updates.l10n.installFailedShort),b.a11y.speak(g,"assertive"),d.trigger("wp-theme-install-error",c)))},b.updates.deleteTheme=function(c){var e;return"themes"===pagenow?e=a(".theme-actions .delete-theme"):"themes-network"===pagenow&&(e=a('[data-slug="'+c.slug+'"]').find(".row-actions a.delete")),c=_.extend({success:b.updates.deleteThemeSuccess,error:b.updates.deleteThemeError},c),e&&e.html()!==b.updates.l10n.deleting&&e.data("originaltext",e.html()).text(b.updates.l10n.deleting),b.a11y.speak(b.updates.l10n.deleting,"polite"),a(".theme-info .update-message").remove(),d.trigger("wp-theme-deleting",c),b.updates.ajax("delete-theme",c)},b.updates.deleteThemeSuccess=function(e){var f=a('[data-slug="'+e.slug+'"]');"themes-network"===pagenow&&f.css({backgroundColor:"#faafaa"}).fadeOut(350,function(){var d=a(".subsubsub"),f=a(this),g=c.themes,h=b.template("item-deleted-row");f.hasClass("plugin-update-tr")||f.after(h({slug:e.slug,colspan:a("#bulk-action-form").find("thead th:not(.hidden), thead td").length,name:f.find(".theme-title strong").text()})),f.remove(),f.hasClass("update")&&(g.upgrade--,b.updates.decrementCount("theme")),f.hasClass("inactive")&&(g.disabled--,g.disabled?d.find(".disabled .count").text("("+g.disabled+")"):d.find(".disabled").remove()),d.find(".all .count").text("("+--g.all+")")}),b.a11y.speak(b.updates.l10n.themeDeleted,"polite"),d.trigger("wp-theme-delete-success",e)},b.updates.deleteThemeError=function(c){var e=a('tr.inactive[data-slug="'+c.slug+'"]'),f=a(".theme-actions .delete-theme"),g=b.template("item-update-row"),h=e.siblings("#"+c.slug+"-update"),i=b.updates.l10n.deleteFailed.replace("%s",c.errorMessage),j=b.updates.adminNotice({className:"update-message notice-error notice-alt",message:i});b.updates.maybeHandleCredentialError(c,"delete-theme")||("themes-network"===pagenow?h.length?(h.find(".notice-error").remove(),h.find(".plugin-update").append(j)):e.addClass("update").after(g({slug:c.slug,colspan:a("#bulk-action-form").find("thead th:not(.hidden), thead td").length,content:j})):a(".theme-info .theme-description").before(j),f.html(f.data("originaltext")),b.a11y.speak(i,"assertive"),d.trigger("wp-theme-delete-error",c))},b.updates._addCallbacks=function(a,c){return"import"===pagenow&&"install-plugin"===c&&(a.success=b.updates.installImporterSuccess,a.error=b.updates.installImporterError),a},b.updates.queueChecker=function(){var a;if(!b.updates.ajaxLocked&&b.updates.queue.length)switch(a=b.updates.queue.shift(),a.action){case"install-plugin":b.updates.installPlugin(a.data);break;case"update-plugin":b.updates.updatePlugin(a.data);break;case"delete-plugin":b.updates.deletePlugin(a.data);break;case"install-theme":b.updates.installTheme(a.data);break;case"update-theme":b.updates.updateTheme(a.data);break;case"delete-theme":b.updates.deleteTheme(a.data)}},b.updates.requestFilesystemCredentials=function(c){!1===b.updates.filesystemCredentials.available&&(c&&!b.updates.$elToReturnFocusToFromCredentialsModal&&(b.updates.$elToReturnFocusToFromCredentialsModal=a(c.target)),b.updates.ajaxLocked=!0,b.updates.requestForCredentialsModalOpen())},b.updates.maybeRequestFilesystemCredentials=function(a){b.updates.shouldRequestFilesystemCredentials&&!b.updates.ajaxLocked&&b.updates.requestFilesystemCredentials(a)},b.updates.keydown=function(c){27===c.keyCode?b.updates.requestForCredentialsModalCancel():9===c.keyCode&&("upgrade"!==c.target.id||c.shiftKey?"hostname"===c.target.id&&c.shiftKey&&(a("#upgrade").focus(),c.preventDefault()):(a("#hostname").focus(),c.preventDefault()))},b.updates.requestForCredentialsModalOpen=function(){var c=a("#request-filesystem-credentials-dialog");a("body").addClass("modal-open"),c.show(),c.find("input:enabled:first").focus(),c.on("keydown",b.updates.keydown)},b.updates.requestForCredentialsModalClose=function(){a("#request-filesystem-credentials-dialog").hide(),a("body").removeClass("modal-open"),b.updates.$elToReturnFocusToFromCredentialsModal&&b.updates.$elToReturnFocusToFromCredentialsModal.focus()},b.updates.requestForCredentialsModalCancel=function(){(b.updates.ajaxLocked||b.updates.queue.length)&&(_.each(b.updates.queue,function(a){d.trigger("credential-modal-cancel",a)}),b.updates.ajaxLocked=!1,b.updates.queue=[],b.updates.requestForCredentialsModalClose())},b.updates.showErrorInCredentialsForm=function(b){var c=a("#request-filesystem-credentials-form");c.find(".notice").remove(),c.find("#request-filesystem-credentials-title").after('<div class="notice notice-alt notice-error"><p>'+b+"</p></div>")},b.updates.credentialError=function(a,c){a=b.updates._addCallbacks(a,c),b.updates.queue.unshift({action:c,data:a}),b.updates.filesystemCredentials.available=!1,b.updates.showErrorInCredentialsForm(a.errorMessage),b.updates.requestFilesystemCredentials()},b.updates.maybeHandleCredentialError=function(a,c){return!(!b.updates.shouldRequestFilesystemCredentials||!a.errorCode||"unable_to_connect_to_filesystem"!==a.errorCode)&&(b.updates.credentialError(a,c),!0)},b.updates.isValidResponse=function(c,d){var e,f=b.updates.l10n.unknownError;if(_.isObject(c)&&!_.isFunction(c.always))return!0;switch(_.isString(c)&&"-1"===c?f=b.updates.l10n.nonceError:_.isString(c)?f=c:"undefined"!=typeof c.readyState&&0===c.readyState?f=b.updates.l10n.connectionError:_.isString(c.responseText)&&""!==c.responseText?f=c.responseText:_.isString(c.statusText)&&(f=c.statusText),d){case"update":e=b.updates.l10n.updateFailed;break;case"install":e=b.updates.l10n.installFailed;break;case"delete":e=b.updates.l10n.deleteFailed}return f=f.replace(/<[\/a-z][^<>]*>/gi,""),e=e.replace("%s",f),b.updates.addAdminNotice({id:"unknown_error",className:"notice-error is-dismissible",message:_.escape(e)}),b.updates.ajaxLocked=!1,b.updates.queue=[],a(".button.updating-message").removeClass("updating-message").removeAttr("aria-label").prop("disabled",!0).text(b.updates.l10n.updateFailedShort),a(".updating-message:not(.button):not(.thickbox)").removeClass("updating-message notice-warning").addClass("notice-error").find("p").removeAttr("aria-label").text(e),b.a11y.speak(e,"assertive"),!1},b.updates.beforeunload=function(){if(b.updates.ajaxLocked)return b.updates.l10n.beforeunload},a(function(){var e=a("#plugin-filter"),f=a("#bulk-action-form"),g=a("#request-filesystem-credentials-form"),h=a("#request-filesystem-credentials-dialog"),i=a(".plugins-php .wp-filter-search"),j=a(".plugin-install-php .wp-filter-search");c=_.extend(c,window._wpUpdatesItemCounts||{}),c.totals&&b.updates.refreshCount(),b.updates.shouldRequestFilesystemCredentials=h.length>0,h.on("submit","form",function(c){c.preventDefault(),b.updates.filesystemCredentials.ftp.hostname=a("#hostname").val(),b.updates.filesystemCredentials.ftp.username=a("#username").val(),b.updates.filesystemCredentials.ftp.password=a("#password").val(),b.updates.filesystemCredentials.ftp.connectionType=a('input[name="connection_type"]:checked').val(),b.updates.filesystemCredentials.ssh.publicKey=a("#public_key").val(),b.updates.filesystemCredentials.ssh.privateKey=a("#private_key").val(),b.updates.filesystemCredentials.fsNonce=a("#_fs_nonce").val(),b.updates.filesystemCredentials.available=!0,b.updates.ajaxLocked=!1,b.updates.queueChecker(),b.updates.requestForCredentialsModalClose()}),h.on("click",'[data-js-action="close"], .notification-dialog-background',b.updates.requestForCredentialsModalCancel),g.on("change",'input[name="connection_type"]',function(){a("#ssh-keys").toggleClass("hidden","ssh"!==a(this).val())}).change(),d.on("credential-modal-cancel",function(c,d){var e,f,g=a(".updating-message");"import"===pagenow?g.removeClass("updating-message"):"plugins"===pagenow||"plugins-network"===pagenow?"update-plugin"===d.action?e=a('tr[data-plugin="'+d.data.plugin+'"]').find(".update-message"):"delete-plugin"===d.action&&(e=a('[data-plugin="'+d.data.plugin+'"]').find(".row-actions a.delete")):"themes"===pagenow||"themes-network"===pagenow?"update-theme"===d.action?e=a('[data-slug="'+d.data.slug+'"]').find(".update-message"):"delete-theme"===d.action&&"themes-network"===pagenow?e=a('[data-slug="'+d.data.slug+'"]').find(".row-actions a.delete"):"delete-theme"===d.action&&"themes"===pagenow&&(e=a(".theme-actions .delete-theme")):e=g,e&&e.hasClass("updating-message")&&(f=e.data("originaltext"),"undefined"==typeof f&&(f=a("<p>").html(e.find("p").data("originaltext"))),e.removeClass("updating-message").html(f),"plugin-install"!==pagenow&&"plugin-install-network"!==pagenow||("update-plugin"===d.action?e.attr("aria-label",b.updates.l10n.pluginUpdateNowLabel.replace("%s",e.data("name"))):"install-plugin"===d.action&&e.attr("aria-label",b.updates.l10n.pluginInstallNowLabel.replace("%s",e.data("name"))))),b.a11y.speak(b.updates.l10n.updateCancel,"polite")}),f.on("click","[data-plugin] .update-link",function(c){var d=a(c.target),e=d.parents("tr");c.preventDefault(),d.hasClass("updating-message")||d.hasClass("button-disabled")||(b.updates.maybeRequestFilesystemCredentials(c),b.updates.$elToReturnFocusToFromCredentialsModal=e.find(".check-column input"),b.updates.updatePlugin({plugin:e.data("plugin"),slug:e.data("slug")}))}),e.on("click",".update-now",function(c){var d=a(c.target);c.preventDefault(),d.hasClass("updating-message")||d.hasClass("button-disabled")||(b.updates.maybeRequestFilesystemCredentials(c),b.updates.updatePlugin({plugin:d.data("plugin"),slug:d.data("slug")}))}),e.on("click",".install-now",function(c){var e=a(c.target);c.preventDefault(),e.hasClass("updating-message")||e.hasClass("button-disabled")||(b.updates.shouldRequestFilesystemCredentials&&!b.updates.ajaxLocked&&(b.updates.requestFilesystemCredentials(c),d.on("credential-modal-cancel",function(){var c=a(".install-now.updating-message");c.removeClass("updating-message").text(b.updates.l10n.installNow),b.a11y.speak(b.updates.l10n.updateCancel,"polite")})),b.updates.installPlugin({slug:e.data("slug")}))}),d.on("click",".importer-item .install-now",function(c){var e=a(c.target),f=a(this).data("name");c.preventDefault(),e.hasClass("updating-message")||(b.updates.shouldRequestFilesystemCredentials&&!b.updates.ajaxLocked&&(b.updates.requestFilesystemCredentials(c),d.on("credential-modal-cancel",function(){e.removeClass("updating-message").text(b.updates.l10n.installNow).attr("aria-label",b.updates.l10n.pluginInstallNowLabel.replace("%s",f)),b.a11y.speak(b.updates.l10n.updateCancel,"polite")})),b.updates.installPlugin({slug:e.data("slug"),pagenow:pagenow,success:b.updates.installImporterSuccess,error:b.updates.installImporterError}))}),f.on("click","[data-plugin] a.delete",function(c){var d=a(c.target).parents("tr");c.preventDefault(),window.confirm(b.updates.l10n.aysDeleteUninstall.replace("%s",d.find(".plugin-title strong").text()))&&(b.updates.maybeRequestFilesystemCredentials(c),b.updates.deletePlugin({plugin:d.data("plugin"),slug:d.data("slug")}))}),d.on("click",".themes-php.network-admin .update-link",function(c){var d=a(c.target),e=d.parents("tr");c.preventDefault(),d.hasClass("updating-message")||d.hasClass("button-disabled")||(b.updates.maybeRequestFilesystemCredentials(c),b.updates.$elToReturnFocusToFromCredentialsModal=e.find(".check-column input"),b.updates.updateTheme({slug:e.data("slug")}))}),d.on("click",".themes-php.network-admin a.delete",function(c){var d=a(c.target).parents("tr");c.preventDefault(),window.confirm(b.updates.l10n.aysDelete.replace("%s",d.find(".theme-title strong").text()))&&(b.updates.maybeRequestFilesystemCredentials(c),b.updates.deleteTheme({slug:d.data("slug")}))}),f.on("click",'[type="submit"]:not([name="clear-recent-list"])',function(c){var e,g,h=a(c.target).siblings("select").val(),i=f.find('input[name="checked[]"]:checked'),j=0,k=0,l=[];switch(pagenow){case"plugins":case"plugins-network":e="plugin";break;case"themes-network":e="theme";break;default:return}if(!i.length)return c.preventDefault(),a("html, body").animate({scrollTop:0}),b.updates.addAdminNotice({id:"no-items-selected",className:"notice-error is-dismissible",message:b.updates.l10n.noItemsSelected});switch(h){case"update-selected":g=h.replace("selected",e);break;case"delete-selected":if(!window.confirm("plugin"===e?b.updates.l10n.aysBulkDelete:b.updates.l10n.aysBulkDeleteThemes))return void c.preventDefault();g=h.replace("selected",e);break;default:return}b.updates.maybeRequestFilesystemCredentials(c),c.preventDefault(),f.find('.manage-column [type="checkbox"]').prop("checked",!1),d.trigger("wp-"+e+"-bulk-"+h,i),i.each(function(c,d){var e=a(d),f=e.parents("tr");return"update-selected"!==h||f.hasClass("update")&&!f.find("notice-error").length?void b.updates.queue.push({action:g,data:{plugin:f.data("plugin"),slug:f.data("slug")}}):void e.prop("checked",!1)}),d.on("wp-plugin-update-success wp-plugin-update-error wp-theme-update-success wp-theme-update-error",function(c,d){var e,f,g=a('[data-slug="'+d.slug+'"]');"wp-"+d.update+"-update-success"===c.type?j++:(f=d.pluginName?d.pluginName:g.find(".column-primary strong").text(),k++,l.push(f+": "+d.errorMessage)),g.find('input[name="checked[]"]:checked').prop("checked",!1),b.updates.adminNotice=b.template("wp-bulk-updates-admin-notice"),b.updates.addAdminNotice({id:"bulk-action-notice",className:"bulk-action-notice",successes:j,errors:k,errorMessages:l,type:d.update}),e=a("#bulk-action-notice").on("click","button",function(){a(this).toggleClass("bulk-action-errors-collapsed").attr("aria-expanded",!a(this).hasClass("bulk-action-errors-collapsed")),e.find(".bulk-action-errors").toggleClass("hidden")}),k>0&&!b.updates.queue.length&&a("html, body").animate({scrollTop:0})}),d.on("wp-updates-notice-added",function(){b.updates.adminNotice=b.template("wp-updates-admin-notice")}),b.updates.queueChecker()}),j.length&&j.attr("aria-describedby","live-search-desc"),j.on("keyup input",_.debounce(function(c,d){var f,g,h=a(".plugin-install-search");f={_ajax_nonce:b.updates.ajaxNonce,s:c.target.value,tab:"search",type:a("#typeselector").val(),pagenow:pagenow},g=location.href.split("?")[0]+"?"+a.param(_.omit(f,["_ajax_nonce","pagenow"])),"keyup"===c.type&&27===c.which&&(c.target.value=""),b.updates.searchTerm===f.s&&"typechange"!==d||(e.empty(),b.updates.searchTerm=f.s,
+window.history&&window.history.replaceState&&window.history.replaceState(null,"",g),h.length||(h=a('<li class="plugin-install-search" />').append(a("<a />",{"class":"current",href:g,text:b.updates.l10n.searchResultsLabel})),a(".wp-filter .filter-links .current").removeClass("current").parents(".filter-links").prepend(h),e.prev("p").remove(),a(".plugins-popular-tags-wrapper").remove()),"undefined"!=typeof b.updates.searchRequest&&b.updates.searchRequest.abort(),a("body").addClass("loading-content"),b.updates.searchRequest=b.ajax.post("search-install-plugins",f).done(function(c){a("body").removeClass("loading-content"),e.append(c.items),delete b.updates.searchRequest,0===c.count?b.a11y.speak(b.updates.l10n.noPluginsFound):b.a11y.speak(b.updates.l10n.pluginsFound.replace("%d",c.count))}))},500)),i.length&&i.attr("aria-describedby","live-search-desc"),i.on("keyup input",_.debounce(function(c){var d,e={_ajax_nonce:b.updates.ajaxNonce,s:c.target.value,pagenow:pagenow,plugin_status:"all"};"keyup"===c.type&&27===c.which&&(c.target.value=""),b.updates.searchTerm!==e.s&&(b.updates.searchTerm=e.s,d=_.object(_.compact(_.map(location.search.slice(1).split("&"),function(a){if(a)return a.split("=")}))),e.plugin_status=d.plugin_status||"all",window.history&&window.history.replaceState&&window.history.replaceState(null,"",location.href.split("?")[0]+"?s="+e.s+"&plugin_status="+e.plugin_status),"undefined"!=typeof b.updates.searchRequest&&b.updates.searchRequest.abort(),f.empty(),a("body").addClass("loading-content"),a(".subsubsub .current").removeClass("current"),b.updates.searchRequest=b.ajax.post("search-plugins",e).done(function(c){var d=a("<span />").addClass("subtitle").html(b.updates.l10n.searchResults.replace("%s",_.escape(e.s))),g=a(".wrap .subtitle");e.s.length?g.length?g.replaceWith(d):a(".wp-header-end").before(d):(g.remove(),a(".subsubsub ."+e.plugin_status+" a").addClass("current")),a("body").removeClass("loading-content"),f.append(c.items),delete b.updates.searchRequest,0===c.count?b.a11y.speak(b.updates.l10n.noPluginsFound):b.a11y.speak(b.updates.l10n.pluginsFound.replace("%d",c.count))}))},500)),d.on("submit",".search-plugins",function(b){b.preventDefault(),a("input.wp-filter-search").trigger("input")}),d.on("click",".try-again",function(a){a.preventDefault(),j.trigger("input")}),a("#typeselector").on("change",function(){var b=a('input[name="s"]');b.val().length&&b.trigger("input","typechange")}),a("#plugin_update_from_iframe").on("click",function(b){var c,d=window.parent===window?null:window.parent;a.support.postMessage=!!window.postMessage,!1!==a.support.postMessage&&null!==d&&-1===window.parent.location.pathname.indexOf("update-core.php")&&(b.preventDefault(),c={action:"update-plugin",data:{plugin:a(this).data("plugin"),slug:a(this).data("slug")}},d.postMessage(JSON.stringify(c),window.location.origin))}),a("#plugin_install_from_iframe").on("click",function(b){var c,d=window.parent===window?null:window.parent;a.support.postMessage=!!window.postMessage,!1!==a.support.postMessage&&null!==d&&-1===window.parent.location.pathname.indexOf("index.php")&&(b.preventDefault(),c={action:"install-plugin",data:{slug:a(this).data("slug")}},d.postMessage(JSON.stringify(c),window.location.origin))}),a(window).on("message",function(c){var d,e=c.originalEvent,f=document.location.protocol+"//"+document.location.hostname;if(e.origin===f){try{d=a.parseJSON(e.data)}catch(g){return}if(d&&"undefined"!=typeof d.action)switch(d.action){case"decrementUpdateCount":b.updates.decrementCount(d.upgradeType);break;case"install-plugin":case"update-plugin":window.tb_remove(),d.data=b.updates._addCallbacks(d.data,d.action),b.updates.queue.push(d),b.updates.queueChecker()}}}),a(window).on("beforeunload",b.updates.beforeunload)})}(jQuery,window.wp,window._wpUpdatesSettings); \ No newline at end of file
diff --git a/www/crm/wp-admin/js/user-profile.js b/www/crm/wp-admin/js/user-profile.js
new file mode 100644
index 00000000..386f8ae5
--- /dev/null
+++ b/www/crm/wp-admin/js/user-profile.js
@@ -0,0 +1,462 @@
+/**
+ * @output wp-admin/js/user-profile.js
+ */
+
+/* global ajaxurl, pwsL10n, userProfileL10n */
+(function($) {
+ var updateLock = false,
+
+ $pass1Row,
+ $pass1Wrap,
+ $pass1,
+ $pass1Text,
+ $pass1Label,
+ $pass2,
+ $weakRow,
+ $weakCheckbox,
+ $toggleButton,
+ $submitButtons,
+ $submitButton,
+ currentPass;
+
+ function generatePassword() {
+ if ( typeof zxcvbn !== 'function' ) {
+ setTimeout( generatePassword, 50 );
+ return;
+ } else if ( ! $pass1.val() ) {
+ // zxcvbn loaded before user entered password.
+ $pass1.val( $pass1.data( 'pw' ) );
+ $pass1.trigger( 'pwupdate' );
+ showOrHideWeakPasswordCheckbox();
+ }
+ else {
+ // zxcvbn loaded after the user entered password, check strength.
+ check_pass_strength();
+ showOrHideWeakPasswordCheckbox();
+ }
+
+ if ( 1 !== parseInt( $toggleButton.data( 'start-masked' ), 10 ) ) {
+ $pass1Wrap.addClass( 'show-password' );
+ } else {
+ $toggleButton.trigger( 'click' );
+ }
+
+ // Once zxcvbn loads, passwords strength is known.
+ $( '#pw-weak-text-label' ).html( userProfileL10n.warnWeak );
+ }
+
+ function bindPass1() {
+ currentPass = $pass1.val();
+
+ $pass1Wrap = $pass1.parent();
+
+ $pass1Text = $( '<input type="text"/>' )
+ .attr( {
+ 'id': 'pass1-text',
+ 'name': 'pass1-text',
+ 'autocomplete': 'off'
+ } )
+ .addClass( $pass1[0].className )
+ .data( 'pw', $pass1.data( 'pw' ) )
+ .val( $pass1.val() )
+ .on( 'input', function () {
+ if ( $pass1Text.val() === currentPass ) {
+ return;
+ }
+ $pass2.val( $pass1Text.val() );
+ $pass1.val( $pass1Text.val() ).trigger( 'pwupdate' );
+ currentPass = $pass1Text.val();
+ } );
+
+ $pass1.after( $pass1Text );
+
+ if ( 1 === parseInt( $pass1.data( 'reveal' ), 10 ) ) {
+ generatePassword();
+ }
+
+ $pass1.on( 'input' + ' pwupdate', function () {
+ if ( $pass1.val() === currentPass ) {
+ return;
+ }
+
+ currentPass = $pass1.val();
+ if ( $pass1Text.val() !== currentPass ) {
+ $pass1Text.val( currentPass );
+ }
+ $pass1.add( $pass1Text ).removeClass( 'short bad good strong' );
+ showOrHideWeakPasswordCheckbox();
+ } );
+ }
+
+ function resetToggle() {
+ $toggleButton
+ .data( 'toggle', 0 )
+ .attr({
+ 'aria-label': userProfileL10n.ariaHide
+ })
+ .find( '.text' )
+ .text( userProfileL10n.hide )
+ .end()
+ .find( '.dashicons' )
+ .removeClass( 'dashicons-visibility' )
+ .addClass( 'dashicons-hidden' );
+
+ $pass1Text.focus();
+
+ $pass1Label.attr( 'for', 'pass1-text' );
+ }
+
+ function bindToggleButton() {
+ $toggleButton = $pass1Row.find('.wp-hide-pw');
+ $toggleButton.show().on( 'click', function () {
+ if ( 1 === parseInt( $toggleButton.data( 'toggle' ), 10 ) ) {
+ $pass1Wrap.addClass( 'show-password' );
+
+ resetToggle();
+
+ if ( ! _.isUndefined( $pass1Text[0].setSelectionRange ) ) {
+ $pass1Text[0].setSelectionRange( 0, 100 );
+ }
+ } else {
+ $pass1Wrap.removeClass( 'show-password' );
+ $toggleButton
+ .data( 'toggle', 1 )
+ .attr({
+ 'aria-label': userProfileL10n.ariaShow
+ })
+ .find( '.text' )
+ .text( userProfileL10n.show )
+ .end()
+ .find( '.dashicons' )
+ .removeClass('dashicons-hidden')
+ .addClass('dashicons-visibility');
+
+ $pass1.focus();
+
+ $pass1Label.attr( 'for', 'pass1' );
+
+ if ( ! _.isUndefined( $pass1[0].setSelectionRange ) ) {
+ $pass1[0].setSelectionRange( 0, 100 );
+ }
+ }
+ });
+ }
+
+ function bindPasswordForm() {
+ var $passwordWrapper,
+ $generateButton,
+ $cancelButton;
+
+ $pass1Row = $('.user-pass1-wrap');
+ $pass1Label = $pass1Row.find('th label').attr( 'for', 'pass1-text' );
+
+ // hide this
+ $('.user-pass2-wrap').hide();
+
+ $submitButton = $( '#submit, #wp-submit' ).on( 'click', function () {
+ updateLock = false;
+ });
+
+ $submitButtons = $submitButton.add( ' #createusersub' );
+
+ $weakRow = $( '.pw-weak' );
+ $weakCheckbox = $weakRow.find( '.pw-checkbox' );
+ $weakCheckbox.change( function() {
+ $submitButtons.prop( 'disabled', ! $weakCheckbox.prop( 'checked' ) );
+ } );
+
+ $pass1 = $('#pass1');
+ if ( $pass1.length ) {
+ bindPass1();
+ }
+
+ /**
+ * Fix a LastPass mismatch issue, LastPass only changes pass2.
+ *
+ * This fixes the issue by copying any changes from the hidden
+ * pass2 field to the pass1 field, then running check_pass_strength.
+ */
+ $pass2 = $( '#pass2' ).on( 'input', function () {
+ if ( $pass2.val().length > 0 ) {
+ $pass1.val( $pass2.val() );
+ $pass2.val('');
+ currentPass = '';
+ $pass1.trigger( 'pwupdate' );
+ }
+ } );
+
+ // Disable hidden inputs to prevent autofill and submission.
+ if ( $pass1.is( ':hidden' ) ) {
+ $pass1.prop( 'disabled', true );
+ $pass2.prop( 'disabled', true );
+ $pass1Text.prop( 'disabled', true );
+ }
+
+ $passwordWrapper = $pass1Row.find( '.wp-pwd' );
+ $generateButton = $pass1Row.find( 'button.wp-generate-pw' );
+
+ bindToggleButton();
+
+ if ( $generateButton.length ) {
+ $passwordWrapper.hide();
+ }
+
+ $generateButton.show();
+ $generateButton.on( 'click', function () {
+ updateLock = true;
+
+ $generateButton.hide();
+ $passwordWrapper.show();
+
+ // Enable the inputs when showing.
+ $pass1.attr( 'disabled', false );
+ $pass2.attr( 'disabled', false );
+ $pass1Text.attr( 'disabled', false );
+
+ if ( $pass1Text.val().length === 0 ) {
+ generatePassword();
+ }
+
+ _.defer( function() {
+ $pass1Text.focus();
+ if ( ! _.isUndefined( $pass1Text[0].setSelectionRange ) ) {
+ $pass1Text[0].setSelectionRange( 0, 100 );
+ }
+ }, 0 );
+ } );
+
+ $cancelButton = $pass1Row.find( 'button.wp-cancel-pw' );
+ $cancelButton.on( 'click', function () {
+ updateLock = false;
+
+ // Clear any entered password.
+ $pass1Text.val( '' );
+
+ // Generate a new password.
+ wp.ajax.post( 'generate-password' )
+ .done( function( data ) {
+ $pass1.data( 'pw', data );
+ } );
+
+ $generateButton.show().focus();
+ $passwordWrapper.hide();
+
+ $weakRow.hide( 0, function () {
+ $weakCheckbox.removeProp( 'checked' );
+ } );
+
+ // Disable the inputs when hiding to prevent autofill and submission.
+ $pass1.prop( 'disabled', true );
+ $pass2.prop( 'disabled', true );
+ $pass1Text.prop( 'disabled', true );
+
+ resetToggle();
+
+ if ( $pass1Row.closest( 'form' ).is( '#your-profile' ) ) {
+ // Clear password field to prevent update
+ $pass1.val( '' ).trigger( 'pwupdate' );
+ $submitButtons.prop( 'disabled', false );
+ }
+ } );
+
+ $pass1Row.closest( 'form' ).on( 'submit', function () {
+ updateLock = false;
+
+ $pass1.prop( 'disabled', false );
+ $pass2.prop( 'disabled', false );
+ $pass2.val( $pass1.val() );
+ $pass1Wrap.removeClass( 'show-password' );
+ });
+ }
+
+ function check_pass_strength() {
+ var pass1 = $('#pass1').val(), strength;
+
+ $('#pass-strength-result').removeClass('short bad good strong');
+ if ( ! pass1 ) {
+ $('#pass-strength-result').html( '&nbsp;' );
+ return;
+ }
+
+ strength = wp.passwordStrength.meter( pass1, wp.passwordStrength.userInputBlacklist(), pass1 );
+
+ switch ( strength ) {
+ case -1:
+ $( '#pass-strength-result' ).addClass( 'bad' ).html( pwsL10n.unknown );
+ break;
+ case 2:
+ $('#pass-strength-result').addClass('bad').html( pwsL10n.bad );
+ break;
+ case 3:
+ $('#pass-strength-result').addClass('good').html( pwsL10n.good );
+ break;
+ case 4:
+ $('#pass-strength-result').addClass('strong').html( pwsL10n.strong );
+ break;
+ case 5:
+ $('#pass-strength-result').addClass('short').html( pwsL10n.mismatch );
+ break;
+ default:
+ $('#pass-strength-result').addClass('short').html( pwsL10n['short'] );
+ }
+ }
+
+ function showOrHideWeakPasswordCheckbox() {
+ var passStrength = $('#pass-strength-result')[0];
+
+ if ( passStrength.className ) {
+ $pass1.add( $pass1Text ).addClass( passStrength.className );
+ if ( $( passStrength ).is( '.short, .bad' ) ) {
+ if ( ! $weakCheckbox.prop( 'checked' ) ) {
+ $submitButtons.prop( 'disabled', true );
+ }
+ $weakRow.show();
+ } else {
+ $submitButtons.prop( 'disabled', false );
+ $weakRow.hide();
+ }
+ }
+ }
+
+ $(document).ready( function() {
+ var $colorpicker, $stylesheet, user_id, current_user_id,
+ select = $( '#display_name' ),
+ current_name = select.val(),
+ greeting = $( '#wp-admin-bar-my-account' ).find( '.display-name' );
+
+ $( '#pass1' ).val( '' ).on( 'input' + ' pwupdate', check_pass_strength );
+ $('#pass-strength-result').show();
+ $('.color-palette').click( function() {
+ $(this).siblings('input[name="admin_color"]').prop('checked', true);
+ });
+
+ if ( select.length ) {
+ $('#first_name, #last_name, #nickname').bind( 'blur.user_profile', function() {
+ var dub = [],
+ inputs = {
+ display_nickname : $('#nickname').val() || '',
+ display_username : $('#user_login').val() || '',
+ display_firstname : $('#first_name').val() || '',
+ display_lastname : $('#last_name').val() || ''
+ };
+
+ if ( inputs.display_firstname && inputs.display_lastname ) {
+ inputs.display_firstlast = inputs.display_firstname + ' ' + inputs.display_lastname;
+ inputs.display_lastfirst = inputs.display_lastname + ' ' + inputs.display_firstname;
+ }
+
+ $.each( $('option', select), function( i, el ){
+ dub.push( el.value );
+ });
+
+ $.each(inputs, function( id, value ) {
+ if ( ! value ) {
+ return;
+ }
+
+ var val = value.replace(/<\/?[a-z][^>]*>/gi, '');
+
+ if ( inputs[id].length && $.inArray( val, dub ) === -1 ) {
+ dub.push(val);
+ $('<option />', {
+ 'text': val
+ }).appendTo( select );
+ }
+ });
+ });
+
+ /**
+ * Replaces "Howdy, *" in the admin toolbar whenever the display name dropdown is updated for one's own profile.
+ */
+ select.on( 'change', function() {
+ if ( user_id !== current_user_id ) {
+ return;
+ }
+
+ var display_name = $.trim( this.value ) || current_name;
+
+ greeting.text( display_name );
+ } );
+ }
+
+ $colorpicker = $( '#color-picker' );
+ $stylesheet = $( '#colors-css' );
+ user_id = $( 'input#user_id' ).val();
+ current_user_id = $( 'input[name="checkuser_id"]' ).val();
+
+ $colorpicker.on( 'click.colorpicker', '.color-option', function() {
+ var colors,
+ $this = $(this);
+
+ if ( $this.hasClass( 'selected' ) ) {
+ return;
+ }
+
+ $this.siblings( '.selected' ).removeClass( 'selected' );
+ $this.addClass( 'selected' ).find( 'input[type="radio"]' ).prop( 'checked', true );
+
+ // Set color scheme
+ if ( user_id === current_user_id ) {
+ // Load the colors stylesheet.
+ // The default color scheme won't have one, so we'll need to create an element.
+ if ( 0 === $stylesheet.length ) {
+ $stylesheet = $( '<link rel="stylesheet" />' ).appendTo( 'head' );
+ }
+ $stylesheet.attr( 'href', $this.children( '.css_url' ).val() );
+
+ // repaint icons
+ if ( typeof wp !== 'undefined' && wp.svgPainter ) {
+ try {
+ colors = $.parseJSON( $this.children( '.icon_colors' ).val() );
+ } catch ( error ) {}
+
+ if ( colors ) {
+ wp.svgPainter.setColors( colors );
+ wp.svgPainter.paint();
+ }
+ }
+
+ // update user option
+ $.post( ajaxurl, {
+ action: 'save-user-color-scheme',
+ color_scheme: $this.children( 'input[name="admin_color"]' ).val(),
+ nonce: $('#color-nonce').val()
+ }).done( function( response ) {
+ if ( response.success ) {
+ $( 'body' ).removeClass( response.data.previousScheme ).addClass( response.data.currentScheme );
+ }
+ });
+ }
+ });
+
+ bindPasswordForm();
+ });
+
+ $( '#destroy-sessions' ).on( 'click', function( e ) {
+ var $this = $(this);
+
+ wp.ajax.post( 'destroy-sessions', {
+ nonce: $( '#_wpnonce' ).val(),
+ user_id: $( '#user_id' ).val()
+ }).done( function( response ) {
+ $this.prop( 'disabled', true );
+ $this.siblings( '.notice' ).remove();
+ $this.before( '<div class="notice notice-success inline"><p>' + response.message + '</p></div>' );
+ }).fail( function( response ) {
+ $this.siblings( '.notice' ).remove();
+ $this.before( '<div class="notice notice-error inline"><p>' + response.message + '</p></div>' );
+ });
+
+ e.preventDefault();
+ });
+
+ window.generatePassword = generatePassword;
+
+ /* Warn the user if password was generated but not saved */
+ $( window ).on( 'beforeunload', function () {
+ if ( true === updateLock ) {
+ return userProfileL10n.warn;
+ }
+ } );
+
+})(jQuery);
diff --git a/www/crm/wp-admin/js/user-profile.min.js b/www/crm/wp-admin/js/user-profile.min.js
new file mode 100644
index 00000000..f6b2d8f7
--- /dev/null
+++ b/www/crm/wp-admin/js/user-profile.min.js
@@ -0,0 +1 @@
+!function(a){function b(){return"function"!=typeof zxcvbn?void setTimeout(b,50):(k.val()?(g(),h()):(k.val(k.data("pw")),k.trigger("pwupdate"),h()),1!==parseInt(q.data("start-masked"),10)?j.addClass("show-password"):q.trigger("click"),void a("#pw-weak-text-label").html(userProfileL10n.warnWeak))}function c(){t=k.val(),j=k.parent(),l=a('<input type="text"/>').attr({id:"pass1-text",name:"pass1-text",autocomplete:"off"}).addClass(k[0].className).data("pw",k.data("pw")).val(k.val()).on("input",function(){l.val()!==t&&(n.val(l.val()),k.val(l.val()).trigger("pwupdate"),t=l.val())}),k.after(l),1===parseInt(k.data("reveal"),10)&&b(),k.on("input pwupdate",function(){k.val()!==t&&(t=k.val(),l.val()!==t&&l.val(t),k.add(l).removeClass("short bad good strong"),h())})}function d(){q.data("toggle",0).attr({"aria-label":userProfileL10n.ariaHide}).find(".text").text(userProfileL10n.hide).end().find(".dashicons").removeClass("dashicons-visibility").addClass("dashicons-hidden"),l.focus(),m.attr("for","pass1-text")}function e(){q=i.find(".wp-hide-pw"),q.show().on("click",function(){1===parseInt(q.data("toggle"),10)?(j.addClass("show-password"),d(),_.isUndefined(l[0].setSelectionRange)||l[0].setSelectionRange(0,100)):(j.removeClass("show-password"),q.data("toggle",1).attr({"aria-label":userProfileL10n.ariaShow}).find(".text").text(userProfileL10n.show).end().find(".dashicons").removeClass("dashicons-hidden").addClass("dashicons-visibility"),k.focus(),m.attr("for","pass1"),_.isUndefined(k[0].setSelectionRange)||k[0].setSelectionRange(0,100))})}function f(){var f,g,h;i=a(".user-pass1-wrap"),m=i.find("th label").attr("for","pass1-text"),a(".user-pass2-wrap").hide(),s=a("#submit, #wp-submit").on("click",function(){u=!1}),r=s.add(" #createusersub"),o=a(".pw-weak"),p=o.find(".pw-checkbox"),p.change(function(){r.prop("disabled",!p.prop("checked"))}),k=a("#pass1"),k.length&&c(),n=a("#pass2").on("input",function(){n.val().length>0&&(k.val(n.val()),n.val(""),t="",k.trigger("pwupdate"))}),k.is(":hidden")&&(k.prop("disabled",!0),n.prop("disabled",!0),l.prop("disabled",!0)),f=i.find(".wp-pwd"),g=i.find("button.wp-generate-pw"),e(),g.length&&f.hide(),g.show(),g.on("click",function(){u=!0,g.hide(),f.show(),k.attr("disabled",!1),n.attr("disabled",!1),l.attr("disabled",!1),0===l.val().length&&b(),_.defer(function(){l.focus(),_.isUndefined(l[0].setSelectionRange)||l[0].setSelectionRange(0,100)},0)}),h=i.find("button.wp-cancel-pw"),h.on("click",function(){u=!1,l.val(""),wp.ajax.post("generate-password").done(function(a){k.data("pw",a)}),g.show().focus(),f.hide(),o.hide(0,function(){p.removeProp("checked")}),k.prop("disabled",!0),n.prop("disabled",!0),l.prop("disabled",!0),d(),i.closest("form").is("#your-profile")&&(k.val("").trigger("pwupdate"),r.prop("disabled",!1))}),i.closest("form").on("submit",function(){u=!1,k.prop("disabled",!1),n.prop("disabled",!1),n.val(k.val()),j.removeClass("show-password")})}function g(){var b,c=a("#pass1").val();if(a("#pass-strength-result").removeClass("short bad good strong"),!c)return void a("#pass-strength-result").html("&nbsp;");switch(b=wp.passwordStrength.meter(c,wp.passwordStrength.userInputBlacklist(),c)){case-1:a("#pass-strength-result").addClass("bad").html(pwsL10n.unknown);break;case 2:a("#pass-strength-result").addClass("bad").html(pwsL10n.bad);break;case 3:a("#pass-strength-result").addClass("good").html(pwsL10n.good);break;case 4:a("#pass-strength-result").addClass("strong").html(pwsL10n.strong);break;case 5:a("#pass-strength-result").addClass("short").html(pwsL10n.mismatch);break;default:a("#pass-strength-result").addClass("short").html(pwsL10n["short"])}}function h(){var b=a("#pass-strength-result")[0];b.className&&(k.add(l).addClass(b.className),a(b).is(".short, .bad")?(p.prop("checked")||r.prop("disabled",!0),o.show()):(r.prop("disabled",!1),o.hide()))}var i,j,k,l,m,n,o,p,q,r,s,t,u=!1;a(document).ready(function(){var b,c,d,e,h=a("#display_name"),i=h.val(),j=a("#wp-admin-bar-my-account").find(".display-name");a("#pass1").val("").on("input pwupdate",g),a("#pass-strength-result").show(),a(".color-palette").click(function(){a(this).siblings('input[name="admin_color"]').prop("checked",!0)}),h.length&&(a("#first_name, #last_name, #nickname").bind("blur.user_profile",function(){var b=[],c={display_nickname:a("#nickname").val()||"",display_username:a("#user_login").val()||"",display_firstname:a("#first_name").val()||"",display_lastname:a("#last_name").val()||""};c.display_firstname&&c.display_lastname&&(c.display_firstlast=c.display_firstname+" "+c.display_lastname,c.display_lastfirst=c.display_lastname+" "+c.display_firstname),a.each(a("option",h),function(a,c){b.push(c.value)}),a.each(c,function(d,e){if(e){var f=e.replace(/<\/?[a-z][^>]*>/gi,"");c[d].length&&a.inArray(f,b)===-1&&(b.push(f),a("<option />",{text:f}).appendTo(h))}})}),h.on("change",function(){if(d===e){var b=a.trim(this.value)||i;j.text(b)}})),b=a("#color-picker"),c=a("#colors-css"),d=a("input#user_id").val(),e=a('input[name="checkuser_id"]').val(),b.on("click.colorpicker",".color-option",function(){var b,f=a(this);if(!f.hasClass("selected")&&(f.siblings(".selected").removeClass("selected"),f.addClass("selected").find('input[type="radio"]').prop("checked",!0),d===e)){if(0===c.length&&(c=a('<link rel="stylesheet" />').appendTo("head")),c.attr("href",f.children(".css_url").val()),"undefined"!=typeof wp&&wp.svgPainter){try{b=a.parseJSON(f.children(".icon_colors").val())}catch(g){}b&&(wp.svgPainter.setColors(b),wp.svgPainter.paint())}a.post(ajaxurl,{action:"save-user-color-scheme",color_scheme:f.children('input[name="admin_color"]').val(),nonce:a("#color-nonce").val()}).done(function(b){b.success&&a("body").removeClass(b.data.previousScheme).addClass(b.data.currentScheme)})}}),f()}),a("#destroy-sessions").on("click",function(b){var c=a(this);wp.ajax.post("destroy-sessions",{nonce:a("#_wpnonce").val(),user_id:a("#user_id").val()}).done(function(a){c.prop("disabled",!0),c.siblings(".notice").remove(),c.before('<div class="notice notice-success inline"><p>'+a.message+"</p></div>")}).fail(function(a){c.siblings(".notice").remove(),c.before('<div class="notice notice-error inline"><p>'+a.message+"</p></div>")}),b.preventDefault()}),window.generatePassword=b,a(window).on("beforeunload",function(){if(!0===u)return userProfileL10n.warn})}(jQuery); \ No newline at end of file
diff --git a/www/crm/wp-admin/js/user-suggest.js b/www/crm/wp-admin/js/user-suggest.js
new file mode 100644
index 00000000..19770df0
--- /dev/null
+++ b/www/crm/wp-admin/js/user-suggest.js
@@ -0,0 +1,64 @@
+/**
+ * Suggests users in a multisite environment.
+ *
+ * For input fields where the admin can select a user based on email or
+ * username, this script shows an autocompletion menu for these inputs. Should
+ * only be used in a multisite environment. Only users in the currently active
+ * site are shown.
+ *
+ * @since 3.4.0
+ * @output wp-admin/js/user-suggest.js
+ */
+
+/* global ajaxurl, current_site_id, isRtl */
+
+(function( $ ) {
+ var id = ( typeof current_site_id !== 'undefined' ) ? '&site_id=' + current_site_id : '';
+ $(document).ready( function() {
+ var position = { offset: '0, -1' };
+ if ( typeof isRtl !== 'undefined' && isRtl ) {
+ position.my = 'right top';
+ position.at = 'right bottom';
+ }
+
+ /**
+ * Adds an autocomplete function to input fields marked with the class
+ * 'wp-suggest-user'.
+ *
+ * A minimum of two characters is required to trigger the suggestions. The
+ * autocompletion menu is shown at the left bottom of the input field. On
+ * RTL installations, it is shown at the right top. Adds the class 'open' to
+ * the input field when the autocompletion menu is shown.
+ *
+ * Does a backend call to retrieve the users.
+ *
+ * Optional data-attributes:
+ * - data-autocomplete-type (add, search)
+ * The action that is going to be performed: search for existing users
+ * or add a new one. Default: add
+ * - data-autocomplete-field (user_login, user_email)
+ * The field that is returned as the value for the suggestion.
+ * Default: user_login
+ *
+ * @see wp-admin/includes/admin-actions.php:wp_ajax_autocomplete_user()
+ */
+ $( '.wp-suggest-user' ).each( function(){
+ var $this = $( this ),
+ autocompleteType = ( typeof $this.data( 'autocompleteType' ) !== 'undefined' ) ? $this.data( 'autocompleteType' ) : 'add',
+ autocompleteField = ( typeof $this.data( 'autocompleteField' ) !== 'undefined' ) ? $this.data( 'autocompleteField' ) : 'user_login';
+
+ $this.autocomplete({
+ source: ajaxurl + '?action=autocomplete-user&autocomplete_type=' + autocompleteType + '&autocomplete_field=' + autocompleteField + id,
+ delay: 500,
+ minLength: 2,
+ position: position,
+ open: function() {
+ $( this ).addClass( 'open' );
+ },
+ close: function() {
+ $( this ).removeClass( 'open' );
+ }
+ });
+ });
+ });
+})( jQuery );
diff --git a/www/crm/wp-admin/js/user-suggest.min.js b/www/crm/wp-admin/js/user-suggest.min.js
new file mode 100644
index 00000000..9caf1e5f
--- /dev/null
+++ b/www/crm/wp-admin/js/user-suggest.min.js
@@ -0,0 +1 @@
+!function(a){var b="undefined"!=typeof current_site_id?"&site_id="+current_site_id:"";a(document).ready(function(){var c={offset:"0, -1"};"undefined"!=typeof isRtl&&isRtl&&(c.my="right top",c.at="right bottom"),a(".wp-suggest-user").each(function(){var d=a(this),e="undefined"!=typeof d.data("autocompleteType")?d.data("autocompleteType"):"add",f="undefined"!=typeof d.data("autocompleteField")?d.data("autocompleteField"):"user_login";d.autocomplete({source:ajaxurl+"?action=autocomplete-user&autocomplete_type="+e+"&autocomplete_field="+f+b,delay:500,minLength:2,position:c,open:function(){a(this).addClass("open")},close:function(){a(this).removeClass("open")}})})})}(jQuery); \ No newline at end of file
diff --git a/www/crm/wp-admin/js/widgets.js b/www/crm/wp-admin/js/widgets.js
new file mode 100644
index 00000000..00ae62db
--- /dev/null
+++ b/www/crm/wp-admin/js/widgets.js
@@ -0,0 +1,761 @@
+/**
+ * @output wp-admin/js/widgets.js
+ */
+
+/* global ajaxurl, isRtl, wpWidgets */
+
+(function($) {
+ var $document = $( document );
+
+window.wpWidgets = {
+ /**
+ * A closed Sidebar that gets a Widget dragged over it.
+ *
+ * @var {element|null}
+ */
+ hoveredSidebar: null,
+
+ /**
+ * Translations.
+ *
+ * Exported from PHP in wp_default_scripts().
+ *
+ * @var {object}
+ */
+ l10n: {
+ save: '{save}',
+ saved: '{saved}',
+ saveAlert: '{saveAlert}',
+ widgetAdded: '{widgetAdded}'
+ },
+
+ /**
+ * Lookup of which widgets have had change events triggered.
+ *
+ * @var {object}
+ */
+ dirtyWidgets: {},
+
+ init : function() {
+ var rem, the_id,
+ self = this,
+ chooser = $('.widgets-chooser'),
+ selectSidebar = chooser.find('.widgets-chooser-sidebars'),
+ sidebars = $('div.widgets-sortables'),
+ isRTL = !! ( 'undefined' !== typeof isRtl && isRtl );
+
+ // Handle the widgets containers in the right column.
+ $( '#widgets-right .sidebar-name' )
+ /*
+ * Toggle the widgets containers when clicked and update the toggle
+ * button `aria-expanded` attribute value.
+ */
+ .click( function() {
+ var $this = $( this ),
+ $wrap = $this.closest( '.widgets-holder-wrap '),
+ $toggle = $this.find( '.handlediv' );
+
+ if ( $wrap.hasClass( 'closed' ) ) {
+ $wrap.removeClass( 'closed' );
+ $toggle.attr( 'aria-expanded', 'true' );
+ // Refresh the jQuery UI sortable items.
+ $this.parent().sortable( 'refresh' );
+ } else {
+ $wrap.addClass( 'closed' );
+ $toggle.attr( 'aria-expanded', 'false' );
+ }
+
+ // Update the admin menu "sticky" state.
+ $document.triggerHandler( 'wp-pin-menu' );
+ })
+ /*
+ * Set the initial `aria-expanded` attribute value on the widgets
+ * containers toggle button. The first one is expanded by default.
+ */
+ .find( '.handlediv' ).each( function( index ) {
+ if ( 0 === index ) {
+ // jQuery equivalent of `continue` within an `each()` loop.
+ return;
+ }
+
+ $( this ).attr( 'aria-expanded', 'false' );
+ });
+
+ // Show AYS dialog when there are unsaved widget changes.
+ $( window ).on( 'beforeunload.widgets', function( event ) {
+ var dirtyWidgetIds = [], unsavedWidgetsElements;
+ $.each( self.dirtyWidgets, function( widgetId, dirty ) {
+ if ( dirty ) {
+ dirtyWidgetIds.push( widgetId );
+ }
+ });
+ if ( 0 !== dirtyWidgetIds.length ) {
+ unsavedWidgetsElements = $( '#widgets-right' ).find( '.widget' ).filter( function() {
+ return -1 !== dirtyWidgetIds.indexOf( $( this ).prop( 'id' ).replace( /^widget-\d+_/, '' ) );
+ });
+ unsavedWidgetsElements.each( function() {
+ if ( ! $( this ).hasClass( 'open' ) ) {
+ $( this ).find( '.widget-title-action:first' ).click();
+ }
+ });
+
+ // Bring the first unsaved widget into view and focus on the first tabbable field.
+ unsavedWidgetsElements.first().each( function() {
+ if ( this.scrollIntoViewIfNeeded ) {
+ this.scrollIntoViewIfNeeded();
+ } else {
+ this.scrollIntoView();
+ }
+ $( this ).find( '.widget-inside :tabbable:first' ).focus();
+ } );
+
+ event.returnValue = wpWidgets.l10n.saveAlert;
+ return event.returnValue;
+ }
+ });
+
+ // Handle the widgets containers in the left column.
+ $( '#widgets-left .sidebar-name' ).click( function() {
+ var $wrap = $( this ).closest( '.widgets-holder-wrap' );
+
+ $wrap
+ .toggleClass( 'closed' )
+ .find( '.handlediv' ).attr( 'aria-expanded', ! $wrap.hasClass( 'closed' ) );
+
+ // Update the admin menu "sticky" state.
+ $document.triggerHandler( 'wp-pin-menu' );
+ });
+
+ $(document.body).bind('click.widgets-toggle', function(e) {
+ var target = $(e.target),
+ css = { 'z-index': 100 },
+ widget, inside, targetWidth, widgetWidth, margin, saveButton, widgetId,
+ toggleBtn = target.closest( '.widget' ).find( '.widget-top button.widget-action' );
+
+ if ( target.parents('.widget-top').length && ! target.parents('#available-widgets').length ) {
+ widget = target.closest('div.widget');
+ inside = widget.children('.widget-inside');
+ targetWidth = parseInt( widget.find('input.widget-width').val(), 10 );
+ widgetWidth = widget.parent().width();
+ widgetId = inside.find( '.widget-id' ).val();
+
+ // Save button is initially disabled, but is enabled when a field is changed.
+ if ( ! widget.data( 'dirty-state-initialized' ) ) {
+ saveButton = inside.find( '.widget-control-save' );
+ saveButton.prop( 'disabled', true ).val( wpWidgets.l10n.saved );
+ inside.on( 'input change', function() {
+ self.dirtyWidgets[ widgetId ] = true;
+ widget.addClass( 'widget-dirty' );
+ saveButton.prop( 'disabled', false ).val( wpWidgets.l10n.save );
+ });
+ widget.data( 'dirty-state-initialized', true );
+ }
+
+ if ( inside.is(':hidden') ) {
+ if ( targetWidth > 250 && ( targetWidth + 30 > widgetWidth ) && widget.closest('div.widgets-sortables').length ) {
+ if ( widget.closest('div.widget-liquid-right').length ) {
+ margin = isRTL ? 'margin-right' : 'margin-left';
+ } else {
+ margin = isRTL ? 'margin-left' : 'margin-right';
+ }
+
+ css[ margin ] = widgetWidth - ( targetWidth + 30 ) + 'px';
+ widget.css( css );
+ }
+ /*
+ * Don't change the order of attributes changes and animation:
+ * it's important for screen readers, see ticket #31476.
+ */
+ toggleBtn.attr( 'aria-expanded', 'true' );
+ inside.slideDown( 'fast', function() {
+ widget.addClass( 'open' );
+ });
+ } else {
+ /*
+ * Don't change the order of attributes changes and animation:
+ * it's important for screen readers, see ticket #31476.
+ */
+ toggleBtn.attr( 'aria-expanded', 'false' );
+ inside.slideUp( 'fast', function() {
+ widget.attr( 'style', '' );
+ widget.removeClass( 'open' );
+ });
+ }
+ } else if ( target.hasClass('widget-control-save') ) {
+ wpWidgets.save( target.closest('div.widget'), 0, 1, 0 );
+ e.preventDefault();
+ } else if ( target.hasClass('widget-control-remove') ) {
+ wpWidgets.save( target.closest('div.widget'), 1, 1, 0 );
+ } else if ( target.hasClass('widget-control-close') ) {
+ widget = target.closest('div.widget');
+ widget.removeClass( 'open' );
+ toggleBtn.attr( 'aria-expanded', 'false' );
+ wpWidgets.close( widget );
+ } else if ( target.attr( 'id' ) === 'inactive-widgets-control-remove' ) {
+ wpWidgets.removeInactiveWidgets();
+ e.preventDefault();
+ }
+ });
+
+ sidebars.children('.widget').each( function() {
+ var $this = $(this);
+
+ wpWidgets.appendTitle( this );
+
+ if ( $this.find( 'p.widget-error' ).length ) {
+ $this.find( '.widget-action' ).trigger( 'click' ).attr( 'aria-expanded', 'true' );
+ }
+ });
+
+ $('#widget-list').children('.widget').draggable({
+ connectToSortable: 'div.widgets-sortables',
+ handle: '> .widget-top > .widget-title',
+ distance: 2,
+ helper: 'clone',
+ zIndex: 100,
+ containment: '#wpwrap',
+ refreshPositions: true,
+ start: function( event, ui ) {
+ var chooser = $(this).find('.widgets-chooser');
+
+ ui.helper.find('div.widget-description').hide();
+ the_id = this.id;
+
+ if ( chooser.length ) {
+ // Hide the chooser and move it out of the widget
+ $( '#wpbody-content' ).append( chooser.hide() );
+ // Delete the cloned chooser from the drag helper
+ ui.helper.find('.widgets-chooser').remove();
+ self.clearWidgetSelection();
+ }
+ },
+ stop: function() {
+ if ( rem ) {
+ $(rem).hide();
+ }
+
+ rem = '';
+ }
+ });
+
+ /**
+ * Opens and closes previously closed Sidebars when Widgets are dragged over/out of them.
+ */
+ sidebars.droppable( {
+ tolerance: 'intersect',
+
+ /**
+ * Open Sidebar when a Widget gets dragged over it.
+ *
+ * @ignore
+ *
+ * @param {object} event jQuery event object.
+ */
+ over: function( event ) {
+ var $wrap = $( event.target ).parent();
+
+ if ( wpWidgets.hoveredSidebar && ! $wrap.is( wpWidgets.hoveredSidebar ) ) {
+ // Close the previous Sidebar as the Widget has been dragged onto another Sidebar.
+ wpWidgets.closeSidebar( event );
+ }
+
+ if ( $wrap.hasClass( 'closed' ) ) {
+ wpWidgets.hoveredSidebar = $wrap;
+ $wrap
+ .removeClass( 'closed' )
+ .find( '.handlediv' ).attr( 'aria-expanded', 'true' );
+ }
+
+ $( this ).sortable( 'refresh' );
+ },
+
+ /**
+ * Close Sidebar when the Widget gets dragged out of it.
+ *
+ * @ignore
+ *
+ * @param {object} event jQuery event object.
+ */
+ out: function( event ) {
+ if ( wpWidgets.hoveredSidebar ) {
+ wpWidgets.closeSidebar( event );
+ }
+ }
+ } );
+
+ sidebars.sortable({
+ placeholder: 'widget-placeholder',
+ items: '> .widget',
+ handle: '> .widget-top > .widget-title',
+ cursor: 'move',
+ distance: 2,
+ containment: '#wpwrap',
+ tolerance: 'pointer',
+ refreshPositions: true,
+ start: function( event, ui ) {
+ var height, $this = $(this),
+ $wrap = $this.parent(),
+ inside = ui.item.children('.widget-inside');
+
+ if ( inside.css('display') === 'block' ) {
+ ui.item.removeClass('open');
+ ui.item.find( '.widget-top button.widget-action' ).attr( 'aria-expanded', 'false' );
+ inside.hide();
+ $(this).sortable('refreshPositions');
+ }
+
+ if ( ! $wrap.hasClass('closed') ) {
+ // Lock all open sidebars min-height when starting to drag.
+ // Prevents jumping when dragging a widget from an open sidebar to a closed sidebar below.
+ height = ui.item.hasClass('ui-draggable') ? $this.height() : 1 + $this.height();
+ $this.css( 'min-height', height + 'px' );
+ }
+ },
+
+ stop: function( event, ui ) {
+ var addNew, widgetNumber, $sidebar, $children, child, item,
+ $widget = ui.item,
+ id = the_id;
+
+ // Reset the var to hold a previously closed sidebar.
+ wpWidgets.hoveredSidebar = null;
+
+ if ( $widget.hasClass('deleting') ) {
+ wpWidgets.save( $widget, 1, 0, 1 ); // delete widget
+ $widget.remove();
+ return;
+ }
+
+ addNew = $widget.find('input.add_new').val();
+ widgetNumber = $widget.find('input.multi_number').val();
+
+ $widget.attr( 'style', '' ).removeClass('ui-draggable');
+ the_id = '';
+
+ if ( addNew ) {
+ if ( 'multi' === addNew ) {
+ $widget.html(
+ $widget.html().replace( /<[^<>]+>/g, function( tag ) {
+ return tag.replace( /__i__|%i%/g, widgetNumber );
+ })
+ );
+
+ $widget.attr( 'id', id.replace( '__i__', widgetNumber ) );
+ widgetNumber++;
+
+ $( 'div#' + id ).find( 'input.multi_number' ).val( widgetNumber );
+ } else if ( 'single' === addNew ) {
+ $widget.attr( 'id', 'new-' + id );
+ rem = 'div#' + id;
+ }
+
+ wpWidgets.save( $widget, 0, 0, 1 );
+ $widget.find('input.add_new').val('');
+ $document.trigger( 'widget-added', [ $widget ] );
+ }
+
+ $sidebar = $widget.parent();
+
+ if ( $sidebar.parent().hasClass('closed') ) {
+ $sidebar.parent()
+ .removeClass( 'closed' )
+ .find( '.handlediv' ).attr( 'aria-expanded', 'true' );
+
+ $children = $sidebar.children('.widget');
+
+ // Make sure the dropped widget is at the top
+ if ( $children.length > 1 ) {
+ child = $children.get(0);
+ item = $widget.get(0);
+
+ if ( child.id && item.id && child.id !== item.id ) {
+ $( child ).before( $widget );
+ }
+ }
+ }
+
+ if ( addNew ) {
+ $widget.find( '.widget-action' ).trigger( 'click' );
+ } else {
+ wpWidgets.saveOrder( $sidebar.attr('id') );
+ }
+ },
+
+ activate: function() {
+ $(this).parent().addClass( 'widget-hover' );
+ },
+
+ deactivate: function() {
+ // Remove all min-height added on "start"
+ $(this).css( 'min-height', '' ).parent().removeClass( 'widget-hover' );
+ },
+
+ receive: function( event, ui ) {
+ var $sender = $( ui.sender );
+
+ // Don't add more widgets to orphaned sidebars
+ if ( this.id.indexOf('orphaned_widgets') > -1 ) {
+ $sender.sortable('cancel');
+ return;
+ }
+
+ // If the last widget was moved out of an orphaned sidebar, close and remove it.
+ if ( $sender.attr('id').indexOf('orphaned_widgets') > -1 && ! $sender.children('.widget').length ) {
+ $sender.parents('.orphan-sidebar').slideUp( 400, function(){ $(this).remove(); } );
+ }
+ }
+ }).sortable( 'option', 'connectWith', 'div.widgets-sortables' );
+
+ $('#available-widgets').droppable({
+ tolerance: 'pointer',
+ accept: function(o){
+ return $(o).parent().attr('id') !== 'widget-list';
+ },
+ drop: function(e,ui) {
+ ui.draggable.addClass('deleting');
+ $('#removing-widget').hide().children('span').empty();
+ },
+ over: function(e,ui) {
+ ui.draggable.addClass('deleting');
+ $('div.widget-placeholder').hide();
+
+ if ( ui.draggable.hasClass('ui-sortable-helper') ) {
+ $('#removing-widget').show().children('span')
+ .html( ui.draggable.find( 'div.widget-title' ).children( 'h3' ).html() );
+ }
+ },
+ out: function(e,ui) {
+ ui.draggable.removeClass('deleting');
+ $('div.widget-placeholder').show();
+ $('#removing-widget').hide().children('span').empty();
+ }
+ });
+
+ // Area Chooser
+ $( '#widgets-right .widgets-holder-wrap' ).each( function( index, element ) {
+ var $element = $( element ),
+ name = $element.find( '.sidebar-name h2' ).text(),
+ ariaLabel = $element.find( '.sidebar-name' ).data( 'add-to' ),
+ id = $element.find( '.widgets-sortables' ).attr( 'id' ),
+ li = $( '<li>' ),
+ button = $( '<button>', {
+ type: 'button',
+ 'aria-pressed': 'false',
+ 'class': 'widgets-chooser-button',
+ 'aria-label': ariaLabel
+ } ).text( $.trim( name ) );
+
+ li.append( button );
+
+ if ( index === 0 ) {
+ li.addClass( 'widgets-chooser-selected' );
+ button.attr( 'aria-pressed', 'true' );
+ }
+
+ selectSidebar.append( li );
+ li.data( 'sidebarId', id );
+ });
+
+ $( '#available-widgets .widget .widget-top' ).on( 'click.widgets-chooser', function() {
+ var $widget = $( this ).closest( '.widget' ),
+ toggleButton = $( this ).find( '.widget-action' ),
+ chooserButtons = selectSidebar.find( '.widgets-chooser-button' );
+
+ if ( $widget.hasClass( 'widget-in-question' ) || $( '#widgets-left' ).hasClass( 'chooser' ) ) {
+ toggleButton.attr( 'aria-expanded', 'false' );
+ self.closeChooser();
+ } else {
+ // Open the chooser
+ self.clearWidgetSelection();
+ $( '#widgets-left' ).addClass( 'chooser' );
+ // Add CSS class and insert the chooser after the widget description.
+ $widget.addClass( 'widget-in-question' ).children( '.widget-description' ).after( chooser );
+ // Open the chooser with a slide down animation.
+ chooser.slideDown( 300, function() {
+ // Update the toggle button aria-expanded attribute after previous DOM manipulations.
+ toggleButton.attr( 'aria-expanded', 'true' );
+ });
+
+ chooserButtons.on( 'click.widgets-chooser', function() {
+ selectSidebar.find( '.widgets-chooser-selected' ).removeClass( 'widgets-chooser-selected' );
+ chooserButtons.attr( 'aria-pressed', 'false' );
+ $( this )
+ .attr( 'aria-pressed', 'true' )
+ .closest( 'li' ).addClass( 'widgets-chooser-selected' );
+ } );
+ }
+ });
+
+ // Add event handlers
+ chooser.on( 'click.widgets-chooser', function( event ) {
+ var $target = $( event.target );
+
+ if ( $target.hasClass('button-primary') ) {
+ self.addWidget( chooser );
+ self.closeChooser();
+ } else if ( $target.hasClass( 'widgets-chooser-cancel' ) ) {
+ self.closeChooser();
+ }
+ }).on( 'keyup.widgets-chooser', function( event ) {
+ if ( event.which === $.ui.keyCode.ESCAPE ) {
+ self.closeChooser();
+ }
+ });
+ },
+
+ saveOrder : function( sidebarId ) {
+ var data = {
+ action: 'widgets-order',
+ savewidgets: $('#_wpnonce_widgets').val(),
+ sidebars: []
+ };
+
+ if ( sidebarId ) {
+ $( '#' + sidebarId ).find( '.spinner:first' ).addClass( 'is-active' );
+ }
+
+ $('div.widgets-sortables').each( function() {
+ if ( $(this).sortable ) {
+ data['sidebars[' + $(this).attr('id') + ']'] = $(this).sortable('toArray').join(',');
+ }
+ });
+
+ $.post( ajaxurl, data, function() {
+ $( '#inactive-widgets-control-remove' ).prop( 'disabled' , ! $( '#wp_inactive_widgets .widget' ).length );
+ $( '.spinner' ).removeClass( 'is-active' );
+ });
+ },
+
+ save : function( widget, del, animate, order ) {
+ var self = this, data, a,
+ sidebarId = widget.closest( 'div.widgets-sortables' ).attr( 'id' ),
+ form = widget.find( 'form' ),
+ isAdd = widget.find( 'input.add_new' ).val();
+
+ if ( ! del && ! isAdd && form.prop( 'checkValidity' ) && ! form[0].checkValidity() ) {
+ return;
+ }
+
+ data = form.serialize();
+
+ widget = $(widget);
+ $( '.spinner', widget ).addClass( 'is-active' );
+
+ a = {
+ action: 'save-widget',
+ savewidgets: $('#_wpnonce_widgets').val(),
+ sidebar: sidebarId
+ };
+
+ if ( del ) {
+ a.delete_widget = 1;
+ }
+
+ data += '&' + $.param(a);
+
+ $.post( ajaxurl, data, function(r) {
+ var id = $('input.widget-id', widget).val();
+
+ if ( del ) {
+ if ( ! $('input.widget_number', widget).val() ) {
+ $('#available-widgets').find('input.widget-id').each(function(){
+ if ( $(this).val() === id ) {
+ $(this).closest('div.widget').show();
+ }
+ });
+ }
+
+ if ( animate ) {
+ order = 0;
+ widget.slideUp( 'fast', function() {
+ $( this ).remove();
+ wpWidgets.saveOrder();
+ delete self.dirtyWidgets[ id ];
+ });
+ } else {
+ widget.remove();
+ delete self.dirtyWidgets[ id ];
+
+ if ( sidebarId === 'wp_inactive_widgets' ) {
+ $( '#inactive-widgets-control-remove' ).prop( 'disabled' , ! $( '#wp_inactive_widgets .widget' ).length );
+ }
+ }
+ } else {
+ $( '.spinner' ).removeClass( 'is-active' );
+ if ( r && r.length > 2 ) {
+ $( 'div.widget-content', widget ).html( r );
+ wpWidgets.appendTitle( widget );
+
+ // Re-disable the save button.
+ widget.find( '.widget-control-save' ).prop( 'disabled', true ).val( wpWidgets.l10n.saved );
+
+ widget.removeClass( 'widget-dirty' );
+
+ // Clear the dirty flag from the widget.
+ delete self.dirtyWidgets[ id ];
+
+ $document.trigger( 'widget-updated', [ widget ] );
+
+ if ( sidebarId === 'wp_inactive_widgets' ) {
+ $( '#inactive-widgets-control-remove' ).prop( 'disabled' , ! $( '#wp_inactive_widgets .widget' ).length );
+ }
+ }
+ }
+
+ if ( order ) {
+ wpWidgets.saveOrder();
+ }
+ });
+ },
+
+ removeInactiveWidgets : function() {
+ var $element = $( '.remove-inactive-widgets' ), self = this, a, data;
+
+ $( '.spinner', $element ).addClass( 'is-active' );
+
+ a = {
+ action : 'delete-inactive-widgets',
+ removeinactivewidgets : $( '#_wpnonce_remove_inactive_widgets' ).val()
+ };
+
+ data = $.param( a );
+
+ $.post( ajaxurl, data, function() {
+ $( '#wp_inactive_widgets .widget' ).each(function() {
+ var $widget = $( this );
+ delete self.dirtyWidgets[ $widget.find( 'input.widget-id' ).val() ];
+ $widget.remove();
+ });
+ $( '#inactive-widgets-control-remove' ).prop( 'disabled', true );
+ $( '.spinner', $element ).removeClass( 'is-active' );
+ } );
+ },
+
+ appendTitle : function(widget) {
+ var title = $('input[id*="-title"]', widget).val() || '';
+
+ if ( title ) {
+ title = ': ' + title.replace(/<[^<>]+>/g, '').replace(/</g, '&lt;').replace(/>/g, '&gt;');
+ }
+
+ $(widget).children('.widget-top').children('.widget-title').children()
+ .children('.in-widget-title').html(title);
+
+ },
+
+ close : function(widget) {
+ widget.children('.widget-inside').slideUp('fast', function() {
+ widget.attr( 'style', '' )
+ .find( '.widget-top button.widget-action' )
+ .attr( 'aria-expanded', 'false' )
+ .focus();
+ });
+ },
+
+ addWidget: function( chooser ) {
+ var widget, widgetId, add, n, viewportTop, viewportBottom, sidebarBounds,
+ sidebarId = chooser.find( '.widgets-chooser-selected' ).data('sidebarId'),
+ sidebar = $( '#' + sidebarId );
+
+ widget = $('#available-widgets').find('.widget-in-question').clone();
+ widgetId = widget.attr('id');
+ add = widget.find( 'input.add_new' ).val();
+ n = widget.find( 'input.multi_number' ).val();
+
+ // Remove the cloned chooser from the widget
+ widget.find('.widgets-chooser').remove();
+
+ if ( 'multi' === add ) {
+ widget.html(
+ widget.html().replace( /<[^<>]+>/g, function(m) {
+ return m.replace( /__i__|%i%/g, n );
+ })
+ );
+
+ widget.attr( 'id', widgetId.replace( '__i__', n ) );
+ n++;
+ $( '#' + widgetId ).find('input.multi_number').val(n);
+ } else if ( 'single' === add ) {
+ widget.attr( 'id', 'new-' + widgetId );
+ $( '#' + widgetId ).hide();
+ }
+
+ // Open the widgets container.
+ sidebar.closest( '.widgets-holder-wrap' )
+ .removeClass( 'closed' )
+ .find( '.handlediv' ).attr( 'aria-expanded', 'true' );
+
+ sidebar.append( widget );
+ sidebar.sortable('refresh');
+
+ wpWidgets.save( widget, 0, 0, 1 );
+ // No longer "new" widget
+ widget.find( 'input.add_new' ).val('');
+
+ $document.trigger( 'widget-added', [ widget ] );
+
+ /*
+ * Check if any part of the sidebar is visible in the viewport. If it is, don't scroll.
+ * Otherwise, scroll up to so the sidebar is in view.
+ *
+ * We do this by comparing the top and bottom, of the sidebar so see if they are within
+ * the bounds of the viewport.
+ */
+ viewportTop = $(window).scrollTop();
+ viewportBottom = viewportTop + $(window).height();
+ sidebarBounds = sidebar.offset();
+
+ sidebarBounds.bottom = sidebarBounds.top + sidebar.outerHeight();
+
+ if ( viewportTop > sidebarBounds.bottom || viewportBottom < sidebarBounds.top ) {
+ $( 'html, body' ).animate({
+ scrollTop: sidebarBounds.top - 130
+ }, 200 );
+ }
+
+ window.setTimeout( function() {
+ // Cannot use a callback in the animation above as it fires twice,
+ // have to queue this "by hand".
+ widget.find( '.widget-title' ).trigger('click');
+ // At the end of the animation, announce the widget has been added.
+ window.wp.a11y.speak( wpWidgets.l10n.widgetAdded, 'assertive' );
+ }, 250 );
+ },
+
+ closeChooser: function() {
+ var self = this,
+ widgetInQuestion = $( '#available-widgets .widget-in-question' );
+
+ $( '.widgets-chooser' ).slideUp( 200, function() {
+ $( '#wpbody-content' ).append( this );
+ self.clearWidgetSelection();
+ // Move focus back to the toggle button.
+ widgetInQuestion.find( '.widget-action' ).attr( 'aria-expanded', 'false' ).focus();
+ });
+ },
+
+ clearWidgetSelection: function() {
+ $( '#widgets-left' ).removeClass( 'chooser' );
+ $( '.widget-in-question' ).removeClass( 'widget-in-question' );
+ },
+
+ /**
+ * Closes a Sidebar that was previously closed, but opened by dragging a Widget over it.
+ *
+ * Used when a Widget gets dragged in/out of the Sidebar and never dropped.
+ *
+ * @param {object} event jQuery event object.
+ */
+ closeSidebar: function( event ) {
+ this.hoveredSidebar
+ .addClass( 'closed' )
+ .find( '.handlediv' ).attr( 'aria-expanded', 'false' );
+
+ $( event.target ).css( 'min-height', '' );
+ this.hoveredSidebar = null;
+ }
+};
+
+$document.ready( function(){ wpWidgets.init(); } );
+
+})(jQuery);
diff --git a/www/crm/wp-admin/js/widgets.min.js b/www/crm/wp-admin/js/widgets.min.js
new file mode 100644
index 00000000..4e79bf77
--- /dev/null
+++ b/www/crm/wp-admin/js/widgets.min.js
@@ -0,0 +1 @@
+!function(a){var b=a(document);window.wpWidgets={hoveredSidebar:null,l10n:{save:"{save}",saved:"{saved}",saveAlert:"{saveAlert}",widgetAdded:"{widgetAdded}"},dirtyWidgets:{},init:function(){var c,d,e=this,f=a(".widgets-chooser"),g=f.find(".widgets-chooser-sidebars"),h=a("div.widgets-sortables"),i=!("undefined"==typeof isRtl||!isRtl);a("#widgets-right .sidebar-name").click(function(){var c=a(this),d=c.closest(".widgets-holder-wrap "),e=c.find(".handlediv");d.hasClass("closed")?(d.removeClass("closed"),e.attr("aria-expanded","true"),c.parent().sortable("refresh")):(d.addClass("closed"),e.attr("aria-expanded","false")),b.triggerHandler("wp-pin-menu")}).find(".handlediv").each(function(b){0!==b&&a(this).attr("aria-expanded","false")}),a(window).on("beforeunload.widgets",function(b){var c,d=[];if(a.each(e.dirtyWidgets,function(a,b){b&&d.push(a)}),0!==d.length)return c=a("#widgets-right").find(".widget").filter(function(){return-1!==d.indexOf(a(this).prop("id").replace(/^widget-\d+_/,""))}),c.each(function(){a(this).hasClass("open")||a(this).find(".widget-title-action:first").click()}),c.first().each(function(){this.scrollIntoViewIfNeeded?this.scrollIntoViewIfNeeded():this.scrollIntoView(),a(this).find(".widget-inside :tabbable:first").focus()}),b.returnValue=wpWidgets.l10n.saveAlert,b.returnValue}),a("#widgets-left .sidebar-name").click(function(){var c=a(this).closest(".widgets-holder-wrap");c.toggleClass("closed").find(".handlediv").attr("aria-expanded",!c.hasClass("closed")),b.triggerHandler("wp-pin-menu")}),a(document.body).bind("click.widgets-toggle",function(b){var c,d,f,g,h,j,k,l=a(b.target),m={"z-index":100},n=l.closest(".widget").find(".widget-top button.widget-action");l.parents(".widget-top").length&&!l.parents("#available-widgets").length?(c=l.closest("div.widget"),d=c.children(".widget-inside"),f=parseInt(c.find("input.widget-width").val(),10),g=c.parent().width(),k=d.find(".widget-id").val(),c.data("dirty-state-initialized")||(j=d.find(".widget-control-save"),j.prop("disabled",!0).val(wpWidgets.l10n.saved),d.on("input change",function(){e.dirtyWidgets[k]=!0,c.addClass("widget-dirty"),j.prop("disabled",!1).val(wpWidgets.l10n.save)}),c.data("dirty-state-initialized",!0)),d.is(":hidden")?(f>250&&f+30>g&&c.closest("div.widgets-sortables").length&&(h=c.closest("div.widget-liquid-right").length?i?"margin-right":"margin-left":i?"margin-left":"margin-right",m[h]=g-(f+30)+"px",c.css(m)),n.attr("aria-expanded","true"),d.slideDown("fast",function(){c.addClass("open")})):(n.attr("aria-expanded","false"),d.slideUp("fast",function(){c.attr("style",""),c.removeClass("open")}))):l.hasClass("widget-control-save")?(wpWidgets.save(l.closest("div.widget"),0,1,0),b.preventDefault()):l.hasClass("widget-control-remove")?wpWidgets.save(l.closest("div.widget"),1,1,0):l.hasClass("widget-control-close")?(c=l.closest("div.widget"),c.removeClass("open"),n.attr("aria-expanded","false"),wpWidgets.close(c)):"inactive-widgets-control-remove"===l.attr("id")&&(wpWidgets.removeInactiveWidgets(),b.preventDefault())}),h.children(".widget").each(function(){var b=a(this);wpWidgets.appendTitle(this),b.find("p.widget-error").length&&b.find(".widget-action").trigger("click").attr("aria-expanded","true")}),a("#widget-list").children(".widget").draggable({connectToSortable:"div.widgets-sortables",handle:"> .widget-top > .widget-title",distance:2,helper:"clone",zIndex:100,containment:"#wpwrap",refreshPositions:!0,start:function(b,c){var f=a(this).find(".widgets-chooser");c.helper.find("div.widget-description").hide(),d=this.id,f.length&&(a("#wpbody-content").append(f.hide()),c.helper.find(".widgets-chooser").remove(),e.clearWidgetSelection())},stop:function(){c&&a(c).hide(),c=""}}),h.droppable({tolerance:"intersect",over:function(b){var c=a(b.target).parent();wpWidgets.hoveredSidebar&&!c.is(wpWidgets.hoveredSidebar)&&wpWidgets.closeSidebar(b),c.hasClass("closed")&&(wpWidgets.hoveredSidebar=c,c.removeClass("closed").find(".handlediv").attr("aria-expanded","true")),a(this).sortable("refresh")},out:function(a){wpWidgets.hoveredSidebar&&wpWidgets.closeSidebar(a)}}),h.sortable({placeholder:"widget-placeholder",items:"> .widget",handle:"> .widget-top > .widget-title",cursor:"move",distance:2,containment:"#wpwrap",tolerance:"pointer",refreshPositions:!0,start:function(b,c){var d,e=a(this),f=e.parent(),g=c.item.children(".widget-inside");"block"===g.css("display")&&(c.item.removeClass("open"),c.item.find(".widget-top button.widget-action").attr("aria-expanded","false"),g.hide(),a(this).sortable("refreshPositions")),f.hasClass("closed")||(d=c.item.hasClass("ui-draggable")?e.height():1+e.height(),e.css("min-height",d+"px"))},stop:function(e,f){var g,h,i,j,k,l,m=f.item,n=d;return wpWidgets.hoveredSidebar=null,m.hasClass("deleting")?(wpWidgets.save(m,1,0,1),void m.remove()):(g=m.find("input.add_new").val(),h=m.find("input.multi_number").val(),m.attr("style","").removeClass("ui-draggable"),d="",g&&("multi"===g?(m.html(m.html().replace(/<[^<>]+>/g,function(a){return a.replace(/__i__|%i%/g,h)})),m.attr("id",n.replace("__i__",h)),h++,a("div#"+n).find("input.multi_number").val(h)):"single"===g&&(m.attr("id","new-"+n),c="div#"+n),wpWidgets.save(m,0,0,1),m.find("input.add_new").val(""),b.trigger("widget-added",[m])),i=m.parent(),i.parent().hasClass("closed")&&(i.parent().removeClass("closed").find(".handlediv").attr("aria-expanded","true"),j=i.children(".widget"),j.length>1&&(k=j.get(0),l=m.get(0),k.id&&l.id&&k.id!==l.id&&a(k).before(m))),void(g?m.find(".widget-action").trigger("click"):wpWidgets.saveOrder(i.attr("id"))))},activate:function(){a(this).parent().addClass("widget-hover")},deactivate:function(){a(this).css("min-height","").parent().removeClass("widget-hover")},receive:function(b,c){var d=a(c.sender);return this.id.indexOf("orphaned_widgets")>-1?void d.sortable("cancel"):void(d.attr("id").indexOf("orphaned_widgets")>-1&&!d.children(".widget").length&&d.parents(".orphan-sidebar").slideUp(400,function(){a(this).remove()}))}}).sortable("option","connectWith","div.widgets-sortables"),a("#available-widgets").droppable({tolerance:"pointer",accept:function(b){return"widget-list"!==a(b).parent().attr("id")},drop:function(b,c){c.draggable.addClass("deleting"),a("#removing-widget").hide().children("span").empty()},over:function(b,c){c.draggable.addClass("deleting"),a("div.widget-placeholder").hide(),c.draggable.hasClass("ui-sortable-helper")&&a("#removing-widget").show().children("span").html(c.draggable.find("div.widget-title").children("h3").html())},out:function(b,c){c.draggable.removeClass("deleting"),a("div.widget-placeholder").show(),a("#removing-widget").hide().children("span").empty()}}),a("#widgets-right .widgets-holder-wrap").each(function(b,c){var d=a(c),e=d.find(".sidebar-name h2").text(),f=d.find(".sidebar-name").data("add-to"),h=d.find(".widgets-sortables").attr("id"),i=a("<li>"),j=a("<button>",{type:"button","aria-pressed":"false","class":"widgets-chooser-button","aria-label":f}).text(a.trim(e));i.append(j),0===b&&(i.addClass("widgets-chooser-selected"),j.attr("aria-pressed","true")),g.append(i),i.data("sidebarId",h)}),a("#available-widgets .widget .widget-top").on("click.widgets-chooser",function(){var b=a(this).closest(".widget"),c=a(this).find(".widget-action"),d=g.find(".widgets-chooser-button");b.hasClass("widget-in-question")||a("#widgets-left").hasClass("chooser")?(c.attr("aria-expanded","false"),e.closeChooser()):(e.clearWidgetSelection(),a("#widgets-left").addClass("chooser"),b.addClass("widget-in-question").children(".widget-description").after(f),f.slideDown(300,function(){c.attr("aria-expanded","true")}),d.on("click.widgets-chooser",function(){g.find(".widgets-chooser-selected").removeClass("widgets-chooser-selected"),d.attr("aria-pressed","false"),a(this).attr("aria-pressed","true").closest("li").addClass("widgets-chooser-selected")}))}),f.on("click.widgets-chooser",function(b){var c=a(b.target);c.hasClass("button-primary")?(e.addWidget(f),e.closeChooser()):c.hasClass("widgets-chooser-cancel")&&e.closeChooser()}).on("keyup.widgets-chooser",function(b){b.which===a.ui.keyCode.ESCAPE&&e.closeChooser()})},saveOrder:function(b){var c={action:"widgets-order",savewidgets:a("#_wpnonce_widgets").val(),sidebars:[]};b&&a("#"+b).find(".spinner:first").addClass("is-active"),a("div.widgets-sortables").each(function(){a(this).sortable&&(c["sidebars["+a(this).attr("id")+"]"]=a(this).sortable("toArray").join(","))}),a.post(ajaxurl,c,function(){a("#inactive-widgets-control-remove").prop("disabled",!a("#wp_inactive_widgets .widget").length),a(".spinner").removeClass("is-active")})},save:function(c,d,e,f){var g,h,i=this,j=c.closest("div.widgets-sortables").attr("id"),k=c.find("form"),l=c.find("input.add_new").val();(d||l||!k.prop("checkValidity")||k[0].checkValidity())&&(g=k.serialize(),c=a(c),a(".spinner",c).addClass("is-active"),h={action:"save-widget",savewidgets:a("#_wpnonce_widgets").val(),sidebar:j},d&&(h.delete_widget=1),g+="&"+a.param(h),a.post(ajaxurl,g,function(g){var h=a("input.widget-id",c).val();d?(a("input.widget_number",c).val()||a("#available-widgets").find("input.widget-id").each(function(){a(this).val()===h&&a(this).closest("div.widget").show()}),e?(f=0,c.slideUp("fast",function(){a(this).remove(),wpWidgets.saveOrder(),delete i.dirtyWidgets[h]})):(c.remove(),delete i.dirtyWidgets[h],"wp_inactive_widgets"===j&&a("#inactive-widgets-control-remove").prop("disabled",!a("#wp_inactive_widgets .widget").length))):(a(".spinner").removeClass("is-active"),g&&g.length>2&&(a("div.widget-content",c).html(g),wpWidgets.appendTitle(c),c.find(".widget-control-save").prop("disabled",!0).val(wpWidgets.l10n.saved),c.removeClass("widget-dirty"),delete i.dirtyWidgets[h],b.trigger("widget-updated",[c]),"wp_inactive_widgets"===j&&a("#inactive-widgets-control-remove").prop("disabled",!a("#wp_inactive_widgets .widget").length))),f&&wpWidgets.saveOrder()}))},removeInactiveWidgets:function(){var b,c,d=a(".remove-inactive-widgets"),e=this;a(".spinner",d).addClass("is-active"),b={action:"delete-inactive-widgets",removeinactivewidgets:a("#_wpnonce_remove_inactive_widgets").val()},c=a.param(b),a.post(ajaxurl,c,function(){a("#wp_inactive_widgets .widget").each(function(){var b=a(this);delete e.dirtyWidgets[b.find("input.widget-id").val()],b.remove()}),a("#inactive-widgets-control-remove").prop("disabled",!0),a(".spinner",d).removeClass("is-active")})},appendTitle:function(b){var c=a('input[id*="-title"]',b).val()||"";c&&(c=": "+c.replace(/<[^<>]+>/g,"").replace(/</g,"&lt;").replace(/>/g,"&gt;")),a(b).children(".widget-top").children(".widget-title").children().children(".in-widget-title").html(c)},close:function(a){a.children(".widget-inside").slideUp("fast",function(){a.attr("style","").find(".widget-top button.widget-action").attr("aria-expanded","false").focus()})},addWidget:function(c){var d,e,f,g,h,i,j,k=c.find(".widgets-chooser-selected").data("sidebarId"),l=a("#"+k);d=a("#available-widgets").find(".widget-in-question").clone(),e=d.attr("id"),f=d.find("input.add_new").val(),g=d.find("input.multi_number").val(),d.find(".widgets-chooser").remove(),"multi"===f?(d.html(d.html().replace(/<[^<>]+>/g,function(a){return a.replace(/__i__|%i%/g,g)})),d.attr("id",e.replace("__i__",g)),g++,a("#"+e).find("input.multi_number").val(g)):"single"===f&&(d.attr("id","new-"+e),a("#"+e).hide()),l.closest(".widgets-holder-wrap").removeClass("closed").find(".handlediv").attr("aria-expanded","true"),l.append(d),l.sortable("refresh"),wpWidgets.save(d,0,0,1),d.find("input.add_new").val(""),b.trigger("widget-added",[d]),h=a(window).scrollTop(),i=h+a(window).height(),j=l.offset(),j.bottom=j.top+l.outerHeight(),(h>j.bottom||i<j.top)&&a("html, body").animate({scrollTop:j.top-130},200),window.setTimeout(function(){d.find(".widget-title").trigger("click"),window.wp.a11y.speak(wpWidgets.l10n.widgetAdded,"assertive")},250)},closeChooser:function(){var b=this,c=a("#available-widgets .widget-in-question");a(".widgets-chooser").slideUp(200,function(){a("#wpbody-content").append(this),b.clearWidgetSelection(),c.find(".widget-action").attr("aria-expanded","false").focus()})},clearWidgetSelection:function(){a("#widgets-left").removeClass("chooser"),a(".widget-in-question").removeClass("widget-in-question")},closeSidebar:function(b){this.hoveredSidebar.addClass("closed").find(".handlediv").attr("aria-expanded","false"),a(b.target).css("min-height",""),this.hoveredSidebar=null}},b.ready(function(){wpWidgets.init()})}(jQuery); \ No newline at end of file
diff --git a/www/crm/wp-admin/js/widgets/custom-html-widgets.js b/www/crm/wp-admin/js/widgets/custom-html-widgets.js
new file mode 100644
index 00000000..93aa901c
--- /dev/null
+++ b/www/crm/wp-admin/js/widgets/custom-html-widgets.js
@@ -0,0 +1,456 @@
+/**
+ * @output wp-admin/js/widgets/custom-html-widgets.js
+ */
+
+/* global wp */
+/* eslint consistent-this: [ "error", "control" ] */
+/* eslint no-magic-numbers: ["error", { "ignore": [0,1,-1] }] */
+
+/**
+ * @namespace wp.customHtmlWidget
+ * @memberOf wp
+ */
+wp.customHtmlWidgets = ( function( $ ) {
+ 'use strict';
+
+ var component = {
+ idBases: [ 'custom_html' ],
+ codeEditorSettings: {},
+ l10n: {
+ errorNotice: {
+ singular: '',
+ plural: ''
+ }
+ }
+ };
+
+ component.CustomHtmlWidgetControl = Backbone.View.extend(/** @lends wp.customHtmlWidgets.CustomHtmlWidgetControl.prototype */{
+
+ /**
+ * View events.
+ *
+ * @type {Object}
+ */
+ events: {},
+
+ /**
+ * Text widget control.
+ *
+ * @constructs wp.customHtmlWidgets.CustomHtmlWidgetControl
+ * @augments Backbone.View
+ * @abstract
+ *
+ * @param {Object} options - Options.
+ * @param {jQuery} options.el - Control field container element.
+ * @param {jQuery} options.syncContainer - Container element where fields are synced for the server.
+ *
+ * @returns {void}
+ */
+ initialize: function initialize( options ) {
+ var control = this;
+
+ if ( ! options.el ) {
+ throw new Error( 'Missing options.el' );
+ }
+ if ( ! options.syncContainer ) {
+ throw new Error( 'Missing options.syncContainer' );
+ }
+
+ Backbone.View.prototype.initialize.call( control, options );
+ control.syncContainer = options.syncContainer;
+ control.widgetIdBase = control.syncContainer.parent().find( '.id_base' ).val();
+ control.widgetNumber = control.syncContainer.parent().find( '.widget_number' ).val();
+ control.customizeSettingId = 'widget_' + control.widgetIdBase + '[' + String( control.widgetNumber ) + ']';
+
+ control.$el.addClass( 'custom-html-widget-fields' );
+ control.$el.html( wp.template( 'widget-custom-html-control-fields' )( { codeEditorDisabled: component.codeEditorSettings.disabled } ) );
+
+ control.errorNoticeContainer = control.$el.find( '.code-editor-error-container' );
+ control.currentErrorAnnotations = [];
+ control.saveButton = control.syncContainer.add( control.syncContainer.parent().find( '.widget-control-actions' ) ).find( '.widget-control-save, #savewidget' );
+ control.saveButton.addClass( 'custom-html-widget-save-button' ); // To facilitate style targeting.
+
+ control.fields = {
+ title: control.$el.find( '.title' ),
+ content: control.$el.find( '.content' )
+ };
+
+ // Sync input fields to hidden sync fields which actually get sent to the server.
+ _.each( control.fields, function( fieldInput, fieldName ) {
+ fieldInput.on( 'input change', function updateSyncField() {
+ var syncInput = control.syncContainer.find( '.sync-input.' + fieldName );
+ if ( syncInput.val() !== fieldInput.val() ) {
+ syncInput.val( fieldInput.val() );
+ syncInput.trigger( 'change' );
+ }
+ });
+
+ // Note that syncInput cannot be re-used because it will be destroyed with each widget-updated event.
+ fieldInput.val( control.syncContainer.find( '.sync-input.' + fieldName ).val() );
+ });
+ },
+
+ /**
+ * Update input fields from the sync fields.
+ *
+ * This function is called at the widget-updated and widget-synced events.
+ * A field will only be updated if it is not currently focused, to avoid
+ * overwriting content that the user is entering.
+ *
+ * @returns {void}
+ */
+ updateFields: function updateFields() {
+ var control = this, syncInput;
+
+ if ( ! control.fields.title.is( document.activeElement ) ) {
+ syncInput = control.syncContainer.find( '.sync-input.title' );
+ control.fields.title.val( syncInput.val() );
+ }
+
+ /*
+ * Prevent updating content when the editor is focused or if there are current error annotations,
+ * to prevent the editor's contents from getting sanitized as soon as a user removes focus from
+ * the editor. This is particularly important for users who cannot unfiltered_html.
+ */
+ control.contentUpdateBypassed = control.fields.content.is( document.activeElement ) || control.editor && control.editor.codemirror.state.focused || 0 !== control.currentErrorAnnotations.length;
+ if ( ! control.contentUpdateBypassed ) {
+ syncInput = control.syncContainer.find( '.sync-input.content' );
+ control.fields.content.val( syncInput.val() );
+ }
+ },
+
+ /**
+ * Show linting error notice.
+ *
+ * @param {Array} errorAnnotations - Error annotations.
+ * @returns {void}
+ */
+ updateErrorNotice: function( errorAnnotations ) {
+ var control = this, errorNotice, message = '', customizeSetting;
+
+ if ( 1 === errorAnnotations.length ) {
+ message = component.l10n.errorNotice.singular.replace( '%d', '1' );
+ } else if ( errorAnnotations.length > 1 ) {
+ message = component.l10n.errorNotice.plural.replace( '%d', String( errorAnnotations.length ) );
+ }
+
+ if ( control.fields.content[0].setCustomValidity ) {
+ control.fields.content[0].setCustomValidity( message );
+ }
+
+ if ( wp.customize && wp.customize.has( control.customizeSettingId ) ) {
+ customizeSetting = wp.customize( control.customizeSettingId );
+ customizeSetting.notifications.remove( 'htmlhint_error' );
+ if ( 0 !== errorAnnotations.length ) {
+ customizeSetting.notifications.add( 'htmlhint_error', new wp.customize.Notification( 'htmlhint_error', {
+ message: message,
+ type: 'error'
+ } ) );
+ }
+ } else if ( 0 !== errorAnnotations.length ) {
+ errorNotice = $( '<div class="inline notice notice-error notice-alt"></div>' );
+ errorNotice.append( $( '<p></p>', {
+ text: message
+ } ) );
+ control.errorNoticeContainer.empty();
+ control.errorNoticeContainer.append( errorNotice );
+ control.errorNoticeContainer.slideDown( 'fast' );
+ wp.a11y.speak( message );
+ } else {
+ control.errorNoticeContainer.slideUp( 'fast' );
+ }
+ },
+
+ /**
+ * Initialize editor.
+ *
+ * @returns {void}
+ */
+ initializeEditor: function initializeEditor() {
+ var control = this, settings;
+
+ if ( component.codeEditorSettings.disabled ) {
+ return;
+ }
+
+ settings = _.extend( {}, component.codeEditorSettings, {
+
+ /**
+ * Handle tabbing to the field before the editor.
+ *
+ * @ignore
+ *
+ * @returns {void}
+ */
+ onTabPrevious: function onTabPrevious() {
+ control.fields.title.focus();
+ },
+
+ /**
+ * Handle tabbing to the field after the editor.
+ *
+ * @ignore
+ *
+ * @returns {void}
+ */
+ onTabNext: function onTabNext() {
+ var tabbables = control.syncContainer.add( control.syncContainer.parent().find( '.widget-position, .widget-control-actions' ) ).find( ':tabbable' );
+ tabbables.first().focus();
+ },
+
+ /**
+ * Disable save button and store linting errors for use in updateFields.
+ *
+ * @ignore
+ *
+ * @param {Array} errorAnnotations - Error notifications.
+ * @returns {void}
+ */
+ onChangeLintingErrors: function onChangeLintingErrors( errorAnnotations ) {
+ control.currentErrorAnnotations = errorAnnotations;
+ },
+
+ /**
+ * Update error notice.
+ *
+ * @ignore
+ *
+ * @param {Array} errorAnnotations - Error annotations.
+ * @returns {void}
+ */
+ onUpdateErrorNotice: function onUpdateErrorNotice( errorAnnotations ) {
+ control.saveButton.toggleClass( 'validation-blocked disabled', errorAnnotations.length > 0 );
+ control.updateErrorNotice( errorAnnotations );
+ }
+ });
+
+ control.editor = wp.codeEditor.initialize( control.fields.content, settings );
+
+ // Improve the editor accessibility.
+ $( control.editor.codemirror.display.lineDiv )
+ .attr({
+ role: 'textbox',
+ 'aria-multiline': 'true',
+ 'aria-labelledby': control.fields.content[0].id + '-label',
+ 'aria-describedby': 'editor-keyboard-trap-help-1 editor-keyboard-trap-help-2 editor-keyboard-trap-help-3 editor-keyboard-trap-help-4'
+ });
+
+ // Focus the editor when clicking on its label.
+ $( '#' + control.fields.content[0].id + '-label' ).on( 'click', function() {
+ control.editor.codemirror.focus();
+ });
+
+ control.fields.content.on( 'change', function() {
+ if ( this.value !== control.editor.codemirror.getValue() ) {
+ control.editor.codemirror.setValue( this.value );
+ }
+ });
+ control.editor.codemirror.on( 'change', function() {
+ var value = control.editor.codemirror.getValue();
+ if ( value !== control.fields.content.val() ) {
+ control.fields.content.val( value ).trigger( 'change' );
+ }
+ });
+
+ // Make sure the editor gets updated if the content was updated on the server (sanitization) but not updated in the editor since it was focused.
+ control.editor.codemirror.on( 'blur', function() {
+ if ( control.contentUpdateBypassed ) {
+ control.syncContainer.find( '.sync-input.content' ).trigger( 'change' );
+ }
+ });
+
+ // Prevent hitting Esc from collapsing the widget control.
+ if ( wp.customize ) {
+ control.editor.codemirror.on( 'keydown', function onKeydown( codemirror, event ) {
+ var escKeyCode = 27;
+ if ( escKeyCode === event.keyCode ) {
+ event.stopPropagation();
+ }
+ });
+ }
+ }
+ });
+
+ /**
+ * Mapping of widget ID to instances of CustomHtmlWidgetControl subclasses.
+ *
+ * @alias wp.customHtmlWidgets.widgetControls
+ *
+ * @type {Object.<string, wp.textWidgets.CustomHtmlWidgetControl>}
+ */
+ component.widgetControls = {};
+
+ /**
+ * Handle widget being added or initialized for the first time at the widget-added event.
+ *
+ * @alias wp.customHtmlWidgets.handleWidgetAdded
+ *
+ * @param {jQuery.Event} event - Event.
+ * @param {jQuery} widgetContainer - Widget container element.
+ *
+ * @returns {void}
+ */
+ component.handleWidgetAdded = function handleWidgetAdded( event, widgetContainer ) {
+ var widgetForm, idBase, widgetControl, widgetId, animatedCheckDelay = 50, renderWhenAnimationDone, fieldContainer, syncContainer;
+ widgetForm = widgetContainer.find( '> .widget-inside > .form, > .widget-inside > form' ); // Note: '.form' appears in the customizer, whereas 'form' on the widgets admin screen.
+
+ idBase = widgetForm.find( '> .id_base' ).val();
+ if ( -1 === component.idBases.indexOf( idBase ) ) {
+ return;
+ }
+
+ // Prevent initializing already-added widgets.
+ widgetId = widgetForm.find( '.widget-id' ).val();
+ if ( component.widgetControls[ widgetId ] ) {
+ return;
+ }
+
+ /*
+ * Create a container element for the widget control fields.
+ * This is inserted into the DOM immediately before the the .widget-content
+ * element because the contents of this element are essentially "managed"
+ * by PHP, where each widget update cause the entire element to be emptied
+ * and replaced with the rendered output of WP_Widget::form() which is
+ * sent back in Ajax request made to save/update the widget instance.
+ * To prevent a "flash of replaced DOM elements and re-initialized JS
+ * components", the JS template is rendered outside of the normal form
+ * container.
+ */
+ fieldContainer = $( '<div></div>' );
+ syncContainer = widgetContainer.find( '.widget-content:first' );
+ syncContainer.before( fieldContainer );
+
+ widgetControl = new component.CustomHtmlWidgetControl({
+ el: fieldContainer,
+ syncContainer: syncContainer
+ });
+
+ component.widgetControls[ widgetId ] = widgetControl;
+
+ /*
+ * Render the widget once the widget parent's container finishes animating,
+ * as the widget-added event fires with a slideDown of the container.
+ * This ensures that the textarea is visible and the editor can be initialized.
+ */
+ renderWhenAnimationDone = function() {
+ if ( ! ( wp.customize ? widgetContainer.parent().hasClass( 'expanded' ) : widgetContainer.hasClass( 'open' ) ) ) { // Core merge: The wp.customize condition can be eliminated with this change being in core: https://github.com/xwp/wordpress-develop/pull/247/commits/5322387d
+ setTimeout( renderWhenAnimationDone, animatedCheckDelay );
+ } else {
+ widgetControl.initializeEditor();
+ }
+ };
+ renderWhenAnimationDone();
+ };
+
+ /**
+ * Setup widget in accessibility mode.
+ *
+ * @alias wp.customHtmlWidgets.setupAccessibleMode
+ *
+ * @returns {void}
+ */
+ component.setupAccessibleMode = function setupAccessibleMode() {
+ var widgetForm, idBase, widgetControl, fieldContainer, syncContainer;
+ widgetForm = $( '.editwidget > form' );
+ if ( 0 === widgetForm.length ) {
+ return;
+ }
+
+ idBase = widgetForm.find( '> .widget-control-actions > .id_base' ).val();
+ if ( -1 === component.idBases.indexOf( idBase ) ) {
+ return;
+ }
+
+ fieldContainer = $( '<div></div>' );
+ syncContainer = widgetForm.find( '> .widget-inside' );
+ syncContainer.before( fieldContainer );
+
+ widgetControl = new component.CustomHtmlWidgetControl({
+ el: fieldContainer,
+ syncContainer: syncContainer
+ });
+
+ widgetControl.initializeEditor();
+ };
+
+ /**
+ * Sync widget instance data sanitized from server back onto widget model.
+ *
+ * This gets called via the 'widget-updated' event when saving a widget from
+ * the widgets admin screen and also via the 'widget-synced' event when making
+ * a change to a widget in the customizer.
+ *
+ * @alias wp.customHtmlWidgets.handleWidgetUpdated
+ *
+ * @param {jQuery.Event} event - Event.
+ * @param {jQuery} widgetContainer - Widget container element.
+ * @returns {void}
+ */
+ component.handleWidgetUpdated = function handleWidgetUpdated( event, widgetContainer ) {
+ var widgetForm, widgetId, widgetControl, idBase;
+ widgetForm = widgetContainer.find( '> .widget-inside > .form, > .widget-inside > form' );
+
+ idBase = widgetForm.find( '> .id_base' ).val();
+ if ( -1 === component.idBases.indexOf( idBase ) ) {
+ return;
+ }
+
+ widgetId = widgetForm.find( '> .widget-id' ).val();
+ widgetControl = component.widgetControls[ widgetId ];
+ if ( ! widgetControl ) {
+ return;
+ }
+
+ widgetControl.updateFields();
+ };
+
+ /**
+ * Initialize functionality.
+ *
+ * This function exists to prevent the JS file from having to boot itself.
+ * When WordPress enqueues this script, it should have an inline script
+ * attached which calls wp.textWidgets.init().
+ *
+ * @alias wp.customHtmlWidgets.init
+ *
+ * @param {object} settings - Options for code editor, exported from PHP.
+ *
+ * @returns {void}
+ */
+ component.init = function init( settings ) {
+ var $document = $( document );
+ _.extend( component.codeEditorSettings, settings );
+
+ $document.on( 'widget-added', component.handleWidgetAdded );
+ $document.on( 'widget-synced widget-updated', component.handleWidgetUpdated );
+
+ /*
+ * Manually trigger widget-added events for media widgets on the admin
+ * screen once they are expanded. The widget-added event is not triggered
+ * for each pre-existing widget on the widgets admin screen like it is
+ * on the customizer. Likewise, the customizer only triggers widget-added
+ * when the widget is expanded to just-in-time construct the widget form
+ * when it is actually going to be displayed. So the following implements
+ * the same for the widgets admin screen, to invoke the widget-added
+ * handler when a pre-existing media widget is expanded.
+ */
+ $( function initializeExistingWidgetContainers() {
+ var widgetContainers;
+ if ( 'widgets' !== window.pagenow ) {
+ return;
+ }
+ widgetContainers = $( '.widgets-holder-wrap:not(#available-widgets)' ).find( 'div.widget' );
+ widgetContainers.one( 'click.toggle-widget-expanded', function toggleWidgetExpanded() {
+ var widgetContainer = $( this );
+ component.handleWidgetAdded( new jQuery.Event( 'widget-added' ), widgetContainer );
+ });
+
+ // Accessibility mode.
+ $( window ).on( 'load', function() {
+ component.setupAccessibleMode();
+ });
+ });
+ };
+
+ return component;
+})( jQuery );
diff --git a/www/crm/wp-admin/js/widgets/custom-html-widgets.min.js b/www/crm/wp-admin/js/widgets/custom-html-widgets.min.js
new file mode 100644
index 00000000..285d018d
--- /dev/null
+++ b/www/crm/wp-admin/js/widgets/custom-html-widgets.min.js
@@ -0,0 +1 @@
+wp.customHtmlWidgets=function(a){"use strict";var b={idBases:["custom_html"],codeEditorSettings:{},l10n:{errorNotice:{singular:"",plural:""}}};return b.CustomHtmlWidgetControl=Backbone.View.extend({events:{},initialize:function(a){var c=this;if(!a.el)throw new Error("Missing options.el");if(!a.syncContainer)throw new Error("Missing options.syncContainer");Backbone.View.prototype.initialize.call(c,a),c.syncContainer=a.syncContainer,c.widgetIdBase=c.syncContainer.parent().find(".id_base").val(),c.widgetNumber=c.syncContainer.parent().find(".widget_number").val(),c.customizeSettingId="widget_"+c.widgetIdBase+"["+String(c.widgetNumber)+"]",c.$el.addClass("custom-html-widget-fields"),c.$el.html(wp.template("widget-custom-html-control-fields")({codeEditorDisabled:b.codeEditorSettings.disabled})),c.errorNoticeContainer=c.$el.find(".code-editor-error-container"),c.currentErrorAnnotations=[],c.saveButton=c.syncContainer.add(c.syncContainer.parent().find(".widget-control-actions")).find(".widget-control-save, #savewidget"),c.saveButton.addClass("custom-html-widget-save-button"),c.fields={title:c.$el.find(".title"),content:c.$el.find(".content")},_.each(c.fields,function(a,b){a.on("input change",function(){var d=c.syncContainer.find(".sync-input."+b);d.val()!==a.val()&&(d.val(a.val()),d.trigger("change"))}),a.val(c.syncContainer.find(".sync-input."+b).val())})},updateFields:function(){var a,b=this;b.fields.title.is(document.activeElement)||(a=b.syncContainer.find(".sync-input.title"),b.fields.title.val(a.val())),b.contentUpdateBypassed=b.fields.content.is(document.activeElement)||b.editor&&b.editor.codemirror.state.focused||0!==b.currentErrorAnnotations.length,b.contentUpdateBypassed||(a=b.syncContainer.find(".sync-input.content"),b.fields.content.val(a.val()))},updateErrorNotice:function(c){var d,e,f=this,g="";1===c.length?g=b.l10n.errorNotice.singular.replace("%d","1"):c.length>1&&(g=b.l10n.errorNotice.plural.replace("%d",String(c.length))),f.fields.content[0].setCustomValidity&&f.fields.content[0].setCustomValidity(g),wp.customize&&wp.customize.has(f.customizeSettingId)?(e=wp.customize(f.customizeSettingId),e.notifications.remove("htmlhint_error"),0!==c.length&&e.notifications.add("htmlhint_error",new wp.customize.Notification("htmlhint_error",{message:g,type:"error"}))):0!==c.length?(d=a('<div class="inline notice notice-error notice-alt"></div>'),d.append(a("<p></p>",{text:g})),f.errorNoticeContainer.empty(),f.errorNoticeContainer.append(d),f.errorNoticeContainer.slideDown("fast"),wp.a11y.speak(g)):f.errorNoticeContainer.slideUp("fast")},initializeEditor:function(){var c,d=this;b.codeEditorSettings.disabled||(c=_.extend({},b.codeEditorSettings,{onTabPrevious:function(){d.fields.title.focus()},onTabNext:function(){var a=d.syncContainer.add(d.syncContainer.parent().find(".widget-position, .widget-control-actions")).find(":tabbable");a.first().focus()},onChangeLintingErrors:function(a){d.currentErrorAnnotations=a},onUpdateErrorNotice:function(a){d.saveButton.toggleClass("validation-blocked disabled",a.length>0),d.updateErrorNotice(a)}}),d.editor=wp.codeEditor.initialize(d.fields.content,c),a(d.editor.codemirror.display.lineDiv).attr({role:"textbox","aria-multiline":"true","aria-labelledby":d.fields.content[0].id+"-label","aria-describedby":"editor-keyboard-trap-help-1 editor-keyboard-trap-help-2 editor-keyboard-trap-help-3 editor-keyboard-trap-help-4"}),a("#"+d.fields.content[0].id+"-label").on("click",function(){d.editor.codemirror.focus()}),d.fields.content.on("change",function(){this.value!==d.editor.codemirror.getValue()&&d.editor.codemirror.setValue(this.value)}),d.editor.codemirror.on("change",function(){var a=d.editor.codemirror.getValue();a!==d.fields.content.val()&&d.fields.content.val(a).trigger("change")}),d.editor.codemirror.on("blur",function(){d.contentUpdateBypassed&&d.syncContainer.find(".sync-input.content").trigger("change")}),wp.customize&&d.editor.codemirror.on("keydown",function(a,b){var c=27;c===b.keyCode&&b.stopPropagation()}))}}),b.widgetControls={},b.handleWidgetAdded=function(c,d){var e,f,g,h,i,j,k,l=50;e=d.find("> .widget-inside > .form, > .widget-inside > form"),f=e.find("> .id_base").val(),-1!==b.idBases.indexOf(f)&&(h=e.find(".widget-id").val(),b.widgetControls[h]||(j=a("<div></div>"),k=d.find(".widget-content:first"),k.before(j),g=new b.CustomHtmlWidgetControl({el:j,syncContainer:k}),b.widgetControls[h]=g,(i=function(){(wp.customize?d.parent().hasClass("expanded"):d.hasClass("open"))?g.initializeEditor():setTimeout(i,l)})()))},b.setupAccessibleMode=function(){var c,d,e,f,g;c=a(".editwidget > form"),0!==c.length&&(d=c.find("> .widget-control-actions > .id_base").val(),-1!==b.idBases.indexOf(d)&&(f=a("<div></div>"),g=c.find("> .widget-inside"),g.before(f),e=new b.CustomHtmlWidgetControl({el:f,syncContainer:g}),e.initializeEditor()))},b.handleWidgetUpdated=function(a,c){var d,e,f,g;d=c.find("> .widget-inside > .form, > .widget-inside > form"),g=d.find("> .id_base").val(),-1!==b.idBases.indexOf(g)&&(e=d.find("> .widget-id").val(),f=b.widgetControls[e],f&&f.updateFields())},b.init=function(c){var d=a(document);_.extend(b.codeEditorSettings,c),d.on("widget-added",b.handleWidgetAdded),d.on("widget-synced widget-updated",b.handleWidgetUpdated),a(function(){var c;"widgets"===window.pagenow&&(c=a(".widgets-holder-wrap:not(#available-widgets)").find("div.widget"),c.one("click.toggle-widget-expanded",function(){var c=a(this);b.handleWidgetAdded(new jQuery.Event("widget-added"),c)}),a(window).on("load",function(){b.setupAccessibleMode()}))})},b}(jQuery); \ No newline at end of file
diff --git a/www/crm/wp-admin/js/widgets/media-audio-widget.js b/www/crm/wp-admin/js/widgets/media-audio-widget.js
new file mode 100644
index 00000000..e050c30a
--- /dev/null
+++ b/www/crm/wp-admin/js/widgets/media-audio-widget.js
@@ -0,0 +1,154 @@
+/**
+ * @output wp-admin/js/widgets/media-audio-widget.js
+ */
+
+/* eslint consistent-this: [ "error", "control" ] */
+(function( component ) {
+ 'use strict';
+
+ var AudioWidgetModel, AudioWidgetControl, AudioDetailsMediaFrame;
+
+ /**
+ * Custom audio details frame that removes the replace-audio state.
+ *
+ * @class wp.mediaWidgets.controlConstructors~AudioDetailsMediaFrame
+ * @augments wp.media.view.MediaFrame.AudioDetails
+ */
+ AudioDetailsMediaFrame = wp.media.view.MediaFrame.AudioDetails.extend(/** @lends wp.mediaWidgets.controlConstructors~AudioDetailsMediaFrame.prototype */{
+
+ /**
+ * Create the default states.
+ *
+ * @returns {void}
+ */
+ createStates: function createStates() {
+ this.states.add([
+ new wp.media.controller.AudioDetails({
+ media: this.media
+ }),
+
+ new wp.media.controller.MediaLibrary({
+ type: 'audio',
+ id: 'add-audio-source',
+ title: wp.media.view.l10n.audioAddSourceTitle,
+ toolbar: 'add-audio-source',
+ media: this.media,
+ menu: false
+ })
+ ]);
+ }
+ });
+
+ /**
+ * Audio widget model.
+ *
+ * See WP_Widget_Audio::enqueue_admin_scripts() for amending prototype from PHP exports.
+ *
+ * @class wp.mediaWidgets.modelConstructors.media_audio
+ * @augments wp.mediaWidgets.MediaWidgetModel
+ */
+ AudioWidgetModel = component.MediaWidgetModel.extend({});
+
+ /**
+ * Audio widget control.
+ *
+ * See WP_Widget_Audio::enqueue_admin_scripts() for amending prototype from PHP exports.
+ *
+ * @class wp.mediaWidgets.controlConstructors.media_audio
+ * @augments wp.mediaWidgets.MediaWidgetControl
+ */
+ AudioWidgetControl = component.MediaWidgetControl.extend(/** @lends wp.mediaWidgets.controlConstructors.media_audio.prototype */{
+
+ /**
+ * Show display settings.
+ *
+ * @type {boolean}
+ */
+ showDisplaySettings: false,
+
+ /**
+ * Map model props to media frame props.
+ *
+ * @param {Object} modelProps - Model props.
+ * @returns {Object} Media frame props.
+ */
+ mapModelToMediaFrameProps: function mapModelToMediaFrameProps( modelProps ) {
+ var control = this, mediaFrameProps;
+ mediaFrameProps = component.MediaWidgetControl.prototype.mapModelToMediaFrameProps.call( control, modelProps );
+ mediaFrameProps.link = 'embed';
+ return mediaFrameProps;
+ },
+
+ /**
+ * Render preview.
+ *
+ * @returns {void}
+ */
+ renderPreview: function renderPreview() {
+ var control = this, previewContainer, previewTemplate, attachmentId, attachmentUrl;
+ attachmentId = control.model.get( 'attachment_id' );
+ attachmentUrl = control.model.get( 'url' );
+
+ if ( ! attachmentId && ! attachmentUrl ) {
+ return;
+ }
+
+ previewContainer = control.$el.find( '.media-widget-preview' );
+ previewTemplate = wp.template( 'wp-media-widget-audio-preview' );
+
+ previewContainer.html( previewTemplate({
+ model: {
+ attachment_id: control.model.get( 'attachment_id' ),
+ src: attachmentUrl
+ },
+ error: control.model.get( 'error' )
+ }));
+ wp.mediaelement.initialize();
+ },
+
+ /**
+ * Open the media audio-edit frame to modify the selected item.
+ *
+ * @returns {void}
+ */
+ editMedia: function editMedia() {
+ var control = this, mediaFrame, metadata, updateCallback;
+
+ metadata = control.mapModelToMediaFrameProps( control.model.toJSON() );
+
+ // Set up the media frame.
+ mediaFrame = new AudioDetailsMediaFrame({
+ frame: 'audio',
+ state: 'audio-details',
+ metadata: metadata
+ });
+ wp.media.frame = mediaFrame;
+ mediaFrame.$el.addClass( 'media-widget' );
+
+ updateCallback = function( mediaFrameProps ) {
+
+ // Update cached attachment object to avoid having to re-fetch. This also triggers re-rendering of preview.
+ control.selectedAttachment.set( mediaFrameProps );
+
+ control.model.set( _.extend(
+ control.model.defaults(),
+ control.mapMediaToModelProps( mediaFrameProps ),
+ { error: false }
+ ) );
+ };
+
+ mediaFrame.state( 'audio-details' ).on( 'update', updateCallback );
+ mediaFrame.state( 'replace-audio' ).on( 'replace', updateCallback );
+ mediaFrame.on( 'close', function() {
+ mediaFrame.detach();
+ });
+
+ mediaFrame.open();
+ }
+ });
+
+ // Exports.
+ component.controlConstructors.media_audio = AudioWidgetControl;
+ component.modelConstructors.media_audio = AudioWidgetModel;
+
+})( wp.mediaWidgets );
diff --git a/www/crm/wp-admin/js/widgets/media-audio-widget.min.js b/www/crm/wp-admin/js/widgets/media-audio-widget.min.js
new file mode 100644
index 00000000..d816f516
--- /dev/null
+++ b/www/crm/wp-admin/js/widgets/media-audio-widget.min.js
@@ -0,0 +1 @@
+!function(a){"use strict";var b,c,d;d=wp.media.view.MediaFrame.AudioDetails.extend({createStates:function(){this.states.add([new wp.media.controller.AudioDetails({media:this.media}),new wp.media.controller.MediaLibrary({type:"audio",id:"add-audio-source",title:wp.media.view.l10n.audioAddSourceTitle,toolbar:"add-audio-source",media:this.media,menu:!1})])}}),b=a.MediaWidgetModel.extend({}),c=a.MediaWidgetControl.extend({showDisplaySettings:!1,mapModelToMediaFrameProps:function(b){var c,d=this;return c=a.MediaWidgetControl.prototype.mapModelToMediaFrameProps.call(d,b),c.link="embed",c},renderPreview:function(){var a,b,c,d,e=this;c=e.model.get("attachment_id"),d=e.model.get("url"),(c||d)&&(a=e.$el.find(".media-widget-preview"),b=wp.template("wp-media-widget-audio-preview"),a.html(b({model:{attachment_id:e.model.get("attachment_id"),src:d},error:e.model.get("error")})),wp.mediaelement.initialize())},editMedia:function(){var a,b,c,e=this;b=e.mapModelToMediaFrameProps(e.model.toJSON()),a=new d({frame:"audio",state:"audio-details",metadata:b}),wp.media.frame=a,a.$el.addClass("media-widget"),c=function(a){e.selectedAttachment.set(a),e.model.set(_.extend(e.model.defaults(),e.mapMediaToModelProps(a),{error:!1}))},a.state("audio-details").on("update",c),a.state("replace-audio").on("replace",c),a.on("close",function(){a.detach()}),a.open()}}),a.controlConstructors.media_audio=c,a.modelConstructors.media_audio=b}(wp.mediaWidgets); \ No newline at end of file
diff --git a/www/crm/wp-admin/js/widgets/media-gallery-widget.js b/www/crm/wp-admin/js/widgets/media-gallery-widget.js
new file mode 100644
index 00000000..45671e7e
--- /dev/null
+++ b/www/crm/wp-admin/js/widgets/media-gallery-widget.js
@@ -0,0 +1,341 @@
+/**
+ * @output wp-admin/js/widgets/media-gallery-widget.js
+ */
+
+/* eslint consistent-this: [ "error", "control" ] */
+(function( component ) {
+ 'use strict';
+
+ var GalleryWidgetModel, GalleryWidgetControl, GalleryDetailsMediaFrame;
+
+ /**
+ * Custom gallery details frame.
+ *
+ * @since 4.9.0
+ * @class wp.mediaWidgets~GalleryDetailsMediaFrame
+ * @augments wp.media.view.MediaFrame.Post
+ */
+ GalleryDetailsMediaFrame = wp.media.view.MediaFrame.Post.extend(/** @lends wp.mediaWidgets~GalleryDetailsMediaFrame.prototype */{
+
+ /**
+ * Create the default states.
+ *
+ * @since 4.9.0
+ * @returns {void}
+ */
+ createStates: function createStates() {
+ this.states.add([
+ new wp.media.controller.Library({
+ id: 'gallery',
+ title: wp.media.view.l10n.createGalleryTitle,
+ priority: 40,
+ toolbar: 'main-gallery',
+ filterable: 'uploaded',
+ multiple: 'add',
+ editable: true,
+
+ library: wp.media.query( _.defaults({
+ type: 'image'
+ }, this.options.library ) )
+ }),
+
+ // Gallery states.
+ new wp.media.controller.GalleryEdit({
+ library: this.options.selection,
+ editing: this.options.editing,
+ menu: 'gallery'
+ }),
+
+ new wp.media.controller.GalleryAdd()
+ ]);
+ }
+ } );
+
+ /**
+ * Gallery widget model.
+ *
+ * See WP_Widget_Gallery::enqueue_admin_scripts() for amending prototype from PHP exports.
+ *
+ * @since 4.9.0
+ *
+ * @class wp.mediaWidgets.modelConstructors.media_gallery
+ * @augments wp.mediaWidgets.MediaWidgetModel
+ */
+ GalleryWidgetModel = component.MediaWidgetModel.extend(/** @lends wp.mediaWidgets.modelConstructors.media_gallery.prototype */{} );
+
+ GalleryWidgetControl = component.MediaWidgetControl.extend(/** @lends wp.mediaWidgets.controlConstructors.media_gallery.prototype */{
+
+ /**
+ * View events.
+ *
+ * @since 4.9.0
+ * @type {object}
+ */
+ events: _.extend( {}, component.MediaWidgetControl.prototype.events, {
+ 'click .media-widget-gallery-preview': 'editMedia'
+ } ),
+
+ /**
+ * Gallery widget control.
+ *
+ * See WP_Widget_Gallery::enqueue_admin_scripts() for amending prototype from PHP exports.
+ *
+ * @constructs wp.mediaWidgets.controlConstructors.media_gallery
+ * @augments wp.mediaWidgets.MediaWidgetControl
+ *
+ * @since 4.9.0
+ * @param {Object} options - Options.
+ * @param {Backbone.Model} options.model - Model.
+ * @param {jQuery} options.el - Control field container element.
+ * @param {jQuery} options.syncContainer - Container element where fields are synced for the server.
+ * @returns {void}
+ */
+ initialize: function initialize( options ) {
+ var control = this;
+
+ component.MediaWidgetControl.prototype.initialize.call( control, options );
+
+ _.bindAll( control, 'updateSelectedAttachments', 'handleAttachmentDestroy' );
+ control.selectedAttachments = new wp.media.model.Attachments();
+ control.model.on( 'change:ids', control.updateSelectedAttachments );
+ control.selectedAttachments.on( 'change', control.renderPreview );
+ control.selectedAttachments.on( 'reset', control.renderPreview );
+ control.updateSelectedAttachments();
+
+ /*
+ * Refresh a Gallery widget partial when the user modifies one of the selected attachments.
+ * This ensures that when an attachment's caption is updated in the media modal the Gallery
+ * widget in the preview will then be refreshed to show the change. Normally doing this
+ * would not be necessary because all of the state should be contained inside the changeset,
+ * as everything done in the Customizer should not make a change to the site unless the
+ * changeset itself is published. Attachments are a current exception to this rule.
+ * For a proposal to include attachments in the customized state, see #37887.
+ */
+ if ( wp.customize && wp.customize.previewer ) {
+ control.selectedAttachments.on( 'change', function() {
+ wp.customize.previewer.send( 'refresh-widget-partial', control.model.get( 'widget_id' ) );
+ } );
+ }
+ },
+
+ /**
+ * Update the selected attachments if necessary.
+ *
+ * @since 4.9.0
+ * @returns {void}
+ */
+ updateSelectedAttachments: function updateSelectedAttachments() {
+ var control = this, newIds, oldIds, removedIds, addedIds, addedQuery;
+
+ newIds = control.model.get( 'ids' );
+ oldIds = _.pluck( control.selectedAttachments.models, 'id' );
+
+ removedIds = _.difference( oldIds, newIds );
+ _.each( removedIds, function( removedId ) {
+ control.selectedAttachments.remove( control.selectedAttachments.get( removedId ) );
+ });
+
+ addedIds = _.difference( newIds, oldIds );
+ if ( addedIds.length ) {
+ addedQuery = wp.media.query({
+ order: 'ASC',
+ orderby: 'post__in',
+ perPage: -1,
+ post__in: newIds,
+ query: true,
+ type: 'image'
+ });
+ addedQuery.more().done( function() {
+ control.selectedAttachments.reset( addedQuery.models );
+ });
+ }
+ },
+
+ /**
+ * Render preview.
+ *
+ * @since 4.9.0
+ * @returns {void}
+ */
+ renderPreview: function renderPreview() {
+ var control = this, previewContainer, previewTemplate, data;
+
+ previewContainer = control.$el.find( '.media-widget-preview' );
+ previewTemplate = wp.template( 'wp-media-widget-gallery-preview' );
+
+ data = control.previewTemplateProps.toJSON();
+ data.attachments = {};
+ control.selectedAttachments.each( function( attachment ) {
+ data.attachments[ attachment.id ] = attachment.toJSON();
+ } );
+
+ previewContainer.html( previewTemplate( data ) );
+ },
+
+ /**
+ * Determine whether there are selected attachments.
+ *
+ * @since 4.9.0
+ * @returns {boolean} Selected.
+ */
+ isSelected: function isSelected() {
+ var control = this;
+
+ if ( control.model.get( 'error' ) ) {
+ return false;
+ }
+
+ return control.model.get( 'ids' ).length > 0;
+ },
+
+ /**
+ * Open the media select frame to edit images.
+ *
+ * @since 4.9.0
+ * @returns {void}
+ */
+ editMedia: function editMedia() {
+ var control = this, selection, mediaFrame, mediaFrameProps;
+
+ selection = new wp.media.model.Selection( control.selectedAttachments.models, {
+ multiple: true
+ });
+
+ mediaFrameProps = control.mapModelToMediaFrameProps( control.model.toJSON() );
+ selection.gallery = new Backbone.Model( mediaFrameProps );
+ if ( mediaFrameProps.size ) {
+ control.displaySettings.set( 'size', mediaFrameProps.size );
+ }
+ mediaFrame = new GalleryDetailsMediaFrame({
+ frame: 'manage',
+ text: control.l10n.add_to_widget,
+ selection: selection,
+ mimeType: control.mime_type,
+ selectedDisplaySettings: control.displaySettings,
+ showDisplaySettings: control.showDisplaySettings,
+ metadata: mediaFrameProps,
+ editing: true,
+ multiple: true,
+ state: 'gallery-edit'
+ });
+ wp.media.frame = mediaFrame; // See wp.media().
+
+ // Handle selection of a media item.
+ mediaFrame.on( 'update', function onUpdate( newSelection ) {
+ var state = mediaFrame.state(), resultSelection;
+
+ resultSelection = newSelection || state.get( 'selection' );
+ if ( ! resultSelection ) {
+ return;
+ }
+
+ // Copy orderby_random from gallery state.
+ if ( resultSelection.gallery ) {
+ control.model.set( control.mapMediaToModelProps( resultSelection.gallery.toJSON() ) );
+ }
+
+ // Directly update selectedAttachments to prevent needing to do additional request.
+ control.selectedAttachments.reset( resultSelection.models );
+
+ // Update models in the widget instance.
+ control.model.set( {
+ ids: _.pluck( resultSelection.models, 'id' )
+ } );
+ } );
+
+ mediaFrame.$el.addClass( 'media-widget' );
+ mediaFrame.open();
+
+ if ( selection ) {
+ selection.on( 'destroy', control.handleAttachmentDestroy );
+ }
+ },
+
+ /**
+ * Open the media select frame to chose an item.
+ *
+ * @since 4.9.0
+ * @returns {void}
+ */
+ selectMedia: function selectMedia() {
+ var control = this, selection, mediaFrame, mediaFrameProps;
+ selection = new wp.media.model.Selection( control.selectedAttachments.models, {
+ multiple: true
+ });
+
+ mediaFrameProps = control.mapModelToMediaFrameProps( control.model.toJSON() );
+ if ( mediaFrameProps.size ) {
+ control.displaySettings.set( 'size', mediaFrameProps.size );
+ }
+ mediaFrame = new GalleryDetailsMediaFrame({
+ frame: 'select',
+ text: control.l10n.add_to_widget,
+ selection: selection,
+ mimeType: control.mime_type,
+ selectedDisplaySettings: control.displaySettings,
+ showDisplaySettings: control.showDisplaySettings,
+ metadata: mediaFrameProps,
+ state: 'gallery'
+ });
+ wp.media.frame = mediaFrame; // See wp.media().
+
+ // Handle selection of a media item.
+ mediaFrame.on( 'update', function onUpdate( newSelection ) {
+ var state = mediaFrame.state(), resultSelection;
+
+ resultSelection = newSelection || state.get( 'selection' );
+ if ( ! resultSelection ) {
+ return;
+ }
+
+ // Copy orderby_random from gallery state.
+ if ( resultSelection.gallery ) {
+ control.model.set( control.mapMediaToModelProps( resultSelection.gallery.toJSON() ) );
+ }
+
+ // Directly update selectedAttachments to prevent needing to do additional request.
+ control.selectedAttachments.reset( resultSelection.models );
+
+ // Update widget instance.
+ control.model.set( {
+ ids: _.pluck( resultSelection.models, 'id' )
+ } );
+ } );
+
+ mediaFrame.$el.addClass( 'media-widget' );
+ mediaFrame.open();
+
+ if ( selection ) {
+ selection.on( 'destroy', control.handleAttachmentDestroy );
+ }
+
+ /*
+ * Make sure focus is set inside of modal so that hitting Esc will close
+ * the modal and not inadvertently cause the widget to collapse in the customizer.
+ */
+ mediaFrame.$el.find( ':focusable:first' ).focus();
+ },
+
+ /**
+ * Clear the selected attachment when it is deleted in the media select frame.
+ *
+ * @since 4.9.0
+ * @param {wp.media.models.Attachment} attachment - Attachment.
+ * @returns {void}
+ */
+ handleAttachmentDestroy: function handleAttachmentDestroy( attachment ) {
+ var control = this;
+ control.model.set( {
+ ids: _.difference(
+ control.model.get( 'ids' ),
+ [ attachment.id ]
+ )
+ } );
+ }
+ } );
+
+ // Exports.
+ component.controlConstructors.media_gallery = GalleryWidgetControl;
+ component.modelConstructors.media_gallery = GalleryWidgetModel;
+
+})( wp.mediaWidgets );
diff --git a/www/crm/wp-admin/js/widgets/media-gallery-widget.min.js b/www/crm/wp-admin/js/widgets/media-gallery-widget.min.js
new file mode 100644
index 00000000..ec64b7d3
--- /dev/null
+++ b/www/crm/wp-admin/js/widgets/media-gallery-widget.min.js
@@ -0,0 +1 @@
+!function(a){"use strict";var b,c,d;d=wp.media.view.MediaFrame.Post.extend({createStates:function(){this.states.add([new wp.media.controller.Library({id:"gallery",title:wp.media.view.l10n.createGalleryTitle,priority:40,toolbar:"main-gallery",filterable:"uploaded",multiple:"add",editable:!0,library:wp.media.query(_.defaults({type:"image"},this.options.library))}),new wp.media.controller.GalleryEdit({library:this.options.selection,editing:this.options.editing,menu:"gallery"}),new wp.media.controller.GalleryAdd])}}),b=a.MediaWidgetModel.extend({}),c=a.MediaWidgetControl.extend({events:_.extend({},a.MediaWidgetControl.prototype.events,{"click .media-widget-gallery-preview":"editMedia"}),initialize:function(b){var c=this;a.MediaWidgetControl.prototype.initialize.call(c,b),_.bindAll(c,"updateSelectedAttachments","handleAttachmentDestroy"),c.selectedAttachments=new wp.media.model.Attachments,c.model.on("change:ids",c.updateSelectedAttachments),c.selectedAttachments.on("change",c.renderPreview),c.selectedAttachments.on("reset",c.renderPreview),c.updateSelectedAttachments(),wp.customize&&wp.customize.previewer&&c.selectedAttachments.on("change",function(){wp.customize.previewer.send("refresh-widget-partial",c.model.get("widget_id"))})},updateSelectedAttachments:function(){var a,b,c,d,e,f=this;a=f.model.get("ids"),b=_.pluck(f.selectedAttachments.models,"id"),c=_.difference(b,a),_.each(c,function(a){f.selectedAttachments.remove(f.selectedAttachments.get(a))}),d=_.difference(a,b),d.length&&(e=wp.media.query({order:"ASC",orderby:"post__in",perPage:-1,post__in:a,query:!0,type:"image"}),e.more().done(function(){f.selectedAttachments.reset(e.models)}))},renderPreview:function(){var a,b,c,d=this;a=d.$el.find(".media-widget-preview"),b=wp.template("wp-media-widget-gallery-preview"),c=d.previewTemplateProps.toJSON(),c.attachments={},d.selectedAttachments.each(function(a){c.attachments[a.id]=a.toJSON()}),a.html(b(c))},isSelected:function(){var a=this;return!a.model.get("error")&&a.model.get("ids").length>0},editMedia:function(){var a,b,c,e=this;a=new wp.media.model.Selection(e.selectedAttachments.models,{multiple:!0}),c=e.mapModelToMediaFrameProps(e.model.toJSON()),a.gallery=new Backbone.Model(c),c.size&&e.displaySettings.set("size",c.size),b=new d({frame:"manage",text:e.l10n.add_to_widget,selection:a,mimeType:e.mime_type,selectedDisplaySettings:e.displaySettings,showDisplaySettings:e.showDisplaySettings,metadata:c,editing:!0,multiple:!0,state:"gallery-edit"}),wp.media.frame=b,b.on("update",function(a){var c,d=b.state();c=a||d.get("selection"),c&&(c.gallery&&e.model.set(e.mapMediaToModelProps(c.gallery.toJSON())),e.selectedAttachments.reset(c.models),e.model.set({ids:_.pluck(c.models,"id")}))}),b.$el.addClass("media-widget"),b.open(),a&&a.on("destroy",e.handleAttachmentDestroy)},selectMedia:function(){var a,b,c,e=this;a=new wp.media.model.Selection(e.selectedAttachments.models,{multiple:!0}),c=e.mapModelToMediaFrameProps(e.model.toJSON()),c.size&&e.displaySettings.set("size",c.size),b=new d({frame:"select",text:e.l10n.add_to_widget,selection:a,mimeType:e.mime_type,selectedDisplaySettings:e.displaySettings,showDisplaySettings:e.showDisplaySettings,metadata:c,state:"gallery"}),wp.media.frame=b,b.on("update",function(a){var c,d=b.state();c=a||d.get("selection"),c&&(c.gallery&&e.model.set(e.mapMediaToModelProps(c.gallery.toJSON())),e.selectedAttachments.reset(c.models),e.model.set({ids:_.pluck(c.models,"id")}))}),b.$el.addClass("media-widget"),b.open(),a&&a.on("destroy",e.handleAttachmentDestroy),b.$el.find(":focusable:first").focus()},handleAttachmentDestroy:function(a){var b=this;b.model.set({ids:_.difference(b.model.get("ids"),[a.id])})}}),a.controlConstructors.media_gallery=c,a.modelConstructors.media_gallery=b}(wp.mediaWidgets); \ No newline at end of file
diff --git a/www/crm/wp-admin/js/widgets/media-image-widget.js b/www/crm/wp-admin/js/widgets/media-image-widget.js
new file mode 100644
index 00000000..3ea4f9be
--- /dev/null
+++ b/www/crm/wp-admin/js/widgets/media-image-widget.js
@@ -0,0 +1,170 @@
+/**
+ * @output wp-admin/js/widgets/media-image-widget.js
+ */
+
+/* eslint consistent-this: [ "error", "control" ] */
+(function( component, $ ) {
+ 'use strict';
+
+ var ImageWidgetModel, ImageWidgetControl;
+
+ /**
+ * Image widget model.
+ *
+ * See WP_Widget_Media_Image::enqueue_admin_scripts() for amending prototype from PHP exports.
+ *
+ * @class wp.mediaWidgets.modelConstructors.media_image
+ * @augments wp.mediaWidgets.MediaWidgetModel
+ */
+ ImageWidgetModel = component.MediaWidgetModel.extend({});
+
+ /**
+ * Image widget control.
+ *
+ * See WP_Widget_Media_Image::enqueue_admin_scripts() for amending prototype from PHP exports.
+ *
+ * @class wp.mediaWidgets.controlConstructors.media_audio
+ * @augments wp.mediaWidgets.MediaWidgetControl
+ */
+ ImageWidgetControl = component.MediaWidgetControl.extend(/** @lends wp.mediaWidgets.controlConstructors.media_image.prototype */{
+
+ /**
+ * View events.
+ *
+ * @type {object}
+ */
+ events: _.extend( {}, component.MediaWidgetControl.prototype.events, {
+ 'click .media-widget-preview.populated': 'editMedia'
+ } ),
+
+ /**
+ * Render preview.
+ *
+ * @returns {void}
+ */
+ renderPreview: function renderPreview() {
+ var control = this, previewContainer, previewTemplate, fieldsContainer, fieldsTemplate, linkInput;
+ if ( ! control.model.get( 'attachment_id' ) && ! control.model.get( 'url' ) ) {
+ return;
+ }
+
+ previewContainer = control.$el.find( '.media-widget-preview' );
+ previewTemplate = wp.template( 'wp-media-widget-image-preview' );
+ previewContainer.html( previewTemplate( control.previewTemplateProps.toJSON() ) );
+ previewContainer.addClass( 'populated' );
+
+ linkInput = control.$el.find( '.link' );
+ if ( ! linkInput.is( document.activeElement ) ) {
+ fieldsContainer = control.$el.find( '.media-widget-fields' );
+ fieldsTemplate = wp.template( 'wp-media-widget-image-fields' );
+ fieldsContainer.html( fieldsTemplate( control.previewTemplateProps.toJSON() ) );
+ }
+ },
+
+ /**
+ * Open the media image-edit frame to modify the selected item.
+ *
+ * @returns {void}
+ */
+ editMedia: function editMedia() {
+ var control = this, mediaFrame, updateCallback, defaultSync, metadata;
+
+ metadata = control.mapModelToMediaFrameProps( control.model.toJSON() );
+
+ // Needed or else none will not be selected if linkUrl is not also empty.
+ if ( 'none' === metadata.link ) {
+ metadata.linkUrl = '';
+ }
+
+ // Set up the media frame.
+ mediaFrame = wp.media({
+ frame: 'image',
+ state: 'image-details',
+ metadata: metadata
+ });
+ mediaFrame.$el.addClass( 'media-widget' );
+
+ updateCallback = function() {
+ var mediaProps, linkType;
+
+ // Update cached attachment object to avoid having to re-fetch. This also triggers re-rendering of preview.
+ mediaProps = mediaFrame.state().attributes.image.toJSON();
+ linkType = mediaProps.link;
+ mediaProps.link = mediaProps.linkUrl;
+ control.selectedAttachment.set( mediaProps );
+ control.displaySettings.set( 'link', linkType );
+
+ control.model.set( _.extend(
+ control.mapMediaToModelProps( mediaProps ),
+ { error: false }
+ ) );
+ };
+
+ mediaFrame.state( 'image-details' ).on( 'update', updateCallback );
+ mediaFrame.state( 'replace-image' ).on( 'replace', updateCallback );
+
+ // Disable syncing of attachment changes back to server. See <https://core.trac.wordpress.org/ticket/40403>.
+ defaultSync = wp.media.model.Attachment.prototype.sync;
+ wp.media.model.Attachment.prototype.sync = function rejectedSync() {
+ return $.Deferred().rejectWith( this ).promise();
+ };
+ mediaFrame.on( 'close', function onClose() {
+ mediaFrame.detach();
+ wp.media.model.Attachment.prototype.sync = defaultSync;
+ });
+
+ mediaFrame.open();
+ },
+
+ /**
+ * Get props which are merged on top of the model when an embed is chosen (as opposed to an attachment).
+ *
+ * @returns {Object} Reset/override props.
+ */
+ getEmbedResetProps: function getEmbedResetProps() {
+ return _.extend(
+ component.MediaWidgetControl.prototype.getEmbedResetProps.call( this ),
+ {
+ size: 'full',
+ width: 0,
+ height: 0
+ }
+ );
+ },
+
+ /**
+ * Get the instance props from the media selection frame.
+ *
+ * Prevent the image_title attribute from being initially set when adding an image from the media library.
+ *
+ * @param {wp.media.view.MediaFrame.Select} mediaFrame - Select frame.
+ * @returns {Object} Props.
+ */
+ getModelPropsFromMediaFrame: function getModelPropsFromMediaFrame( mediaFrame ) {
+ var control = this;
+ return _.omit(
+ component.MediaWidgetControl.prototype.getModelPropsFromMediaFrame.call( control, mediaFrame ),
+ 'image_title'
+ );
+ },
+
+ /**
+ * Map model props to preview template props.
+ *
+ * @returns {Object} Preview template props.
+ */
+ mapModelToPreviewTemplateProps: function mapModelToPreviewTemplateProps() {
+ var control = this, previewTemplateProps, url;
+ url = control.model.get( 'url' );
+ previewTemplateProps = component.MediaWidgetControl.prototype.mapModelToPreviewTemplateProps.call( control );
+ previewTemplateProps.currentFilename = url ? url.replace( /\?.*$/, '' ).replace( /^.+\//, '' ) : '';
+ previewTemplateProps.link_url = control.model.get( 'link_url' );
+ return previewTemplateProps;
+ }
+ });
+
+ // Exports.
+ component.controlConstructors.media_image = ImageWidgetControl;
+ component.modelConstructors.media_image = ImageWidgetModel;
+
+})( wp.mediaWidgets, jQuery );
diff --git a/www/crm/wp-admin/js/widgets/media-image-widget.min.js b/www/crm/wp-admin/js/widgets/media-image-widget.min.js
new file mode 100644
index 00000000..901d5c41
--- /dev/null
+++ b/www/crm/wp-admin/js/widgets/media-image-widget.min.js
@@ -0,0 +1 @@
+!function(a,b){"use strict";var c,d;c=a.MediaWidgetModel.extend({}),d=a.MediaWidgetControl.extend({events:_.extend({},a.MediaWidgetControl.prototype.events,{"click .media-widget-preview.populated":"editMedia"}),renderPreview:function(){var a,b,c,d,e,f=this;(f.model.get("attachment_id")||f.model.get("url"))&&(a=f.$el.find(".media-widget-preview"),b=wp.template("wp-media-widget-image-preview"),a.html(b(f.previewTemplateProps.toJSON())),a.addClass("populated"),e=f.$el.find(".link"),e.is(document.activeElement)||(c=f.$el.find(".media-widget-fields"),d=wp.template("wp-media-widget-image-fields"),c.html(d(f.previewTemplateProps.toJSON()))))},editMedia:function(){var a,c,d,e,f=this;e=f.mapModelToMediaFrameProps(f.model.toJSON()),"none"===e.link&&(e.linkUrl=""),a=wp.media({frame:"image",state:"image-details",metadata:e}),a.$el.addClass("media-widget"),c=function(){var b,c;b=a.state().attributes.image.toJSON(),c=b.link,b.link=b.linkUrl,f.selectedAttachment.set(b),f.displaySettings.set("link",c),f.model.set(_.extend(f.mapMediaToModelProps(b),{error:!1}))},a.state("image-details").on("update",c),a.state("replace-image").on("replace",c),d=wp.media.model.Attachment.prototype.sync,wp.media.model.Attachment.prototype.sync=function(){return b.Deferred().rejectWith(this).promise()},a.on("close",function(){a.detach(),wp.media.model.Attachment.prototype.sync=d}),a.open()},getEmbedResetProps:function(){return _.extend(a.MediaWidgetControl.prototype.getEmbedResetProps.call(this),{size:"full",width:0,height:0})},getModelPropsFromMediaFrame:function(b){var c=this;return _.omit(a.MediaWidgetControl.prototype.getModelPropsFromMediaFrame.call(c,b),"image_title")},mapModelToPreviewTemplateProps:function(){var b,c,d=this;return c=d.model.get("url"),b=a.MediaWidgetControl.prototype.mapModelToPreviewTemplateProps.call(d),b.currentFilename=c?c.replace(/\?.*$/,"").replace(/^.+\//,""):"",b.link_url=d.model.get("link_url"),b}}),a.controlConstructors.media_image=d,a.modelConstructors.media_image=c}(wp.mediaWidgets,jQuery); \ No newline at end of file
diff --git a/www/crm/wp-admin/js/widgets/media-video-widget.js b/www/crm/wp-admin/js/widgets/media-video-widget.js
new file mode 100644
index 00000000..b716cce4
--- /dev/null
+++ b/www/crm/wp-admin/js/widgets/media-video-widget.js
@@ -0,0 +1,256 @@
+/**
+ * @output wp-admin/js/widgets/media-video-widget.js
+ */
+
+/* eslint consistent-this: [ "error", "control" ] */
+(function( component ) {
+ 'use strict';
+
+ var VideoWidgetModel, VideoWidgetControl, VideoDetailsMediaFrame;
+
+ /**
+ * Custom video details frame that removes the replace-video state.
+ *
+ * @class wp.mediaWidgets.controlConstructors~VideoDetailsMediaFrame
+ * @augments wp.media.view.MediaFrame.VideoDetails
+ *
+ * @private
+ */
+ VideoDetailsMediaFrame = wp.media.view.MediaFrame.VideoDetails.extend(/** @lends wp.mediaWidgets.controlConstructors~VideoDetailsMediaFrame.prototype */{
+
+ /**
+ * Create the default states.
+ *
+ * @returns {void}
+ */
+ createStates: function createStates() {
+ this.states.add([
+ new wp.media.controller.VideoDetails({
+ media: this.media
+ }),
+
+ new wp.media.controller.MediaLibrary({
+ type: 'video',
+ id: 'add-video-source',
+ title: wp.media.view.l10n.videoAddSourceTitle,
+ toolbar: 'add-video-source',
+ media: this.media,
+ menu: false
+ }),
+
+ new wp.media.controller.MediaLibrary({
+ type: 'text',
+ id: 'add-track',
+ title: wp.media.view.l10n.videoAddTrackTitle,
+ toolbar: 'add-track',
+ media: this.media,
+ menu: 'video-details'
+ })
+ ]);
+ }
+ });
+
+ /**
+ * Video widget model.
+ *
+ * See WP_Widget_Video::enqueue_admin_scripts() for amending prototype from PHP exports.
+ *
+ * @class wp.mediaWidgets.modelConstructors.media_video
+ * @augments wp.mediaWidgets.MediaWidgetModel
+ */
+ VideoWidgetModel = component.MediaWidgetModel.extend({});
+
+ /**
+ * Video widget control.
+ *
+ * See WP_Widget_Video::enqueue_admin_scripts() for amending prototype from PHP exports.
+ *
+ * @class wp.mediaWidgets.controlConstructors.media_video
+ * @augments wp.mediaWidgets.MediaWidgetControl
+ */
+ VideoWidgetControl = component.MediaWidgetControl.extend(/** @lends wp.mediaWidgets.controlConstructors.media_video.prototype */{
+
+ /**
+ * Show display settings.
+ *
+ * @type {boolean}
+ */
+ showDisplaySettings: false,
+
+ /**
+ * Cache of oembed responses.
+ *
+ * @type {Object}
+ */
+ oembedResponses: {},
+
+ /**
+ * Map model props to media frame props.
+ *
+ * @param {Object} modelProps - Model props.
+ * @returns {Object} Media frame props.
+ */
+ mapModelToMediaFrameProps: function mapModelToMediaFrameProps( modelProps ) {
+ var control = this, mediaFrameProps;
+ mediaFrameProps = component.MediaWidgetControl.prototype.mapModelToMediaFrameProps.call( control, modelProps );
+ mediaFrameProps.link = 'embed';
+ return mediaFrameProps;
+ },
+
+ /**
+ * Fetches embed data for external videos.
+ *
+ * @returns {void}
+ */
+ fetchEmbed: function fetchEmbed() {
+ var control = this, url;
+ url = control.model.get( 'url' );
+
+ // If we already have a local cache of the embed response, return.
+ if ( control.oembedResponses[ url ] ) {
+ return;
+ }
+
+ // If there is an in-flight embed request, abort it.
+ if ( control.fetchEmbedDfd && 'pending' === control.fetchEmbedDfd.state() ) {
+ control.fetchEmbedDfd.abort();
+ }
+
+ control.fetchEmbedDfd = wp.apiRequest({
+ url: wp.media.view.settings.oEmbedProxyUrl,
+ data: {
+ url: control.model.get( 'url' ),
+ maxwidth: control.model.get( 'width' ),
+ maxheight: control.model.get( 'height' ),
+ discover: false
+ },
+ type: 'GET',
+ dataType: 'json',
+ context: control
+ });
+
+ control.fetchEmbedDfd.done( function( response ) {
+ control.oembedResponses[ url ] = response;
+ control.renderPreview();
+ });
+
+ control.fetchEmbedDfd.fail( function() {
+ control.oembedResponses[ url ] = null;
+ });
+ },
+
+ /**
+ * Whether a url is a supported external host.
+ *
+ * @deprecated since 4.9.
+ *
+ * @returns {boolean} Whether url is a supported video host.
+ */
+ isHostedVideo: function isHostedVideo() {
+ return true;
+ },
+
+ /**
+ * Render preview.
+ *
+ * @returns {void}
+ */
+ renderPreview: function renderPreview() {
+ var control = this, previewContainer, previewTemplate, attachmentId, attachmentUrl, poster, html = '', isOEmbed = false, mime, error, urlParser, matches;
+ attachmentId = control.model.get( 'attachment_id' );
+ attachmentUrl = control.model.get( 'url' );
+ error = control.model.get( 'error' );
+
+ if ( ! attachmentId && ! attachmentUrl ) {
+ return;
+ }
+
+ // Verify the selected attachment mime is supported.
+ mime = control.selectedAttachment.get( 'mime' );
+ if ( mime && attachmentId ) {
+ if ( ! _.contains( _.values( wp.media.view.settings.embedMimes ), mime ) ) {
+ error = 'unsupported_file_type';
+ }
+ } else if ( ! attachmentId ) {
+ urlParser = document.createElement( 'a' );
+ urlParser.href = attachmentUrl;
+ matches = urlParser.pathname.toLowerCase().match( /\.(\w+)$/ );
+ if ( matches ) {
+ if ( ! _.contains( _.keys( wp.media.view.settings.embedMimes ), matches[1] ) ) {
+ error = 'unsupported_file_type';
+ }
+ } else {
+ isOEmbed = true;
+ }
+ }
+
+ if ( isOEmbed ) {
+ control.fetchEmbed();
+ if ( control.oembedResponses[ attachmentUrl ] ) {
+ poster = control.oembedResponses[ attachmentUrl ].thumbnail_url;
+ html = control.oembedResponses[ attachmentUrl ].html.replace( /\swidth="\d+"/, ' width="100%"' ).replace( /\sheight="\d+"/, '' );
+ }
+ }
+
+ previewContainer = control.$el.find( '.media-widget-preview' );
+ previewTemplate = wp.template( 'wp-media-widget-video-preview' );
+
+ previewContainer.html( previewTemplate({
+ model: {
+ attachment_id: attachmentId,
+ html: html,
+ src: attachmentUrl,
+ poster: poster
+ },
+ is_oembed: isOEmbed,
+ error: error
+ }));
+ wp.mediaelement.initialize();
+ },
+
+ /**
+ * Open the media image-edit frame to modify the selected item.
+ *
+ * @returns {void}
+ */
+ editMedia: function editMedia() {
+ var control = this, mediaFrame, metadata, updateCallback;
+
+ metadata = control.mapModelToMediaFrameProps( control.model.toJSON() );
+
+ // Set up the media frame.
+ mediaFrame = new VideoDetailsMediaFrame({
+ frame: 'video',
+ state: 'video-details',
+ metadata: metadata
+ });
+ wp.media.frame = mediaFrame;
+ mediaFrame.$el.addClass( 'media-widget' );
+
+ updateCallback = function( mediaFrameProps ) {
+
+ // Update cached attachment object to avoid having to re-fetch. This also triggers re-rendering of preview.
+ control.selectedAttachment.set( mediaFrameProps );
+
+ control.model.set( _.extend(
+ _.omit( control.model.defaults(), 'title' ),
+ control.mapMediaToModelProps( mediaFrameProps ),
+ { error: false }
+ ) );
+ };
+
+ mediaFrame.state( 'video-details' ).on( 'update', updateCallback );
+ mediaFrame.state( 'replace-video' ).on( 'replace', updateCallback );
+ mediaFrame.on( 'close', function() {
+ mediaFrame.detach();
+ });
+
+ mediaFrame.open();
+ }
+ });
+
+ // Exports.
+ component.controlConstructors.media_video = VideoWidgetControl;
+ component.modelConstructors.media_video = VideoWidgetModel;
+
+})( wp.mediaWidgets );
diff --git a/www/crm/wp-admin/js/widgets/media-video-widget.min.js b/www/crm/wp-admin/js/widgets/media-video-widget.min.js
new file mode 100644
index 00000000..0df82322
--- /dev/null
+++ b/www/crm/wp-admin/js/widgets/media-video-widget.min.js
@@ -0,0 +1 @@
+!function(a){"use strict";var b,c,d;d=wp.media.view.MediaFrame.VideoDetails.extend({createStates:function(){this.states.add([new wp.media.controller.VideoDetails({media:this.media}),new wp.media.controller.MediaLibrary({type:"video",id:"add-video-source",title:wp.media.view.l10n.videoAddSourceTitle,toolbar:"add-video-source",media:this.media,menu:!1}),new wp.media.controller.MediaLibrary({type:"text",id:"add-track",title:wp.media.view.l10n.videoAddTrackTitle,toolbar:"add-track",media:this.media,menu:"video-details"})])}}),b=a.MediaWidgetModel.extend({}),c=a.MediaWidgetControl.extend({showDisplaySettings:!1,oembedResponses:{},mapModelToMediaFrameProps:function(b){var c,d=this;return c=a.MediaWidgetControl.prototype.mapModelToMediaFrameProps.call(d,b),c.link="embed",c},fetchEmbed:function(){var a,b=this;a=b.model.get("url"),b.oembedResponses[a]||(b.fetchEmbedDfd&&"pending"===b.fetchEmbedDfd.state()&&b.fetchEmbedDfd.abort(),b.fetchEmbedDfd=wp.apiRequest({url:wp.media.view.settings.oEmbedProxyUrl,data:{url:b.model.get("url"),maxwidth:b.model.get("width"),maxheight:b.model.get("height"),discover:!1},type:"GET",dataType:"json",context:b}),b.fetchEmbedDfd.done(function(c){b.oembedResponses[a]=c,b.renderPreview()}),b.fetchEmbedDfd.fail(function(){b.oembedResponses[a]=null}))},isHostedVideo:function(){return!0},renderPreview:function(){var a,b,c,d,e,f,g,h,i,j=this,k="",l=!1;c=j.model.get("attachment_id"),d=j.model.get("url"),g=j.model.get("error"),(c||d)&&(f=j.selectedAttachment.get("mime"),f&&c?_.contains(_.values(wp.media.view.settings.embedMimes),f)||(g="unsupported_file_type"):c||(h=document.createElement("a"),h.href=d,i=h.pathname.toLowerCase().match(/\.(\w+)$/),i?_.contains(_.keys(wp.media.view.settings.embedMimes),i[1])||(g="unsupported_file_type"):l=!0),l&&(j.fetchEmbed(),j.oembedResponses[d]&&(e=j.oembedResponses[d].thumbnail_url,k=j.oembedResponses[d].html.replace(/\swidth="\d+"/,' width="100%"').replace(/\sheight="\d+"/,""))),a=j.$el.find(".media-widget-preview"),b=wp.template("wp-media-widget-video-preview"),a.html(b({model:{attachment_id:c,html:k,src:d,poster:e},is_oembed:l,error:g})),wp.mediaelement.initialize())},editMedia:function(){var a,b,c,e=this;b=e.mapModelToMediaFrameProps(e.model.toJSON()),a=new d({frame:"video",state:"video-details",metadata:b}),wp.media.frame=a,a.$el.addClass("media-widget"),c=function(a){e.selectedAttachment.set(a),e.model.set(_.extend(_.omit(e.model.defaults(),"title"),e.mapMediaToModelProps(a),{error:!1}))},a.state("video-details").on("update",c),a.state("replace-video").on("replace",c),a.on("close",function(){a.detach()}),a.open()}}),a.controlConstructors.media_video=c,a.modelConstructors.media_video=b}(wp.mediaWidgets); \ No newline at end of file
diff --git a/www/crm/wp-admin/js/widgets/media-widgets.js b/www/crm/wp-admin/js/widgets/media-widgets.js
new file mode 100644
index 00000000..31d093b4
--- /dev/null
+++ b/www/crm/wp-admin/js/widgets/media-widgets.js
@@ -0,0 +1,1334 @@
+/**
+ * @output wp-admin/js/widgets/media-widgets.js
+ */
+
+/* eslint consistent-this: [ "error", "control" ] */
+
+/**
+ * @namespace wp.mediaWidgets
+ * @memberOf wp
+ */
+wp.mediaWidgets = ( function( $ ) {
+ 'use strict';
+
+ var component = {};
+
+ /**
+ * Widget control (view) constructors, mapping widget id_base to subclass of MediaWidgetControl.
+ *
+ * Media widgets register themselves by assigning subclasses of MediaWidgetControl onto this object by widget ID base.
+ *
+ * @memberOf wp.mediaWidgets
+ *
+ * @type {Object.<string, wp.mediaWidgets.MediaWidgetModel>}
+ */
+ component.controlConstructors = {};
+
+ /**
+ * Widget model constructors, mapping widget id_base to subclass of MediaWidgetModel.
+ *
+ * Media widgets register themselves by assigning subclasses of MediaWidgetControl onto this object by widget ID base.
+ *
+ * @memberOf wp.mediaWidgets
+ *
+ * @type {Object.<string, wp.mediaWidgets.MediaWidgetModel>}
+ */
+ component.modelConstructors = {};
+
+ component.PersistentDisplaySettingsLibrary = wp.media.controller.Library.extend(/** @lends wp.mediaWidgets.PersistentDisplaySettingsLibrary.prototype */{
+
+ /**
+ * Library which persists the customized display settings across selections.
+ *
+ * @constructs wp.mediaWidgets.PersistentDisplaySettingsLibrary
+ * @augments wp.media.controller.Library
+ *
+ * @param {Object} options - Options.
+ *
+ * @returns {void}
+ */
+ initialize: function initialize( options ) {
+ _.bindAll( this, 'handleDisplaySettingChange' );
+ wp.media.controller.Library.prototype.initialize.call( this, options );
+ },
+
+ /**
+ * Sync changes to the current display settings back into the current customized.
+ *
+ * @param {Backbone.Model} displaySettings - Modified display settings.
+ * @returns {void}
+ */
+ handleDisplaySettingChange: function handleDisplaySettingChange( displaySettings ) {
+ this.get( 'selectedDisplaySettings' ).set( displaySettings.attributes );
+ },
+
+ /**
+ * Get the display settings model.
+ *
+ * Model returned is updated with the current customized display settings,
+ * and an event listener is added so that changes made to the settings
+ * will sync back into the model storing the session's customized display
+ * settings.
+ *
+ * @param {Backbone.Model} model - Display settings model.
+ * @returns {Backbone.Model} Display settings model.
+ */
+ display: function getDisplaySettingsModel( model ) {
+ var display, selectedDisplaySettings = this.get( 'selectedDisplaySettings' );
+ display = wp.media.controller.Library.prototype.display.call( this, model );
+
+ display.off( 'change', this.handleDisplaySettingChange ); // Prevent duplicated event handlers.
+ display.set( selectedDisplaySettings.attributes );
+ if ( 'custom' === selectedDisplaySettings.get( 'link_type' ) ) {
+ display.linkUrl = selectedDisplaySettings.get( 'link_url' );
+ }
+ display.on( 'change', this.handleDisplaySettingChange );
+ return display;
+ }
+ });
+
+ /**
+ * Extended view for managing the embed UI.
+ *
+ * @class wp.mediaWidgets.MediaEmbedView
+ * @augments wp.media.view.Embed
+ */
+ component.MediaEmbedView = wp.media.view.Embed.extend(/** @lends wp.mediaWidgets.MediaEmbedView.prototype */{
+
+ /**
+ * Initialize.
+ *
+ * @since 4.9.0
+ *
+ * @param {object} options - Options.
+ * @returns {void}
+ */
+ initialize: function( options ) {
+ var view = this, embedController; // eslint-disable-line consistent-this
+ wp.media.view.Embed.prototype.initialize.call( view, options );
+ if ( 'image' !== view.controller.options.mimeType ) {
+ embedController = view.controller.states.get( 'embed' );
+ embedController.off( 'scan', embedController.scanImage, embedController );
+ }
+ },
+
+ /**
+ * Refresh embed view.
+ *
+ * Forked override of {wp.media.view.Embed#refresh()} to suppress irrelevant "link text" field.
+ *
+ * @returns {void}
+ */
+ refresh: function refresh() {
+ /**
+ * @class wp.mediaWidgets~Constructor
+ */
+ var Constructor;
+
+ if ( 'image' === this.controller.options.mimeType ) {
+ Constructor = wp.media.view.EmbedImage;
+ } else {
+
+ // This should be eliminated once #40450 lands of when this is merged into core.
+ Constructor = wp.media.view.EmbedLink.extend(/** @lends wp.mediaWidgets~Constructor.prototype */{
+
+ /**
+ * Set the disabled state on the Add to Widget button.
+ *
+ * @param {boolean} disabled - Disabled.
+ * @returns {void}
+ */
+ setAddToWidgetButtonDisabled: function setAddToWidgetButtonDisabled( disabled ) {
+ this.views.parent.views.parent.views.get( '.media-frame-toolbar' )[0].$el.find( '.media-button-select' ).prop( 'disabled', disabled );
+ },
+
+ /**
+ * Set or clear an error notice.
+ *
+ * @param {string} notice - Notice.
+ * @returns {void}
+ */
+ setErrorNotice: function setErrorNotice( notice ) {
+ var embedLinkView = this, noticeContainer; // eslint-disable-line consistent-this
+
+ noticeContainer = embedLinkView.views.parent.$el.find( '> .notice:first-child' );
+ if ( ! notice ) {
+ if ( noticeContainer.length ) {
+ noticeContainer.slideUp( 'fast' );
+ }
+ } else {
+ if ( ! noticeContainer.length ) {
+ noticeContainer = $( '<div class="media-widget-embed-notice notice notice-error notice-alt"></div>' );
+ noticeContainer.hide();
+ embedLinkView.views.parent.$el.prepend( noticeContainer );
+ }
+ noticeContainer.empty();
+ noticeContainer.append( $( '<p>', {
+ html: notice
+ }));
+ noticeContainer.slideDown( 'fast' );
+ }
+ },
+
+ /**
+ * Update oEmbed.
+ *
+ * @since 4.9.0
+ *
+ * @returns {void}
+ */
+ updateoEmbed: function() {
+ var embedLinkView = this, url; // eslint-disable-line consistent-this
+
+ url = embedLinkView.model.get( 'url' );
+
+ // Abort if the URL field was emptied out.
+ if ( ! url ) {
+ embedLinkView.setErrorNotice( '' );
+ embedLinkView.setAddToWidgetButtonDisabled( true );
+ return;
+ }
+
+ if ( ! url.match( /^(http|https):\/\/.+\// ) ) {
+ embedLinkView.controller.$el.find( '#embed-url-field' ).addClass( 'invalid' );
+ embedLinkView.setAddToWidgetButtonDisabled( true );
+ }
+
+ wp.media.view.EmbedLink.prototype.updateoEmbed.call( embedLinkView );
+ },
+
+ /**
+ * Fetch media.
+ *
+ * @returns {void}
+ */
+ fetch: function() {
+ var embedLinkView = this, fetchSuccess, matches, fileExt, urlParser, url, re, youTubeEmbedMatch; // eslint-disable-line consistent-this
+ url = embedLinkView.model.get( 'url' );
+
+ if ( embedLinkView.dfd && 'pending' === embedLinkView.dfd.state() ) {
+ embedLinkView.dfd.abort();
+ }
+
+ fetchSuccess = function( response ) {
+ embedLinkView.renderoEmbed({
+ data: {
+ body: response
+ }
+ });
+
+ embedLinkView.controller.$el.find( '#embed-url-field' ).removeClass( 'invalid' );
+ embedLinkView.setErrorNotice( '' );
+ embedLinkView.setAddToWidgetButtonDisabled( false );
+ };
+
+ urlParser = document.createElement( 'a' );
+ urlParser.href = url;
+ matches = urlParser.pathname.toLowerCase().match( /\.(\w+)$/ );
+ if ( matches ) {
+ fileExt = matches[1];
+ if ( ! wp.media.view.settings.embedMimes[ fileExt ] ) {
+ embedLinkView.renderFail();
+ } else if ( 0 !== wp.media.view.settings.embedMimes[ fileExt ].indexOf( embedLinkView.controller.options.mimeType ) ) {
+ embedLinkView.renderFail();
+ } else {
+ fetchSuccess( '<!--success-->' );
+ }
+ return;
+ }
+
+ // Support YouTube embed links.
+ re = /https?:\/\/www\.youtube\.com\/embed\/([^/]+)/;
+ youTubeEmbedMatch = re.exec( url );
+ if ( youTubeEmbedMatch ) {
+ url = 'https://www.youtube.com/watch?v=' + youTubeEmbedMatch[ 1 ];
+ // silently change url to proper oembed-able version.
+ embedLinkView.model.attributes.url = url;
+ }
+
+ embedLinkView.dfd = wp.apiRequest({
+ url: wp.media.view.settings.oEmbedProxyUrl,
+ data: {
+ url: url,
+ maxwidth: embedLinkView.model.get( 'width' ),
+ maxheight: embedLinkView.model.get( 'height' ),
+ discover: false
+ },
+ type: 'GET',
+ dataType: 'json',
+ context: embedLinkView
+ });
+
+ embedLinkView.dfd.done( function( response ) {
+ if ( embedLinkView.controller.options.mimeType !== response.type ) {
+ embedLinkView.renderFail();
+ return;
+ }
+ fetchSuccess( response.html );
+ });
+ embedLinkView.dfd.fail( _.bind( embedLinkView.renderFail, embedLinkView ) );
+ },
+
+ /**
+ * Handle render failure.
+ *
+ * Overrides the {EmbedLink#renderFail()} method to prevent showing the "Link Text" field.
+ * The element is getting display:none in the stylesheet, but the underlying method uses
+ * uses {jQuery.fn.show()} which adds an inline style. This avoids the need for !important.
+ *
+ * @returns {void}
+ */
+ renderFail: function renderFail() {
+ var embedLinkView = this; // eslint-disable-line consistent-this
+ embedLinkView.controller.$el.find( '#embed-url-field' ).addClass( 'invalid' );
+ embedLinkView.setErrorNotice( embedLinkView.controller.options.invalidEmbedTypeError || 'ERROR' );
+ embedLinkView.setAddToWidgetButtonDisabled( true );
+ }
+ });
+ }
+
+ this.settings( new Constructor({
+ controller: this.controller,
+ model: this.model.props,
+ priority: 40
+ }));
+ }
+ });
+
+ /**
+ * Custom media frame for selecting uploaded media or providing media by URL.
+ *
+ * @class wp.mediaWidgets.MediaFrameSelect
+ * @augments wp.media.view.MediaFrame.Post
+ */
+ component.MediaFrameSelect = wp.media.view.MediaFrame.Post.extend(/** @lends wp.mediaWidgets.MediaFrameSelect.prototype */{
+
+ /**
+ * Create the default states.
+ *
+ * @returns {void}
+ */
+ createStates: function createStates() {
+ var mime = this.options.mimeType, specificMimes = [];
+ _.each( wp.media.view.settings.embedMimes, function( embedMime ) {
+ if ( 0 === embedMime.indexOf( mime ) ) {
+ specificMimes.push( embedMime );
+ }
+ });
+ if ( specificMimes.length > 0 ) {
+ mime = specificMimes;
+ }
+
+ this.states.add([
+
+ // Main states.
+ new component.PersistentDisplaySettingsLibrary({
+ id: 'insert',
+ title: this.options.title,
+ selection: this.options.selection,
+ priority: 20,
+ toolbar: 'main-insert',
+ filterable: 'dates',
+ library: wp.media.query({
+ type: mime
+ }),
+ multiple: false,
+ editable: true,
+
+ selectedDisplaySettings: this.options.selectedDisplaySettings,
+ displaySettings: _.isUndefined( this.options.showDisplaySettings ) ? true : this.options.showDisplaySettings,
+ displayUserSettings: false // We use the display settings from the current/default widget instance props.
+ }),
+
+ new wp.media.controller.EditImage({ model: this.options.editImage }),
+
+ // Embed states.
+ new wp.media.controller.Embed({
+ metadata: this.options.metadata,
+ type: 'image' === this.options.mimeType ? 'image' : 'link',
+ invalidEmbedTypeError: this.options.invalidEmbedTypeError
+ })
+ ]);
+ },
+
+ /**
+ * Main insert toolbar.
+ *
+ * Forked override of {wp.media.view.MediaFrame.Post#mainInsertToolbar()} to override text.
+ *
+ * @param {wp.Backbone.View} view - Toolbar view.
+ * @this {wp.media.controller.Library}
+ * @returns {void}
+ */
+ mainInsertToolbar: function mainInsertToolbar( view ) {
+ var controller = this; // eslint-disable-line consistent-this
+ view.set( 'insert', {
+ style: 'primary',
+ priority: 80,
+ text: controller.options.text, // The whole reason for the fork.
+ requires: { selection: true },
+
+ /**
+ * Handle click.
+ *
+ * @ignore
+ *
+ * @fires wp.media.controller.State#insert()
+ * @returns {void}
+ */
+ click: function onClick() {
+ var state = controller.state(),
+ selection = state.get( 'selection' );
+
+ controller.close();
+ state.trigger( 'insert', selection ).reset();
+ }
+ });
+ },
+
+ /**
+ * Main embed toolbar.
+ *
+ * Forked override of {wp.media.view.MediaFrame.Post#mainEmbedToolbar()} to override text.
+ *
+ * @param {wp.Backbone.View} toolbar - Toolbar view.
+ * @this {wp.media.controller.Library}
+ * @returns {void}
+ */
+ mainEmbedToolbar: function mainEmbedToolbar( toolbar ) {
+ toolbar.view = new wp.media.view.Toolbar.Embed({
+ controller: this,
+ text: this.options.text,
+ event: 'insert'
+ });
+ },
+
+ /**
+ * Embed content.
+ *
+ * Forked override of {wp.media.view.MediaFrame.Post#embedContent()} to suppress irrelevant "link text" field.
+ *
+ * @returns {void}
+ */
+ embedContent: function embedContent() {
+ var view = new component.MediaEmbedView({
+ controller: this,
+ model: this.state()
+ }).render();
+
+ this.content.set( view );
+
+ if ( ! wp.media.isTouchDevice ) {
+ view.url.focus();
+ }
+ }
+ });
+
+ component.MediaWidgetControl = Backbone.View.extend(/** @lends wp.mediaWidgets.MediaWidgetControl.prototype */{
+
+ /**
+ * Translation strings.
+ *
+ * The mapping of translation strings is handled by media widget subclasses,
+ * exported from PHP to JS such as is done in WP_Widget_Media_Image::enqueue_admin_scripts().
+ *
+ * @type {Object}
+ */
+ l10n: {
+ add_to_widget: '{{add_to_widget}}',
+ add_media: '{{add_media}}'
+ },
+
+ /**
+ * Widget ID base.
+ *
+ * This may be defined by the subclass. It may be exported from PHP to JS
+ * such as is done in WP_Widget_Media_Image::enqueue_admin_scripts(). If not,
+ * it will attempt to be discovered by looking to see if this control
+ * instance extends each member of component.controlConstructors, and if
+ * it does extend one, will use the key as the id_base.
+ *
+ * @type {string}
+ */
+ id_base: '',
+
+ /**
+ * Mime type.
+ *
+ * This must be defined by the subclass. It may be exported from PHP to JS
+ * such as is done in WP_Widget_Media_Image::enqueue_admin_scripts().
+ *
+ * @type {string}
+ */
+ mime_type: '',
+
+ /**
+ * View events.
+ *
+ * @type {Object}
+ */
+ events: {
+ 'click .notice-missing-attachment a': 'handleMediaLibraryLinkClick',
+ 'click .select-media': 'selectMedia',
+ 'click .placeholder': 'selectMedia',
+ 'click .edit-media': 'editMedia'
+ },
+
+ /**
+ * Show display settings.
+ *
+ * @type {boolean}
+ */
+ showDisplaySettings: true,
+
+ /**
+ * Media Widget Control.
+ *
+ * @constructs wp.mediaWidgets.MediaWidgetControl
+ * @augments Backbone.View
+ * @abstract
+ *
+ * @param {Object} options - Options.
+ * @param {Backbone.Model} options.model - Model.
+ * @param {jQuery} options.el - Control field container element.
+ * @param {jQuery} options.syncContainer - Container element where fields are synced for the server.
+ *
+ * @returns {void}
+ */
+ initialize: function initialize( options ) {
+ var control = this;
+
+ Backbone.View.prototype.initialize.call( control, options );
+
+ if ( ! ( control.model instanceof component.MediaWidgetModel ) ) {
+ throw new Error( 'Missing options.model' );
+ }
+ if ( ! options.el ) {
+ throw new Error( 'Missing options.el' );
+ }
+ if ( ! options.syncContainer ) {
+ throw new Error( 'Missing options.syncContainer' );
+ }
+
+ control.syncContainer = options.syncContainer;
+
+ control.$el.addClass( 'media-widget-control' );
+
+ // Allow methods to be passed in with control context preserved.
+ _.bindAll( control, 'syncModelToInputs', 'render', 'updateSelectedAttachment', 'renderPreview' );
+
+ if ( ! control.id_base ) {
+ _.find( component.controlConstructors, function( Constructor, idBase ) {
+ if ( control instanceof Constructor ) {
+ control.id_base = idBase;
+ return true;
+ }
+ return false;
+ });
+ if ( ! control.id_base ) {
+ throw new Error( 'Missing id_base.' );
+ }
+ }
+
+ // Track attributes needed to renderPreview in it's own model.
+ control.previewTemplateProps = new Backbone.Model( control.mapModelToPreviewTemplateProps() );
+
+ // Re-render the preview when the attachment changes.
+ control.selectedAttachment = new wp.media.model.Attachment();
+ control.renderPreview = _.debounce( control.renderPreview );
+ control.listenTo( control.previewTemplateProps, 'change', control.renderPreview );
+
+ // Make sure a copy of the selected attachment is always fetched.
+ control.model.on( 'change:attachment_id', control.updateSelectedAttachment );
+ control.model.on( 'change:url', control.updateSelectedAttachment );
+ control.updateSelectedAttachment();
+
+ /*
+ * Sync the widget instance model attributes onto the hidden inputs that widgets currently use to store the state.
+ * In the future, when widgets are JS-driven, the underlying widget instance data should be exposed as a model
+ * from the start, without having to sync with hidden fields. See <https://core.trac.wordpress.org/ticket/33507>.
+ */
+ control.listenTo( control.model, 'change', control.syncModelToInputs );
+ control.listenTo( control.model, 'change', control.syncModelToPreviewProps );
+ control.listenTo( control.model, 'change', control.render );
+
+ // Update the title.
+ control.$el.on( 'input change', '.title', function updateTitle() {
+ control.model.set({
+ title: $.trim( $( this ).val() )
+ });
+ });
+
+ // Update link_url attribute.
+ control.$el.on( 'input change', '.link', function updateLinkUrl() {
+ var linkUrl = $.trim( $( this ).val() ), linkType = 'custom';
+ if ( control.selectedAttachment.get( 'linkUrl' ) === linkUrl || control.selectedAttachment.get( 'link' ) === linkUrl ) {
+ linkType = 'post';
+ } else if ( control.selectedAttachment.get( 'url' ) === linkUrl ) {
+ linkType = 'file';
+ }
+ control.model.set( {
+ link_url: linkUrl,
+ link_type: linkType
+ });
+
+ // Update display settings for the next time the user opens to select from the media library.
+ control.displaySettings.set( {
+ link: linkType,
+ linkUrl: linkUrl
+ });
+ });
+
+ /*
+ * Copy current display settings from the widget model to serve as basis
+ * of customized display settings for the current media frame session.
+ * Changes to display settings will be synced into this model, and
+ * when a new selection is made, the settings from this will be synced
+ * into that AttachmentDisplay's model to persist the setting changes.
+ */
+ control.displaySettings = new Backbone.Model( _.pick(
+ control.mapModelToMediaFrameProps(
+ _.extend( control.model.defaults(), control.model.toJSON() )
+ ),
+ _.keys( wp.media.view.settings.defaultProps )
+ ) );
+ },
+
+ /**
+ * Update the selected attachment if necessary.
+ *
+ * @returns {void}
+ */
+ updateSelectedAttachment: function updateSelectedAttachment() {
+ var control = this, attachment;
+
+ if ( 0 === control.model.get( 'attachment_id' ) ) {
+ control.selectedAttachment.clear();
+ control.model.set( 'error', false );
+ } else if ( control.model.get( 'attachment_id' ) !== control.selectedAttachment.get( 'id' ) ) {
+ attachment = new wp.media.model.Attachment({
+ id: control.model.get( 'attachment_id' )
+ });
+ attachment.fetch()
+ .done( function done() {
+ control.model.set( 'error', false );
+ control.selectedAttachment.set( attachment.toJSON() );
+ })
+ .fail( function fail() {
+ control.model.set( 'error', 'missing_attachment' );
+ });
+ }
+ },
+
+ /**
+ * Sync the model attributes to the hidden inputs, and update previewTemplateProps.
+ *
+ * @returns {void}
+ */
+ syncModelToPreviewProps: function syncModelToPreviewProps() {
+ var control = this;
+ control.previewTemplateProps.set( control.mapModelToPreviewTemplateProps() );
+ },
+
+ /**
+ * Sync the model attributes to the hidden inputs, and update previewTemplateProps.
+ *
+ * @returns {void}
+ */
+ syncModelToInputs: function syncModelToInputs() {
+ var control = this;
+ control.syncContainer.find( '.media-widget-instance-property' ).each( function() {
+ var input = $( this ), value, propertyName;
+ propertyName = input.data( 'property' );
+ value = control.model.get( propertyName );
+ if ( _.isUndefined( value ) ) {
+ return;
+ }
+
+ if ( 'array' === control.model.schema[ propertyName ].type && _.isArray( value ) ) {
+ value = value.join( ',' );
+ } else if ( 'boolean' === control.model.schema[ propertyName ].type ) {
+ value = value ? '1' : ''; // Because in PHP, strval( true ) === '1' && strval( false ) === ''.
+ } else {
+ value = String( value );
+ }
+
+ if ( input.val() !== value ) {
+ input.val( value );
+ input.trigger( 'change' );
+ }
+ });
+ },
+
+ /**
+ * Get template.
+ *
+ * @returns {Function} Template.
+ */
+ template: function template() {
+ var control = this;
+ if ( ! $( '#tmpl-widget-media-' + control.id_base + '-control' ).length ) {
+ throw new Error( 'Missing widget control template for ' + control.id_base );
+ }
+ return wp.template( 'widget-media-' + control.id_base + '-control' );
+ },
+
+ /**
+ * Render template.
+ *
+ * @returns {void}
+ */
+ render: function render() {
+ var control = this, titleInput;
+
+ if ( ! control.templateRendered ) {
+ control.$el.html( control.template()( control.model.toJSON() ) );
+ control.renderPreview(); // Hereafter it will re-render when control.selectedAttachment changes.
+ control.templateRendered = true;
+ }
+
+ titleInput = control.$el.find( '.title' );
+ if ( ! titleInput.is( document.activeElement ) ) {
+ titleInput.val( control.model.get( 'title' ) );
+ }
+
+ control.$el.toggleClass( 'selected', control.isSelected() );
+ },
+
+ /**
+ * Render media preview.
+ *
+ * @abstract
+ * @returns {void}
+ */
+ renderPreview: function renderPreview() {
+ throw new Error( 'renderPreview must be implemented' );
+ },
+
+ /**
+ * Whether a media item is selected.
+ *
+ * @returns {boolean} Whether selected and no error.
+ */
+ isSelected: function isSelected() {
+ var control = this;
+
+ if ( control.model.get( 'error' ) ) {
+ return false;
+ }
+
+ return Boolean( control.model.get( 'attachment_id' ) || control.model.get( 'url' ) );
+ },
+
+ /**
+ * Handle click on link to Media Library to open modal, such as the link that appears when in the missing attachment error notice.
+ *
+ * @param {jQuery.Event} event - Event.
+ * @returns {void}
+ */
+ handleMediaLibraryLinkClick: function handleMediaLibraryLinkClick( event ) {
+ var control = this;
+ event.preventDefault();
+ control.selectMedia();
+ },
+
+ /**
+ * Open the media select frame to chose an item.
+ *
+ * @returns {void}
+ */
+ selectMedia: function selectMedia() {
+ var control = this, selection, mediaFrame, defaultSync, mediaFrameProps, selectionModels = [];
+
+ if ( control.isSelected() && 0 !== control.model.get( 'attachment_id' ) ) {
+ selectionModels.push( control.selectedAttachment );
+ }
+
+ selection = new wp.media.model.Selection( selectionModels, { multiple: false } );
+
+ mediaFrameProps = control.mapModelToMediaFrameProps( control.model.toJSON() );
+ if ( mediaFrameProps.size ) {
+ control.displaySettings.set( 'size', mediaFrameProps.size );
+ }
+
+ mediaFrame = new component.MediaFrameSelect({
+ title: control.l10n.add_media,
+ frame: 'post',
+ text: control.l10n.add_to_widget,
+ selection: selection,
+ mimeType: control.mime_type,
+ selectedDisplaySettings: control.displaySettings,
+ showDisplaySettings: control.showDisplaySettings,
+ metadata: mediaFrameProps,
+ state: control.isSelected() && 0 === control.model.get( 'attachment_id' ) ? 'embed' : 'insert',
+ invalidEmbedTypeError: control.l10n.unsupported_file_type
+ });
+ wp.media.frame = mediaFrame; // See wp.media().
+
+ // Handle selection of a media item.
+ mediaFrame.on( 'insert', function onInsert() {
+ var attachment = {}, state = mediaFrame.state();
+
+ // Update cached attachment object to avoid having to re-fetch. This also triggers re-rendering of preview.
+ if ( 'embed' === state.get( 'id' ) ) {
+ _.extend( attachment, { id: 0 }, state.props.toJSON() );
+ } else {
+ _.extend( attachment, state.get( 'selection' ).first().toJSON() );
+ }
+
+ control.selectedAttachment.set( attachment );
+ control.model.set( 'error', false );
+
+ // Update widget instance.
+ control.model.set( control.getModelPropsFromMediaFrame( mediaFrame ) );
+ });
+
+ // Disable syncing of attachment changes back to server (except for deletions). See <https://core.trac.wordpress.org/ticket/40403>.
+ defaultSync = wp.media.model.Attachment.prototype.sync;
+ wp.media.model.Attachment.prototype.sync = function( method ) {
+ if ( 'delete' === method ) {
+ return defaultSync.apply( this, arguments );
+ } else {
+ return $.Deferred().rejectWith( this ).promise();
+ }
+ };
+ mediaFrame.on( 'close', function onClose() {
+ wp.media.model.Attachment.prototype.sync = defaultSync;
+ });
+
+ mediaFrame.$el.addClass( 'media-widget' );
+ mediaFrame.open();
+
+ // Clear the selected attachment when it is deleted in the media select frame.
+ if ( selection ) {
+ selection.on( 'destroy', function onDestroy( attachment ) {
+ if ( control.model.get( 'attachment_id' ) === attachment.get( 'id' ) ) {
+ control.model.set({
+ attachment_id: 0,
+ url: ''
+ });
+ }
+ });
+ }
+
+ /*
+ * Make sure focus is set inside of modal so that hitting Esc will close
+ * the modal and not inadvertently cause the widget to collapse in the customizer.
+ */
+ mediaFrame.$el.find( '.media-frame-menu .media-menu-item.active' ).focus();
+ },
+
+ /**
+ * Get the instance props from the media selection frame.
+ *
+ * @param {wp.media.view.MediaFrame.Select} mediaFrame - Select frame.
+ * @returns {Object} Props.
+ */
+ getModelPropsFromMediaFrame: function getModelPropsFromMediaFrame( mediaFrame ) {
+ var control = this, state, mediaFrameProps, modelProps;
+
+ state = mediaFrame.state();
+ if ( 'insert' === state.get( 'id' ) ) {
+ mediaFrameProps = state.get( 'selection' ).first().toJSON();
+ mediaFrameProps.postUrl = mediaFrameProps.link;
+
+ if ( control.showDisplaySettings ) {
+ _.extend(
+ mediaFrameProps,
+ mediaFrame.content.get( '.attachments-browser' ).sidebar.get( 'display' ).model.toJSON()
+ );
+ }
+ if ( mediaFrameProps.sizes && mediaFrameProps.size && mediaFrameProps.sizes[ mediaFrameProps.size ] ) {
+ mediaFrameProps.url = mediaFrameProps.sizes[ mediaFrameProps.size ].url;
+ }
+ } else if ( 'embed' === state.get( 'id' ) ) {
+ mediaFrameProps = _.extend(
+ state.props.toJSON(),
+ { attachment_id: 0 }, // Because some media frames use `attachment_id` not `id`.
+ control.model.getEmbedResetProps()
+ );
+ } else {
+ throw new Error( 'Unexpected state: ' + state.get( 'id' ) );
+ }
+
+ if ( mediaFrameProps.id ) {
+ mediaFrameProps.attachment_id = mediaFrameProps.id;
+ }
+
+ modelProps = control.mapMediaToModelProps( mediaFrameProps );
+
+ // Clear the extension prop so sources will be reset for video and audio media.
+ _.each( wp.media.view.settings.embedExts, function( ext ) {
+ if ( ext in control.model.schema && modelProps.url !== modelProps[ ext ] ) {
+ modelProps[ ext ] = '';
+ }
+ });
+
+ return modelProps;
+ },
+
+ /**
+ * Map media frame props to model props.
+ *
+ * @param {Object} mediaFrameProps - Media frame props.
+ * @returns {Object} Model props.
+ */
+ mapMediaToModelProps: function mapMediaToModelProps( mediaFrameProps ) {
+ var control = this, mediaFramePropToModelPropMap = {}, modelProps = {}, extension;
+ _.each( control.model.schema, function( fieldSchema, modelProp ) {
+
+ // Ignore widget title attribute.
+ if ( 'title' === modelProp ) {
+ return;
+ }
+ mediaFramePropToModelPropMap[ fieldSchema.media_prop || modelProp ] = modelProp;
+ });
+
+ _.each( mediaFrameProps, function( value, mediaProp ) {
+ var propName = mediaFramePropToModelPropMap[ mediaProp ] || mediaProp;
+ if ( control.model.schema[ propName ] ) {
+ modelProps[ propName ] = value;
+ }
+ });
+
+ if ( 'custom' === mediaFrameProps.size ) {
+ modelProps.width = mediaFrameProps.customWidth;
+ modelProps.height = mediaFrameProps.customHeight;
+ }
+
+ if ( 'post' === mediaFrameProps.link ) {
+ modelProps.link_url = mediaFrameProps.postUrl || mediaFrameProps.linkUrl;
+ } else if ( 'file' === mediaFrameProps.link ) {
+ modelProps.link_url = mediaFrameProps.url;
+ }
+
+ // Because some media frames use `id` instead of `attachment_id`.
+ if ( ! mediaFrameProps.attachment_id && mediaFrameProps.id ) {
+ modelProps.attachment_id = mediaFrameProps.id;
+ }
+
+ if ( mediaFrameProps.url ) {
+ extension = mediaFrameProps.url.replace( /#.*$/, '' ).replace( /\?.*$/, '' ).split( '.' ).pop().toLowerCase();
+ if ( extension in control.model.schema ) {
+ modelProps[ extension ] = mediaFrameProps.url;
+ }
+ }
+
+ // Always omit the titles derived from mediaFrameProps.
+ return _.omit( modelProps, 'title' );
+ },
+
+ /**
+ * Map model props to media frame props.
+ *
+ * @param {Object} modelProps - Model props.
+ * @returns {Object} Media frame props.
+ */
+ mapModelToMediaFrameProps: function mapModelToMediaFrameProps( modelProps ) {
+ var control = this, mediaFrameProps = {};
+
+ _.each( modelProps, function( value, modelProp ) {
+ var fieldSchema = control.model.schema[ modelProp ] || {};
+ mediaFrameProps[ fieldSchema.media_prop || modelProp ] = value;
+ });
+
+ // Some media frames use attachment_id.
+ mediaFrameProps.attachment_id = mediaFrameProps.id;
+
+ if ( 'custom' === mediaFrameProps.size ) {
+ mediaFrameProps.customWidth = control.model.get( 'width' );
+ mediaFrameProps.customHeight = control.model.get( 'height' );
+ }
+
+ return mediaFrameProps;
+ },
+
+ /**
+ * Map model props to previewTemplateProps.
+ *
+ * @returns {Object} Preview Template Props.
+ */
+ mapModelToPreviewTemplateProps: function mapModelToPreviewTemplateProps() {
+ var control = this, previewTemplateProps = {};
+ _.each( control.model.schema, function( value, prop ) {
+ if ( ! value.hasOwnProperty( 'should_preview_update' ) || value.should_preview_update ) {
+ previewTemplateProps[ prop ] = control.model.get( prop );
+ }
+ });
+
+ // Templates need to be aware of the error.
+ previewTemplateProps.error = control.model.get( 'error' );
+ return previewTemplateProps;
+ },
+
+ /**
+ * Open the media frame to modify the selected item.
+ *
+ * @abstract
+ * @returns {void}
+ */
+ editMedia: function editMedia() {
+ throw new Error( 'editMedia not implemented' );
+ }
+ });
+
+ /**
+ * Media widget model.
+ *
+ * @class wp.mediaWidgets.MediaWidgetModel
+ * @augments Backbone.Model
+ */
+ component.MediaWidgetModel = Backbone.Model.extend(/** @lends wp.mediaWidgets.MediaWidgetModel.prototype */{
+
+ /**
+ * Id attribute.
+ *
+ * @type {string}
+ */
+ idAttribute: 'widget_id',
+
+ /**
+ * Instance schema.
+ *
+ * This adheres to JSON Schema and subclasses should have their schema
+ * exported from PHP to JS such as is done in WP_Widget_Media_Image::enqueue_admin_scripts().
+ *
+ * @type {Object.<string, Object>}
+ */
+ schema: {
+ title: {
+ type: 'string',
+ 'default': ''
+ },
+ attachment_id: {
+ type: 'integer',
+ 'default': 0
+ },
+ url: {
+ type: 'string',
+ 'default': ''
+ }
+ },
+
+ /**
+ * Get default attribute values.
+ *
+ * @returns {Object} Mapping of property names to their default values.
+ */
+ defaults: function() {
+ var defaults = {};
+ _.each( this.schema, function( fieldSchema, field ) {
+ defaults[ field ] = fieldSchema['default'];
+ });
+ return defaults;
+ },
+
+ /**
+ * Set attribute value(s).
+ *
+ * This is a wrapped version of Backbone.Model#set() which allows us to
+ * cast the attribute values from the hidden inputs' string values into
+ * the appropriate data types (integers or booleans).
+ *
+ * @param {string|Object} key - Attribute name or attribute pairs.
+ * @param {mixed|Object} [val] - Attribute value or options object.
+ * @param {Object} [options] - Options when attribute name and value are passed separately.
+ * @returns {wp.mediaWidgets.MediaWidgetModel} This model.
+ */
+ set: function set( key, val, options ) {
+ var model = this, attrs, opts, castedAttrs; // eslint-disable-line consistent-this
+ if ( null === key ) {
+ return model;
+ }
+ if ( 'object' === typeof key ) {
+ attrs = key;
+ opts = val;
+ } else {
+ attrs = {};
+ attrs[ key ] = val;
+ opts = options;
+ }
+
+ castedAttrs = {};
+ _.each( attrs, function( value, name ) {
+ var type;
+ if ( ! model.schema[ name ] ) {
+ castedAttrs[ name ] = value;
+ return;
+ }
+ type = model.schema[ name ].type;
+ if ( 'array' === type ) {
+ castedAttrs[ name ] = value;
+ if ( ! _.isArray( castedAttrs[ name ] ) ) {
+ castedAttrs[ name ] = castedAttrs[ name ].split( /,/ ); // Good enough for parsing an ID list.
+ }
+ if ( model.schema[ name ].items && 'integer' === model.schema[ name ].items.type ) {
+ castedAttrs[ name ] = _.filter(
+ _.map( castedAttrs[ name ], function( id ) {
+ return parseInt( id, 10 );
+ },
+ function( id ) {
+ return 'number' === typeof id;
+ }
+ ) );
+ }
+ } else if ( 'integer' === type ) {
+ castedAttrs[ name ] = parseInt( value, 10 );
+ } else if ( 'boolean' === type ) {
+ castedAttrs[ name ] = ! ( ! value || '0' === value || 'false' === value );
+ } else {
+ castedAttrs[ name ] = value;
+ }
+ });
+
+ return Backbone.Model.prototype.set.call( this, castedAttrs, opts );
+ },
+
+ /**
+ * Get props which are merged on top of the model when an embed is chosen (as opposed to an attachment).
+ *
+ * @returns {Object} Reset/override props.
+ */
+ getEmbedResetProps: function getEmbedResetProps() {
+ return {
+ id: 0
+ };
+ }
+ });
+
+ /**
+ * Collection of all widget model instances.
+ *
+ * @memberOf wp.mediaWidgets
+ *
+ * @type {Backbone.Collection}
+ */
+ component.modelCollection = new ( Backbone.Collection.extend( {
+ model: component.MediaWidgetModel
+ }) )();
+
+ /**
+ * Mapping of widget ID to instances of MediaWidgetControl subclasses.
+ *
+ * @memberOf wp.mediaWidgets
+ *
+ * @type {Object.<string, wp.mediaWidgets.MediaWidgetControl>}
+ */
+ component.widgetControls = {};
+
+ /**
+ * Handle widget being added or initialized for the first time at the widget-added event.
+ *
+ * @memberOf wp.mediaWidgets
+ *
+ * @param {jQuery.Event} event - Event.
+ * @param {jQuery} widgetContainer - Widget container element.
+ *
+ * @returns {void}
+ */
+ component.handleWidgetAdded = function handleWidgetAdded( event, widgetContainer ) {
+ var fieldContainer, syncContainer, widgetForm, idBase, ControlConstructor, ModelConstructor, modelAttributes, widgetControl, widgetModel, widgetId, animatedCheckDelay = 50, renderWhenAnimationDone;
+ widgetForm = widgetContainer.find( '> .widget-inside > .form, > .widget-inside > form' ); // Note: '.form' appears in the customizer, whereas 'form' on the widgets admin screen.
+ idBase = widgetForm.find( '> .id_base' ).val();
+ widgetId = widgetForm.find( '> .widget-id' ).val();
+
+ // Prevent initializing already-added widgets.
+ if ( component.widgetControls[ widgetId ] ) {
+ return;
+ }
+
+ ControlConstructor = component.controlConstructors[ idBase ];
+ if ( ! ControlConstructor ) {
+ return;
+ }
+
+ ModelConstructor = component.modelConstructors[ idBase ] || component.MediaWidgetModel;
+
+ /*
+ * Create a container element for the widget control (Backbone.View).
+ * This is inserted into the DOM immediately before the .widget-content
+ * element because the contents of this element are essentially "managed"
+ * by PHP, where each widget update cause the entire element to be emptied
+ * and replaced with the rendered output of WP_Widget::form() which is
+ * sent back in Ajax request made to save/update the widget instance.
+ * To prevent a "flash of replaced DOM elements and re-initialized JS
+ * components", the JS template is rendered outside of the normal form
+ * container.
+ */
+ fieldContainer = $( '<div></div>' );
+ syncContainer = widgetContainer.find( '.widget-content:first' );
+ syncContainer.before( fieldContainer );
+
+ /*
+ * Sync the widget instance model attributes onto the hidden inputs that widgets currently use to store the state.
+ * In the future, when widgets are JS-driven, the underlying widget instance data should be exposed as a model
+ * from the start, without having to sync with hidden fields. See <https://core.trac.wordpress.org/ticket/33507>.
+ */
+ modelAttributes = {};
+ syncContainer.find( '.media-widget-instance-property' ).each( function() {
+ var input = $( this );
+ modelAttributes[ input.data( 'property' ) ] = input.val();
+ });
+ modelAttributes.widget_id = widgetId;
+
+ widgetModel = new ModelConstructor( modelAttributes );
+
+ widgetControl = new ControlConstructor({
+ el: fieldContainer,
+ syncContainer: syncContainer,
+ model: widgetModel
+ });
+
+ /*
+ * Render the widget once the widget parent's container finishes animating,
+ * as the widget-added event fires with a slideDown of the container.
+ * This ensures that the container's dimensions are fixed so that ME.js
+ * can initialize with the proper dimensions.
+ */
+ renderWhenAnimationDone = function() {
+ if ( ! widgetContainer.hasClass( 'open' ) ) {
+ setTimeout( renderWhenAnimationDone, animatedCheckDelay );
+ } else {
+ widgetControl.render();
+ }
+ };
+ renderWhenAnimationDone();
+
+ /*
+ * Note that the model and control currently won't ever get garbage-collected
+ * when a widget gets removed/deleted because there is no widget-removed event.
+ */
+ component.modelCollection.add( [ widgetModel ] );
+ component.widgetControls[ widgetModel.get( 'widget_id' ) ] = widgetControl;
+ };
+
+ /**
+ * Setup widget in accessibility mode.
+ *
+ * @memberOf wp.mediaWidgets
+ *
+ * @returns {void}
+ */
+ component.setupAccessibleMode = function setupAccessibleMode() {
+ var widgetForm, widgetId, idBase, widgetControl, ControlConstructor, ModelConstructor, modelAttributes, fieldContainer, syncContainer;
+ widgetForm = $( '.editwidget > form' );
+ if ( 0 === widgetForm.length ) {
+ return;
+ }
+
+ idBase = widgetForm.find( '> .widget-control-actions > .id_base' ).val();
+
+ ControlConstructor = component.controlConstructors[ idBase ];
+ if ( ! ControlConstructor ) {
+ return;
+ }
+
+ widgetId = widgetForm.find( '> .widget-control-actions > .widget-id' ).val();
+
+ ModelConstructor = component.modelConstructors[ idBase ] || component.MediaWidgetModel;
+ fieldContainer = $( '<div></div>' );
+ syncContainer = widgetForm.find( '> .widget-inside' );
+ syncContainer.before( fieldContainer );
+
+ modelAttributes = {};
+ syncContainer.find( '.media-widget-instance-property' ).each( function() {
+ var input = $( this );
+ modelAttributes[ input.data( 'property' ) ] = input.val();
+ });
+ modelAttributes.widget_id = widgetId;
+
+ widgetControl = new ControlConstructor({
+ el: fieldContainer,
+ syncContainer: syncContainer,
+ model: new ModelConstructor( modelAttributes )
+ });
+
+ component.modelCollection.add( [ widgetControl.model ] );
+ component.widgetControls[ widgetControl.model.get( 'widget_id' ) ] = widgetControl;
+
+ widgetControl.render();
+ };
+
+ /**
+ * Sync widget instance data sanitized from server back onto widget model.
+ *
+ * This gets called via the 'widget-updated' event when saving a widget from
+ * the widgets admin screen and also via the 'widget-synced' event when making
+ * a change to a widget in the customizer.
+ *
+ * @memberOf wp.mediaWidgets
+ *
+ * @param {jQuery.Event} event - Event.
+ * @param {jQuery} widgetContainer - Widget container element.
+ *
+ * @returns {void}
+ */
+ component.handleWidgetUpdated = function handleWidgetUpdated( event, widgetContainer ) {
+ var widgetForm, widgetContent, widgetId, widgetControl, attributes = {};
+ widgetForm = widgetContainer.find( '> .widget-inside > .form, > .widget-inside > form' );
+ widgetId = widgetForm.find( '> .widget-id' ).val();
+
+ widgetControl = component.widgetControls[ widgetId ];
+ if ( ! widgetControl ) {
+ return;
+ }
+
+ // Make sure the server-sanitized values get synced back into the model.
+ widgetContent = widgetForm.find( '> .widget-content' );
+ widgetContent.find( '.media-widget-instance-property' ).each( function() {
+ var property = $( this ).data( 'property' );
+ attributes[ property ] = $( this ).val();
+ });
+
+ // Suspend syncing model back to inputs when syncing from inputs to model, preventing infinite loop.
+ widgetControl.stopListening( widgetControl.model, 'change', widgetControl.syncModelToInputs );
+ widgetControl.model.set( attributes );
+ widgetControl.listenTo( widgetControl.model, 'change', widgetControl.syncModelToInputs );
+ };
+
+ /**
+ * Initialize functionality.
+ *
+ * This function exists to prevent the JS file from having to boot itself.
+ * When WordPress enqueues this script, it should have an inline script
+ * attached which calls wp.mediaWidgets.init().
+ *
+ * @memberOf wp.mediaWidgets
+ *
+ * @returns {void}
+ */
+ component.init = function init() {
+ var $document = $( document );
+ $document.on( 'widget-added', component.handleWidgetAdded );
+ $document.on( 'widget-synced widget-updated', component.handleWidgetUpdated );
+
+ /*
+ * Manually trigger widget-added events for media widgets on the admin
+ * screen once they are expanded. The widget-added event is not triggered
+ * for each pre-existing widget on the widgets admin screen like it is
+ * on the customizer. Likewise, the customizer only triggers widget-added
+ * when the widget is expanded to just-in-time construct the widget form
+ * when it is actually going to be displayed. So the following implements
+ * the same for the widgets admin screen, to invoke the widget-added
+ * handler when a pre-existing media widget is expanded.
+ */
+ $( function initializeExistingWidgetContainers() {
+ var widgetContainers;
+ if ( 'widgets' !== window.pagenow ) {
+ return;
+ }
+ widgetContainers = $( '.widgets-holder-wrap:not(#available-widgets)' ).find( 'div.widget' );
+ widgetContainers.one( 'click.toggle-widget-expanded', function toggleWidgetExpanded() {
+ var widgetContainer = $( this );
+ component.handleWidgetAdded( new jQuery.Event( 'widget-added' ), widgetContainer );
+ });
+
+ // Accessibility mode.
+ $( window ).on( 'load', function() {
+ component.setupAccessibleMode();
+ });
+ });
+ };
+
+ return component;
+})( jQuery );
diff --git a/www/crm/wp-admin/js/widgets/media-widgets.min.js b/www/crm/wp-admin/js/widgets/media-widgets.min.js
new file mode 100644
index 00000000..94b0ac54
--- /dev/null
+++ b/www/crm/wp-admin/js/widgets/media-widgets.min.js
@@ -0,0 +1 @@
+wp.mediaWidgets=function(a){"use strict";var b={};return b.controlConstructors={},b.modelConstructors={},b.PersistentDisplaySettingsLibrary=wp.media.controller.Library.extend({initialize:function(a){_.bindAll(this,"handleDisplaySettingChange"),wp.media.controller.Library.prototype.initialize.call(this,a)},handleDisplaySettingChange:function(a){this.get("selectedDisplaySettings").set(a.attributes)},display:function(a){var b,c=this.get("selectedDisplaySettings");return b=wp.media.controller.Library.prototype.display.call(this,a),b.off("change",this.handleDisplaySettingChange),b.set(c.attributes),"custom"===c.get("link_type")&&(b.linkUrl=c.get("link_url")),b.on("change",this.handleDisplaySettingChange),b}}),b.MediaEmbedView=wp.media.view.Embed.extend({initialize:function(a){var b,c=this;wp.media.view.Embed.prototype.initialize.call(c,a),"image"!==c.controller.options.mimeType&&(b=c.controller.states.get("embed"),b.off("scan",b.scanImage,b))},refresh:function(){var b;b="image"===this.controller.options.mimeType?wp.media.view.EmbedImage:wp.media.view.EmbedLink.extend({setAddToWidgetButtonDisabled:function(a){this.views.parent.views.parent.views.get(".media-frame-toolbar")[0].$el.find(".media-button-select").prop("disabled",a)},setErrorNotice:function(b){var c,d=this;c=d.views.parent.$el.find("> .notice:first-child"),b?(c.length||(c=a('<div class="media-widget-embed-notice notice notice-error notice-alt"></div>'),c.hide(),d.views.parent.$el.prepend(c)),c.empty(),c.append(a("<p>",{html:b})),c.slideDown("fast")):c.length&&c.slideUp("fast")},updateoEmbed:function(){var a,b=this;return(a=b.model.get("url"))?(a.match(/^(http|https):\/\/.+\//)||(b.controller.$el.find("#embed-url-field").addClass("invalid"),b.setAddToWidgetButtonDisabled(!0)),void wp.media.view.EmbedLink.prototype.updateoEmbed.call(b)):(b.setErrorNotice(""),void b.setAddToWidgetButtonDisabled(!0))},fetch:function(){var a,b,c,d,e,f,g,h=this;return e=h.model.get("url"),h.dfd&&"pending"===h.dfd.state()&&h.dfd.abort(),a=function(a){h.renderoEmbed({data:{body:a}}),h.controller.$el.find("#embed-url-field").removeClass("invalid"),h.setErrorNotice(""),h.setAddToWidgetButtonDisabled(!1)},d=document.createElement("a"),d.href=e,(b=d.pathname.toLowerCase().match(/\.(\w+)$/))?(c=b[1],void(wp.media.view.settings.embedMimes[c]?0!==wp.media.view.settings.embedMimes[c].indexOf(h.controller.options.mimeType)?h.renderFail():a("<!--success-->"):h.renderFail())):(f=/https?:\/\/www\.youtube\.com\/embed\/([^\/]+)/,g=f.exec(e),g&&(e="https://www.youtube.com/watch?v="+g[1],h.model.attributes.url=e),h.dfd=wp.apiRequest({url:wp.media.view.settings.oEmbedProxyUrl,data:{url:e,maxwidth:h.model.get("width"),maxheight:h.model.get("height"),discover:!1},type:"GET",dataType:"json",context:h}),h.dfd.done(function(b){return h.controller.options.mimeType!==b.type?void h.renderFail():void a(b.html)}),void h.dfd.fail(_.bind(h.renderFail,h)))},renderFail:function(){var a=this;a.controller.$el.find("#embed-url-field").addClass("invalid"),a.setErrorNotice(a.controller.options.invalidEmbedTypeError||"ERROR"),a.setAddToWidgetButtonDisabled(!0)}}),this.settings(new b({controller:this.controller,model:this.model.props,priority:40}))}}),b.MediaFrameSelect=wp.media.view.MediaFrame.Post.extend({createStates:function(){var a=this.options.mimeType,c=[];_.each(wp.media.view.settings.embedMimes,function(b){0===b.indexOf(a)&&c.push(b)}),c.length>0&&(a=c),this.states.add([new b.PersistentDisplaySettingsLibrary({id:"insert",title:this.options.title,selection:this.options.selection,priority:20,toolbar:"main-insert",filterable:"dates",library:wp.media.query({type:a}),multiple:!1,editable:!0,selectedDisplaySettings:this.options.selectedDisplaySettings,displaySettings:!!_.isUndefined(this.options.showDisplaySettings)||this.options.showDisplaySettings,displayUserSettings:!1}),new wp.media.controller.EditImage({model:this.options.editImage}),new wp.media.controller.Embed({metadata:this.options.metadata,type:"image"===this.options.mimeType?"image":"link",invalidEmbedTypeError:this.options.invalidEmbedTypeError})])},mainInsertToolbar:function(a){var b=this;a.set("insert",{style:"primary",priority:80,text:b.options.text,requires:{selection:!0},click:function(){var a=b.state(),c=a.get("selection");b.close(),a.trigger("insert",c).reset()}})},mainEmbedToolbar:function(a){a.view=new wp.media.view.Toolbar.Embed({controller:this,text:this.options.text,event:"insert"})},embedContent:function(){var a=new b.MediaEmbedView({controller:this,model:this.state()}).render();this.content.set(a),wp.media.isTouchDevice||a.url.focus()}}),b.MediaWidgetControl=Backbone.View.extend({l10n:{add_to_widget:"{{add_to_widget}}",add_media:"{{add_media}}"},id_base:"",mime_type:"",events:{"click .notice-missing-attachment a":"handleMediaLibraryLinkClick","click .select-media":"selectMedia","click .placeholder":"selectMedia","click .edit-media":"editMedia"},showDisplaySettings:!0,initialize:function(c){var d=this;if(Backbone.View.prototype.initialize.call(d,c),!(d.model instanceof b.MediaWidgetModel))throw new Error("Missing options.model");if(!c.el)throw new Error("Missing options.el");if(!c.syncContainer)throw new Error("Missing options.syncContainer");if(d.syncContainer=c.syncContainer,d.$el.addClass("media-widget-control"),_.bindAll(d,"syncModelToInputs","render","updateSelectedAttachment","renderPreview"),!d.id_base&&(_.find(b.controlConstructors,function(a,b){return d instanceof a&&(d.id_base=b,!0)}),!d.id_base))throw new Error("Missing id_base.");d.previewTemplateProps=new Backbone.Model(d.mapModelToPreviewTemplateProps()),d.selectedAttachment=new wp.media.model.Attachment,d.renderPreview=_.debounce(d.renderPreview),d.listenTo(d.previewTemplateProps,"change",d.renderPreview),d.model.on("change:attachment_id",d.updateSelectedAttachment),d.model.on("change:url",d.updateSelectedAttachment),d.updateSelectedAttachment(),d.listenTo(d.model,"change",d.syncModelToInputs),d.listenTo(d.model,"change",d.syncModelToPreviewProps),d.listenTo(d.model,"change",d.render),d.$el.on("input change",".title",function(){d.model.set({title:a.trim(a(this).val())})}),d.$el.on("input change",".link",function(){var b=a.trim(a(this).val()),c="custom";d.selectedAttachment.get("linkUrl")===b||d.selectedAttachment.get("link")===b?c="post":d.selectedAttachment.get("url")===b&&(c="file"),d.model.set({link_url:b,link_type:c}),d.displaySettings.set({link:c,linkUrl:b})}),d.displaySettings=new Backbone.Model(_.pick(d.mapModelToMediaFrameProps(_.extend(d.model.defaults(),d.model.toJSON())),_.keys(wp.media.view.settings.defaultProps)))},updateSelectedAttachment:function(){var a,b=this;0===b.model.get("attachment_id")?(b.selectedAttachment.clear(),b.model.set("error",!1)):b.model.get("attachment_id")!==b.selectedAttachment.get("id")&&(a=new wp.media.model.Attachment({id:b.model.get("attachment_id")}),a.fetch().done(function(){b.model.set("error",!1),b.selectedAttachment.set(a.toJSON())}).fail(function(){b.model.set("error","missing_attachment")}))},syncModelToPreviewProps:function(){var a=this;a.previewTemplateProps.set(a.mapModelToPreviewTemplateProps())},syncModelToInputs:function(){var b=this;b.syncContainer.find(".media-widget-instance-property").each(function(){var c,d,e=a(this);d=e.data("property"),c=b.model.get(d),_.isUndefined(c)||(c="array"===b.model.schema[d].type&&_.isArray(c)?c.join(","):"boolean"===b.model.schema[d].type?c?"1":"":String(c),e.val()!==c&&(e.val(c),e.trigger("change")))})},template:function(){var b=this;if(!a("#tmpl-widget-media-"+b.id_base+"-control").length)throw new Error("Missing widget control template for "+b.id_base);return wp.template("widget-media-"+b.id_base+"-control")},render:function(){var a,b=this;b.templateRendered||(b.$el.html(b.template()(b.model.toJSON())),b.renderPreview(),b.templateRendered=!0),a=b.$el.find(".title"),a.is(document.activeElement)||a.val(b.model.get("title")),b.$el.toggleClass("selected",b.isSelected())},renderPreview:function(){throw new Error("renderPreview must be implemented")},isSelected:function(){var a=this;return!a.model.get("error")&&Boolean(a.model.get("attachment_id")||a.model.get("url"))},handleMediaLibraryLinkClick:function(a){var b=this;a.preventDefault(),b.selectMedia()},selectMedia:function(){var c,d,e,f,g=this,h=[];g.isSelected()&&0!==g.model.get("attachment_id")&&h.push(g.selectedAttachment),c=new wp.media.model.Selection(h,{multiple:!1}),f=g.mapModelToMediaFrameProps(g.model.toJSON()),f.size&&g.displaySettings.set("size",f.size),d=new b.MediaFrameSelect({title:g.l10n.add_media,frame:"post",text:g.l10n.add_to_widget,selection:c,mimeType:g.mime_type,selectedDisplaySettings:g.displaySettings,showDisplaySettings:g.showDisplaySettings,metadata:f,state:g.isSelected()&&0===g.model.get("attachment_id")?"embed":"insert",invalidEmbedTypeError:g.l10n.unsupported_file_type}),wp.media.frame=d,d.on("insert",function(){var a={},b=d.state();"embed"===b.get("id")?_.extend(a,{id:0},b.props.toJSON()):_.extend(a,b.get("selection").first().toJSON()),g.selectedAttachment.set(a),g.model.set("error",!1),g.model.set(g.getModelPropsFromMediaFrame(d))}),e=wp.media.model.Attachment.prototype.sync,wp.media.model.Attachment.prototype.sync=function(b){return"delete"===b?e.apply(this,arguments):a.Deferred().rejectWith(this).promise()},d.on("close",function(){wp.media.model.Attachment.prototype.sync=e}),d.$el.addClass("media-widget"),d.open(),c&&c.on("destroy",function(a){g.model.get("attachment_id")===a.get("id")&&g.model.set({attachment_id:0,url:""})}),d.$el.find(".media-frame-menu .media-menu-item.active").focus()},getModelPropsFromMediaFrame:function(a){var b,c,d,e=this;if(b=a.state(),"insert"===b.get("id"))c=b.get("selection").first().toJSON(),c.postUrl=c.link,e.showDisplaySettings&&_.extend(c,a.content.get(".attachments-browser").sidebar.get("display").model.toJSON()),c.sizes&&c.size&&c.sizes[c.size]&&(c.url=c.sizes[c.size].url);else{if("embed"!==b.get("id"))throw new Error("Unexpected state: "+b.get("id"));c=_.extend(b.props.toJSON(),{attachment_id:0},e.model.getEmbedResetProps())}return c.id&&(c.attachment_id=c.id),d=e.mapMediaToModelProps(c),_.each(wp.media.view.settings.embedExts,function(a){a in e.model.schema&&d.url!==d[a]&&(d[a]="")}),d},mapMediaToModelProps:function(a){var b,c=this,d={},e={};return _.each(c.model.schema,function(a,b){"title"!==b&&(d[a.media_prop||b]=b)}),_.each(a,function(a,b){var f=d[b]||b;c.model.schema[f]&&(e[f]=a)}),"custom"===a.size&&(e.width=a.customWidth,e.height=a.customHeight),"post"===a.link?e.link_url=a.postUrl||a.linkUrl:"file"===a.link&&(e.link_url=a.url),!a.attachment_id&&a.id&&(e.attachment_id=a.id),a.url&&(b=a.url.replace(/#.*$/,"").replace(/\?.*$/,"").split(".").pop().toLowerCase(),b in c.model.schema&&(e[b]=a.url)),_.omit(e,"title")},mapModelToMediaFrameProps:function(a){var b=this,c={};return _.each(a,function(a,d){var e=b.model.schema[d]||{};c[e.media_prop||d]=a}),c.attachment_id=c.id,"custom"===c.size&&(c.customWidth=b.model.get("width"),c.customHeight=b.model.get("height")),c},mapModelToPreviewTemplateProps:function(){var a=this,b={};return _.each(a.model.schema,function(c,d){c.hasOwnProperty("should_preview_update")&&!c.should_preview_update||(b[d]=a.model.get(d))}),b.error=a.model.get("error"),b},editMedia:function(){throw new Error("editMedia not implemented")}}),b.MediaWidgetModel=Backbone.Model.extend({idAttribute:"widget_id",schema:{title:{type:"string","default":""},attachment_id:{type:"integer","default":0},url:{type:"string","default":""}},defaults:function(){var a={};return _.each(this.schema,function(b,c){a[c]=b["default"]}),a},set:function(a,b,c){var d,e,f,g=this;return null===a?g:("object"==typeof a?(d=a,e=b):(d={},d[a]=b,e=c),f={},_.each(d,function(a,b){var c;return g.schema[b]?(c=g.schema[b].type,void("array"===c?(f[b]=a,_.isArray(f[b])||(f[b]=f[b].split(/,/)),g.schema[b].items&&"integer"===g.schema[b].items.type&&(f[b]=_.filter(_.map(f[b],function(a){return parseInt(a,10)},function(a){return"number"==typeof a})))):"integer"===c?f[b]=parseInt(a,10):"boolean"===c?f[b]=!(!a||"0"===a||"false"===a):f[b]=a)):void(f[b]=a)}),Backbone.Model.prototype.set.call(this,f,e))},getEmbedResetProps:function(){return{id:0}}}),b.modelCollection=new(Backbone.Collection.extend({model:b.MediaWidgetModel})),b.widgetControls={},b.handleWidgetAdded=function(c,d){var e,f,g,h,i,j,k,l,m,n,o,p=50;g=d.find("> .widget-inside > .form, > .widget-inside > form"),h=g.find("> .id_base").val(),n=g.find("> .widget-id").val(),b.widgetControls[n]||(i=b.controlConstructors[h],i&&(j=b.modelConstructors[h]||b.MediaWidgetModel,e=a("<div></div>"),f=d.find(".widget-content:first"),f.before(e),k={},f.find(".media-widget-instance-property").each(function(){var b=a(this);k[b.data("property")]=b.val()}),k.widget_id=n,m=new j(k),l=new i({el:e,syncContainer:f,model:m}),o=function(){d.hasClass("open")?l.render():setTimeout(o,p)},o(),b.modelCollection.add([m]),b.widgetControls[m.get("widget_id")]=l))},b.setupAccessibleMode=function(){var c,d,e,f,g,h,i,j,k;c=a(".editwidget > form"),0!==c.length&&(e=c.find("> .widget-control-actions > .id_base").val(),g=b.controlConstructors[e],g&&(d=c.find("> .widget-control-actions > .widget-id").val(),h=b.modelConstructors[e]||b.MediaWidgetModel,j=a("<div></div>"),k=c.find("> .widget-inside"),k.before(j),i={},k.find(".media-widget-instance-property").each(function(){var b=a(this);i[b.data("property")]=b.val()}),i.widget_id=d,f=new g({el:j,syncContainer:k,model:new h(i)}),b.modelCollection.add([f.model]),b.widgetControls[f.model.get("widget_id")]=f,f.render()))},b.handleWidgetUpdated=function(c,d){var e,f,g,h,i={};e=d.find("> .widget-inside > .form, > .widget-inside > form"),g=e.find("> .widget-id").val(),h=b.widgetControls[g],h&&(f=e.find("> .widget-content"),f.find(".media-widget-instance-property").each(function(){var b=a(this).data("property");i[b]=a(this).val()}),h.stopListening(h.model,"change",h.syncModelToInputs),h.model.set(i),h.listenTo(h.model,"change",h.syncModelToInputs))},b.init=function(){var c=a(document);c.on("widget-added",b.handleWidgetAdded),c.on("widget-synced widget-updated",b.handleWidgetUpdated),a(function(){var c;"widgets"===window.pagenow&&(c=a(".widgets-holder-wrap:not(#available-widgets)").find("div.widget"),c.one("click.toggle-widget-expanded",function(){var c=a(this);b.handleWidgetAdded(new jQuery.Event("widget-added"),c)}),a(window).on("load",function(){b.setupAccessibleMode()}))})},b}(jQuery); \ No newline at end of file
diff --git a/www/crm/wp-admin/js/widgets/text-widgets.js b/www/crm/wp-admin/js/widgets/text-widgets.js
new file mode 100644
index 00000000..184e7242
--- /dev/null
+++ b/www/crm/wp-admin/js/widgets/text-widgets.js
@@ -0,0 +1,552 @@
+/**
+ * @output wp-admin/js/widgets/text-widgets.js
+ */
+
+/* global tinymce, switchEditors */
+/* eslint consistent-this: [ "error", "control" ] */
+
+/**
+ * @namespace wp.textWidgets
+ */
+wp.textWidgets = ( function( $ ) {
+ 'use strict';
+
+ var component = {
+ dismissedPointers: [],
+ idBases: [ 'text' ]
+ };
+
+ component.TextWidgetControl = Backbone.View.extend(/** @lends wp.textWidgets.TextWidgetControl.prototype */{
+
+ /**
+ * View events.
+ *
+ * @type {Object}
+ */
+ events: {},
+
+ /**
+ * Text widget control.
+ *
+ * @constructs wp.textWidgets.TextWidgetControl
+ * @augments Backbone.View
+ * @abstract
+ *
+ * @param {Object} options - Options.
+ * @param {jQuery} options.el - Control field container element.
+ * @param {jQuery} options.syncContainer - Container element where fields are synced for the server.
+ *
+ * @returns {void}
+ */
+ initialize: function initialize( options ) {
+ var control = this;
+
+ if ( ! options.el ) {
+ throw new Error( 'Missing options.el' );
+ }
+ if ( ! options.syncContainer ) {
+ throw new Error( 'Missing options.syncContainer' );
+ }
+
+ Backbone.View.prototype.initialize.call( control, options );
+ control.syncContainer = options.syncContainer;
+
+ control.$el.addClass( 'text-widget-fields' );
+ control.$el.html( wp.template( 'widget-text-control-fields' ) );
+
+ control.customHtmlWidgetPointer = control.$el.find( '.wp-pointer.custom-html-widget-pointer' );
+ if ( control.customHtmlWidgetPointer.length ) {
+ control.customHtmlWidgetPointer.find( '.close' ).on( 'click', function( event ) {
+ event.preventDefault();
+ control.customHtmlWidgetPointer.hide();
+ $( '#' + control.fields.text.attr( 'id' ) + '-html' ).focus();
+ control.dismissPointers( [ 'text_widget_custom_html' ] );
+ });
+ control.customHtmlWidgetPointer.find( '.add-widget' ).on( 'click', function( event ) {
+ event.preventDefault();
+ control.customHtmlWidgetPointer.hide();
+ control.openAvailableWidgetsPanel();
+ });
+ }
+
+ control.pasteHtmlPointer = control.$el.find( '.wp-pointer.paste-html-pointer' );
+ if ( control.pasteHtmlPointer.length ) {
+ control.pasteHtmlPointer.find( '.close' ).on( 'click', function( event ) {
+ event.preventDefault();
+ control.pasteHtmlPointer.hide();
+ control.editor.focus();
+ control.dismissPointers( [ 'text_widget_custom_html', 'text_widget_paste_html' ] );
+ });
+ }
+
+ control.fields = {
+ title: control.$el.find( '.title' ),
+ text: control.$el.find( '.text' )
+ };
+
+ // Sync input fields to hidden sync fields which actually get sent to the server.
+ _.each( control.fields, function( fieldInput, fieldName ) {
+ fieldInput.on( 'input change', function updateSyncField() {
+ var syncInput = control.syncContainer.find( '.sync-input.' + fieldName );
+ if ( syncInput.val() !== fieldInput.val() ) {
+ syncInput.val( fieldInput.val() );
+ syncInput.trigger( 'change' );
+ }
+ });
+
+ // Note that syncInput cannot be re-used because it will be destroyed with each widget-updated event.
+ fieldInput.val( control.syncContainer.find( '.sync-input.' + fieldName ).val() );
+ });
+ },
+
+ /**
+ * Dismiss pointers for Custom HTML widget.
+ *
+ * @since 4.8.1
+ *
+ * @param {Array} pointers Pointer IDs to dismiss.
+ * @returns {void}
+ */
+ dismissPointers: function dismissPointers( pointers ) {
+ _.each( pointers, function( pointer ) {
+ wp.ajax.post( 'dismiss-wp-pointer', {
+ pointer: pointer
+ });
+ component.dismissedPointers.push( pointer );
+ });
+ },
+
+ /**
+ * Open available widgets panel.
+ *
+ * @since 4.8.1
+ * @returns {void}
+ */
+ openAvailableWidgetsPanel: function openAvailableWidgetsPanel() {
+ var sidebarControl;
+ wp.customize.section.each( function( section ) {
+ if ( section.extended( wp.customize.Widgets.SidebarSection ) && section.expanded() ) {
+ sidebarControl = wp.customize.control( 'sidebars_widgets[' + section.params.sidebarId + ']' );
+ }
+ });
+ if ( ! sidebarControl ) {
+ return;
+ }
+ setTimeout( function() { // Timeout to prevent click event from causing panel to immediately collapse.
+ wp.customize.Widgets.availableWidgetsPanel.open( sidebarControl );
+ wp.customize.Widgets.availableWidgetsPanel.$search.val( 'HTML' ).trigger( 'keyup' );
+ });
+ },
+
+ /**
+ * Update input fields from the sync fields.
+ *
+ * This function is called at the widget-updated and widget-synced events.
+ * A field will only be updated if it is not currently focused, to avoid
+ * overwriting content that the user is entering.
+ *
+ * @returns {void}
+ */
+ updateFields: function updateFields() {
+ var control = this, syncInput;
+
+ if ( ! control.fields.title.is( document.activeElement ) ) {
+ syncInput = control.syncContainer.find( '.sync-input.title' );
+ control.fields.title.val( syncInput.val() );
+ }
+
+ syncInput = control.syncContainer.find( '.sync-input.text' );
+ if ( control.fields.text.is( ':visible' ) ) {
+ if ( ! control.fields.text.is( document.activeElement ) ) {
+ control.fields.text.val( syncInput.val() );
+ }
+ } else if ( control.editor && ! control.editorFocused && syncInput.val() !== control.fields.text.val() ) {
+ control.editor.setContent( wp.editor.autop( syncInput.val() ) );
+ }
+ },
+
+ /**
+ * Initialize editor.
+ *
+ * @returns {void}
+ */
+ initializeEditor: function initializeEditor() {
+ var control = this, changeDebounceDelay = 1000, id, textarea, triggerChangeIfDirty, restoreTextMode = false, needsTextareaChangeTrigger = false, previousValue;
+ textarea = control.fields.text;
+ id = textarea.attr( 'id' );
+ previousValue = textarea.val();
+
+ /**
+ * Trigger change if dirty.
+ *
+ * @returns {void}
+ */
+ triggerChangeIfDirty = function() {
+ var updateWidgetBuffer = 300; // See wp.customize.Widgets.WidgetControl._setupUpdateUI() which uses 250ms for updateWidgetDebounced.
+ if ( control.editor.isDirty() ) {
+
+ /*
+ * Account for race condition in customizer where user clicks Save & Publish while
+ * focus was just previously given to the editor. Since updates to the editor
+ * are debounced at 1 second and since widget input changes are only synced to
+ * settings after 250ms, the customizer needs to be put into the processing
+ * state during the time between the change event is triggered and updateWidget
+ * logic starts. Note that the debounced update-widget request should be able
+ * to be removed with the removal of the update-widget request entirely once
+ * widgets are able to mutate their own instance props directly in JS without
+ * having to make server round-trips to call the respective WP_Widget::update()
+ * callbacks. See <https://core.trac.wordpress.org/ticket/33507>.
+ */
+ if ( wp.customize && wp.customize.state ) {
+ wp.customize.state( 'processing' ).set( wp.customize.state( 'processing' ).get() + 1 );
+ _.delay( function() {
+ wp.customize.state( 'processing' ).set( wp.customize.state( 'processing' ).get() - 1 );
+ }, updateWidgetBuffer );
+ }
+
+ if ( ! control.editor.isHidden() ) {
+ control.editor.save();
+ }
+ }
+
+ // Trigger change on textarea when it has changed so the widget can enter a dirty state.
+ if ( needsTextareaChangeTrigger && previousValue !== textarea.val() ) {
+ textarea.trigger( 'change' );
+ needsTextareaChangeTrigger = false;
+ previousValue = textarea.val();
+ }
+ };
+
+ // Just-in-time force-update the hidden input fields.
+ control.syncContainer.closest( '.widget' ).find( '[name=savewidget]:first' ).on( 'click', function onClickSaveButton() {
+ triggerChangeIfDirty();
+ });
+
+ /**
+ * Build (or re-build) the visual editor.
+ *
+ * @returns {void}
+ */
+ function buildEditor() {
+ var editor, onInit, showPointerElement;
+
+ // Abort building if the textarea is gone, likely due to the widget having been deleted entirely.
+ if ( ! document.getElementById( id ) ) {
+ return;
+ }
+
+ // The user has disabled TinyMCE.
+ if ( typeof window.tinymce === 'undefined' ) {
+ wp.editor.initialize( id, {
+ quicktags: true,
+ mediaButtons: true
+ });
+
+ return;
+ }
+
+ // Destroy any existing editor so that it can be re-initialized after a widget-updated event.
+ if ( tinymce.get( id ) ) {
+ restoreTextMode = tinymce.get( id ).isHidden();
+ wp.editor.remove( id );
+ }
+
+ // Add or enable the `wpview` plugin.
+ $( document ).one( 'wp-before-tinymce-init.text-widget-init', function( event, init ) {
+ // If somebody has removed all plugins, they must have a good reason.
+ // Keep it that way.
+ if ( ! init.plugins ) {
+ return;
+ } else if ( ! /\bwpview\b/.test( init.plugins ) ) {
+ init.plugins += ',wpview';
+ }
+ } );
+
+ wp.editor.initialize( id, {
+ tinymce: {
+ wpautop: true
+ },
+ quicktags: true,
+ mediaButtons: true
+ });
+
+ /**
+ * Show a pointer, focus on dismiss, and speak the contents for a11y.
+ *
+ * @param {jQuery} pointerElement Pointer element.
+ * @returns {void}
+ */
+ showPointerElement = function( pointerElement ) {
+ pointerElement.show();
+ pointerElement.find( '.close' ).focus();
+ wp.a11y.speak( pointerElement.find( 'h3, p' ).map( function() {
+ return $( this ).text();
+ } ).get().join( '\n\n' ) );
+ };
+
+ editor = window.tinymce.get( id );
+ if ( ! editor ) {
+ throw new Error( 'Failed to initialize editor' );
+ }
+ onInit = function() {
+
+ // When a widget is moved in the DOM the dynamically-created TinyMCE iframe will be destroyed and has to be re-built.
+ $( editor.getWin() ).on( 'unload', function() {
+ _.defer( buildEditor );
+ });
+
+ // If a prior mce instance was replaced, and it was in text mode, toggle to text mode.
+ if ( restoreTextMode ) {
+ switchEditors.go( id, 'html' );
+ }
+
+ // Show the pointer.
+ $( '#' + id + '-html' ).on( 'click', function() {
+ control.pasteHtmlPointer.hide(); // Hide the HTML pasting pointer.
+
+ if ( -1 !== component.dismissedPointers.indexOf( 'text_widget_custom_html' ) ) {
+ return;
+ }
+ showPointerElement( control.customHtmlWidgetPointer );
+ });
+
+ // Hide the pointer when switching tabs.
+ $( '#' + id + '-tmce' ).on( 'click', function() {
+ control.customHtmlWidgetPointer.hide();
+ });
+
+ // Show pointer when pasting HTML.
+ editor.on( 'pastepreprocess', function( event ) {
+ var content = event.content;
+ if ( -1 !== component.dismissedPointers.indexOf( 'text_widget_paste_html' ) || ! content || ! /&lt;\w+.*?&gt;/.test( content ) ) {
+ return;
+ }
+
+ // Show the pointer after a slight delay so the user sees what they pasted.
+ _.delay( function() {
+ showPointerElement( control.pasteHtmlPointer );
+ }, 250 );
+ });
+ };
+
+ if ( editor.initialized ) {
+ onInit();
+ } else {
+ editor.on( 'init', onInit );
+ }
+
+ control.editorFocused = false;
+
+ editor.on( 'focus', function onEditorFocus() {
+ control.editorFocused = true;
+ });
+ editor.on( 'paste', function onEditorPaste() {
+ editor.setDirty( true ); // Because pasting doesn't currently set the dirty state.
+ triggerChangeIfDirty();
+ });
+ editor.on( 'NodeChange', function onNodeChange() {
+ needsTextareaChangeTrigger = true;
+ });
+ editor.on( 'NodeChange', _.debounce( triggerChangeIfDirty, changeDebounceDelay ) );
+ editor.on( 'blur hide', function onEditorBlur() {
+ control.editorFocused = false;
+ triggerChangeIfDirty();
+ });
+
+ control.editor = editor;
+ }
+
+ buildEditor();
+ }
+ });
+
+ /**
+ * Mapping of widget ID to instances of TextWidgetControl subclasses.
+ *
+ * @memberOf wp.textWidgets
+ *
+ * @type {Object.<string, wp.textWidgets.TextWidgetControl>}
+ */
+ component.widgetControls = {};
+
+ /**
+ * Handle widget being added or initialized for the first time at the widget-added event.
+ *
+ * @memberOf wp.textWidgets
+ *
+ * @param {jQuery.Event} event - Event.
+ * @param {jQuery} widgetContainer - Widget container element.
+ *
+ * @returns {void}
+ */
+ component.handleWidgetAdded = function handleWidgetAdded( event, widgetContainer ) {
+ var widgetForm, idBase, widgetControl, widgetId, animatedCheckDelay = 50, renderWhenAnimationDone, fieldContainer, syncContainer;
+ widgetForm = widgetContainer.find( '> .widget-inside > .form, > .widget-inside > form' ); // Note: '.form' appears in the customizer, whereas 'form' on the widgets admin screen.
+
+ idBase = widgetForm.find( '> .id_base' ).val();
+ if ( -1 === component.idBases.indexOf( idBase ) ) {
+ return;
+ }
+
+ // Prevent initializing already-added widgets.
+ widgetId = widgetForm.find( '.widget-id' ).val();
+ if ( component.widgetControls[ widgetId ] ) {
+ return;
+ }
+
+ // Bypass using TinyMCE when widget is in legacy mode.
+ if ( ! widgetForm.find( '.visual' ).val() ) {
+ return;
+ }
+
+ /*
+ * Create a container element for the widget control fields.
+ * This is inserted into the DOM immediately before the .widget-content
+ * element because the contents of this element are essentially "managed"
+ * by PHP, where each widget update cause the entire element to be emptied
+ * and replaced with the rendered output of WP_Widget::form() which is
+ * sent back in Ajax request made to save/update the widget instance.
+ * To prevent a "flash of replaced DOM elements and re-initialized JS
+ * components", the JS template is rendered outside of the normal form
+ * container.
+ */
+ fieldContainer = $( '<div></div>' );
+ syncContainer = widgetContainer.find( '.widget-content:first' );
+ syncContainer.before( fieldContainer );
+
+ widgetControl = new component.TextWidgetControl({
+ el: fieldContainer,
+ syncContainer: syncContainer
+ });
+
+ component.widgetControls[ widgetId ] = widgetControl;
+
+ /*
+ * Render the widget once the widget parent's container finishes animating,
+ * as the widget-added event fires with a slideDown of the container.
+ * This ensures that the textarea is visible and an iframe can be embedded
+ * with TinyMCE being able to set contenteditable on it.
+ */
+ renderWhenAnimationDone = function() {
+ if ( ! widgetContainer.hasClass( 'open' ) ) {
+ setTimeout( renderWhenAnimationDone, animatedCheckDelay );
+ } else {
+ widgetControl.initializeEditor();
+ }
+ };
+ renderWhenAnimationDone();
+ };
+
+ /**
+ * Setup widget in accessibility mode.
+ *
+ * @memberOf wp.textWidgets
+ *
+ * @returns {void}
+ */
+ component.setupAccessibleMode = function setupAccessibleMode() {
+ var widgetForm, idBase, widgetControl, fieldContainer, syncContainer;
+ widgetForm = $( '.editwidget > form' );
+ if ( 0 === widgetForm.length ) {
+ return;
+ }
+
+ idBase = widgetForm.find( '> .widget-control-actions > .id_base' ).val();
+ if ( -1 === component.idBases.indexOf( idBase ) ) {
+ return;
+ }
+
+ // Bypass using TinyMCE when widget is in legacy mode.
+ if ( ! widgetForm.find( '.visual' ).val() ) {
+ return;
+ }
+
+ fieldContainer = $( '<div></div>' );
+ syncContainer = widgetForm.find( '> .widget-inside' );
+ syncContainer.before( fieldContainer );
+
+ widgetControl = new component.TextWidgetControl({
+ el: fieldContainer,
+ syncContainer: syncContainer
+ });
+
+ widgetControl.initializeEditor();
+ };
+
+ /**
+ * Sync widget instance data sanitized from server back onto widget model.
+ *
+ * This gets called via the 'widget-updated' event when saving a widget from
+ * the widgets admin screen and also via the 'widget-synced' event when making
+ * a change to a widget in the customizer.
+ *
+ * @memberOf wp.textWidgets
+ *
+ * @param {jQuery.Event} event - Event.
+ * @param {jQuery} widgetContainer - Widget container element.
+ * @returns {void}
+ */
+ component.handleWidgetUpdated = function handleWidgetUpdated( event, widgetContainer ) {
+ var widgetForm, widgetId, widgetControl, idBase;
+ widgetForm = widgetContainer.find( '> .widget-inside > .form, > .widget-inside > form' );
+
+ idBase = widgetForm.find( '> .id_base' ).val();
+ if ( -1 === component.idBases.indexOf( idBase ) ) {
+ return;
+ }
+
+ widgetId = widgetForm.find( '> .widget-id' ).val();
+ widgetControl = component.widgetControls[ widgetId ];
+ if ( ! widgetControl ) {
+ return;
+ }
+
+ widgetControl.updateFields();
+ };
+
+ /**
+ * Initialize functionality.
+ *
+ * This function exists to prevent the JS file from having to boot itself.
+ * When WordPress enqueues this script, it should have an inline script
+ * attached which calls wp.textWidgets.init().
+ *
+ * @memberOf wp.textWidgets
+ *
+ * @returns {void}
+ */
+ component.init = function init() {
+ var $document = $( document );
+ $document.on( 'widget-added', component.handleWidgetAdded );
+ $document.on( 'widget-synced widget-updated', component.handleWidgetUpdated );
+
+ /*
+ * Manually trigger widget-added events for media widgets on the admin
+ * screen once they are expanded. The widget-added event is not triggered
+ * for each pre-existing widget on the widgets admin screen like it is
+ * on the customizer. Likewise, the customizer only triggers widget-added
+ * when the widget is expanded to just-in-time construct the widget form
+ * when it is actually going to be displayed. So the following implements
+ * the same for the widgets admin screen, to invoke the widget-added
+ * handler when a pre-existing media widget is expanded.
+ */
+ $( function initializeExistingWidgetContainers() {
+ var widgetContainers;
+ if ( 'widgets' !== window.pagenow ) {
+ return;
+ }
+ widgetContainers = $( '.widgets-holder-wrap:not(#available-widgets)' ).find( 'div.widget' );
+ widgetContainers.one( 'click.toggle-widget-expanded', function toggleWidgetExpanded() {
+ var widgetContainer = $( this );
+ component.handleWidgetAdded( new jQuery.Event( 'widget-added' ), widgetContainer );
+ });
+
+ // Accessibility mode.
+ $( window ).on( 'load', function() {
+ component.setupAccessibleMode();
+ });
+ });
+ };
+
+ return component;
+})( jQuery );
diff --git a/www/crm/wp-admin/js/widgets/text-widgets.min.js b/www/crm/wp-admin/js/widgets/text-widgets.min.js
new file mode 100644
index 00000000..98e40d26
--- /dev/null
+++ b/www/crm/wp-admin/js/widgets/text-widgets.min.js
@@ -0,0 +1 @@
+wp.textWidgets=function(a){"use strict";var b={dismissedPointers:[],idBases:["text"]};return b.TextWidgetControl=Backbone.View.extend({events:{},initialize:function(b){var c=this;if(!b.el)throw new Error("Missing options.el");if(!b.syncContainer)throw new Error("Missing options.syncContainer");Backbone.View.prototype.initialize.call(c,b),c.syncContainer=b.syncContainer,c.$el.addClass("text-widget-fields"),c.$el.html(wp.template("widget-text-control-fields")),c.customHtmlWidgetPointer=c.$el.find(".wp-pointer.custom-html-widget-pointer"),c.customHtmlWidgetPointer.length&&(c.customHtmlWidgetPointer.find(".close").on("click",function(b){b.preventDefault(),c.customHtmlWidgetPointer.hide(),a("#"+c.fields.text.attr("id")+"-html").focus(),c.dismissPointers(["text_widget_custom_html"])}),c.customHtmlWidgetPointer.find(".add-widget").on("click",function(a){a.preventDefault(),c.customHtmlWidgetPointer.hide(),c.openAvailableWidgetsPanel()})),c.pasteHtmlPointer=c.$el.find(".wp-pointer.paste-html-pointer"),c.pasteHtmlPointer.length&&c.pasteHtmlPointer.find(".close").on("click",function(a){a.preventDefault(),c.pasteHtmlPointer.hide(),c.editor.focus(),c.dismissPointers(["text_widget_custom_html","text_widget_paste_html"])}),c.fields={title:c.$el.find(".title"),text:c.$el.find(".text")},_.each(c.fields,function(a,b){a.on("input change",function(){var d=c.syncContainer.find(".sync-input."+b);d.val()!==a.val()&&(d.val(a.val()),d.trigger("change"))}),a.val(c.syncContainer.find(".sync-input."+b).val())})},dismissPointers:function(a){_.each(a,function(a){wp.ajax.post("dismiss-wp-pointer",{pointer:a}),b.dismissedPointers.push(a)})},openAvailableWidgetsPanel:function(){var a;wp.customize.section.each(function(b){b.extended(wp.customize.Widgets.SidebarSection)&&b.expanded()&&(a=wp.customize.control("sidebars_widgets["+b.params.sidebarId+"]"))}),a&&setTimeout(function(){wp.customize.Widgets.availableWidgetsPanel.open(a),wp.customize.Widgets.availableWidgetsPanel.$search.val("HTML").trigger("keyup")})},updateFields:function(){var a,b=this;b.fields.title.is(document.activeElement)||(a=b.syncContainer.find(".sync-input.title"),b.fields.title.val(a.val())),a=b.syncContainer.find(".sync-input.text"),b.fields.text.is(":visible")?b.fields.text.is(document.activeElement)||b.fields.text.val(a.val()):b.editor&&!b.editorFocused&&a.val()!==b.fields.text.val()&&b.editor.setContent(wp.editor.autop(a.val()))},initializeEditor:function(){function c(){var e,g,l;if(document.getElementById(d)){if("undefined"==typeof window.tinymce)return void wp.editor.initialize(d,{quicktags:!0,mediaButtons:!0});if(tinymce.get(d)&&(j=tinymce.get(d).isHidden(),wp.editor.remove(d)),a(document).one("wp-before-tinymce-init.text-widget-init",function(a,b){b.plugins&&(/\bwpview\b/.test(b.plugins)||(b.plugins+=",wpview"))}),wp.editor.initialize(d,{tinymce:{wpautop:!0},quicktags:!0,mediaButtons:!0}),l=function(b){b.show(),b.find(".close").focus(),wp.a11y.speak(b.find("h3, p").map(function(){return a(this).text()}).get().join("\n\n"))},e=window.tinymce.get(d),!e)throw new Error("Failed to initialize editor");g=function(){a(e.getWin()).on("unload",function(){_.defer(c)}),j&&switchEditors.go(d,"html"),a("#"+d+"-html").on("click",function(){h.pasteHtmlPointer.hide(),-1===b.dismissedPointers.indexOf("text_widget_custom_html")&&l(h.customHtmlWidgetPointer)}),a("#"+d+"-tmce").on("click",function(){h.customHtmlWidgetPointer.hide()}),e.on("pastepreprocess",function(a){var c=a.content;-1===b.dismissedPointers.indexOf("text_widget_paste_html")&&c&&/&lt;\w+.*?&gt;/.test(c)&&_.delay(function(){l(h.pasteHtmlPointer)},250)})},e.initialized?g():e.on("init",g),h.editorFocused=!1,e.on("focus",function(){h.editorFocused=!0}),e.on("paste",function(){e.setDirty(!0),f()}),e.on("NodeChange",function(){k=!0}),e.on("NodeChange",_.debounce(f,i)),e.on("blur hide",function(){h.editorFocused=!1,f()}),h.editor=e}}var d,e,f,g,h=this,i=1e3,j=!1,k=!1;e=h.fields.text,d=e.attr("id"),g=e.val(),f=function(){var a=300;h.editor.isDirty()&&(wp.customize&&wp.customize.state&&(wp.customize.state("processing").set(wp.customize.state("processing").get()+1),_.delay(function(){wp.customize.state("processing").set(wp.customize.state("processing").get()-1)},a)),h.editor.isHidden()||h.editor.save()),k&&g!==e.val()&&(e.trigger("change"),k=!1,g=e.val())},h.syncContainer.closest(".widget").find("[name=savewidget]:first").on("click",function(){f()}),c()}}),b.widgetControls={},b.handleWidgetAdded=function(c,d){var e,f,g,h,i,j,k,l=50;e=d.find("> .widget-inside > .form, > .widget-inside > form"),f=e.find("> .id_base").val(),-1!==b.idBases.indexOf(f)&&(h=e.find(".widget-id").val(),b.widgetControls[h]||e.find(".visual").val()&&(j=a("<div></div>"),k=d.find(".widget-content:first"),k.before(j),g=new b.TextWidgetControl({el:j,syncContainer:k}),b.widgetControls[h]=g,(i=function(){d.hasClass("open")?g.initializeEditor():setTimeout(i,l)})()))},b.setupAccessibleMode=function(){var c,d,e,f,g;c=a(".editwidget > form"),0!==c.length&&(d=c.find("> .widget-control-actions > .id_base").val(),-1!==b.idBases.indexOf(d)&&c.find(".visual").val()&&(f=a("<div></div>"),g=c.find("> .widget-inside"),g.before(f),e=new b.TextWidgetControl({el:f,syncContainer:g}),e.initializeEditor()))},b.handleWidgetUpdated=function(a,c){var d,e,f,g;d=c.find("> .widget-inside > .form, > .widget-inside > form"),g=d.find("> .id_base").val(),-1!==b.idBases.indexOf(g)&&(e=d.find("> .widget-id").val(),f=b.widgetControls[e],f&&f.updateFields())},b.init=function(){var c=a(document);c.on("widget-added",b.handleWidgetAdded),c.on("widget-synced widget-updated",b.handleWidgetUpdated),a(function(){var c;"widgets"===window.pagenow&&(c=a(".widgets-holder-wrap:not(#available-widgets)").find("div.widget"),c.one("click.toggle-widget-expanded",function(){var c=a(this);b.handleWidgetAdded(new jQuery.Event("widget-added"),c)}),a(window).on("load",function(){b.setupAccessibleMode()}))})},b}(jQuery); \ No newline at end of file
diff --git a/www/crm/wp-admin/js/word-count.js b/www/crm/wp-admin/js/word-count.js
new file mode 100644
index 00000000..999a42c5
--- /dev/null
+++ b/www/crm/wp-admin/js/word-count.js
@@ -0,0 +1,218 @@
+/**
+ * Word or character counting functionality. Count words or characters in a
+ * provided text string.
+ *
+ * @namespace wp.utils
+ * @since 2.6.0
+ * @output wp-admin/js/word-count.js
+ */
+
+( function() {
+ /**
+ * Word counting utility
+ *
+ * @namespace wp.utils.wordcounter
+ * @memberof wp.utils
+ *
+ * @class
+ *
+ * @param {Object} settings Optional. Key-value object containing overrides for
+ * settings.
+ * @param {RegExp} settings.HTMLRegExp Optional. Regular expression to find HTML elements.
+ * @param {RegExp} settings.HTMLcommentRegExp Optional. Regular expression to find HTML comments.
+ * @param {RegExp} settings.spaceRegExp Optional. Regular expression to find irregular space
+ * characters.
+ * @param {RegExp} settings.HTMLEntityRegExp Optional. Regular expression to find HTML entities.
+ * @param {RegExp} settings.connectorRegExp Optional. Regular expression to find connectors that
+ * split words.
+ * @param {RegExp} settings.removeRegExp Optional. Regular expression to find remove unwanted
+ * characters to reduce false-positives.
+ * @param {RegExp} settings.astralRegExp Optional. Regular expression to find unwanted
+ * characters when searching for non-words.
+ * @param {RegExp} settings.wordsRegExp Optional. Regular expression to find words by spaces.
+ * @param {RegExp} settings.characters_excluding_spacesRegExp Optional. Regular expression to find characters which
+ * are non-spaces.
+ * @param {RegExp} settings.characters_including_spacesRegExp Optional. Regular expression to find characters
+ * including spaces.
+ * @param {RegExp} settings.shortcodesRegExp Optional. Regular expression to find shortcodes.
+ * @param {Object} settings.l10n Optional. Localization object containing specific
+ * configuration for the current localization.
+ * @param {String} settings.l10n.type Optional. Method of finding words to count.
+ * @param {Array} settings.l10n.shortcodes Optional. Array of shortcodes that should be removed
+ * from the text.
+ *
+ * @return void
+ */
+ function WordCounter( settings ) {
+ var key,
+ shortcodes;
+
+ // Apply provided settings to object settings.
+ if ( settings ) {
+ for ( key in settings ) {
+
+ // Only apply valid settings.
+ if ( settings.hasOwnProperty( key ) ) {
+ this.settings[ key ] = settings[ key ];
+ }
+ }
+ }
+
+ shortcodes = this.settings.l10n.shortcodes;
+
+ // If there are any localization shortcodes, add this as type in the settings.
+ if ( shortcodes && shortcodes.length ) {
+ this.settings.shortcodesRegExp = new RegExp( '\\[\\/?(?:' + shortcodes.join( '|' ) + ')[^\\]]*?\\]', 'g' );
+ }
+ }
+
+ // Default settings.
+ WordCounter.prototype.settings = {
+ HTMLRegExp: /<\/?[a-z][^>]*?>/gi,
+ HTMLcommentRegExp: /<!--[\s\S]*?-->/g,
+ spaceRegExp: /&nbsp;|&#160;/gi,
+ HTMLEntityRegExp: /&\S+?;/g,
+
+ // \u2014 = em-dash
+ connectorRegExp: /--|\u2014/g,
+
+ // Characters to be removed from input text.
+ removeRegExp: new RegExp( [
+ '[',
+
+ // Basic Latin (extract)
+ '\u0021-\u0040\u005B-\u0060\u007B-\u007E',
+
+ // Latin-1 Supplement (extract)
+ '\u0080-\u00BF\u00D7\u00F7',
+
+ /*
+ * The following range consists of:
+ * General Punctuation
+ * Superscripts and Subscripts
+ * Currency Symbols
+ * Combining Diacritical Marks for Symbols
+ * Letterlike Symbols
+ * Number Forms
+ * Arrows
+ * Mathematical Operators
+ * Miscellaneous Technical
+ * Control Pictures
+ * Optical Character Recognition
+ * Enclosed Alphanumerics
+ * Box Drawing
+ * Block Elements
+ * Geometric Shapes
+ * Miscellaneous Symbols
+ * Dingbats
+ * Miscellaneous Mathematical Symbols-A
+ * Supplemental Arrows-A
+ * Braille Patterns
+ * Supplemental Arrows-B
+ * Miscellaneous Mathematical Symbols-B
+ * Supplemental Mathematical Operators
+ * Miscellaneous Symbols and Arrows
+ */
+ '\u2000-\u2BFF',
+
+ // Supplemental Punctuation
+ '\u2E00-\u2E7F',
+ ']'
+ ].join( '' ), 'g' ),
+
+ // Remove UTF-16 surrogate points, see https://en.wikipedia.org/wiki/UTF-16#U.2BD800_to_U.2BDFFF
+ astralRegExp: /[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
+ wordsRegExp: /\S\s+/g,
+ characters_excluding_spacesRegExp: /\S/g,
+
+ /*
+ * Match anything that is not a formatting character, excluding:
+ * \f = form feed
+ * \n = new line
+ * \r = carriage return
+ * \t = tab
+ * \v = vertical tab
+ * \u00AD = soft hyphen
+ * \u2028 = line separator
+ * \u2029 = paragraph separator
+ */
+ characters_including_spacesRegExp: /[^\f\n\r\t\v\u00AD\u2028\u2029]/g,
+ l10n: window.wordCountL10n || {}
+ };
+
+ /**
+ * Counts the number of words (or other specified type) in the specified text.
+ *
+ * @since 2.6.0
+ * @memberof wp.utils.wordcounter
+ *
+ * @param {String} text Text to count elements in.
+ * @param {String} type Optional. Specify type to use.
+ *
+ * @return {Number} The number of items counted.
+ */
+ WordCounter.prototype.count = function( text, type ) {
+ var count = 0;
+
+ // Use default type if none was provided.
+ type = type || this.settings.l10n.type;
+
+ // Sanitize type to one of three possibilities: 'words', 'characters_excluding_spaces' or 'characters_including_spaces'.
+ if ( type !== 'characters_excluding_spaces' && type !== 'characters_including_spaces' ) {
+ type = 'words';
+ }
+
+ // If we have any text at all.
+ if ( text ) {
+ text = text + '\n';
+
+ // Replace all HTML with a new-line.
+ text = text.replace( this.settings.HTMLRegExp, '\n' );
+
+ // Remove all HTML comments.
+ text = text.replace( this.settings.HTMLcommentRegExp, '' );
+
+ // If a shortcode regular expression has been provided use it to remove shortcodes.
+ if ( this.settings.shortcodesRegExp ) {
+ text = text.replace( this.settings.shortcodesRegExp, '\n' );
+ }
+
+ // Normalize non-breaking space to a normal space.
+ text = text.replace( this.settings.spaceRegExp, ' ' );
+
+ if ( type === 'words' ) {
+
+ // Remove HTML Entities.
+ text = text.replace( this.settings.HTMLEntityRegExp, '' );
+
+ // Convert connectors to spaces to count attached text as words.
+ text = text.replace( this.settings.connectorRegExp, ' ' );
+
+ // Remove unwanted characters.
+ text = text.replace( this.settings.removeRegExp, '' );
+ } else {
+
+ // Convert HTML Entities to "a".
+ text = text.replace( this.settings.HTMLEntityRegExp, 'a' );
+
+ // Remove surrogate points.
+ text = text.replace( this.settings.astralRegExp, 'a' );
+ }
+
+ // Match with the selected type regular expression to count the items.
+ text = text.match( this.settings[ type + 'RegExp' ] );
+
+ // If we have any matches, set the count to the number of items found.
+ if ( text ) {
+ count = text.length;
+ }
+ }
+
+ return count;
+ };
+
+ // Add the WordCounter to the WP Utils.
+ window.wp = window.wp || {};
+ window.wp.utils = window.wp.utils || {};
+ window.wp.utils.WordCounter = WordCounter;
+} )();
diff --git a/www/crm/wp-admin/js/word-count.min.js b/www/crm/wp-admin/js/word-count.min.js
new file mode 100644
index 00000000..296ef257
--- /dev/null
+++ b/www/crm/wp-admin/js/word-count.min.js
@@ -0,0 +1 @@
+!function(){function a(a){var b,c;if(a)for(b in a)a.hasOwnProperty(b)&&(this.settings[b]=a[b]);c=this.settings.l10n.shortcodes,c&&c.length&&(this.settings.shortcodesRegExp=new RegExp("\\[\\/?(?:"+c.join("|")+")[^\\]]*?\\]","g"))}a.prototype.settings={HTMLRegExp:/<\/?[a-z][^>]*?>/gi,HTMLcommentRegExp:/<!--[\s\S]*?-->/g,spaceRegExp:/&nbsp;|&#160;/gi,HTMLEntityRegExp:/&\S+?;/g,connectorRegExp:/--|\u2014/g,removeRegExp:new RegExp(["[","!-@[-`{-~","\x80-\xbf\xd7\xf7","\u2000-\u2bff","\u2e00-\u2e7f","]"].join(""),"g"),astralRegExp:/[\uD800-\uDBFF][\uDC00-\uDFFF]/g,wordsRegExp:/\S\s+/g,characters_excluding_spacesRegExp:/\S/g,characters_including_spacesRegExp:/[^\f\n\r\t\v\u00AD\u2028\u2029]/g,l10n:window.wordCountL10n||{}},a.prototype.count=function(a,b){var c=0;return b=b||this.settings.l10n.type,"characters_excluding_spaces"!==b&&"characters_including_spaces"!==b&&(b="words"),a&&(a+="\n",a=a.replace(this.settings.HTMLRegExp,"\n"),a=a.replace(this.settings.HTMLcommentRegExp,""),this.settings.shortcodesRegExp&&(a=a.replace(this.settings.shortcodesRegExp,"\n")),a=a.replace(this.settings.spaceRegExp," "),"words"===b?(a=a.replace(this.settings.HTMLEntityRegExp,""),a=a.replace(this.settings.connectorRegExp," "),a=a.replace(this.settings.removeRegExp,"")):(a=a.replace(this.settings.HTMLEntityRegExp,"a"),a=a.replace(this.settings.astralRegExp,"a")),a=a.match(this.settings[b+"RegExp"]),a&&(c=a.length)),c},window.wp=window.wp||{},window.wp.utils=window.wp.utils||{},window.wp.utils.WordCounter=a}(); \ No newline at end of file
diff --git a/www/crm/wp-admin/js/wp-fullscreen-stub.js b/www/crm/wp-admin/js/wp-fullscreen-stub.js
new file mode 100644
index 00000000..490d18d0
--- /dev/null
+++ b/www/crm/wp-admin/js/wp-fullscreen-stub.js
@@ -0,0 +1,40 @@
+/**
+ * Distraction-Free Writing (wp-fullscreen) backward compatibility stub.
+ *
+ * @deprecated 4.1
+ * @removed 4.3.
+ * @output wp-admin/js/wp-fullscreen-stub.js
+ */
+( function() {
+ var noop = function(){};
+
+ window.wp = window.wp || {};
+ window.wp.editor = window.wp.editor || {};
+ window.wp.editor.fullscreen = {
+ bind_resize: noop,
+ dfwWidth: noop,
+ off: noop,
+ on: noop,
+ refreshButtons: noop,
+ resizeTextarea: noop,
+ save: noop,
+ switchmode: noop,
+ toggleUI: noop,
+
+ settings: {},
+ pubsub: {
+ publish: noop,
+ subscribe: noop,
+ unsubscribe: noop,
+ topics: {}
+ },
+ fade: {
+ In: noop,
+ Out: noop
+ },
+ ui: {
+ fade: noop,
+ init: noop
+ }
+ };
+}());
diff --git a/www/crm/wp-admin/js/wp-fullscreen-stub.min.js b/www/crm/wp-admin/js/wp-fullscreen-stub.min.js
new file mode 100644
index 00000000..11f3357e
--- /dev/null
+++ b/www/crm/wp-admin/js/wp-fullscreen-stub.min.js
@@ -0,0 +1 @@
+!function(){var a=function(){};window.wp=window.wp||{},window.wp.editor=window.wp.editor||{},window.wp.editor.fullscreen={bind_resize:a,dfwWidth:a,off:a,on:a,refreshButtons:a,resizeTextarea:a,save:a,switchmode:a,toggleUI:a,settings:{},pubsub:{publish:a,subscribe:a,unsubscribe:a,topics:{}},fade:{In:a,Out:a},ui:{fade:a,init:a}}}(); \ No newline at end of file
diff --git a/www/crm/wp-admin/js/xfn.js b/www/crm/wp-admin/js/xfn.js
new file mode 100644
index 00000000..61605a32
--- /dev/null
+++ b/www/crm/wp-admin/js/xfn.js
@@ -0,0 +1,279 @@
+/**
+ * Generates the XHTML Friends Network 'rel' string from the inputs.
+ *
+ * @deprecated 3.5.0
+ * @output wp-admin/js/xfn.js
+ */
+jQuery( document ).ready(function( $ ) {
+ $( '#link_rel' ).prop( 'readonly', true );
+ $( '#linkxfndiv input' ).bind( 'click keyup', function() {
+ var isMe = $( '#me' ).is( ':checked' ), inputs = '';
+ $( 'input.valinp' ).each( function() {
+ if ( isMe ) {
+ $( this ).prop( 'disabled', true ).parent().addClass( 'disabled' );
+ } else {
+ $( this ).removeAttr( 'disabled' ).parent().removeClass( 'disabled' );
+ if ( $( this ).is( ':checked' ) && $( this ).val() !== '') {
+ inputs += $( this ).val() + ' ';
+ }
+ }
+ });
+ $( '#link_rel' ).val( ( isMe ) ? 'me' : inputs.substr( 0,inputs.length - 1 ) );
+ });
+});
+
+// Privacy request action handling
+jQuery( document ).ready( function( $ ) {
+ var strings = window.privacyToolsL10n || {};
+
+ function setActionState( $action, state ) {
+ $action.children().hide();
+ $action.children( '.' + state ).show();
+ }
+
+ function clearResultsAfterRow( $requestRow ) {
+ $requestRow.removeClass( 'has-request-results' );
+
+ if ( $requestRow.next().hasClass( 'request-results' ) ) {
+ $requestRow.next().remove();
+ }
+ }
+
+ function appendResultsAfterRow( $requestRow, classes, summaryMessage, additionalMessages ) {
+ var itemList = '',
+ resultRowClasses = 'request-results';
+
+ clearResultsAfterRow( $requestRow );
+
+ if ( additionalMessages.length ) {
+ $.each( additionalMessages, function( index, value ) {
+ itemList = itemList + '<li>' + value + '</li>';
+ });
+ itemList = '<ul>' + itemList + '</ul>';
+ }
+
+ $requestRow.addClass( 'has-request-results' );
+
+ if ( $requestRow.hasClass( 'status-request-confirmed' ) ) {
+ resultRowClasses = resultRowClasses + ' status-request-confirmed';
+ }
+
+ if ( $requestRow.hasClass( 'status-request-failed' ) ) {
+ resultRowClasses = resultRowClasses + ' status-request-failed';
+ }
+
+ $requestRow.after( function() {
+ return '<tr class="' + resultRowClasses + '"><th colspan="5">' +
+ '<div class="notice inline notice-alt ' + classes + '">' +
+ '<p>' + summaryMessage + '</p>' +
+ itemList +
+ '</div>' +
+ '</td>' +
+ '</tr>';
+ });
+ }
+
+ $( '.export-personal-data-handle' ).click( function( event ) {
+
+ var $this = $( this ),
+ $action = $this.parents( '.export-personal-data' ),
+ $requestRow = $this.parents( 'tr' ),
+ requestID = $action.data( 'request-id' ),
+ nonce = $action.data( 'nonce' ),
+ exportersCount = $action.data( 'exporters-count' ),
+ sendAsEmail = $action.data( 'send-as-email' ) ? true : false;
+
+ event.preventDefault();
+ event.stopPropagation();
+
+ $action.blur();
+ clearResultsAfterRow( $requestRow );
+
+ function onExportDoneSuccess( zipUrl ) {
+ setActionState( $action, 'export-personal-data-success' );
+ if ( 'undefined' !== typeof zipUrl ) {
+ window.location = zipUrl;
+ } else if ( ! sendAsEmail ) {
+ onExportFailure( strings.noExportFile );
+ }
+ }
+
+ function onExportFailure( errorMessage ) {
+ setActionState( $action, 'export-personal-data-failed' );
+ if ( errorMessage ) {
+ appendResultsAfterRow( $requestRow, 'notice-error', strings.exportError, [ errorMessage ] );
+ }
+ }
+
+ function doNextExport( exporterIndex, pageIndex ) {
+ $.ajax(
+ {
+ url: window.ajaxurl,
+ data: {
+ action: 'wp-privacy-export-personal-data',
+ exporter: exporterIndex,
+ id: requestID,
+ page: pageIndex,
+ security: nonce,
+ sendAsEmail: sendAsEmail
+ },
+ method: 'post'
+ }
+ ).done( function( response ) {
+ var responseData = response.data;
+
+ if ( ! response.success ) {
+
+ // e.g. invalid request ID
+ onExportFailure( response.data );
+ return;
+ }
+
+ if ( ! responseData.done ) {
+ setTimeout( doNextExport( exporterIndex, pageIndex + 1 ) );
+ } else {
+ if ( exporterIndex < exportersCount ) {
+ setTimeout( doNextExport( exporterIndex + 1, 1 ) );
+ } else {
+ onExportDoneSuccess( responseData.url );
+ }
+ }
+ }).fail( function( jqxhr, textStatus, error ) {
+
+ // e.g. Nonce failure
+ onExportFailure( error );
+ });
+ }
+
+ // And now, let's begin
+ setActionState( $action, 'export-personal-data-processing' );
+ doNextExport( 1, 1 );
+ });
+
+ $( '.remove-personal-data-handle' ).click( function( event ) {
+
+ var $this = $( this ),
+ $action = $this.parents( '.remove-personal-data' ),
+ $requestRow = $this.parents( 'tr' ),
+ requestID = $action.data( 'request-id' ),
+ nonce = $action.data( 'nonce' ),
+ erasersCount = $action.data( 'erasers-count' ),
+ hasRemoved = false,
+ hasRetained = false,
+ messages = [];
+
+ event.stopPropagation();
+
+ $action.blur();
+ clearResultsAfterRow( $requestRow );
+
+ function onErasureDoneSuccess() {
+ var summaryMessage = strings.noDataFound;
+ var classes = 'notice-success';
+
+ setActionState( $action, 'remove-personal-data-idle' );
+
+ if ( false === hasRemoved ) {
+ if ( false === hasRetained ) {
+ summaryMessage = strings.noDataFound;
+ } else {
+ summaryMessage = strings.noneRemoved;
+ classes = 'notice-warning';
+ }
+ } else {
+ if ( false === hasRetained ) {
+ summaryMessage = strings.foundAndRemoved;
+ } else {
+ summaryMessage = strings.someNotRemoved;
+ classes = 'notice-warning';
+ }
+ }
+ appendResultsAfterRow( $requestRow, 'notice-success', summaryMessage, messages );
+ }
+
+ function onErasureFailure() {
+ setActionState( $action, 'remove-personal-data-failed' );
+ appendResultsAfterRow( $requestRow, 'notice-error', strings.removalError, [] );
+ }
+
+ function doNextErasure( eraserIndex, pageIndex ) {
+ $.ajax({
+ url: window.ajaxurl,
+ data: {
+ action: 'wp-privacy-erase-personal-data',
+ eraser: eraserIndex,
+ id: requestID,
+ page: pageIndex,
+ security: nonce
+ },
+ method: 'post'
+ }).done( function( response ) {
+ var responseData = response.data;
+
+ if ( ! response.success ) {
+ onErasureFailure();
+ return;
+ }
+ if ( responseData.items_removed ) {
+ hasRemoved = hasRemoved || responseData.items_removed;
+ }
+ if ( responseData.items_retained ) {
+ hasRetained = hasRetained || responseData.items_retained;
+ }
+ if ( responseData.messages ) {
+ messages = messages.concat( responseData.messages );
+ }
+ if ( ! responseData.done ) {
+ setTimeout( doNextErasure( eraserIndex, pageIndex + 1 ) );
+ } else {
+ if ( eraserIndex < erasersCount ) {
+ setTimeout( doNextErasure( eraserIndex + 1, 1 ) );
+ } else {
+ onErasureDoneSuccess();
+ }
+ }
+ }).fail( function() {
+ onErasureFailure();
+ });
+ }
+
+ // And now, let's begin
+ setActionState( $action, 'remove-personal-data-processing' );
+
+ doNextErasure( 1, 1 );
+ });
+});
+
+( function( $ ) {
+
+ // Privacy policy page, copy button.
+ $( document ).on( 'click', function( event ) {
+ var $target = $( event.target );
+ var $parent, $container, range;
+
+ if ( $target.is( 'button.privacy-text-copy' ) ) {
+ $parent = $target.parent().parent();
+ $container = $parent.find( 'div.wp-suggested-text' );
+
+ if ( ! $container.length ) {
+ $container = $parent.find( 'div.policy-text' );
+ }
+
+ if ( $container.length ) {
+ try {
+ window.getSelection().removeAllRanges();
+ range = document.createRange();
+ $container.addClass( 'hide-privacy-policy-tutorial' );
+
+ range.selectNodeContents( $container[0] );
+ window.getSelection().addRange( range );
+ document.execCommand( 'copy' );
+
+ $container.removeClass( 'hide-privacy-policy-tutorial' );
+ window.getSelection().removeAllRanges();
+ } catch ( er ) {}
+ }
+ }
+ });
+
+} ( jQuery ) );
diff --git a/www/crm/wp-admin/js/xfn.min.js b/www/crm/wp-admin/js/xfn.min.js
new file mode 100644
index 00000000..53250bb7
--- /dev/null
+++ b/www/crm/wp-admin/js/xfn.min.js
@@ -0,0 +1 @@
+jQuery(document).ready(function(a){a("#link_rel").prop("readonly",!0),a("#linkxfndiv input").bind("click keyup",function(){var b=a("#me").is(":checked"),c="";a("input.valinp").each(function(){b?a(this).prop("disabled",!0).parent().addClass("disabled"):(a(this).removeAttr("disabled").parent().removeClass("disabled"),a(this).is(":checked")&&""!==a(this).val()&&(c+=a(this).val()+" "))}),a("#link_rel").val(b?"me":c.substr(0,c.length-1))})}),jQuery(document).ready(function(a){function b(a,b){a.children().hide(),a.children("."+b).show()}function c(a){a.removeClass("has-request-results"),a.next().hasClass("request-results")&&a.next().remove()}function d(b,d,e,f){var g="",h="request-results";c(b),f.length&&(a.each(f,function(a,b){g=g+"<li>"+b+"</li>"}),g="<ul>"+g+"</ul>"),b.addClass("has-request-results"),b.hasClass("status-request-confirmed")&&(h+=" status-request-confirmed"),b.hasClass("status-request-failed")&&(h+=" status-request-failed"),b.after(function(){return'<tr class="'+h+'"><th colspan="5"><div class="notice inline notice-alt '+d+'"><p>'+e+"</p>"+g+"</div></td></tr>"})}var e=window.privacyToolsL10n||{};a(".export-personal-data-handle").click(function(f){function g(a){b(k,"export-personal-data-success"),"undefined"!=typeof a?window.location=a:p||h(e.noExportFile)}function h(a){b(k,"export-personal-data-failed"),a&&d(l,"notice-error",e.exportError,[a])}function i(b,c){a.ajax({url:window.ajaxurl,data:{action:"wp-privacy-export-personal-data",exporter:b,id:m,page:c,security:n,sendAsEmail:p},method:"post"}).done(function(a){var d=a.data;return a.success?void(d.done?b<o?setTimeout(i(b+1,1)):g(d.url):setTimeout(i(b,c+1))):void h(a.data)}).fail(function(a,b,c){h(c)})}var j=a(this),k=j.parents(".export-personal-data"),l=j.parents("tr"),m=k.data("request-id"),n=k.data("nonce"),o=k.data("exporters-count"),p=!!k.data("send-as-email");f.preventDefault(),f.stopPropagation(),k.blur(),c(l),b(k,"export-personal-data-processing"),i(1,1)}),a(".remove-personal-data-handle").click(function(f){function g(){var a=e.noDataFound,c="notice-success";b(k,"remove-personal-data-idle"),!1===p?!1===q?a=e.noDataFound:(a=e.noneRemoved,c="notice-warning"):!1===q?a=e.foundAndRemoved:(a=e.someNotRemoved,c="notice-warning"),d(l,"notice-success",a,r)}function h(){b(k,"remove-personal-data-failed"),d(l,"notice-error",e.removalError,[])}function i(b,c){a.ajax({url:window.ajaxurl,data:{action:"wp-privacy-erase-personal-data",eraser:b,id:m,page:c,security:n},method:"post"}).done(function(a){var d=a.data;return a.success?(d.items_removed&&(p=p||d.items_removed),d.items_retained&&(q=q||d.items_retained),d.messages&&(r=r.concat(d.messages)),void(d.done?b<o?setTimeout(i(b+1,1)):g():setTimeout(i(b,c+1)))):void h()}).fail(function(){h()})}var j=a(this),k=j.parents(".remove-personal-data"),l=j.parents("tr"),m=k.data("request-id"),n=k.data("nonce"),o=k.data("erasers-count"),p=!1,q=!1,r=[];f.stopPropagation(),k.blur(),c(l),b(k,"remove-personal-data-processing"),i(1,1)})}),function(a){a(document).on("click",function(b){var c,d,e,f=a(b.target);if(f.is("button.privacy-text-copy")&&(c=f.parent().parent(),d=c.find("div.wp-suggested-text"),d.length||(d=c.find("div.policy-text")),d.length))try{window.getSelection().removeAllRanges(),e=document.createRange(),d.addClass("hide-privacy-policy-tutorial"),e.selectNodeContents(d[0]),window.getSelection().addRange(e),document.execCommand("copy"),d.removeClass("hide-privacy-policy-tutorial"),window.getSelection().removeAllRanges()}catch(g){}})}(jQuery); \ No newline at end of file