/** * Cause gallery shortcode is hardcoded into WP core and has a lot of relations with Media Manager and other parts * had to copy related code and adjust to the DG needs. * Since WP core make use of shortcode tag to get (build) needed function name while handling shortcode (especially in visual editor), * named all DG related variables and functions using prefix or suffix (depending on context) same as shortcode tag - dg * As long as WP core has strange conditional statements for order and orderby attributes had to rename them by adding dg prefix. * */ ( function ($, _) { // Check if environment is suitable for the code to be executed if ( typeof wp.media.collection !== 'function' ) return; var l10n, media = wp.media; // Link any localized strings. l10n = media.view.l10n = typeof _wpMediaViewsL10n === 'undefined' ? {} : _wpMediaViewsL10n; jQuery.extend(l10n, DGl10n); // Based on code from /wp-includes/js/media-views.js WP 4.1 /** * wp.media.controller.dgEdit * * A state for editing a dg's images and settings. * * @class * @augments wp.media.controller.Library * @augments wp.media.controller.State * @augments Backbone.Model * * @param {object} [attributes] The attributes hash passed to the state. * @param {string} [attributes.id=dg-edit] Unique identifier. * @param {string} [attributes.title=Edit Document Gallery] Title for the state. Displays in the frame's title region. * @param {wp.media.model.Attachments} [attributes.library] The collection of attachments in the dg. * If one is not supplied, an empty media.model.Selection collection is created. * @param {boolean} [attributes.multiple=false] Whether multi-select is enabled. * @param {boolean} [attributes.searchable=false] Whether the library is searchable. * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. * @param {string|false} [attributes.content=browse] Initial mode for the content region. * @param {string|false} [attributes.toolbar=image-details] Initial mode for the toolbar region. * @param {boolean} [attributes.describe=true] Whether to offer UI to describe attachments - e.g. captioning images in a dg. * @param {boolean} [attributes.displaySettings=true] Whether to show the attachment display settings interface. * @param {boolean} [attributes.dragInfo=true] Whether to show instructional text about the attachments being sortable. * @param {int} [attributes.idealColumnWidth=170] The ideal column width in pixels for attachments. * @param {boolean} [attributes.editing=false] Whether the dg is being created, or editing an existing instance. * @param {int} [attributes.priority=60] The priority for the state link in the media menu. * @param {boolean} [attributes.syncSelection=false] Whether the Attachments selection should be persisted from the last state. * Defaults to false for this state, because the library passed in *is* the selection. * @param {view} [attributes.AttachmentView] The single `Attachment` view to be used in the `Attachments`. * If none supplied, defaults to wp.media.view.Attachment.EditLibrary. */ media.controller.dgEdit = media.controller.Library.extend({ defaults: { id: 'dg-edit', title: l10n.editdgTitle, multiple: false, searchable: false, date: false, sortable: true, display: false, content: 'browse', toolbar: 'dg-edit', describe: true, displaySettings: true, dragInfo: true, idealColumnWidth: 170, editing: false, priority: 60, syncSelection: false }, /** * @since 3.5.0 */ initialize: function () { // If we haven't been provided a `library`, create a `Selection`. if (!this.get('library')) this.set('library', new media.model.Selection()); // The single `Attachment` view to be used in the `Attachments` view. if (!this.get('AttachmentView')) this.set('AttachmentView', media.view.Attachment.EditLibrary); media.controller.Library.prototype.initialize.apply(this, arguments); }, /** * @since 3.5.0 */ activate: function () { var library = this.get('library'); // Limit the library to images only. //library.props.set( 'type', 'image' ); // Watch for uploaded attachments. this.get('library').observe(wp.Uploader.queue); this.frame.on('content:render:browse', this.dgSettings, this); media.controller.Library.prototype.activate.apply(this, arguments); }, /** * @since 3.5.0 */ deactivate: function () { // Stop watching for uploaded attachments. this.get('library').unobserve(wp.Uploader.queue); this.frame.off('content:render:browse', this.dgSettings, this); media.controller.Library.prototype.deactivate.apply(this, arguments); }, /** * @since 3.5.0 * * @param browser */ dgSettings: function (browser) { if (!this.get('displaySettings')) { return; } var library = this.get('library'); if (!library || !browser) { return; } library.dg = library.dg || new Backbone.Model(); browser.sidebar.set({ dg: new media.view.Settings.dg({ controller: this, model: library.dg, priority: 40 }) }); browser.toolbar.set('reverse', { text: l10n.reverseOrder, priority: 80, click: function () { library.reset(library.toArray().reverse()); } }); } }); /** * A state for selecting more images to add to a dg. * * @class * @augments wp.media.controller.Library * @augments wp.media.controller.State * @augments Backbone.Model * * @param {object} [attributes] The attributes hash passed to the state. * @param {string} [attributes.id=dg-library] Unique identifier. * @param {string} [attributes.title=Add to Document Gallery] Title for the state. Displays in the frame's title region. * @param {boolean} [attributes.multiple=add] Whether multi-select is enabled. @todo 'add' doesn't seem do anything special, and gets used as a boolean. * @param {wp.media.model.Attachments} [attributes.library] The attachments collection to browse. * If one is not supplied, a collection of all images will be created. * @param {boolean|string} [attributes.filterable=uploaded] Whether the library is filterable, and if so what filters should be shown. * Accepts 'all', 'uploaded', or 'unattached'. * @param {string} [attributes.menu=dg] Initial mode for the menu region. * @param {string} [attributes.content=upload] Initial mode for the content region. * Overridden by persistent user setting if 'contentUserSetting' is true. * @param {string} [attributes.router=browse] Initial mode for the router region. * @param {string} [attributes.toolbar=dg-add] Initial mode for the toolbar region. * @param {boolean} [attributes.searchable=true] Whether the library is searchable. * @param {boolean} [attributes.sortable=true] Whether the Attachments should be sortable. Depends on the orderby property being set to menuOrder on the attachments collection. * @param {boolean} [attributes.autoSelect=true] Whether an uploaded attachment should be automatically added to the selection. * @param {boolean} [attributes.contentUserSetting=true] Whether the content region's mode should be set and persisted per user. * @param {int} [attributes.priority=100] The priority for the state link in the media menu. * @param {boolean} [attributes.syncSelection=false] Whether the Attachments selection should be persisted from the last state. * Defaults to false because for this state, because the library of the Edit Document Gallery state is the selection. */ media.controller.dgAdd = media.controller.Library.extend({ defaults: _.defaults({ id: 'dg-library', title: l10n.addTodgTitle, multiple: 'add', filterable: 'uploaded', menu: 'dg', toolbar: 'dg-add', priority: 100, syncSelection: false }, media.controller.Library.prototype.defaults), /** * @since 3.5.0 */ initialize: function () { // If a library wasn't supplied, create a library of images. if (!this.get('library')) this.set('library', media.query()); media.controller.Library.prototype.initialize.apply(this, arguments); }, /** * @since 3.5.0 */ activate: function () { var library = this.get('library'), edit = this.frame.state('dg-edit').get('library'); if (this.editLibrary && this.editLibrary !== edit) library.unobserve(this.editLibrary); // Accepts attachments that exist in the original library and // that do not exist in dg's library. library.validator = function (attachment) { return !!this.mirroring.get(attachment.cid) && !edit.get(attachment.cid) && media.model.Selection.prototype.validator.apply(this, arguments); }; // Reset the library to ensure that all attachments are re-added // to the collection. Do so silently, as calling `observe` will // trigger the `reset` event. library.reset(library.mirroring.models, {silent: true}); library.observe(edit); this.editLibrary = edit; media.controller.Library.prototype.activate.apply(this, arguments); } }); /** * wp.media.view.Settings.dg * * @class * @augments wp.media.view.Settings * @augments wp.media.View * @augments wp.Backbone.View * @augments Backbone.View */ media.view.Settings.dg = media.view.Settings.extend({ update: function( key ) { var value = this.model.get( key ), $setting = this.$('[data-setting="' + key + '"]'), $buttons, $value; // Bail if we didn't find a matching setting. if ( ! $setting.length ) { return; } // Attempt to determine how the setting is rendered and update // the selected value. // Handle dropdowns. if ( $setting.is('select') ) { $value = $setting.find('[value="' + value + '"]'); if ( $value.length ) { $setting.find('option').prop( 'selected', false ); $value.prop( 'selected', true ); } else { // If we can't find the desired value, record what *is* selected. this.model.set( key, $setting.find(':selected').val() ); } // Handle button groups. } else if ( $setting.hasClass('button-group') ) { $buttons = $setting.find('button').removeClass('active'); $buttons.filter( '[value="' + value + '"]' ).addClass('active'); // Handle text inputs and textareas. } else if ( $setting.is('input[type="text"], input[type="number"], textarea') ) { if ( ! $setting.is(':focus') ) { $setting.val( value ); } // Handle checkboxes. } else if ( $setting.is('input[type="checkbox"]') ) { $setting.prop( 'checked', !! value && 'false' !== value ); } }, className: 'collection-settings dg-settings', template: media.template('dg-settings') }); // supersede the default MediaFrame.Post view // hint from https://gist.github.com/Fab1en/4586865 var wpMediaFramePost = wp.media.view.MediaFrame.Post; wp.media.view.MediaFrame.Post = wpMediaFramePost.extend( { initialize: function () { wpMediaFramePost.prototype.initialize.apply(this, arguments); this.states.add([ new media.controller.Library({ id: 'dg', title: l10n.dgMenuTitle, priority: 50, toolbar: 'main-dg', filterable: 'all', multiple: 'add', editable: false, library: media.query(this.options.library) }), // Document Gallery states. new media.controller.dgEdit({ library: this.options.selection, editing: this.options.editing, menu: 'dg' }), new media.controller.dgAdd() ]); this.on('menu:create:dg', this.createMenu, this); this.on('toolbar:create:main-dg', this.createToolbar, this); this.on('menu:render:dg', this.dgMenu, this); this.on('toolbar:render:main-dg', this.maindgToolbar, this); this.on('toolbar:render:dg-edit', this.dgEditToolbar, this); this.on('toolbar:render:dg-add', this.dgAddToolbar, this); }, /** * @param {wp.Backbone.View} view */ dgMenu: function (view) { var lastState = this.lastState(), previous = lastState && lastState.id, frame = this; view.set({ cancel: { text: l10n.canceldgTitle, priority: 20, click: function () { if (previous) { frame.setState(previous); } else { frame.close(); } // Keep focus inside media modal // after canceling a dg this.controller.modal.focusManager.focus(); } }, separateCancel: new media.View({ className: 'separator', priority: 40 }) }); }, /** * @param {wp.Backbone.View} view */ maindgToolbar: function (view) { var controller = this; this.selectionStatusToolbar(view); view.set('dg', { style: 'primary', text: l10n.dgButton, priority: 60, requires: {selection: true}, click: function () { var selection = controller.state().get('selection'), edit = controller.state('dg-edit'), models = selection.models; edit.set('library', new media.model.Selection(models, { props: selection.props.toJSON(), multiple: true })); this.controller.setState('dg-edit'); // Keep focus inside media modal // after jumping to dg view this.controller.modal.focusManager.focus(); } }); }, dgEditToolbar: function () { var editing = this.state().get('editing'); this.toolbar.set(new media.view.Toolbar({ controller: this, items: { insert: { style: 'primary', text: editing ? l10n.updatedg : l10n.insertdg, priority: 80, requires: {library: true}, /** * @fires wp.media.controller.State#update */ click: function () { var controller = this.controller, state = controller.state(); controller.close(); if ( editing ) { state.trigger('update', state.get('library')); // calling for workflow.state update, so just execute its contents } else { wp.media.editor.insert(wp.media.dg.shortcode(state.get('library')).string().replace(/\sdgorder=/ig, ' order=').replace(/\sdgorderby=/ig, ' orderby=')); } // Restore and reset the default state. controller.setState(controller.options.state); controller.reset(); if (typeof tinyMCE != 'undefined' && tinyMCE.activeEditor !== null) { tinyMCE.activeEditor.fire('update.dg'); } } } } })); }, dgAddToolbar: function () { this.toolbar.set(new media.view.Toolbar({ controller: this, items: { insert: { style: 'primary', text: l10n.addTodg, priority: 80, requires: {selection: true}, /** * @fires wp.media.controller.State#reset */ click: function () { var controller = this.controller, state = controller.state(), edit = controller.state('dg-edit'); edit.get('library').add(state.get('selection').models); state.trigger('reset'); controller.setState('dg-edit'); } } } })); } }); // Based on code from /wp-includes/js/media-editor.js WP 4.1 wp.media._dgDefaults = { id: wp.media.view.settings.post && wp.media.view.settings.post.id, columns: dgDefaults.columns, fancy: dgDefaults.fancy, relation: dgDefaults.relation, limit: dgDefaults.limit, mime_types: dgDefaults.mime_types, post_status: dgDefaults.post_status, post_type: dgDefaults.post_type, attachment_pg: dgDefaults.attachment_pg, descriptions: dgDefaults.descriptions, new_window: dgDefaults.new_window, paginate: dgDefaults.paginate, dgorder: dgDefaults.order, dgorderby: dgDefaults.orderby }; if (wp.media.view.settings.dgDefaults) { wp.media.dgDefaults = _.extend({}, wp.media._dgDefaults, wp.media.view.settings.dgDefaults); } else { wp.media.dgDefaults = wp.media._dgDefaults; } wp.media.dg = new wp.media.collection({ tag: 'dg', //type : 'image', editTitle: wp.media.view.l10n.editdgTitle, defaults: wp.media.dgDefaults, setDefaults: function (attrs) { var self = this, changed = !_.isEqual(wp.media.dgDefaults, wp.media._dgDefaults); _.each(this.defaults, function (value, key) { attrs[key] = self.coerce(attrs, key); if (value === attrs[key] && ( !changed || value === wp.media._dgDefaults[key] )) { delete attrs[key]; } }); return attrs; } }); }(jQuery, _)); // Check if environment is suitable for the code to be executed if ( typeof window.wp.mce !== 'undefined' && typeof window.wp.mce.views !== 'undefined' ) { // Based on code from /wp-includes/js/mce-view.js WP 4.2 /* * The WordPress core TinyMCE views. * View for the dg shortcode. */ (function (window, views, media, $) { var dg; base = { state: [], edit: function (text, update) { // currently the shortcode *must* include ids attribute and may include any of the listed attributes to be editable if (text.search(/\sids\s*=/gi) == -1 || text.search(/\s(?!(?:ids|attachment_pg|columns|new_window|descriptions|fancy|orderby|order|paginate|limit)\s*=)[\w\-]+\s*=/gi) > -1) { tinyMCE.activeEditor.windowManager.alert(DGl10n.unfitSCalert); } else { var type = this.type, frame = media[type].edit(text.replace(/\sorder\s*=/ig, ' dgorder=').replace(/\sorderby\s*=/ig, ' dgorderby=')); this.pausePlayers && this.pausePlayers(); _.each(this.state, function (state) { frame.state(state).on('update', function (selection) { update(media[type].shortcode(selection).string().replace(/\sdgorder=/ig, ' order=').replace(/\sdgorderby=/ig, ' orderby='), type === 'dg'); }); }); frame.on('close', function () { frame.detach(); }); frame.open(); } } }; dg = _.extend({}, base, { state: ['dg-edit'], template: media.template('editor-dg'), initialize: function () { var attachments = media.dg.attachments(this.shortcode, media.view.settings.post.id), attrs = this.shortcode.attrs.named, sc = this.text, atts = {}, self = this; for (prop in attrs) { if (sc.indexOf(' ' + prop + '=') > -1) { atts[prop] = attrs[prop]; } } if (sc.indexOf(' dgorderby=') > -1) { atts['orderby'] = attrs['dgorderby']; } if (sc.indexOf(' dgorder=') > -1) { atts['order'] = attrs['dgorder']; } self.render('