this.pageflow = this.pageflow || {}; this.pageflow._editorGlobalInterop = (function (exports, Backbone, _$1, Marionette, $, I18n$1, ChildViewContainer, IScroll, jquery_minicolors, wysihtml5, Cocktail) { 'use strict'; Backbone = Backbone && Backbone.hasOwnProperty('default') ? Backbone['default'] : Backbone; _$1 = _$1 && _$1.hasOwnProperty('default') ? _$1['default'] : _$1; Marionette = Marionette && Marionette.hasOwnProperty('default') ? Marionette['default'] : Marionette; $ = $ && $.hasOwnProperty('default') ? $['default'] : $; I18n$1 = I18n$1 && I18n$1.hasOwnProperty('default') ? I18n$1['default'] : I18n$1; ChildViewContainer = ChildViewContainer && ChildViewContainer.hasOwnProperty('default') ? ChildViewContainer['default'] : ChildViewContainer; IScroll = IScroll && IScroll.hasOwnProperty('default') ? IScroll['default'] : IScroll; wysihtml5 = wysihtml5 && wysihtml5.hasOwnProperty('default') ? wysihtml5['default'] : wysihtml5; Cocktail = Cocktail && Cocktail.hasOwnProperty('default') ? Cocktail['default'] : Cocktail; (function () { var sync = Backbone.sync; Backbone.sync = function (method, model, options) { if (model.paramRoot && !options.attrs) { options.attrs = options.queryParams || {}; options.attrs[model.paramRoot] = model.toJSON(options); } return sync(method, model, options); }; })(); function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } /*global JST*/ Marionette.Renderer.render = function (template, data) { if (_$1.isFunction(template)) { return template(data); } if (template.indexOf('templates/') === 0) { template = 'pageflow/editor/' + template; } if (!JST[template]) { throw "Template '" + template + "' not found!"; } return JST[template](data); }; /** * Returns an array of translation keys based on the `prefixes` * option and the given `keyName`. * * @param {string} keyName * Suffix to append to prefixes. * * @param {string[]} [options.prefixes] * Array of translation key prefixes. * * @param {string} [options.fallbackPrefix] * Optional additional prefix to form a model based translation * key of the form * `prefix.fallbackModelI18nKey.propertyName.keyName`. * * @param {string} [options.fallbackModelI18nKey] * Required if `fallbackPrefix` option is present. * * @return {string[]} * @memberof i18nUtils * @since 12.0 */ function attributeTranslationKeys(attributeName, keyName, options) { var result = []; if (options.prefixes) { result = result.concat(_$1(options.prefixes).map(function (prefix) { return prefix + '.' + attributeName + '.' + keyName; }, this)); } if (options && options.fallbackPrefix) { result.push(options.fallbackPrefix + '.' + options.fallbackModelI18nKey + '.' + attributeName); } return result; } /** * Takes the same parameters as {@link * #i18nutilsattributetranslationkeys attributeTranslationKeys}, but returns the first existing * translation. * * @return {string} * @memberof i18nUtils * @since 12.0 */ function attributeTranslation(attributeName, keyName, options) { return findTranslation(attributeTranslationKeys(attributeName, keyName, options)); } /** * Find the first key for which a translation exists and return the * translation. * * @param {string[]} keys * Translation key candidates. * * @param {string} [options.defaultValue] * Value to return if none of the keys has a translation. Is * treated like an HTML translation if html flag is set. * * @param {boolean} [options.html] * If true, also search for keys ending in '_html' and HTML-escape * keys that do not end in 'html' * * @memberof i18nUtils * @return {string} */ function findTranslation(keys, options) { options = options || {}; if (options.html) { keys = translationKeysWithSuffix(keys, 'html'); } return _$1.chain(keys).reverse().reduce(function (result, key) { var unescapedTranslation = I18n$1.t(key, _$1.extend({}, options, { defaultValue: result })); if (!options.html || key.match(/_html$/) || result == unescapedTranslation) { return unescapedTranslation; } else { return $('
').text(unescapedTranslation).html(); } }, options.defaultValue).value(); } /** * Return the first key for which a translation exists. Returns the * first if non of the keys has a translation. * * @param {string[]} keys * Translation key candidates. * * @memberof i18nUtils * @return {string} */ function findKeyWithTranslation(keys) { var missing = '_not_translated'; return _$1(keys).detect(function (key) { return I18n$1.t(key, { defaultValue: missing }) !== missing; }) || _$1.first(keys); } function translationKeysWithSuffix(keys, suffix) { return _$1.chain(keys).map(function (key) { return [key + '_' + suffix, key]; }).flatten().value(); } var i18nUtils = /*#__PURE__*/ Object.freeze({ __proto__: null, attributeTranslationKeys: attributeTranslationKeys, attributeTranslation: attributeTranslation, findTranslation: findTranslation, findKeyWithTranslation: findKeyWithTranslation, translationKeysWithSuffix: translationKeysWithSuffix }); function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } function _iterableToArrayLimit(arr, i) { if (!(Symbol.iterator in Object(arr) || Object.prototype.toString.call(arr) === "[object Arguments]")) { return; } var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); } /** * Create object that can be passed to Marionette ui property from CSS * module object. * * @param {Object} styles * Class name mapping imported from `.module.css` file. * * @param {...string} classNames * Keys from the styles object that shall be used in the ui object. * * @return {Object} * * @example * * // MyView.module.css * * .container {} * * // MyView.js * * import Marionette from 'marionette'; * import {cssModulesUtils} from 'pageflow/ui'; * * import styles from './MyView.module.css'; * * export const MyView = Marionette.ItemView({ * template: () => ` *
* `, * * ui: cssModulesUtils.ui(styles, 'container'), * * onRender() { * this.ui.container // => JQuery wrapper for container element * } * }); * * @memberof cssModulesUtils */ function ui(styles) { for (var _len = arguments.length, classNames = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { classNames[_key - 1] = arguments[_key]; } return classNames.reduce(function (result, className) { result[className] = selector(styles, className); return result; }, {}); } /** * Create object that can be passed to Marionette events property from CSS * module object. * * @param {Object} styles * Class name mapping imported from `.module.css` file. * * @param {Object} mapping * Events mapping using keys from the `styles` instead of CSS class names. * * @return {Object} * * @example * * // MyView.module.css * * .addButton {} * * // MyView.js * * import Marionette from 'marionette'; * import {cssModulesUtils} from 'pageflow/ui'; * * import styles from './MyView.module.css'; * * export const MyView = Marionette.ItemView({ * template: () => ` * * `, * * events: cssModulesUtils.ui(styles, { * 'click addButton': () => console.log('clicked add button'); * }) * }); * * @memberof cssModulesUtils */ function events(styles, mapping) { return Object.keys(mapping).reduce(function (result, key) { var _key$split = key.split(' '), _key$split2 = _slicedToArray(_key$split, 2), event = _key$split2[0], className = _key$split2[1]; result["".concat(event, " ").concat(selector(styles, className))] = mapping[key]; return result; }, {}); } /** * Generates a CSS selector from a CSS module rule. * * @param {Object} styles * Class name mapping imported from `.module.css` file. * * @param {String} className * Key from the `styles` object. * * @return {String} CSS Selector * @memberof cssModulesUtils */ function selector(styles, className) { var classNames = styles[className]; if (!classNames) { throw new Error("Unknown class name ".concat(className, " in mapping. Knwon names: ").concat(Object.keys(styles).join(', '), ".")); } return ".".concat(classNames.replace(/ /g, '.')); } var cssModulesUtils = /*#__PURE__*/ Object.freeze({ __proto__: null, ui: ui, events: events, selector: selector }); // https://github.com/jashkenas/backbone/issues/2601 function BaseObject(options) { this.initialize.apply(this, arguments); } _$1.extend(BaseObject.prototype, Backbone.Events, { initialize: function initialize(options) {} }); // The self-propagating extend function that Backbone classes use. BaseObject.extend = Backbone.Model.extend; var CollectionView = Marionette.View.extend({ initialize: function initialize() { this.rendered = false; this.itemViews = new ChildViewContainer(); this.collection.map(this.addItem, this); this.listenTo(this.collection, 'add', this.addItem); this.listenTo(this.collection, 'remove', this.removeItem); this.listenTo(this.collection, 'sort', this.sort); if (this.options.loadingViewConstructor) { this.listenTo(this.collection, 'request', function () { this.loading = true; this.togglePlaceHolder(); }); this.listenTo(this.collection, 'sync', function () { this.loading = false; this.togglePlaceHolder(); }); } }, render: function render() { if (!this.rendered) { this.$el.append(this.itemViews.map(function (itemView) { itemView.$el.data('view', itemView); return itemView.render().el; })); this.togglePlaceHolder(); this.rendered = true; } return this; }, onClose: function onClose() { this.itemViews.call('close'); this.closePlaceHolderView(); }, addItem: function addItem(item) { var view = new this.options.itemViewConstructor(_$1.extend({ model: item }, this.getItemViewOptions(item))); this.itemViews.add(view); if (this.rendered) { var index = this.collection.indexOf(item); view.render(); view.$el.data('view', view); if (index > 0) { this.$el.children().eq(index - 1).after(view.el); } else { this.$el.prepend(view.el); } this.togglePlaceHolder(); } }, removeItem: function removeItem(item) { var view = this.itemViews.findByModel(item); if (view) { this.itemViews.remove(view); view.close(); this.togglePlaceHolder(); } }, sort: function sort() { var last = null; this.collection.each(function (item) { var itemView = this.itemViews.findByModel(item); var element; if (!itemView) { return; } element = itemView.$el; if (last) { last.after(element); } else { this.$el.prepend(element); } last = element; }, this); }, getItemViewOptions: function getItemViewOptions(item) { if (typeof this.options.itemViewOptions === 'function') { return this.options.itemViewOptions(item); } else { return this.options.itemViewOptions || {}; } }, closePlaceHolderView: function closePlaceHolderView() { if (this.placeHolderView) { this.placeHolderView.close(); this.placeHolderView = null; } }, togglePlaceHolder: function togglePlaceHolder() { var lastPlaceholderConstructor = this.placeHolderConstructor; this.placeHolderConstructor = this.getPlaceHolderConstructor(); if (this.itemViews.length || !this.placeHolderConstructor) { this.closePlaceHolderView(); } else if (!this.placeHolderView || lastPlaceholderConstructor !== this.placeHolderConstructor) { this.closePlaceHolderView(); this.placeHolderView = new this.placeHolderConstructor(); this.$el.append(this.placeHolderView.render().el); } }, getPlaceHolderConstructor: function getPlaceHolderConstructor() { if (this.loading && this.options.loadingViewConstructor) { return this.options.loadingViewConstructor; } else if (this.options.blankSlateViewConstructor) { return this.options.blankSlateViewConstructor; } } }); var SortableCollectionView = CollectionView.extend({ render: function render() { CollectionView.prototype.render.call(this); this.$el.sortable({ connectWith: this.options.connectWith, placeholder: 'sortable-placeholder', forcePlaceholderSize: true, delay: 200, update: _$1.bind(function (event, ui) { if (ui.item.parent().is(this.el)) { this.updateOrder(); } }, this), receive: _$1.bind(function (event, ui) { var view = ui.item.data('view'); this.reindexPositions(); this.itemViews.add(view); this.collection.add(view.model); }, this), remove: _$1.bind(function (event, ui) { var view = ui.item.data('view'); this.itemViews.remove(view); this.collection.remove(view.model); }, this) }); return this; }, addItem: function addItem(item) { if (!this.itemViews.findByModel(item)) { CollectionView.prototype.addItem.call(this, item); } }, removeItem: function removeItem(item) { if (this.itemViews.findByModel(item)) { CollectionView.prototype.removeItem.call(this, item); } }, updateOrder: function updateOrder() { this.reindexPositions(); this.collection.sort(); this.collection.saveOrder(); }, reindexPositions: function reindexPositions() { this.$el.children().each(function (index) { $(this).data('view').model.set('position', index); }); } }); var ConfigurationEditorTabView = Marionette.View.extend({ className: 'configuration_editor_tab', initialize: function initialize() { this.inputs = new ChildViewContainer(); this.groups = this.options.groups || ConfigurationEditorTabView.groups; }, input: function input(propertyName, view, options) { this.view(view, _$1.extend({ placeholderModel: this.options.placeholderModel, propertyName: propertyName, attributeTranslationKeyPrefixes: this.options.attributeTranslationKeyPrefixes }, options || {})); }, view: function view(_view, options) { this.inputs.add(new _view(_$1.extend({ model: this.model, parentTab: this.options.tab }, options || {}))); }, group: function group(name, options) { this.groups.apply(name, this, options); }, render: function render() { this.inputs.each(function (input) { this.$el.append(input.render().el); }, this); return this; }, onClose: function onClose() { if (this.inputs) { this.inputs.call('close'); } } }); ConfigurationEditorTabView.Groups = function () { var groups = {}; this.define = function (name, fn) { if (typeof fn !== 'function') { throw 'Group has to be function.'; } groups[name] = fn; }; this.apply = function (name, context, options) { if (!(name in groups)) { throw 'Undefined group named "' + name + '".'; } groups[name].call(context, options || {}); }; }; ConfigurationEditorTabView.groups = new ConfigurationEditorTabView.Groups(); function template(data) { var __p = ''; __p += '
\n \n
\n
\n'; return __p; } /*global pageflow*/ /** * Switch between different views using tabs. * * @param {Object} [options] * * @param {string} [options.defaultTab] * Name of the tab to enable by default. * * @param {string[]} [options.translationKeyPrefixes] * List of prefixes to append tab name to. First exisiting translation is used as label. * * @param {string} [options.fallbackTranslationKeyPrefix] * Translation key prefix to use if non of the `translationKeyPrefixes` result in an * existing translation for a tab name. * * @param {string} [options.i18n] * Legacy alias for `fallbackTranslationKeyPrefix`. * * @class */ var TabsView = Marionette.Layout.extend( /* @lends TabView.prototype */ { template: template, className: 'tabs_view', ui: { headers: '.tabs_view-headers', scroller: '.tabs_view-scroller' }, regions: { container: '.tabs_view-container' }, events: { 'click .tabs_view-headers > li': function clickTabs_viewHeadersLi(event) { this.changeTab($(event.target).data('tab-name')); } }, initialize: function initialize() { this.tabFactoryFns = {}; this.tabNames = []; this.currentTabName = null; this._refreshScrollerOnSideBarResize(); }, tab: function tab(name, factoryFn) { this.tabFactoryFns[name] = factoryFn; this.tabNames.push(name); }, onRender: function onRender() { _$1.each(this.tabNames, function (name) { var label = findTranslation(this._labelTranslationKeys(name)); this.ui.headers.append($('
  • ').attr('data-tab-name', name).text(label)); }, this); this.scroller = new IScroll(this.ui.scroller[0], { scrollX: true, scrollY: false, bounce: false, mouseWheel: true, preventDefault: false }); this.changeTab(this.defaultTab()); }, changeTab: function changeTab(name) { this.container.show(this.tabFactoryFns[name]()); this._updateActiveHeader(name); this.currentTabName = name; }, defaultTab: function defaultTab() { if (_$1.include(this.tabNames, this.options.defaultTab)) { return this.options.defaultTab; } else { return _$1.first(this.tabNames); } }, /** * Rerender current tab. */ refresh: function refresh() { this.changeTab(this.currentTabName); }, /** * Adjust tabs scroller to changed width of view. */ refreshScroller: function refreshScroller() { this.scroller.refresh(); }, toggleSpinnerOnTab: function toggleSpinnerOnTab(name, visible) { this.$('[data-tab-name=' + name + ']').toggleClass('spinner', visible); }, _labelTranslationKeys: function _labelTranslationKeys(name) { var result = _$1.map(this.options.translationKeyPrefixes, function (prefix) { return prefix + '.' + name; }); if (this.options.i18n) { result.push(this.options.i18n + '.' + name); } if (this.options.fallbackTranslationKeyPrefix) { result.push(this.options.fallbackTranslationKeyPrefix + '.' + name); } return result; }, _updateActiveHeader: function _updateActiveHeader(activeTabName) { var scroller = this.scroller; this.ui.headers.children().each(function () { if ($(this).data('tab-name') === activeTabName) { scroller.scrollToElement(this, 200, true); $(this).addClass('active'); } else { $(this).removeClass('active'); } }); }, _refreshScrollerOnSideBarResize: function _refreshScrollerOnSideBarResize() { if (pageflow.app) { this.listenTo(pageflow.app, 'resize', function () { this.scroller.refresh(); }); } } }); /** * Render a inputs on multiple tabs. * * @param {Object} [options] * * @param {string} [options.model] * Backbone model to use for input views. * * @param {string} [options.placeholderModel] * Backbone model to read placeholder values from. * @param {string} [options.tab] * Name of the tab to enable by default. * * @param {string[]} [options.attributeTranslationKeyPrefixes] * List of prefixes to use in input views for attribute based transltions. * * @param {string[]} [options.tabTranslationKeyPrefixes] * List of prefixes to append tab name to. First exisiting translation is used as label. * * @param {string} [options.tabTranslationKeyPrefix] * Prefixes to append tab name to. * * @class */ var ConfigurationEditorView = Marionette.View.extend({ className: 'configuration_editor', initialize: function initialize() { this.tabsView = new TabsView({ translationKeyPrefixes: this.options.tabTranslationKeyPrefixes || [this.options.tabTranslationKeyPrefix], fallbackTranslationKeyPrefix: 'pageflow.ui.configuration_editor.tabs', defaultTab: this.options.tab }); this.configure(); }, configure: function configure() {}, tab: function tab(name, callback) { this.tabsView.tab(name, _$1.bind(function () { var tabView = new ConfigurationEditorTabView({ model: this.model, placeholderModel: this.options.placeholderModel, tab: name, attributeTranslationKeyPrefixes: this.options.attributeTranslationKeyPrefixes }); callback.call(tabView); return tabView; }, this)); }, /** * Rerender current tab. */ refresh: function refresh() { this.tabsView.refresh(); }, /** * Adjust tabs scroller to changed width of view. */ refreshScroller: function refreshScroller() { this.tabsView.refreshScroller(); }, render: function render() { this.$el.append(this.subview(this.tabsView).el); return this; } }); _$1.extend(ConfigurationEditorView, { repository: {}, register: function register(pageTypeName, prototype) { this.repository[pageTypeName] = ConfigurationEditorView.extend(prototype); } }); function template$1(data) { var __p = ''; __p += ''; return __p; } /** * Base class for table cell views. * * Inside sub classes the name of the column options are available as * `this.options.column`. Override the `update` method to populate the * element. * * @param {Object} [options] * * @param {string} [options.className] * Class attribute to apply to the cell element. * * @since 12.0 */ var TableCellView = Marionette.ItemView.extend({ tagName: 'td', template: template$1, className: function className() { return this.options.className; }, onRender: function onRender() { this.listenTo(this.getModel(), 'change:' + this.options.column.name, this.update); this.setupContentBinding(); this.update(); }, /** * Override in concrete cell view. */ update: function update() { throw 'Not implemented'; }, /** * Returns the column attribute's value in the row model. */ attributeValue: function attributeValue() { if (typeof this.options.column.value == 'function') { return this.options.column.value(this.model); } else { return this.getModel().get(this.options.column.name); } }, getModel: function getModel() { if (this.options.column.configurationAttribute) { return this.model.configuration; } else { return this.model; } }, /** * Look up attribute specific translations based on * `attributeTranslationKeyPrefixes` of the the parent `TableView`. * * @param {Object} [options] * Interpolations to apply to the translation. * * @param {string} [options.defaultValue] * Fallback value if no translation is found. * * @protected * * @example * * this.attribute.attributeTranslation("cell_title"); * // Looks for keys of the form: * // ..cell_title */ attributeTranslation: function attributeTranslation(keyName, options) { return findTranslation(this.attributeTranslationKeys(keyName), options); }, attributeTranslationKeys: function attributeTranslationKeys(keyName) { return _$1(this.options.attributeTranslationKeyPrefixes || []).map(function (prefix) { return prefix + '.' + this.options.column.name + '.' + keyName; }, this); }, /** * Set up content binding to update this view upon change of * specified attribute on this.getModel(). * * @param {string} [options.column.contentBinding] * Name of the attribute to which this cell's update is bound * * @protected */ setupContentBinding: function setupContentBinding() { if (this.options.column.contentBinding) { this.listenTo(this.getModel(), 'change:' + this.options.column.contentBinding, this.update); this.update(); } } }); var TableHeaderCellView = TableCellView.extend({ tagName: 'th', render: function render() { this.$el.text(this.attributeTranslation('column_header')); this.$el.data('columnName', this.options.column.name); return this; } }); var TableRowView = Marionette.View.extend({ tagName: 'tr', events: { 'click': function click() { if (this.options.selection) { this.options.selection.set(this.selectionAttribute(), this.model); } } }, initialize: function initialize() { if (this.options.selection) { this.listenTo(this.options.selection, 'change', this.updateClassName); } }, render: function render() { _$1(this.options.columns).each(function (column) { this.appendSubview(new column.cellView(_$1.extend({ model: this.model, column: column, attributeTranslationKeyPrefixes: this.options.attributeTranslationKeyPrefixes }, column.cellViewOptions || {}))); }, this); this.updateClassName(); return this; }, updateClassName: function updateClassName() { this.$el.toggleClass('is_selected', this.isSelected()); }, isSelected: function isSelected() { return this.options.selection && this.options.selection.get(this.selectionAttribute()) === this.model; }, selectionAttribute: function selectionAttribute() { return this.options.selectionAttribute || 'current'; } }); function template$2(data) { var __p = ''; __p += '\n \n \n \n \n \n
    \n'; return __p; } function blankSlateTemplate(data) { var __t, __p = ''; __p += '\n ' + ((__t = data.blankSlateText) == null ? '' : __t) + '\n\n'; return __p; } var TableView = Marionette.ItemView.extend({ tagName: 'table', className: 'table_view', template: template$2, ui: { headRow: 'thead tr', body: 'tbody' }, onRender: function onRender() { var view = this; _$1(this.options.columns).each(function (column) { this.ui.headRow.append(this.subview(new TableHeaderCellView({ column: column, attributeTranslationKeyPrefixes: this.options.attributeTranslationKeyPrefixes })).el); }, this); this.subview(new CollectionView({ el: this.ui.body, collection: this.collection, itemViewConstructor: TableRowView, itemViewOptions: { columns: this.options.columns, selection: this.options.selection, selectionAttribute: this.options.selectionAttribute, attributeTranslationKeyPrefixes: this.options.attributeTranslationKeyPrefixes }, blankSlateViewConstructor: Marionette.ItemView.extend({ tagName: 'tr', className: 'blank_slate', template: blankSlateTemplate, serializeData: function serializeData() { return { blankSlateText: view.options.blankSlateText, colSpan: view.options.columns.length }; } }) })); } }); function template$3(data) { var __p = ''; __p += '\n\n'; return __p; } var TooltipView = Marionette.ItemView.extend({ template: template$3, className: 'tooltip', ui: { label: '.label' }, hide: function hide() { this.visible = false; clearTimeout(this.timeout); this.$el.removeClass('visible'); }, show: function show(text, position, options) { options = options || {}; this.visible = true; clearTimeout(this.timeout); this.timeout = setTimeout(_$1.bind(function () { var offsetTop; var offsetLeft; this.ui.label.text(text); this.$el.toggleClass('align_bottom_right', options.align === 'bottom right'); this.$el.toggleClass('align_bottom_left', options.align === 'bottom left'); if (options.align === 'bottom right' || options.align === 'bottom left') { offsetTop = 10; offsetLeft = 0; } else { offsetTop = -17; offsetLeft = 10; } this.$el.css({ top: position.top + offsetTop + 'px', left: position.left + offsetLeft + 'px' }); this.$el.addClass('visible'); }, this), 200); } }); /** * Mixin for input views handling common concerns like labels, * inline help, visiblity and disabling. * * ## Label and Inline Help Translations * * By default `#labelText` and `#inlineHelpText` are defined through * translations. If no `attributeTranslationKeyPrefixes` are given, * translation keys for labels and inline help are constructed from * the `i18nKey` of the model and the given `propertyName` * option. Suppose the model's `i18nKey` is "page" and the * `propertyName` option is "title". Then the key * * activerecord.attributes.page.title * * will be used for the label. And the key * * pageflow.ui.inline_help.page.title_html * pageflow.ui.inline_help.page.title * * will be used for the inline help. * * ### Attribute Translation Key Prefixes * * The `attributeTranslationKeyPrefixes` option can be used to supply * an array of scopes in which label and inline help translations * shall be looked up based on the `propertyName` option. * * Suppose the array `['some.attributes', 'fallback.attributes']` is * given as `attributeTranslationKeyPrefixes` option. Then, in the * example above, the first existing translation key is used as label: * * some.attributes.title.label * fallback.attributes.title.label * activerecord.attributes.post.title * * Accordingly, for the inline help: * * some.attributes.title.inline_help_html * some.attributes.title.inline_help * fallback.attributes.title.inline_help_html * fallback.attributes.title.inline_help * pageflow.ui.inline_help.post.title_html * pageflow.ui.inline_help.post.title * * This setup allows to keep all translation keys for an attribute * to share a common prefix: * * some: * attributes: * title: * label: "Label" * inline_help: "..." * inline_help_disabled: "..." * * ### Inline Help for Disabled Inputs * * For each inline help translation key, a separate key with an * `"_disabled"` suffix can be supplied, which provides a help string * that shall be displayed when the input is disabled. More specific * attribute translation key prefixes take precedence over suffixed * keys: * * some.attributes.title.inline_help_html * some.attributes.title.inline_help * some.attributes.title.inline_help_disabled_html * some.attributes.title.inline_help_disabled * fallback.attributes.title.inline_help_html * fallback.attributes.title.inline_help * fallback.attributes.title.inline_help_disabled_html * fallback.attributes.title.inline_help_disabled * pageflow.ui.inline_help.post.title_html * pageflow.ui.inline_help.post.title * pageflow.ui.inline_help.post.title_disabled_html * pageflow.ui.inline_help.post.title_disabled * * @param {string} options * Common constructor options for all views that include this mixin. * * @param {string} options.propertyName * Name of the attribute on the model to display and edit. * * @param {string} [options.label] * Label text for the input. * * @param {string[]} [options.attributeTranslationKeyPrefixes] * An array of prefixes to lookup translations for labels and * inline help texts based on attribute names. * * @param {string} [options.additionalInlineHelpText] * A text that will be appended to the translation based inline * text. * * @param {boolean} [options.disabled] * Render input as disabled. * * @param {string} [options.visibleBinding] * Name of an attribute to control whether the input is visible. If * the `visible` and `visibleBindingValue` options are not set, * input will be visible whenever this attribute has a truthy value. * * @param {function|boolean} [options.visible] * A Function taking the value of the `visibleBinding` attribute as * parameter. Input will be visible only if function returns `true`. * * @param {any} [options.visibleBindingValue] * Input will be visible whenever the value of the `visibleBinding` * attribute equals the value of this option. * * @mixin */ var inputView = { ui: { labelText: 'label .name', inlineHelp: 'label .inline_help' }, /** * Returns an array of translation keys based on the * `attributeTranslationKeyPrefixes` option and the given keyName. * * Combined with {@link #i18nutils * i18nUtils.findTranslation}, this can be used inside input views * to obtain additional translations with the same logic as for * labels and inline help texts. * * findTranslation(this.attributeTranslationKeys('default_value')); * * @param {string} keyName * Suffix to append to prefixes. * * @param {string} [options.fallbackPrefix] * Optional additional prefix to form a model based translation * key of the form `prefix.modelI18nKey.propertyName.keyName * * @return {string[]} * @since 0.9 * @member */ attributeTranslationKeys: function attributeTranslationKeys$1(keyName, options) { return attributeTranslationKeys(this.options.propertyName, keyName, _$1.extend({ prefixes: this.options.attributeTranslationKeyPrefixes, fallbackModelI18nKey: this.model.i18nKey }, options || {})); }, onRender: function onRender() { this.$el.addClass('input'); this.$el.addClass(this.model.modelName + '_' + this.options.propertyName); this.$el.data('inputPropertyName', this.options.propertyName); this.$el.data('labelText', this.labelText()); this.$el.data('inlineHelpText', this.inlineHelpText()); this.ui.labelText.text(this.labelText()); this.ui.inlineHelp.html(this.inlineHelpText()); if (!this.inlineHelpText()) { this.ui.inlineHelp.hide(); } this.updateDisabled(); this.setupVisibleBinding(); }, /** * The label to display in the form. * @return {string} */ labelText: function labelText() { return this.options.label || this.localizedAttributeName(); }, localizedAttributeName: function localizedAttributeName() { return findTranslation(this.attributeTranslationKeys('label', { fallbackPrefix: 'activerecord.attributes' })); }, /** * The inline help text for the form field. * @return {string} */ inlineHelpText: function inlineHelpText() { var keys = this.attributeTranslationKeys('inline_help', { fallbackPrefix: 'pageflow.ui.inline_help' }); if (this.options.disabled) { keys = translationKeysWithSuffix(keys, 'disabled'); } return _$1.compact([findTranslation(keys, { defaultValue: '', html: true }), this.options.additionalInlineHelpText]).join(' '); }, updateDisabled: function updateDisabled() { if (this.ui.input) { this.updateDisabledAttribute(this.ui.input); } }, updateDisabledAttribute: function updateDisabledAttribute(element) { if (this.options.disabled) { element.attr('disabled', true); } else { element.removeAttr('disabled'); } }, setupVisibleBinding: function setupVisibleBinding() { var view = this; if (this.options.visibleBinding) { this.listenTo(this.model, 'change:' + this.options.visibleBinding, updateVisible); updateVisible(this.model, this.model.get(this.options.visibleBinding)); } function updateVisible(model, value) { view.$el.toggleClass('input-hidden_via_binding', !isVisible(value)); } function isVisible(value) { if ('visibleBindingValue' in view.options) { return value === view.options.visibleBindingValue; } else if (typeof view.options.visible === 'function') { return !!view.options.visible(value); } else if ('visible' in view.options) { return !!view.options.visible; } else { return !!value; } } } }; function template$4(data) { var __p = ''; __p += '\n
    \n'; return __p; } /** * Input view for attributes storing configuration hashes with boolean values. * See {@link inputView} for further options. * * @param {Object} [options] * * @class */ var CheckBoxGroupInputView = Marionette.ItemView.extend({ mixins: [inputView], template: template$4, className: 'check_box_group_input', events: { 'change': 'save' }, ui: { label: 'label', container: '.check_boxes_container' }, initialize: function initialize() { if (!this.options.texts) { if (!this.options.translationKeys) { var translationKeyPrefix = this.options.translationKeyPrefix || findKeyWithTranslation(this.attributeTranslationKeys('values', { fallbackPrefix: 'activerecord.values' })); this.options.translationKeys = _$1.map(this.options.values, function (value) { return translationKeyPrefix + '.' + value; }, this); } this.options.texts = _$1.map(this.options.translationKeys, function (key) { return I18n$1.t(key); }); } }, onRender: function onRender() { this.ui.label.attr('for', this.cid); this.appendOptions(); this.load(); this.listenTo(this.model, 'change:' + this.options.propertyName, this.load); }, appendOptions: function appendOptions() { _$1.each(this.options.values, function (value, index) { var option = '
    ' + '
    '; this.ui.container.append($(option)); }, this); }, save: function save() { var configured = {}; _$1.each(this.ui.container.find('input'), function (input) { configured[$(input).attr('name')] = $(input).prop('checked'); }); this.model.set(this.options.propertyName, configured); }, load: function load() { if (!this.isClosed) { _$1.each(this.options.values, function (value) { this.ui.container.find('input[name="' + value + '"]').prop('checked', this.model.get(this.options.propertyName)[value]); }, this); } } }); function template$5(data) { var __t, __p = ''; __p += '\n\n ' + ((__t = I18n.t('pageflow.ui.templates.inputs.url_display.link_text')) == null ? '' : __t) + '\n\n'; return __p; } /** * Display view for a link to a URL, to be used like an input view. * See {@link inputView} for further options * * @param {Object} [options] * * @param {string} [options.propertyName] * Target URL for link * * @class */ var UrlDisplayView = Marionette.ItemView.extend({ mixins: [inputView], template: template$5, ui: { link: 'a' }, modelEvents: { 'change': 'update' }, events: { 'click a': function clickA(event) { // Ensure default is not prevented by parent event listener. event.stopPropagation(); } }, onRender: function onRender() { this.update(); }, update: function update() { var url = this.model.get('original_url'); this.$el.toggle(this.model.isUploaded() && !_$1.isEmpty(url)); this.ui.link.attr('href', url); } }); /** * Text based input view that can display a placeholder. * * @param {Object} [options] * * @param {string|function} [options.placeholder] * Display a placeholder string if the input is blank. Either a * string or a function taking the model as a first parameter and * returning a string. * * @param {string} [options.placeholderBinding] * Name of an attribute. Recompute the placeholder function whenever * this attribute changes. * * @param {boolean} [options.hidePlaceholderIfDisabled] * Do not display the placeholder if the input is disabled. * * @param {Backbone.Model} [options.placeholderModel] * Obtain placeholder by looking up the configured `propertyName` * inside a given model. */ var inputWithPlaceholderText = { onRender: function onRender() { this.updatePlaceholder(); if (this.options.placeholderBinding) { this.listenTo(this.model, 'change:' + this.options.placeholderBinding, this.updatePlaceholder); } }, updatePlaceholder: function updatePlaceholder() { this.ui.input.attr('placeholder', this.placeholderText()); }, placeholderText: function placeholderText() { if (!this.options.disabled || !this.options.hidePlaceholderIfDisabled) { if (this.options.placeholder) { if (typeof this.options.placeholder == 'function') { return this.options.placeholder(this.model); } else { return this.options.placeholder; } } else { return this.placeholderModelValue(); } } }, placeholderModelValue: function placeholderModelValue() { return this.options.placeholderModel && this.options.placeholderModel.get(this.options.propertyName); } }; function template$6(data) { var __p = ''; __p += '\n\n'; return __p; } /** * Input view for a single line of text. * * See {@link inputWithPlaceholderText} for placeholder related * further options. See {@link inputView} for further options. * * @param {Object} [options] * * @param {boolean} [options.required=false] * Display an error if the input is blank. * * @param {number} [options.maxLength=255] * Maximum length of characters for this input. To support legacy * data which consists of more characters than the specified * maxLength, the option will only take effect for data which is * shorter than the specified maxLength. * * @class */ var TextInputView = Marionette.ItemView.extend({ mixins: [inputView, inputWithPlaceholderText], template: template$6, ui: { input: 'input' }, events: { 'change': 'onChange' }, onRender: function onRender() { this.load(); this.validate(); this.listenTo(this.model, 'change:' + this.options.propertyName, this.load); }, onChange: function onChange() { if (this.validate()) { this.save(); } }, onClose: function onClose() { if (this.validate()) { this.save(); } }, save: function save() { this.model.set(this.options.propertyName, this.ui.input.val()); }, load: function load() { var input = this.ui.input; input.val(this.model.get(this.options.propertyName)); // set mysql varchar length as default for non-legacy data this.options.maxLength = this.options.maxLength || 255; // do not validate legacy data which length exceeds the specified maximum // for new and maxLength-conforming data: add validation this.validateMaxLength = input.val().length <= this.options.maxLength; }, validate: function validate() { var input = this.ui.input; if (this.options.required && !input.val()) { this.displayValidationError(I18n$1.t('pageflow.ui.views.inputs.text_input_view.required_field')); return false; } if (this.validateMaxLength && input.val().length > this.options.maxLength) { this.displayValidationError(I18n$1.t('pageflow.ui.views.inputs.text_input_view.max_characters_exceeded', { max_length: this.options.maxLength })); return false; } else { this.resetValidationError(); return true; } }, displayValidationError: function displayValidationError(message) { this.$el.addClass('invalid'); this.ui.input.attr('title', message); }, resetValidationError: function resetValidationError(message) { this.$el.removeClass('invalid'); this.ui.input.attr('title', ''); } }); /** * Input view for a color value in hex representation. * See {@link inputView} for further options * * @param {Object} [options] * * @param {string|function} [options.defaultValue] * Color value to display by default. The corresponding value is not * stored in the model. Selecting the default value when a different * value was set before, unsets the attribute in the model. * * @param {string} [options.defaultValueBinding] * Name of an attribute the default value depends on. If a function * is used as defaultValue option, it will be passed the value of the * defaultValueBinding attribute each time it changes. If no * defaultValue option is set, the value of the defaultValueBinding * attribute will be used as default value. * * @param {string[]} [options.swatches] * Preset color values to be displayed inside the picker drop * down. The default value, if present, is always used as the * first swatch automatically. * * @class */ var ColorInputView = Marionette.ItemView.extend({ mixins: [inputView], template: template$6, className: 'color_input', ui: { input: 'input' }, events: { 'mousedown': 'refreshPicker' }, onRender: function onRender() { this.ui.input.minicolors({ changeDelay: 200, change: _$1.bind(function (color) { if (color === this.defaultValue()) { this.model.unset(this.options.propertyName); } else { this.model.set(this.options.propertyName, color); } }, this) }); this.listenTo(this.model, 'change:' + this.options.propertyName, this.load); if (this.options.defaultValueBinding) { this.listenTo(this.model, 'change:' + this.options.defaultValueBinding, this.updateSettings); } this.updateSettings(); }, updateSettings: function updateSettings() { this.ui.input.minicolors('settings', { defaultValue: this.defaultValue(), swatches: this.getSwatches() }); this.load(); }, load: function load() { this.ui.input.minicolors('value', this.model.get(this.options.propertyName) || this.defaultValue()); this.$el.toggleClass('is_default', !this.model.has(this.options.propertyName)); }, refreshPicker: function refreshPicker() { this.ui.input.minicolors('value', {}); }, getSwatches: function getSwatches() { return _$1.chain([this.defaultValue(), this.options.swatches]).flatten().uniq().compact().value(); }, defaultValue: function defaultValue() { var bindingValue; if (this.options.defaultValueBinding) { bindingValue = this.model.get(this.options.defaultValueBinding); } if (typeof this.options.defaultValue === 'function') { return this.options.defaultValue(bindingValue); } else if ('defaultValue' in this.options) { return this.options.defaultValue; } else { return bindingValue; } } }); function template$7(data) { var __p = ''; __p += '\n'; return __p; } /** * A drop down with support for grouped items. * See {@link inputView} for further options * * @param {Object} [options] * * @param {string[]} [options.values] * List of possible values to persist in the attribute. * * @param {string[]} [options.texts] * List of display texts for drop down items. * * @param {string[]} [options.translationKeys] * Translation keys to obtain item texts from. * * @param {string[]} [options.translationKeyPrefix] * Obtain texts for items from translations by appending the item * value to this prefix separated by a dot. By default the * [`attributeTranslationKeyPrefixes` option]{@link inputView} * is used by appending the suffix `.values` to each candidate. * * @param {string[]} [options.groups] * Array of same length as `values` array, containing the display * name of a group header each item shall be grouped under. * * @param {Backbone.Model[]} [options.collection] * Create items for each model in the collection. Use the * `*Property` options to extract values and texts for each items * from the models. * * @param {string} [options.valueProperty] * Attribute to use as item value. * * @param {string} [options.textProperty] * Attribute to use as item display text. * * @param {string} [options.groupProperty] * Attribute to use as item group name. * * @param {string} [options.translationKeyProperty] * Attribute to use as translation key to obtain display text. * * @param {string} [options.groupTranslationKeyProperty] * Attribute to use as translation key to obtain group name. * * @param {boolean} [options.ensureValueDefined] * Set the attribute to the first value on view creation. * * @param {boolean} [options.includeBlank] * Include an item that sets the value of the attribute to a blank * string. * * @param {string} [options.blankText] * Display text for the blank item. * * @param {string} [options.blankTranslationKey] * Translation key to obtain display text for blank item. * * @param {string} [options.placeholderValue] * Include an item that sets the value of the attribute to a blank * string and indicate that the attribute is set to a default * value. Include the display name of the given value, in the * text. This option can be used if a fallback to the * `placeholderValue` occurs whenever the attribute is blank. * * @param {Backbone.Model} [options.placeholderModel] * Behaves like `placeholderValue`, but obtains the value by looking * up the `propertyName` attribute inside the given model. This * option can be used if a fallback to the corresponding attribute * value of the `placeholderModel` occurs whenever the attribute is * blank. * * @class */ var SelectInputView = Marionette.ItemView.extend({ mixins: [inputView], template: template$7, events: { 'change': 'save' }, ui: { select: 'select', input: 'select' }, initialize: function initialize() { if (this.options.collection) { this.options.values = _$1.pluck(this.options.collection, this.options.valueProperty); if (this.options.textProperty) { this.options.texts = _$1.pluck(this.options.collection, this.options.textProperty); } else if (this.options.translationKeyProperty) { this.options.translationKeys = _$1.pluck(this.options.collection, this.options.translationKeyProperty); } if (this.options.groupProperty) { this.options.groups = _$1.pluck(this.options.collection, this.options.groupProperty); } else if (this.options.groupTranslationKeyProperty) { this.options.groupTanslationKeys = _$1.pluck(this.options.collection, this.options.groupTranslationKeyProperty); } } if (!this.options.texts) { if (!this.options.translationKeys) { var translationKeyPrefix = this.options.translationKeyPrefix || findKeyWithTranslation(this.attributeTranslationKeys('values', { fallbackPrefix: 'activerecord.values' })); this.options.translationKeys = _$1.map(this.options.values, function (value) { return translationKeyPrefix + '.' + value; }, this); } this.options.texts = _$1.map(this.options.translationKeys, function (key) { return I18n$1.t(key); }); } if (!this.options.groups) { this.options.groups = _$1.map(this.options.groupTanslationKeys, function (key) { return I18n$1.t(key); }); } this.optGroups = {}; }, onRender: function onRender() { this.appendBlank(); this.appendPlaceholder(); this.appendOptions(); this.load(); this.listenTo(this.model, 'change:' + this.options.propertyName, this.load); if (this.options.ensureValueDefined && !this.model.has(this.options.propertyName)) { this.save(); } }, appendBlank: function appendBlank() { if (!this.options.includeBlank) { return; } if (this.options.blankTranslationKey) { this.options.blankText = I18n$1.t(this.options.blankTranslationKey); } var option = document.createElement('option'); option.value = ''; option.text = this.options.blankText || I18n$1.t('pageflow.ui.views.inputs.select_input_view.none'); this.ui.select.append(option); }, appendPlaceholder: function appendPlaceholder() { if (!this.options.placeholderModel && !this.options.placeholderValue) { return; } var placeholderValue = this.options.placeholderValue || this.options.placeholderModel.get(this.options.propertyName); var placeholderIndex = this.options.values.indexOf(placeholderValue); if (placeholderIndex >= 0) { var option = document.createElement('option'); option.value = ''; option.text = I18n$1.t('pageflow.ui.views.inputs.select_input_view.placeholder', { text: this.options.texts[placeholderIndex] }); this.ui.select.append(option); } }, appendOptions: function appendOptions() { _$1.each(this.options.values, function (value, index) { var option = document.createElement('option'); var group = this.options.groups[index]; option.value = value; option.text = this.options.texts[index]; if (group) { option.setAttribute('data-group', group); this.findOrCreateOptGroup(group).append(option); } else { this.ui.select.append(option); } }, this); }, findOrCreateOptGroup: function findOrCreateOptGroup(label) { if (!this.optGroups[label]) { this.optGroups[label] = $('', { label: label }).appendTo(this.ui.select); } return this.optGroups[label]; }, save: function save() { this.model.set(this.options.propertyName, this.ui.select.val()); }, load: function load() { if (!this.isClosed) { var value = this.model.get(this.options.propertyName); if (this.model.has(this.options.propertyName) && this.ui.select.find('option[value="' + value + '"]').length) { this.ui.select.val(value); } else { this.ui.select.val(this.ui.select.find('option:first').val()); } } } }); var ExtendedSelectInputView = SelectInputView.extend({ className: 'extended_select_input', initialize: function initialize() { SelectInputView.prototype.initialize.apply(this, arguments); if (this.options.collection) { if (this.options.descriptionProperty) { this.options.descriptions = _$1.pluck(this.options.collection, this.options.descriptionProperty); } else if (this.options.descriptionTranslationKeyProperty) { this.options.descriptionTanslationKeys = _$1.pluck(this.options.collection, this.options.descriptionTranslationKeyProperty); } } if (!this.options.descriptions) { this.options.descriptions = _$1.map(this.options.descriptionTanslationKeys, function (key) { return I18n$1.t(key); }); } }, onRender: function onRender() { var view = this, options = this.options; SelectInputView.prototype.onRender.apply(this, arguments); $.widget("custom.extendedselectmenu", $.ui.selectmenu, { _renderItem: function _renderItem(ul, item) { var widget = this; var li = $('
  • ', { "class": item.value }); var container = $('
    ', { "class": 'text-container' }).appendTo(li); var index = options.values.indexOf(item.value); if (item.disabled) { li.addClass('ui-state-disabled'); } if (options.pictogramClass) { $('', { "class": options.pictogramClass }).prependTo(li); } $('

    ', { text: item.label, "class": 'item-text' }).appendTo(container); $('

    ', { text: options.descriptions[index], "class": 'item-description' }).appendTo(container); if (options.helpLinkClicked) { $('', { href: '#', title: I18n$1.t('pageflow.ui.views.extended_select_input_view.display_help') }).on('click', function () { widget.close(); options.helpLinkClicked(item.value); return false; }).appendTo(li); } return li.appendTo(ul); }, _resizeMenu: function _resizeMenu() { this.menuWrap.addClass('extended_select_input_menu'); var menuHeight = this.menu.height(), menuOffset = this.button.offset().top + this.button.outerHeight(), bodyHeight = $('body').height(); if (menuHeight + menuOffset > bodyHeight) { this.menuWrap.outerHeight(bodyHeight - menuOffset - 5).css({ 'overflow-y': 'scroll' }); } else { this.menuWrap.css({ height: 'initial', 'overflow-y': 'initial' }); } } }); this.ui.select.extendedselectmenu({ select: view.select.bind(view), width: '100%', position: { my: 'right top', at: 'right bottom' } }); }, select: function select(event, ui) { this.ui.select.val(ui.item.value); this.save(); } }); function template$8(data) { var __t, __p = ''; __p += '\n\n\n\n\n

    \n \n \n \n \n \n \n\n \n
    \n'; return __p; } /** * Input view for multi line text with simple formatting options. * See {@link inputWithPlaceholderText} for placeholder related options. * See {@link inputView} for further options. * * @param {Object} [options] * * @param {string} [options.size="normal"] * Pass `"short"` to reduce the text area height. * * @param {boolean} [options.disableLinks=false] * Do not allow links inside the text. * * @param {boolean} [options.disableRichtext=false] * Do not provide text formatting options. * * @param {Backbone.View} [options.fragmentLinkInputView] * A view to select an id to use in links which only consist * of a url fragment. Will receive a model with a `linkId` * attribute. * * @class */ var TextAreaInputView = Marionette.ItemView.extend({ mixins: [inputView, inputWithPlaceholderText], template: template$8, ui: { input: 'textarea', toolbar: '.toolbar', linkButton: '.link_button', linkDialog: '.link_dialog', urlInput: '.current_url', targetInput: '.current_target', linkTypeSelection: '.link_type_select', urlLinkRadioButton: '.url_link_radio_button', fragmentLinkRadioButton: '.fragment_link_radio_button', urlLinkPanel: '.url_link_panel', displayUrlInput: '.display_url', openInNewTabCheckBox: '.open_in_new_tab', fragmentLinkPanel: '.fragment_link_panel' }, events: { 'change textarea': 'save', 'click .url_link_radio_button': 'showUrlLinkPanel', 'click .fragment_link_radio_button': 'showFragmentLinkPanel', 'change .open_in_new_tab': 'setTargetFromOpenInNewTabCheckBox', 'change .display_url': 'setUrlFromDisplayUrl' }, onRender: function onRender() { this.ui.input.addClass(this.options.size); this.load(); this.updatePlaceholder(); this.editor = new wysihtml5.Editor(this.ui.input[0], { toolbar: this.ui.toolbar[0], autoLink: this.options.disableLinks ? 0 : 1, parserRules: { tags: { em: { unwrap: this.options.disableRichtext ? 1 : 0, rename_tag: "i" }, strong: { unwrap: this.options.disableRichtext ? 1 : 0, rename_tag: "b" }, u: { unwrap: this.options.disableRichtext ? 1 : 0 }, b: { unwrap: this.options.disableRichtext ? 1 : 0 }, i: { unwrap: this.options.disableRichtext ? 1 : 0 }, ol: { unwrap: this.options.enableLists ? 0 : 1 }, ul: { unwrap: this.options.enableLists ? 0 : 1 }, li: { unwrap: this.options.enableLists ? 0 : 1 }, br: {}, a: { unwrap: this.options.disableLinks ? 1 : 0, check_attributes: { href: 'href', target: 'any' }, set_attributes: { rel: 'nofollow' } } } } }); if (this.options.disableRichtext) { this.ui.toolbar.find('a[data-wysihtml5-command="bold"]').hide(); this.ui.toolbar.find('a[data-wysihtml5-command="italic"]').hide(); this.ui.toolbar.find('a[data-wysihtml5-command="underline"]').hide(); this.ui.toolbar.find('a[data-wysihtml5-command="insertOrderedList"]').hide(); this.ui.toolbar.find('a[data-wysihtml5-command="insertUnorderedList"]').hide(); } if (!this.options.enableLists) { this.ui.toolbar.find('a[data-wysihtml5-command="insertOrderedList"]').hide(); this.ui.toolbar.find('a[data-wysihtml5-command="insertUnorderedList"]').hide(); } if (this.options.disableLinks) { this.ui.toolbar.find('a[data-wysihtml5-command="createLink"]').hide(); } else { this.setupUrlLinkPanel(); this.setupFragmentLinkPanel(); } this.editor.on('change', _$1.bind(this.save, this)); this.editor.on('aftercommand:composer', _$1.bind(this.save, this)); }, onClose: function onClose() { this.editor.fire('destroy:composer'); }, save: function save() { this.model.set(this.options.propertyName, this.editor.getValue()); }, load: function load() { this.ui.input.val(this.model.get(this.options.propertyName)); }, setupUrlLinkPanel: function setupUrlLinkPanel() { this.editor.on('show:dialog', _$1.bind(function () { this.ui.linkDialog.toggleClass('for_existing_link', this.ui.linkButton.hasClass('wysihtml5-command-active')); var currentUrl = this.ui.urlInput.val(); if (currentUrl.startsWith('#')) { this.ui.displayUrlInput.val('http://'); this.ui.openInNewTabCheckBox.prop('checked', true); } else { this.ui.displayUrlInput.val(currentUrl); this.ui.openInNewTabCheckBox.prop('checked', this.ui.targetInput.val() !== '_self'); } }, this)); }, setupFragmentLinkPanel: function setupFragmentLinkPanel() { if (this.options.fragmentLinkInputView) { this.fragmentLinkModel = new Backbone.Model(); this.listenTo(this.fragmentLinkModel, 'change', function (model, options) { if (!options.skipCurrentUrlUpdate) { this.setInputsFromFragmentLinkModel(); } }); this.editor.on('show:dialog', _$1.bind(function () { var currentUrl = this.ui.urlInput.val(); var id = currentUrl.startsWith('#') ? currentUrl.substr(1) : null; this.fragmentLinkModel.set('linkId', id, { skipCurrentUrlUpdate: true }); this.initLinkTypePanels(!id); }, this)); var fragmentLinkInput = new this.options.fragmentLinkInputView({ model: this.fragmentLinkModel, propertyName: 'linkId', label: I18n$1.t('pageflow.ui.templates.inputs.text_area_input.target'), hideUnsetButton: true }); this.ui.fragmentLinkPanel.append(fragmentLinkInput.render().el); } else { this.ui.linkTypeSelection.hide(); this.ui.fragmentLinkPanel.hide(); } }, initLinkTypePanels: function initLinkTypePanels(isUrlLink) { if (isUrlLink) { this.ui.urlLinkRadioButton.prop('checked', true); } else { this.ui.fragmentLinkRadioButton.prop('checked', true); } this.ui.toolbar.toggleClass('fragment_link_panel_active', !isUrlLink); }, showUrlLinkPanel: function showUrlLinkPanel() { this.ui.toolbar.removeClass('fragment_link_panel_active'); this.setUrlFromDisplayUrl(); this.setTargetFromOpenInNewTabCheckBox(); }, showFragmentLinkPanel: function showFragmentLinkPanel() { this.ui.toolbar.addClass('fragment_link_panel_active'); this.setInputsFromFragmentLinkModel(); }, setInputsFromFragmentLinkModel: function setInputsFromFragmentLinkModel() { this.ui.urlInput.val('#' + (this.fragmentLinkModel.get('linkId') || '')); this.ui.targetInput.val('_self'); }, setUrlFromDisplayUrl: function setUrlFromDisplayUrl() { this.ui.urlInput.val(this.ui.displayUrlInput.val()); }, setTargetFromOpenInNewTabCheckBox: function setTargetFromOpenInNewTabCheckBox() { this.ui.targetInput.val(this.ui.openInNewTabCheckBox.is(':checked') ? '_blank' : '_self'); } }); function template$9(data) { var __p = ''; __p += '\n\n
    \n'; return __p; } /** * Input view for URLs. * See {@link inputView} for further options * * @param {Object} [options] * * @param {string[]} options.supportedHosts * List of allowed url prefixes. * * @param {boolean} [options.required=false] * Display an error if the url is blank. * * @param {boolean} [options.permitHttps=false] * Allow urls with https protocol. * * @class */ var UrlInputView = Marionette.Layout.extend( /** @lends UrlInputView.prototype */ { mixins: [inputView], template: template$9, ui: { input: 'input', validation: '.validation' }, events: { 'change': 'onChange' }, onRender: function onRender() { this.ui.validation.hide(); this.load(); this.validate(); }, onChange: function onChange() { var view = this; this.saveDisplayProperty(); this.validate().done(function () { view.save(); }); }, saveDisplayProperty: function saveDisplayProperty() { this.model.set(this.options.displayPropertyName, this.ui.input.val()); this.model.unset(this.options.propertyName); }, save: function save() { var view = this; $.when(this.transformPropertyValue(this.ui.input.val())).then(function (value) { view.model.set(view.options.propertyName, value); }); }, load: function load() { this.ui.input.val(this.model.get(this.options.displayPropertyName)); this.onLoad(); }, /** * Override to be notified when the input has been loaded. */ onLoad: function onLoad() {}, /** * Override to validate the untransformed url. Validation error * message can be passed as rejected promise. Progress notifications * are displayed. Only valid urls are stored in the configuration. * * @return Promise */ validateUrl: function validateUrl(url) { return $.Deferred().resolve().promise(); }, /** * Override to transform the property value before it is stored. * * @return Promise | String */ transformPropertyValue: function transformPropertyValue(value) { return value; }, /** * Override to change the list of supported host names. */ supportedHosts: function supportedHosts() { return this.options.supportedHosts; }, validate: function validate(success) { var view = this; var options = this.options; var value = this.ui.input.val(); if (options.required && !value) { displayValidationError(I18n$1.t('pageflow.ui.views.inputs.url_input_view.required_field')); } else if (value && !isValidUrl(value)) { var errorMessage = I18n$1.t('pageflow.ui.views.inputs.url_input_view.url_hint'); if (options.permitHttps) { errorMessage = I18n$1.t('pageflow.ui.views.inputs.url_input_view.url_hint_https'); } displayValidationError(errorMessage); } else if (value && !hasSupportedHost(value)) { displayValidationError(I18n$1.t('pageflow.ui.views.inputs.url_input_view.supported_vendors') + _$1.map(view.supportedHosts(), function (url) { return '
  • ' + url + '
  • '; }).join('')); } else { return view.validateUrl(value).progress(function (message) { displayValidationPending(message); }).done(function () { resetValidationError(); }).fail(function (error) { displayValidationError(error); }); } return $.Deferred().reject().promise(); function isValidUrl(url) { return options.permitHttps ? url.match(/^https?:\/\//i) : url.match(/^http:\/\//i); } function hasSupportedHost(url) { return _$1.any(view.supportedHosts(), function (host) { return url.match(new RegExp('^' + host)); }); } function displayValidationError(message) { view.$el.addClass('invalid'); view.ui.validation.removeClass('pending').addClass('failed').html(message).show(); } function displayValidationPending(message) { view.$el.removeClass('invalid'); view.ui.validation.removeClass('failed').addClass('pending').html(message).show(); } function resetValidationError(message) { view.$el.removeClass('invalid'); view.ui.validation.hide(); } } }); /** * Input view that verifies that a certain URL is reachable via a * proxy. To conform with same origin restrictions, this input view * lets the user enter some url and saves a rewritten url where the * domain is replaced with some path segment. * * That way, when `/example` is setup to proxy requests to * `http://example.com`, the user can enter an url of the form * `http://example.com/some/path` but the string `/example/some/path` * is persisited to the database. * * See {@link inputView} for further options * * @param {Object} options * * @param {string} options.displayPropertyName * Attribute name to store the url entered by the user. * * @param {Object[]} options.proxies * List of supported proxies. * * @param {string} options.proxies[].url * Supported prefix of an url that can be entered by the user. * * @param {string} options.proxies[].base_path * Path to replace the url prefix with. * * @param {boolean} [options.required=false] * Display an error if the url is blank. * * @param {boolean} [options.permitHttps=false] * Allow urls with https protocol. * * @example * * this.input('url, ProxyUrlInputView, { * proxies: [ * { * url: 'http://example.com', * base_path: '/example' * } * ] * }); * * @class */ var ProxyUrlInputView = UrlInputView.extend( /** @lends ProxyUrlInputView.prototype */ { // @override validateUrl: function validateUrl(url) { var view = this; return $.Deferred(function (deferred) { deferred.notify(I18n$1.t('pageflow.ui.views.inputs.proxy_url_input_view.url_validation')); $.ajax({ url: view.rewriteUrl(url), dataType: 'html' }).done(deferred.resolve).fail(function (xhr) { deferred.reject(I18n$1.t('pageflow.ui.views.inputs.proxy_url_input_view.http_error', { status: xhr.status })); }); }).promise(); }, // override transformPropertyValue: function transformPropertyValue(url) { return this.rewriteUrl(url); }, // override supportedHosts: function supportedHosts() { return _$1.pluck(this.options.proxies, 'url'); }, rewriteUrl: function rewriteUrl(url) { _$1.each(this.options.proxies, function (proxy) { url = url.replace(new RegExp('^' + proxy.url + '/?'), proxy.base_path + '/'); }); return url; } }); function template$a(data) { var __p = ''; __p += '\n
    \n
    \n'; return __p; } /** * A slider for numeric inputs. * See {@link inputView} for options * * @param {Object} [options] * * @class */ var SliderInputView = Marionette.ItemView.extend({ mixins: [inputView], className: 'slider_input', template: template$a, ui: { widget: '.slider', value: '.value' }, events: { 'slidechange': 'save' }, onRender: function onRender() { this.ui.widget.slider({ animate: 'fast', min: 'minValue' in this.options ? this.options.minValue : 0, max: 'maxValue' in this.options ? this.options.maxValue : 100 }); this.load(); }, save: function save() { var value = this.ui.widget.slider('option', 'value'); var unit = 'unit' in this.options ? this.options.unit : '%'; this.ui.value.text(value + unit); this.model.set(this.options.propertyName, value); }, load: function load() { var value; if (this.model.has(this.options.propertyName)) { value = this.model.get(this.options.propertyName); } else { value = 'defaultValue' in this.options ? this.options.defaultValue : 0; } this.ui.widget.slider('option', 'value', value); } }); function template$b(data) { var __p = ''; __p += '\n\n\n'; return __p; } var JsonInputView = Marionette.ItemView.extend({ mixins: [inputView], template: template$b, className: 'json_input', ui: { input: 'textarea' }, events: { 'change': 'onChange', 'keyup': 'validate' }, onRender: function onRender() { this.load(); this.validate(); this.listenTo(this.model, 'change:' + this.options.propertyName, this.load); }, onChange: function onChange() { if (this.validate()) { this.save(); } }, onClose: function onClose() { if (this.validate()) { this.save(); } }, save: function save() { this.model.set(this.options.propertyName, this.ui.input.val() ? JSON.parse(this.ui.input.val()) : null); }, load: function load() { var input = this.ui.input; var value = this.model.get(this.options.propertyName); input.val(value ? JSON.stringify(value, null, 2) : ''); }, validate: function validate() { var input = this.ui.input; if (input.val() && !this.isValidJson(input.val())) { this.displayValidationError(I18n$1.t('pageflow.ui.views.inputs.json_input_view.invalid')); return false; } else { this.resetValidationError(); return true; } }, displayValidationError: function displayValidationError(message) { this.$el.addClass('invalid'); this.ui.input.attr('title', message); }, resetValidationError: function resetValidationError(message) { this.$el.removeClass('invalid'); this.ui.input.attr('title', ''); }, isValidJson: function isValidJson(text) { try { JSON.parse(text); return true; } catch (e) { return false; } } }); function template$c(data) { var __p = ''; __p += '\n'; return __p; } /** * Input view for boolean values. * See {@link inputView} for further options * * @param {Object} [options] * * @param {boolean} [options.displayUncheckedIfDisabled=false] * Ignore the attribute value if the input is disabled and display * an unchecked check box. * * @class */ var CheckBoxInputView = Marionette.ItemView.extend({ mixins: [inputView], template: template$c, className: 'check_box_input', events: { 'change': 'save' }, ui: { input: 'input', label: 'label' }, onRender: function onRender() { this.ui.label.attr('for', this.cid); this.ui.input.attr('id', this.cid); this.load(); this.listenTo(this.model, 'change:' + this.options.propertyName, this.load); }, save: function save() { if (!this.options.disabled) { this.model.set(this.options.propertyName, this.ui.input.is(':checked')); } }, load: function load() { if (!this.isClosed) { this.ui.input.prop('checked', this.displayValue()); } }, displayValue: function displayValue() { if (this.options.disabled && this.options.displayUncheckedIfDisabled) { return false; } else { return this.model.get(this.options.propertyName); } } }); /** * A table cell mapping column attribute values to a list of * translations. * * ## Attribute Translations * * The following attribute translations are used: * * - `.cell_text.` - Used as cell content. * - `.cell_text.blank` - Used as cell content if attribute is blank. * - `.cell_title.` - Used as title attribute. * - `.cell_title.blank` - Used as title attribute if attribute is blank. * * @since 12.0 */ var EnumTableCellView = TableCellView.extend({ className: 'enum_table_cell', update: function update() { this.$el.text(this.attributeTranslation('cell_text.' + (this.attributeValue() || 'blank'))); this.$el.attr('title', this.attributeTranslation('cell_title.' + (this.attributeValue() || 'blank'), { defaultValue: '' })); } }); function template$d(data) { var __t, __p = ''; __p += '\n'; return __p; } /** * A table cell providing a button which destroys the model that the * current row refers to. * * ## Attribute Translations * * The following attribute translation is used: * * - `.cell_title` - Used as title attribute. * * @param {Object} [options] * * @param {function} [options.toggleDeleteButton] * A function with boolean return value to be called on * this.getModel(). Delete button will be visible only if the * function returns a truthy value. * * @param {boolean} [options.invertToggleDeleteButton] * Invert the return value of `toggleDeleteButton`? * * @since 12.0 */ var DeleteRowTableCellView = TableCellView.extend({ className: 'delete_row_table_cell', template: template$d, ui: { removeButton: '.remove' }, events: { 'click .remove': 'destroy', 'click': function click() { return false; } }, showButton: function showButton() { if (this.options.toggleDeleteButton) { var context = this.getModel(); var toggle = context[this.options.toggleDeleteButton].apply(context); if (this.options.invertToggleDeleteButton) { return !toggle; } else { return !!toggle; } } else { return true; } }, update: function update() { this.ui.removeButton.toggleClass('remove', this.showButton()); this.ui.removeButton.attr('title', this.attributeTranslation('cell_title')); }, destroy: function destroy() { this.getModel().destroy(); } }); /** * A table cell representing whether the column attribute is present * on the row model. * * ## Attribute Translations * * The following attribute translations are used: * * - `.cell_title.present` - Used as title attribute if the attribute * is present. The current attribute value is provided as * interpolation `%{value}`. * - `.cell_title.blank` - Used as title attribute if the * attribute is blank. * * @since 12.0 */ var PresenceTableCellView = TableCellView.extend({ className: 'presence_table_cell', update: function update() { var isPresent = !!this.attributeValue(); this.$el.attr('title', isPresent ? this.attributeTranslation('cell_title.present', { value: this.attributeValue() }) : this.attributeTranslation('cell_title.blank')); this.$el.toggleClass('is_present', isPresent); } }); /** * A table cell mapping column attribute values to icons. * * ## Attribute Translations * * The following attribute translations are used: * * - `.cell_title.` - Used as title attribute. * - `.cell_title.blank` - Used as title attribute if attribute is blank. * * @param {Object} [options] * * @param {string[]} [options.icons] * An array of all possible attribute values to be mapped to HTML * classes of the same name. A global mapping from those classes to * icon mixins is provided in * pageflow/ui/table_cells/icon_table_cell.scss. * * @since 12.0 */ var IconTableCellView = TableCellView.extend({ className: 'icon_table_cell', update: function update() { var icon = this.attributeValue(); var isPresent = !!this.attributeValue(); this.removeExistingIcons(); this.$el.attr('title', isPresent ? this.attributeTranslation('cell_title.' + icon, { value: this.attributeValue() }) : this.attributeTranslation('cell_title.blank')); this.$el.addClass(icon); }, removeExistingIcons: function removeExistingIcons() { this.$el.removeClass(this.options.icons.join(' ')); } }); /** * A table cell using the row model's value of the column attribute as * text. If attribute value is empty, use most specific default * available. * * @param {Object} [options] * * @param {function|string} [options.column.default] * A function returning a default value for display if attribute * value is empty. * * @param {string} [options.column.contentBinding] * If this is provided, the function `options.column.default` * receives the values of `options.column.contentBinding` and of * this.getModel() via its options hash. No-op if * `options.column.default` is not a function. * * @since 12.0 */ var TextTableCellView = TableCellView.extend({ className: 'text_table_cell', update: function update() { this.$el.text(this._updateText()); }, _updateText: function _updateText() { if (this.attributeValue()) { return this.attributeValue(); } else if (typeof this.options.column["default"] === 'function') { var options = {}; if (this.options.column.contentBinding) { options = { contentBinding: this.options.column.contentBinding, model: this.getModel() }; } return this.options.column["default"](options); } else if ('default' in this.options.column) { return this.options.column["default"]; } else { return I18n$1.t('pageflow.ui.text_table_cell_view.empty'); } } }); var subviewContainer = { subview: function subview(view) { this.subviews = this.subviews || new ChildViewContainer(); this.subviews.add(view.render()); return view; }, appendSubview: function appendSubview(view) { return this.$el.append(this.subview(view).el); }, onClose: function onClose() { if (this.subviews) { this.subviews.call('close'); } } }; Cocktail.mixin(Marionette.View, subviewContainer); var tooltipContainer = { events: { 'mouseover [data-tooltip]': function mouseoverDataTooltip(event) { if (!this.tooltip.visible) { var target = $(event.target); var key = target.data('tooltip'); var position; if (target.data('tooltipAlign') === 'bottom left') { position = { left: target.position().left, top: target.position().top + target.outerHeight() }; } else if (target.data('tooltipAlign') === 'bottom right') { position = { left: target.position().left + target.outerWidth(), top: target.position().top + target.outerHeight() }; } else { position = { left: target.position().left + target.outerWidth(), top: target.position().top + target.outerHeight() / 2 }; } this.tooltip.show(I18n$1.t(key), position, { align: target.data('tooltipAlign') }); } }, 'mouseout [data-tooltip]': function mouseoutDataTooltip() { this.tooltip.hide(); } }, onRender: function onRender() { this.appendSubview(this.tooltip = new TooltipView()); } }; var CommonPageConfigurationTabs = BaseObject.extend({ initialize: function initialize() { this.configureFns = {}; }, register: function register(name, configureFn) { this.configureFns[name] = configureFn; }, apply: function apply(configurationEditorView) { _$1.each(this.configureFns, function (configureFn, name) { configurationEditorView.tab(name, function () { configureFn.call(prefixInputDecorator(name, this)); }); }); function prefixInputDecorator(name, dsl) { return { input: function input(propertyName, view, options) { return dsl.input(name + '_' + propertyName, view, options); } }; } } }); /** * Failure and subclasses are used in the failures api. * * Subclasses that represent failures that are can not be retried should * override `catRetry` with false. * Retryable failures should implement `retryAction`. * * @class */ var Failure = BaseObject.extend({ canRetry: true, type: 'Failure', initialize: function initialize(model) { this.model = model; }, retry: function retry() { if (this.canRetry) { return this.retryAction(); } }, retryAction: function retryAction() { return this.model.save(); }, key: function key() { return this.model.cid + '-' + this.type; } }); var SavingFailure = Failure.extend({ type: 'SavingFailure' }); var OrderingFailure = Failure.extend({ type: 'OrderingFailure', initialize: function initialize(model, collection) { Failure.prototype.initialize.call(this, model); this.collection = collection; }, retryAction: function retryAction() { return this.collection.saveOrder(); } }); /** * API to allow access to failure UI and recovery. * * Can watch collections for errors saving models and display the error * allong with a retry button. * * editor.failures.watch(collection); * * It's possible to add failures to the UI by adding instances of subclasses of Failure: * * editor.failures.add(new OrderingFailure(model, collection)); * * @alias Failures */ var FailuresAPI = BaseObject.extend( /** @lends Failures.prototype */ { initialize: function initialize() { this.failures = {}; this.length = 0; }, /** * Listen to the `error` and `sync` events of a collection and * create failure objects. */ watch: function watch(collection) { this.listenTo(collection, 'sync', this.remove); this.listenTo(collection, 'error', function (model) { if (!model.isNew()) { this.add(new SavingFailure(model)); } }); }, retry: function retry() { _$1.each(this.failures, function (failure, key) { this.remove(key); failure.retry(); }, this); }, isEmpty: function isEmpty() { return _$1.size(this.failures) === 0; }, /** * Record that a failure occured. * * @param {Failure} failure * The failure object to add. */ add: function add(failure) { this.failures[failure.key()] = failure; this.length = _$1.size(this.failures); }, remove: function remove(key) { delete this.failures[key]; this.length = _$1.size(this.failures); }, count: function count() { return this.length; } }); _$1.extend(FailuresAPI.prototype, Backbone.Events); var UploadError = BaseObject.extend({ setMessage: function setMessage(options) { this.upload = options.upload; var typeTranslation; if (options.typeTranslation) { typeTranslation = options.typeTranslation; } else if (this.upload.type !== '') { typeTranslation = this.upload.type; } else { typeTranslation = I18n$1.t('pageflow.editor.errors.upload.type_empty'); } var interpolations = { name: this.upload.name, type: typeTranslation, validList: options.validList }; this.message = I18n$1.t(options.translationKey, interpolations); } }); var UnmatchedUploadError = UploadError.extend({ name: 'UnmatchedUploadError', initialize: function initialize(upload) { this.setMessage({ upload: upload, translationKey: 'pageflow.editor.errors.unmatched_upload_error' }); } }); var validFileTypeTranslationList = { validFileTypeTranslations: function validFileTypeTranslations(validFileTypes) { return _$1.map(validFileTypes, function (validFileType) { return I18n$1.t('activerecord.models.' + validFileType.i18nKey + '.other'); }).join(', '); } }; var NestedTypeError = UploadError.extend({ name: 'NestedTypeError', initialize: function initialize(upload, options) { var fileType = options.fileType; var fileTypes = options.fileTypes; var validParentFileTypes = fileTypes.filter(function (parentFileType) { return parentFileType.nestedFileTypes.contains(fileType); }); var validParentFileTypeTranslations = this.validFileTypeTranslations(validParentFileTypes); var typeI18nKey = fileTypes.findByUpload(upload).i18nKey; var typePluralTranslation = I18n$1.t('activerecord.models.' + typeI18nKey + '.other'); this.setMessage({ upload: upload, translationKey: 'pageflow.editor.errors.nested_type_error', typeTranslation: typePluralTranslation, validList: validParentFileTypeTranslations }); } }); Cocktail.mixin(NestedTypeError, validFileTypeTranslationList); var InvalidNestedTypeError = UploadError.extend({ name: 'InvalidNestedTypeError', initialize: function initialize(upload, options) { var editor = options.editor; var fileType = options.fileType; var validFileTypes = editor.nextUploadTargetFile.fileType().nestedFileTypes.fileTypes; var validFileTypeTranslations = this.validFileTypeTranslations(validFileTypes); var typeI18nKey = fileType.i18nKey; var typeSingularTranslation = I18n$1.t('activerecord.models.' + typeI18nKey + '.one'); this.setMessage({ upload: upload, translationKey: 'pageflow.editor.errors.invalid_nested_type_error', typeTranslation: typeSingularTranslation, validList: validFileTypeTranslations }); } }); Cocktail.mixin(InvalidNestedTypeError, validFileTypeTranslationList); var FileTypesCollection = BaseObject.extend({ initialize: function initialize(fileTypes) { this._fileTypes = fileTypes; }, findByUpload: function findByUpload(upload) { var result = this.find(function (fileType) { return fileType.matchUpload(upload); }); if (!result) { throw new UnmatchedUploadError(upload); } return result; }, findByCollectionName: function findByCollectionName(collectionName) { var result = this.find(function (fileType) { return fileType.collectionName === collectionName; }); if (!result) { throw 'Could not find file type by collection name "' + collectionName + '"'; } return result; } }); _$1.each(['each', 'map', 'reduce', 'first', 'find', 'contains', 'filter'], function (method) { FileTypesCollection.prototype[method] = function () { var args = Array.prototype.slice.call(arguments); args.unshift(this._fileTypes); return _$1[method].apply(_$1, args); }; }); var state = window.pageflow || {}; function template$e(data) { var __p = ''; __p += ''; return __p } var EditFileView = Marionette.ItemView.extend({ template: template$e, className: 'edit_file', onRender: function onRender() { var fileType = this.model.fileType(); var entry = this.options.entry || state.entry; var tab = new ConfigurationEditorTabView({ model: this.model.configuration, attributeTranslationKeyPrefixes: ['pageflow.editor.files.attributes.' + fileType.collectionName, 'pageflow.editor.files.common_attributes', 'pageflow.editor.nested_files.' + fileType.collectionName, 'pageflow.editor.nested_files.common_attributes'] }); tab.input('file_name', TextInputView, { model: this.model, disabled: true }); tab.input('rights', TextInputView, { model: this.model, placeholder: entry.get('default_file_rights') }); _$1(this.fileTypeInputs()).each(function (options) { tab.input(options.name, options.inputView, options.inputViewOptions); }); tab.input('original_url', UrlDisplayView, { model: this.model }); this.appendSubview(tab); }, fileTypeInputs: function fileTypeInputs() { var fileType = this.model.fileType(); return _$1.chain(fileType.configurationEditorInputs).map(function (inputs) { if (_$1.isFunction(inputs)) { return inputs(this.model); } else { return inputs; } }, this).flatten().value(); } }); var app = new Marionette.Application(); var dialogView = { events: { 'click .close': function clickClose() { this.close(); }, 'click .box': function clickBox() { return false; }, 'click': function click() { this.close(); } } }; function template$f(data) { var __t, __p = ''; __p += '\n'; return __p } var FileSettingsDialogView = Marionette.ItemView.extend({ template: template$f, className: 'file_settings_dialog editor dialog', mixins: [dialogView], ui: { content: '.content' }, onRender: function onRender() { this.tabsView = new TabsView({ model: this.model, i18n: 'pageflow.editor.files.settings_dialog_tabs', defaultTab: this.options.tabName }); _$1.each(this.model.fileType().settingsDialogTabs, function (options) { this.tabsView.tab(options.name, _$1.bind(function () { return this.subview(new options.view(_$1.extend({ model: this.model }, options.viewOptions))); }, this)); }, this); this.ui.content.append(this.subview(this.tabsView).el); } }); FileSettingsDialogView.open = function (options) { app.dialogRegion.show(new FileSettingsDialogView(options)); }; function template$g(data) { var __t, __p = ''; __p += '\n\n\n'; return __p } /** * Base class for views used as `valueView` for file type meta data * attributes. * * @param {Object} [options] * * @param {string} [options.name] * Name of the meta data item used in translation keys. * * @param {string} [options.settingsDialogTabLink] * Dispaly a link to open the specified tab of the file settings * dialog. * * @since 12.0 * * @class */ var FileMetaDataItemValueView = Marionette.ItemView.extend({ template: template$g, ui: { value: '.value', editLink: '.edit' }, events: { 'click .edit': function clickEdit() { FileSettingsDialogView.open({ model: this.model, tabName: this.options.settingsDialogTabLink }); } }, modelEvents: { 'change': 'toggleEditLink' }, onRender: function onRender() { this.listenTo(this.model, 'change:' + this.options.name, this.update); this.toggleEditLink(); this.update(); }, update: function update() { this.ui.value.text(this.getText() || I18n$1.t('pageflow.editor.views.file_meta_data_item_value_view.blank')); }, getText: function getText() { throw new Error('Not implemented'); }, toggleEditLink: function toggleEditLink() { this.ui.editLink.toggle(!!this.options.settingsDialogTabLink && !this.model.isNew()); } }); var TextFileMetaDataItemValueView = FileMetaDataItemValueView.extend({ getText: function getText() { var model; if (this.options.fromConfiguration) { model = this.model.configuration; } else { model = this.model; } return model.get(this.options.name); } }); var FileType = BaseObject.extend({ initialize: function initialize(options) { this.model = options.model; this.typeName = options.typeName; this.collectionName = options.collectionName; this.topLevelType = options.topLevelType; this.paramKey = options.paramKey; this.i18nKey = options.i18nKey; this.nestedFileTypes = []; this.confirmUploadTableColumns = options.confirmUploadTableColumns || []; this.configurationEditorInputs = [].concat(options.configurationEditorInputs || []); this.configurationUpdaters = options.configurationUpdaters || []; this.nestedFileTableColumns = options.nestedFileTableColumns || []; this.nestedFilesOrder = options.nestedFilesOrder; this.skipUploadConfirmation = options.skipUploadConfirmation || false; this.filters = options.filters || []; this.metaDataAttributes = [{ name: 'rights', valueView: TextFileMetaDataItemValueView, valueViewOptions: { settingsDialogTabLink: 'general' } }].concat(options.metaDataAttributes || []); this.settingsDialogTabs = [{ name: 'general', view: EditFileView }].concat(options.settingsDialogTabs || []); if (typeof options.matchUpload === 'function') { this.matchUpload = options.matchUpload; } else if (options.matchUpload instanceof RegExp) { this.matchUpload = function (upload) { return upload.type.match(options.matchUpload); }; } else { throw 'matchUpload option of FileType "' + this.collectionName + '" must either be a function or a RegExp.'; } this.setupModelNaming(); }, setupModelNaming: function setupModelNaming() { this.model.prototype.modelName = this.model.prototype.modelName || this.paramKey; this.model.prototype.paramRoot = this.model.prototype.paramRoot || this.paramKey; this.model.prototype.i18nKey = this.model.prototype.i18nKey || this.i18nKey; }, setNestedFileTypes: function setNestedFileTypes(fileTypesCollection) { this.nestedFileTypes = fileTypesCollection; }, getFilter: function getFilter(name) { var result = _$1(this.filters).find(function (filter) { return filter.name === name; }); if (!result) { throw new Error('Unknown filter "' + name + '" for file type "' + this.collectionName + '".'); } return result; } }); var FileTypes = BaseObject.extend({ modifyableProperties: ['configurationEditorInputs', 'configurationUpdaters', 'confirmUploadTableColumns', 'filters'], initialize: function initialize() { this.clientSideConfigs = []; this.clientSideConfigModifications = {}; }, register: function register(name, config) { if (this._setup) { throw 'File types already set up. Register file types before initializers run.'; } this.clientSideConfigs[name] = config; }, modify: function modify(name, config) { if (this._setup) { throw 'File types already set up. Modify file types before initializers run.'; } this.clientSideConfigModifications[name] = this.clientSideConfigModifications[name] || []; this.clientSideConfigModifications[name].push(config); }, setup: function setup(serverSideConfigs) { var clientSideConfigs = this.clientSideConfigs; this._setup = true; this.collection = new FileTypesCollection(_$1.map(serverSideConfigs, function (serverSideConfig) { var clientSideConfig = clientSideConfigs[serverSideConfig.collectionName]; if (!clientSideConfig) { throw 'Missing client side config for file type "' + serverSideConfig.collectionName + '"'; } _$1(this.clientSideConfigModifications[serverSideConfig.collectionName]).each(function (modification) { this.lintModification(modification, serverSideConfig.collectionName); this.applyModification(clientSideConfig, modification); }, this); return new FileType(_$1.extend({}, serverSideConfig, clientSideConfig)); }, this)); var those = this; _$1.map(serverSideConfigs, function (serverSideConfig) { var fileType = those.findByCollectionName(serverSideConfig.collectionName); fileType.setNestedFileTypes(new FileTypesCollection(_$1.map(serverSideConfig.nestedFileTypes, function (nestedFileType) { return those.findByCollectionName(nestedFileType.collectionName); }))); }); }, lintModification: function lintModification(modification, collectionName) { var unmodifyableProperties = _$1.difference(_$1.keys(modification), this.modifyableProperties); if (unmodifyableProperties.length) { throw 'Only the following properties are allowed in FileTypes#modify: ' + this.modifyableProperties.join(', ') + '. Given in modification for ' + collectionName + ': ' + unmodifyableProperties.join(', ') + '.'; } }, applyModification: function applyModification(target, modification) { _$1(this.modifyableProperties).each(function (property) { target[property] = (target[property] || []).concat(modification[property] || []); }); } }); _$1.each(['each', 'map', 'reduce', 'first', 'find', 'findByUpload', 'findByCollectionName', 'contains', 'filter'], function (method) { FileTypes.prototype[method] = function () { if (!this._setup) { throw 'File types are not yet set up.'; } return this.collection[method].apply(this.collection, arguments); }; }); var FileImporters = BaseObject.extend({ initialize: function initialize() { this.importers = {}; }, register: function register(name, config) { if (this._setup) { throw 'File importers setup is already finished. Register file importers before setup is finished'; } this.importers[name] = config; config.key = name; }, setup: function setup(serverSideConfigs) { this._setup = true; var registeredImporters = this.importers; var importers = {}; serverSideConfigs.forEach(function (importer) { var regImporter = registeredImporters[importer.importerName]; regImporter['authenticationRequired'] = importer.authenticationRequired; regImporter['authenticationProvider'] = importer.authenticationProvider; regImporter['logoSource'] = importer.logoSource; importers[importer.importerName] = regImporter; }); this.importers = importers; }, find: function find(name) { if (!this.importers[name]) { throw 'Could not find file importer with name "' + name + '"'; } return this.importers[name]; }, keys: function keys() { return _.keys(this.importers); }, values: function values() { return _.values(this.importers); } }); var PageLinkConfigurationEditorView = ConfigurationEditorView.extend({ configure: function configure() { this.tab('general', function () { this.group('page_link'); }); } }); var PageType = BaseObject.extend({ initialize: function initialize(name, options, seed) { this.name = name; this.options = options; this.seed = seed; }, translationKey: function translationKey() { return this.seed.translation_key; }, thumbnailCandidates: function thumbnailCandidates() { return this.seed.thumbnail_candidates; }, pageLinks: function pageLinks(configuration) { if ('pageLinks' in this.options) { return this.options.pageLinks(configuration); } }, configurationEditorView: function configurationEditorView() { return this.options.configurationEditorView || ConfigurationEditorView.repository[this.name]; }, embeddedViews: function embeddedViews() { return this.options.embeddedViews; }, createConfigurationEditorView: function createConfigurationEditorView(options) { var constructor = this.configurationEditorView(); options.pageType = this.seed; return new constructor(_$1.extend({ tabTranslationKeyPrefixes: [this.seed.translation_key_prefix + '.page_configuration_tabs', 'pageflow.common_page_configuration_tabs'], attributeTranslationKeyPrefixes: [this.seed.translation_key_prefix + '.page_attributes', 'pageflow.common_page_attributes'] }, options)); }, createPageLinkConfigurationEditorView: function createPageLinkConfigurationEditorView(options) { var constructor = this.options.pageLinkConfigurationEditorView || PageLinkConfigurationEditorView; return new constructor(_$1.extend({ tabTranslationKeyPrefixes: [this.seed.translation_key_prefix + '.page_link_configuration_tabs', 'pageflow.common_page_link_configuration_tabs'], attributeTranslationKeyPrefixes: [this.seed.translation_key_prefix + '.page_link_attributes', 'pageflow.common_page_link_attributes'] }, options)); }, supportsPhoneEmulation: function supportsPhoneEmulation() { return !!this.options.supportsPhoneEmulation; } }); var PageTypes = BaseObject.extend({ initialize: function initialize() { this.clientSideConfigs = {}; }, register: function register(name, config) { if (this._setup) { throw 'Page types already set up. Register page types before initializers run.'; } this.clientSideConfigs[name] = config; }, setup: function setup(serverSideConfigs) { var clientSideConfigs = this.clientSideConfigs; this._setup = true; this.pageTypes = _$1.map(serverSideConfigs, function (serverSideConfig) { var clientSideConfig = clientSideConfigs[serverSideConfig.name] || {}; return new PageType(serverSideConfig.name, clientSideConfig, serverSideConfig); }); }, findByName: function findByName(name) { var result = this.find(function (pageType) { return pageType.name === name; }); if (!result) { throw 'Could not find page type with name "' + name + '"'; } return result; }, findByPage: function findByPage(page) { return this.findByName(page.get('template')); } }); _$1.each(['each', 'map', 'reduce', 'first', 'find', 'pluck'], function (method) { PageTypes.prototype[method] = function () { if (!this._setup) { throw 'Page types are not yet set up.'; } var args = Array.prototype.slice.call(arguments); args.unshift(this.pageTypes); return _$1[method].apply(_$1, args); }; }); // different model types. Backbone.Collection tries to merge records // if they have the same id. var MultiCollection = function MultiCollection() { this.records = {}; this.length = 0; }; _$1.extend(MultiCollection.prototype, { add: function add(record) { if (!this.records[record.cid]) { this.records[record.cid] = record; this.length = _$1.keys(this.records).length; this.trigger('add', record); } }, remove: function remove(record) { if (this.records[record.cid]) { delete this.records[record.cid]; this.length = _$1.keys(this.records).length; this.trigger('remove', record); } }, isEmpty: function isEmpty() { return this.length === 0; } }); _$1.extend(MultiCollection.prototype, Backbone.Events); MultiCollection.extend = Backbone.Collection.extend; /** * Watch Backbone collections to track which models are currently * being saved. Used to update the notifications view displaying * saving status/failutes. */ var SavingRecordsCollection = MultiCollection.extend({ /** * Listen to events of models in collection to track when they are * being saved. * * @param {Backbone.Collection} collection - Collection to watch. */ watch: function watch(collection) { var that = this; this.listenTo(collection, 'request', function (model, xhr) { that.add(model); xhr.always(function () { that.remove(model); }); }); } }); var WidgetType = BaseObject.extend({ initialize: function initialize(serverSideConfig, clientSideConfig) { this.name = serverSideConfig.name; this.translationKey = serverSideConfig.translationKey; this.configurationEditorView = clientSideConfig.configurationEditorView; this.isOptional = clientSideConfig.isOptional; }, hasConfiguration: function hasConfiguration() { return !!this.configurationEditorView; }, createConfigurationEditorView: function createConfigurationEditorView(options) { var constructor = this.configurationEditorView; return new constructor(_$1.extend({ attributeTranslationKeyPrefixes: ['pageflow.editor.widgets.attributes.' + this.name, 'pageflow.editor.widgets.common_attributes'] }, options)); } }); var WidgetTypes = BaseObject.extend({ initialize: function initialize() { this._clientSideConfigs = {}; this._optionalRoles = {}; }, register: function register(name, config) { if (this._setup) { throw 'Widget types already set up. Register widget types before initializers run.'; } this._clientSideConfigs[name] = config; }, setup: function setup(serverSideConfigsByRole) { this._setup = true; this._widgetTypesByName = {}; var roles = _$1.keys(serverSideConfigsByRole); this._widgetTypesByRole = roles.reduce(_$1.bind(function (result, role) { result[role] = serverSideConfigsByRole[role].map(_$1.bind(function (serverSideConfig) { var clientSideConfig = this._clientSideConfigs[serverSideConfig.name] || {}; var widgetType = new WidgetType(serverSideConfig, clientSideConfig); this._widgetTypesByName[serverSideConfig.name] = widgetType; return widgetType; }, this)); return result; }, this), {}); }, findAllByRole: function findAllByRole(role) { return this._widgetTypesByRole[role] || []; }, findByName: function findByName(name) { if (!this._widgetTypesByName[name]) { throw 'Could not find widget type with name "' + name + '"'; } return this._widgetTypesByName[name]; }, registerRole: function registerRole(role, options) { this._optionalRoles[role] = options.isOptional; }, isOptional: function isOptional(role) { return !!this._optionalRoles[role]; } }); function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } /** * Interface for engines providing editor extensions. * @alias editor */ var EditorApi = BaseObject.extend( /** @lends editor */ { initialize: function initialize(options) { this.router = options && options.router; this.sideBarRoutings = []; this.mainMenuItems = []; this.initializers = []; this.fileSelectionHandlers = {}; /** * Failures API * * @returns {Failures} * @memberof editor */ this.failures = new FailuresAPI(); /** * Tracking records that are currently being saved. * * @returns {SavingRecordsCollection} * @memberof editor * @since 15.1 */ this.savingRecords = new SavingRecordsCollection(); /** * Set up editor integration for page types. * @memberof editor */ this.pageTypes = new PageTypes(); /** * Add tabs to the configuration editor of all pages. * @memberof editor */ this.commonPageConfigurationTabs = new CommonPageConfigurationTabs(); /** * Setup editor integration for widget types. * @memberof editor */ this.widgetTypes = new WidgetTypes(); /** * Set up editor integration for file types * @memberof editor */ this.fileTypes = new FileTypes(); /** * List of available file import plugins * @memberof editor */ this.fileImporters = new FileImporters(); }, /** * Configure editor for entry type. * * @param {string} name * Must match name of entry type registered in Ruby configuration. * @param {Object} options * @param {function} options.EntryModel * Backbone model extending {Entry} to store entry state. * @param {function} options.EntryPreviewView * Backbone view that will render the live preview of the entry. * @param {function} options.EntryOutlineView * Backbone view that will be rendered in the side bar. */ registerEntryType: function registerEntryType(name, options) { this.entryType = _objectSpread({ name: name }, options); }, createEntryModel: function createEntryModel(seed, options) { var entry = new this.entryType.entryModel(seed.entry, options); if (entry.setupFromEntryTypeSeed) { entry.setupFromEntryTypeSeed(seed.entry_type, state); } return entry; }, /** * Display Backbone/Marionette View inside the main panel * of the editor. */ showViewInMainPanel: function showViewInMainPanel(view) { app.mainRegion.show(view); }, /** * Display the Pageflow-Preview inside the main panel. */ showPreview: function showPreview() { app.mainRegion.$el.empty(); }, /** * Register additional router and controller for sidebar. * * Supported options: * - router: constructor function of Backbone Marionette app router * - controller: constructor function of Backbone Marionette controller */ registerSideBarRouting: function registerSideBarRouting(options) { this.sideBarRoutings.push(options); }, /** * Set the file that is the parent of nested files when they are * uploaded. This value is automatically set and unset upon * navigating towards the appropriate views. */ setUploadTargetFile: function setUploadTargetFile(file) { this.nextUploadTargetFile = file; }, /** * Set the name of the help entry that shall be selected by * default when the help view is opened. This value is * automatically reset when navigation occurs. */ setDefaultHelpEntry: function setDefaultHelpEntry(name) { this.nextDefaultHelpEntry = name; }, applyDefaultHelpEntry: function applyDefaultHelpEntry(name) { this.defaultHelpEntry = this.nextDefaultHelpEntry; this.nextDefaultHelpEntry = null; }, /** * Register additional menu item to be displayed on the root sidebar * view. * * Supported options: * - translationKey: for the label * - path: route to link to * - click: click handler */ registerMainMenuItem: function registerMainMenuItem(options) { this.mainMenuItems.push(options); }, /** * Register a custom initializer which will be run before the boot * initializer of the editor. */ addInitializer: function addInitializer(fn) { this.initializers.push(fn); }, /** * Navigate to the given path. */ navigate: function navigate(path, options) { if (!this.router) { throw 'Routing has not been initialized yet.'; } this.router.navigate(path, options); }, /** * Extend the interface of page configuration objects. This is * especially convenient to wrap structured data from the page * configuration as Backbone objects. * * Example: * * editor.registerPageConfigurationMixin({ * externalLinks: function() { * return new Backbone.Collection(this.get('external_links')); * } * } * * state.pages.get(1).configuration.externalLinks().each(...); */ registerPageConfigurationMixin: function registerPageConfigurationMixin(mixin) { app.trigger('mixin:configuration', mixin); }, /** * File selection handlers let editor extensions use the files view * to select files for usage in their custom models. * * See {@link #editorselectfile * selectFile} method for details how to trigger file selection. * * Example: * * function MyFileSelectionHandler(options) { this.call = * function(file) { // invoked with the selected file }; * * this.getReferer = function() { // the path to return to * when the back button is clicked // or after file * selection return '/some/path'; } } * * editor.registerFileSelectionHandler('my_file_selection_handler', MyFileSelectionHandler); */ registerFileSelectionHandler: function registerFileSelectionHandler(name, handler) { this.fileSelectionHandlers[name] = handler; }, /** * Trigger selection of the given file type with the given * handler. Payload hash is passed to selection handler as options. * * @param {string|{name: string, filter: string}} fileType * Either collection name of a file type or and object containing * the collection name a file type and a the name of a file type * filter. * * @param {string} handlerName * The name of a handler registered via {@link * #editorregisterfileselectionhandler registerFileSelectionHandler}. * * @param {Object} payload * Options passed to the file selection handler. * * @example * * editor.selectFile('image_files', * 'my_file_selection_handler', * {some: 'option for handler'}); * * editor.selectFile({name: 'image_files', filter: 'some_filter'}, * 'my_file_selection_handler', * {some: 'option for handler'}); */ selectFile: function selectFile(fileType, handlerName, payload) { if (typeof fileType === 'string') { fileType = { name: fileType }; } this.navigate('/files/' + fileType.name + '?handler=' + handlerName + '&payload=' + encodeURIComponent(JSON.stringify(payload)) + (fileType.filter ? '&filter=' + fileType.filter : ''), { trigger: true }); }, /** * Returns a promise which resolves to a page selected by the * user. * * Supported options: * - isAllowed: function which given a page returns true or false depending on * whether the page is a valid selection */ selectPage: function selectPage(options) { return this.pageSelectionView.selectPage(_objectSpread({}, options, { entry: state.entry })); }, createFileSelectionHandler: function createFileSelectionHandler(handlerName, encodedPayload) { if (!this.fileSelectionHandlers[handlerName]) { throw 'Unknown FileSelectionHandler ' + handlerName; } var payloadJson = JSON.parse(decodeURIComponent(encodedPayload)); return new this.fileSelectionHandlers[handlerName](payloadJson); }, createPageConfigurationEditorView: function createPageConfigurationEditorView(page, options) { var view = this.pageTypes.findByPage(page).createConfigurationEditorView(_$1.extend(options, { model: page.configuration })); this.commonPageConfigurationTabs.apply(view); return view; } }); var editor$1 = new EditorApi(); var startEditor = function startEditor(options) { $(function () { $.when($.getJSON('/editor/entries/' + options.entryId + '/seed'), pageflow.browser.detectFeatures()).done(function (result) { app.start(result[0]); }).fail(function () { alert('Error while starting editor.'); }); }); }; /** * Mixins for Backbone models and collections that use entry type * specific editor controllers registered via the `editor_app` entry * type option. */ var entryTypeEditorControllerUrls = { /** * Mixins for Backbone collections that defines `url` method. * * @param {Object} options * @param {String} options.resources - Path suffix of the controller route * * @example * * import {editor, entryTypeEditorControllerUrls} from 'pageflow/editor'; * * editor.registerEntryType('test', { // ... }); * * export const ItemsCollection = Backbone.Collection.extend({ * mixins: [entryTypeEditorControllerUrls.forCollection({resources: 'items'}) * }); * * new ItemsCollection().url() // => '/editor/entries/10/test/items' */ forCollection: function forCollection(_ref) { var resources = _ref.resources; return { url: function url() { return entryTypeEditorControllerUrl(resources); }, urlSuffix: function urlSuffix() { return "/".concat(resources); } }; }, /** * Mixins for Backbone models that defines `urlRoot` method. * * @param {Object} options * @param {String} options.resources - Path suffix of the controller route * * @example * * import {editor, entryTypeEditorControllerUrls} from 'pageflow/editor'; * * editor.registerEntryType('test', { // ... }); * * export const Item = Backbone.Model.extend({ * mixins: [entryTypeEditorControllerUrls.forModel({resources: 'items'}) * }); * * new Item({id: 20}).url() // => '/editor/entries/10/test/items/20' */ forModel: function forModel(_ref2) { var resources = _ref2.resources; return { urlRoot: function urlRoot() { return this.isNew() ? this.collection.url() : entryTypeEditorControllerUrl(resources); } }; } }; function entryTypeEditorControllerUrl(resources) { return [state.entry.url(), editor$1.entryType.name, resources].join('/'); } var formDataUtils = { fromModel: function fromModel(model) { var object = {}; object[model.modelName] = model.toJSON(); return this.fromObject(object); }, fromObject: function fromObject(object) { var queryString = $.param(object).replace(/\+/g, '%20'); return _$1(queryString.split('&')).reduce(function (result, param) { var pair = param.split('='); result[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]); return result; }, {}); } }; var stylesheet = { reload: function reload(name) { var link = this.selectLink(name); if (!link.data('originalHref')) { link.data('originalHref', link.attr('href')); } link.attr('href', link.data('originalHref') + '&reload=' + new Date().getTime()); }, update: function update(name, stylesheetPath) { var link = this.selectLink(name); if (link.attr('href') !== stylesheetPath) { link.attr('href', stylesheetPath); } }, selectLink: function selectLink(name) { return $('head link[data-name=' + name + ']'); } }; var SubsetCollection = Backbone.Collection.extend({ constructor: function constructor(options) { var adding = false; var sorting = false; var parentSorting = false; options = options || {}; this.filter = options.filter || function (item) { return true; }; this.parent = options.parent; this.parentModel = options.parentModel; delete options.filter; delete options.parent; this.model = this.parent.model; this.comparator = options.comparator || this.parent.comparator; this.listenTo(this.parent, 'add', function (model, collection, options) { if (!adding && this.filter(model)) { this.add(model, options); } }); this.listenTo(this.parent, 'remove', function (model) { this.remove(model); }); this.listenTo(this, 'add', function (model, collection, options) { adding = true; this.parent.add(model); adding = false; }); if (options.watchAttribute) { this.listenTo(this.parent, 'change:' + options.watchAttribute, function (model) { if (this.filter(model)) { this.add(model); } else { this.remove(model); } }); } if (options.sortOnParentSort) { this.listenTo(this.parent, 'sort', function () { parentSorting = true; if (!sorting) { this.sort(); } parentSorting = false; }); } this.listenTo(this, 'sort', function () { sorting = true; if (!parentSorting) { this.parent.sort(); } sorting = false; }); Backbone.Collection.prototype.constructor.call(this, this.parent.filter(this.filter, this), options); }, clear: function clear() { this.parent.remove(this.models); this.reset(); }, url: function url() { return this.parentModel.url() + (_$1.result(this.parent, 'urlSuffix') || _$1.result(this.parent, 'url')); }, dispose: function dispose() { this.stopListening(); this.reset(); } }); var FilesCollection = Backbone.Collection.extend({ initialize: function initialize(models, options) { options = options || {}; this.entry = options.entry; this.fileType = options.fileType; this.name = options.fileType.collectionName; }, comparator: function comparator(file) { var fileName = file.get('file_name'); return fileName && fileName.toLowerCase ? fileName.toLowerCase() : fileName; }, url: function url() { return '/editor/entries/' + this.getEntry().get('id') + '/files/' + this.name; }, fetch: function fetch(options) { options = _$1.extend({ fileType: this.fileType }, options || {}); return Backbone.Collection.prototype.fetch.call(this, options); }, findOrCreateBy: function findOrCreateBy(attributes) { return this.findWhere(attributes) || this.create(attributes, { fileType: this.fileType, queryParams: { no_upload: true } }); }, getByPermaId: function getByPermaId(permaId) { return this.findWhere({ perma_id: parseInt(permaId, 10) }); }, getEntry: function getEntry() { return this.entry || state.entry; }, confirmable: function confirmable() { return new SubsetCollection({ parent: this, watchAttribute: 'state', filter: function filter(item) { return item.get('state') === 'waiting_for_confirmation'; } }); }, uploadable: function uploadable() { this._uploadableSubsetCollection = this._uploadableSubsetCollection || new SubsetCollection({ parent: this, watchAttribute: 'state', filter: function filter(item) { return item.get('state') === 'uploadable'; } }); return this._uploadableSubsetCollection; }, withFilter: function withFilter(filterName) { return new SubsetCollection({ parent: this, watchAttribute: 'configuration', filter: this.fileType.getFilter(filterName).matches }); } }); FilesCollection.createForFileTypes = function (fileTypes, files, options) { return fileTypes.reduce(function (result, fileType) { result[fileType.collectionName] = FilesCollection.createForFileType(fileType, files[fileType.collectionName], options); return result; }, {}); }; FilesCollection.createForFileType = function (fileType, files, options) { return new FilesCollection(files, _$1.extend({ fileType: fileType, model: fileType.model }, options || {})); }; var OtherEntry = Backbone.Model.extend({ paramRoot: 'entry', urlRoot: '/entries', modelName: 'entry', i18nKey: 'pageflow/entry', initialize: function initialize() { this.files = {}; }, getFileCollection: function getFileCollection(fileType) { if (!this.files[fileType.collectionName]) { this.files[fileType.collectionName] = FilesCollection.createForFileType(fileType, [], { entry: this }); } return this.files[fileType.collectionName]; }, titleOrSlug: function titleOrSlug() { return this.get('title') || this.get('slug'); } }); var EditLock = Backbone.Model.extend({ paramRoot: 'edit_lock', url: function url() { return '/entries/' + state.entry.get('id') + '/edit_lock?timestamp=' + new Date().getTime(); }, toJSON: function toJSON() { return { id: this.id, force: this.get('force') }; } }); var transientReferences = { initialize: function initialize() { this.transientReferences = {}; this.pendingReferences = {}; }, getReference: function getReference(attribute, collection) { if (typeof collection === 'string') { var fileType = editor$1.fileTypes.findByCollectionName(collection); collection = state.entry.getFileCollection(fileType); } return this.transientReferences[attribute] || collection.getByPermaId(this.get(attribute)); }, setReference: function setReference(attribute, record) { this._cleanUpReference(attribute); this._setReference(attribute, record); this._listenForReady(attribute, record); }, unsetReference: function unsetReference(attribute) { this._cleanUpReference(attribute); this.set(attribute, null); }, _setReference: function _setReference(attribute, record) { if (record.isNew()) { this.transientReferences[attribute] = record; this.set(attribute, null); this._setPermaIdOnceSynced(attribute, record); } else { this.set(attribute, record.get('perma_id')); } }, _setPermaIdOnceSynced: function _setPermaIdOnceSynced(attribute, record) { record.once('change:perma_id', function () { this._onceRecordCanBeFoundInCollection(record, function () { delete this.transientReferences[attribute]; this.set(attribute, record.get('perma_id')); }); }, this); }, _onceRecordCanBeFoundInCollection: function _onceRecordCanBeFoundInCollection(record, callback) { // Backbone collections update their modelsById map in the change // event which is dispatched after the `change:` // events. record.once('change', _$1.bind(callback, this)); }, _listenForReady: function _listenForReady(attribute, record) { if (!record.isReady()) { this.pendingReferences[attribute] = record; this.listenTo(record, 'change:state', function () { if (record.isReady()) { this._cleanUpReadyListener(attribute); this.trigger('change'); this.trigger('change:' + attribute + ':ready'); } }); } }, _cleanUpReference: function _cleanUpReference(attribute) { this._cleanUpSaveListener(attribute); this._cleanUpReadyListener(attribute); }, _cleanUpSaveListener: function _cleanUpSaveListener(attribute) { if (this.transientReferences[attribute]) { this.stopListening(this.transientReferences[attribute], 'change:perma_id'); delete this.transientReferences[attribute]; } }, _cleanUpReadyListener: function _cleanUpReadyListener(attribute) { if (this.pendingReferences[attribute]) { this.stopListening(this.pendingReferences[attribute], 'change:state'); delete this.pendingReferences[attribute]; } } }; var Configuration = Backbone.Model.extend({ modelName: 'page', i18nKey: 'pageflow/page', mixins: [transientReferences], defaults: { gradient_opacity: 100, display_in_navigation: true, transition: 'fade', text_position: 'left', invert: false, hide_title: false, autoplay: true }, /** * Used by views (i.e. FileInputView) to get id which can be used in * routes to lookup configuration via its page. * @private */ getRoutableId: function getRoutableId() { return this.parent.id; }, getImageFileUrl: function getImageFileUrl(attribute, options) { options = options || {}; var file = this.getImageFile(attribute); if (file && file.isReady()) { return file.get(options.styleGroup ? options.styleGroup + '_url' : 'url'); } return ''; }, getImageFile: function getImageFile(attribute) { return this.getReference(attribute, state.imageFiles); }, getFilePosition: function getFilePosition(attribute, coord) { var propertyName = this.filePositionProperty(attribute, coord); return this.has(propertyName) ? this.get(propertyName) : 50; }, setFilePosition: function setFilePosition(attribute, coord, value) { var propertyName = this.filePositionProperty(attribute, coord); this.set(propertyName, value); }, setFilePositions: function setFilePositions(attribute, x, y) { var attributes = {}; attributes[this.filePositionProperty(attribute, 'x')] = x; attributes[this.filePositionProperty(attribute, 'y')] = y; this.set(attributes); }, filePositionProperty: function filePositionProperty(attribute, coord) { return attribute.replace(/_id$/, '_' + coord); }, getVideoFileSources: function getVideoFileSources(attribute) { var file = this.getVideoFile(attribute); if (file && file.isReady()) { return file.get('sources') ? this._appendSuffix(file.get('sources')) : ''; } return ''; }, getVideoFile: function getVideoFile(attribute) { return this.getReference(attribute, state.videoFiles); }, getAudioFileSources: function getAudioFileSources(attribute) { var file = this.getAudioFile(attribute); if (file && file.isReady()) { return file.get('sources') ? this._appendSuffix(file.get('sources')) : ''; } return ''; }, getAudioFile: function getAudioFile(attribute) { return this.getReference(attribute, state.audioFiles); }, getVideoPosterUrl: function getVideoPosterUrl() { var posterFile = this.getReference('poster_image_id', state.imageFiles), videoFile = this.getReference('video_file_id', state.videoFiles); if (posterFile) { return posterFile.get('url'); } else if (videoFile) { return videoFile.get('poster_url'); } return null; }, _appendSuffix: function _appendSuffix(sources) { var parent = this.parent; if (!parent || !parent.id) { return sources; } return _$1.map(sources, function (source) { var clone = _$1.clone(source); clone.src = clone.src + '?e=' + parent.id + '&t=' + new Date().getTime(); return clone; }); } }); app.on('mixin:configuration', function (mixin) { Cocktail.mixin(Configuration, mixin); }); /** * Remove model from collection only after the `DELETE` request has * succeeded. Still allow tracking that the model is being destroyed * by triggering a `destroying` event and adding a `isDestroying` * method. */ var delayedDestroying = { initialize: function initialize() { this._destroying = false; this._destroyed = false; }, /** * Trigger `destroying` event and send `DELETE` request. Only remove * model from collection once the request is done. */ destroyWithDelay: function destroyWithDelay() { var model = this; this._destroying = true; this.trigger('destroying', this); return Backbone.Model.prototype.destroy.call(this, { wait: true, success: function success() { model._destroying = false; model._destroyed = true; }, error: function error() { model._destroying = false; } }); }, /** * Get whether the model is currently being destroyed. */ isDestroying: function isDestroying() { return this._destroying; }, /** * Get whether the model has been destroyed. */ isDestroyed: function isDestroyed() { return this._destroyed; } }; /** * Mixin for Backbone models that shall be watched by {@link * modelLifecycleTrackingView} mixin. */ var failureTracking = { initialize: function initialize() { this._saveFailed = false; this.listenTo(this, 'sync', function () { this._saveFailed = false; this._failureMessage = null; this.trigger('change:failed'); }); this.listenTo(this, 'error', function (model, xhr) { this._saveFailed = true; this._failureMessage = this.translateStatus(xhr); this.trigger('change:failed'); }); }, isFailed: function isFailed() { return this._saveFailed; }, getFailureMessage: function getFailureMessage() { return this._failureMessage; }, translateStatus: function translateStatus(xhr) { if (xhr.status === 401) { return 'Sie müssen angemeldet sein, um diese Aktion auszuführen.'; } else if (xhr.status === 403) { return 'Sie sind nicht berechtigt diese Aktion auszuführen.'; } else if (xhr.status === 404) { return 'Der Datensatz konnte auf dem Server nicht gefunden werden.'; } else if (xhr.status === 409) { return 'Die Reportage wurde außerhalb dieses Editors bearbeitet.'; } else if (xhr.status >= 500 && xhr.status < 600) { return 'Der Server hat einen internen Fehler gemeldet.'; } else if (xhr.statusText === 'timeout') { return 'Der Server ist nicht erreichbar.'; } return ''; } }; var Page = Backbone.Model.extend({ modelName: 'page', paramRoot: 'page', i18nKey: 'pageflow/page', defaults: function defaults() { return { template: 'background_image', configuration: {}, active: false, perma_id: '' }; }, mixins: [failureTracking, delayedDestroying], initialize: function initialize() { this.configuration = new Configuration(this.get('configuration') || {}); this.configuration.parent = this.configuration.page = this; this.listenTo(this.configuration, 'change', function () { this.trigger('change:configuration', this); }); this.listenTo(this.configuration, 'change:title', function () { this.trigger('change:title'); }); this.listenTo(this.configuration, 'change', function () { this.save(); }); this.listenTo(this, 'change:template', function () { this.save(); }); }, urlRoot: function urlRoot() { return this.isNew() ? this.collection.url() : '/pages'; }, storylinePosition: function storylinePosition() { return this.chapter && this.chapter.storylinePosition() || -1; }, chapterPosition: function chapterPosition() { return this.chapter && this.chapter.has('position') ? this.chapter.get('position') : -1; }, isFirstPage: function isFirstPage() { return this.isChapterBeginning() && this.chapterPosition() === 0 && this.storylinePosition() === 1; }, isChapterBeginning: function isChapterBeginning() { return this.get('position') === 0; }, title: function title() { return this.configuration.get('title') || this.configuration.get('additional_title') || ''; }, thumbnailFile: function thumbnailFile() { var configuration = this.configuration; return _$1.reduce(this.pageType().thumbnailCandidates(), function (result, candidate) { if (candidate.condition && !conditionMet(candidate.condition, configuration)) { return result; } return result || configuration.getReference(candidate.attribute, candidate.file_collection); }, null); }, pageLinks: function pageLinks() { return this.pageType().pageLinks(this.configuration); }, pageType: function pageType() { return editor$1.pageTypes.findByName(this.get('template')); }, toJSON: function toJSON() { return _$1.extend(_$1.clone(this.attributes), { configuration: this.configuration.toJSON() }); }, destroy: function destroy() { this.destroyWithDelay(); } }); function conditionMet(condition, configuration) { if (condition.negated) { return configuration.get(condition.attribute) != condition.value; } else { return configuration.get(condition.attribute) == condition.value; } } Page.linkedPagesLayouts = ['default', 'hero_top_left', 'hero_top_right']; Page.textPositions = ['left', 'center', 'right']; Page.textPositionsWithoutCenterOption = ['left', 'right']; Page.scrollIndicatorModes = ['all', 'only_back', 'only_next', 'non']; Page.scrollIndicatorOrientations = ['vertical', 'horizontal']; Page.delayedTextFadeIn = ['no_fade', 'short', 'medium', 'long']; var Scaffold = BaseObject.extend({ initialize: function initialize(parent, options) { this.parent = parent; this.options = options || {}; }, create: function create() { var scaffold = this; var query = this.options.depth ? '?depth=' + this.options.depth : ''; this.model = this.build(); Backbone.sync('create', this.model, { url: this.model.url() + '/scaffold' + query, success: function success(response) { scaffold.load(response); scaffold.model.trigger('sync', scaffold.model, response, {}); } }); }, build: function build() {}, load: function load() {} }); var StorylineScaffold = Scaffold.extend({ build: function build() { this.storyline = this.parent.buildStoryline(this.options.storylineAttributes); this.chapter = this.storyline.buildChapter(); if (this.options.depth === 'page') { this.page = this.chapter.buildPage(); } editor$1.trigger('scaffold:storyline', this.storyline); return this.storyline; }, load: function load(response) { this.storyline.set(response.storyline); this.chapter.set(response.chapter); if (this.page) { this.page.set(response.page); } } }); var FileReuse = Backbone.Model.extend({ modelName: 'file_reuse', paramRoot: 'file_reuse', initialize: function initialize(attributes, options) { this.entry = options.entry; this.collectionName = options.fileType.collectionName; }, url: function url() { return '/editor/entries/' + this.entry.get('id') + '/files/' + this.collectionName + '/reuse'; } }); FileReuse.submit = function (otherEntry, file, options) { new FileReuse({ other_entry_id: otherEntry.get('id'), file_id: file.get('id') }, { entry: options.entry, fileType: file.fileType() }).save(null, options); }; var FileConfiguration = Configuration.extend({ defaults: {}, applyUpdaters: function applyUpdaters(updaters, newAttributes) { _$1(updaters).each(function (updater) { updater(this, newAttributes); }, this); } }); var NestedFilesCollection = SubsetCollection.extend({ constructor: function constructor(options) { var parent = options.parent; var parentFile = options.parentFile; var modelType = parentFile.fileType().typeName; var nestedFilesOrder = parent.fileType.nestedFilesOrder; SubsetCollection.prototype.constructor.call(this, { parent: parent, parentModel: parentFile, filter: function filter(item) { return item.get('parent_file_id') === parentFile.get('id') && item.get('parent_file_model_type') === modelType; }, comparator: nestedFilesOrder && nestedFilesOrder.comparator }); if (nestedFilesOrder) { this.listenTo(this, 'change:configuration:' + nestedFilesOrder.binding, this.sort); } }, getByPermaId: function getByPermaId(permaId) { return this.findWhere({ perma_id: parseInt(permaId, 10) }); } }); var retryable = { retry: function retry(options) { options = options ? _$1.clone(options) : {}; if (options.parse === void 0) options.parse = true; var model = this; options.success = function (resp) { if (!model.set(model.parse(resp, options), options)) return false; model.trigger('sync', model, resp, options); }; options.error = function (resp) { model.trigger('error', model, resp, options); }; options.url = this.url() + '/retry'; return this.sync('create', this, options); } }; var FileStage = Backbone.Model.extend({ initialize: function initialize(attributes, options) { this.file = options.file; this.activeStates = options.activeStates || []; this.finishedStates = options.finishedStates || []; this.failedStates = options.failedStates || []; this.actionRequiredStates = options.actionRequiredStates || []; this.nonFinishedStates = this.activeStates.concat(this.failedStates, this.actionRequiredStates); this.update(); this.listenTo(this.file, 'change:state', this.update); this.listenTo(this.file, 'change:' + this.get('name') + '_progress', this.update); this.listenTo(this.file, 'change:' + this.get('name') + '_error_message', this.update); }, update: function update() { this.updateState(); this.updateProgress(); this.updateErrorMessage(); }, updateState: function updateState() { var state = this.file.get('state'); this.set('active', this.activeStates.indexOf(state) >= 0); this.set('finished', this.finishedStates.indexOf(state) >= 0); this.set('failed', this.failedStates.indexOf(state) >= 0); this.set('action_required', this.actionRequiredStates.indexOf(state) >= 0); if (this.get('active')) { this.set('state', 'active'); } else if (this.get('finished')) { this.set('state', 'finished'); } else if (this.get('failed')) { this.set('state', 'failed'); } else if (this.get('action_required')) { this.set('state', 'action_required'); } else { this.set('state', 'pending'); } }, updateProgress: function updateProgress() { this.set('progress', this.file.get(this.get('name') + '_progress')); }, updateErrorMessage: function updateErrorMessage() { var errorMessageAttribute = this.get('name') + '_error_message'; this.set('error_message', this.file.get(errorMessageAttribute)); }, localizedDescription: function localizedDescription() { var prefix = 'pageflow.editor.files.stages.'; var suffix = this.get('name') + '.' + this.get('state'); return I18n$1.t(prefix + this.file.i18nKey + '.' + suffix, { defaultValue: I18n$1.t(prefix + suffix) }); } }); var stageProvider = { initialize: function initialize() { var finishedStates = [this.readyState]; var stages = _$1.result(this, 'stages') || []; this.stages = new Backbone.Collection(_$1.chain(stages).slice().reverse().map(function (options) { var name = options.name; options.file = this; options.finishedStates = finishedStates; var fileStage = new FileStage({ name: name }, options); finishedStates = finishedStates.concat(fileStage.nonFinishedStates); return fileStage; }, this).reverse().value()); this.unfinishedStages = new SubsetCollection({ parent: this.stages, watchAttribute: 'finished', filter: function filter(stage) { return !stage.get('finished'); } }); }, currentStage: function currentStage() { return this.stages.find(function (stage) { return stage.get('active') || stage.get('action_required') || stage.get('failed'); }); } }; var ReusableFile = Backbone.Model.extend({ mixins: [stageProvider, retryable], initialize: function initialize(attributes, options) { this.options = options || {}; this.configuration = new FileConfiguration(this.get('configuration') || {}); this.configuration.i18nKey = this.i18nKey; this.configuration.parent = this; this.listenTo(this.configuration, 'change', function () { this.trigger('change:configuration', this); _$1.chain(this.configuration.changed).keys().each(function (name) { this.trigger('change:configuration:' + name, this, this.configuration.get(name)); }, this); if (!this.isNew()) { this.save(); } }); this.listenTo(this, 'change:rights', function () { if (!this.isNew()) { this.save(); } }); this.listenTo(this, 'change', function (model, options) { if (options.applyConfigurationUpdaters) { this.configuration.applyUpdaters(this.fileType().configurationUpdaters, this.attributes.configuration); } }); }, urlRoot: function urlRoot() { return this.collection.url(); }, fileType: function fileType() { return this.options.fileType; }, title: function title() { return this.get('file_name'); }, thumbnailFile: function thumbnailFile() { return this; }, nestedFiles: function nestedFiles(supersetCollection) { if (typeof supersetCollection === 'function') { supersetCollection = supersetCollection(); } var collectionName = supersetCollection.fileType.collectionName; this.nestedFilesCollections = this.nestedFilesCollections || {}; this.nestedFilesCollections[collectionName] = this.nestedFilesCollections[collectionName] || new NestedFilesCollection({ parent: supersetCollection, parentFile: this }); return this.nestedFilesCollections[collectionName]; }, isUploading: function isUploading() { return this.get('state') === 'uploading'; }, isUploaded: function isUploaded() { return this.get('state') !== 'uploading' && this.get('state') !== 'uploading_failed'; }, isPending: function isPending() { return !this.isReady() && !this.isFailed(); }, isReady: function isReady() { return this.get('state') === this.readyState; }, isFailed: function isFailed() { return this.get('state') && !!this.get('state').match(/_failed$/); }, isRetryable: function isRetryable() { return !!this.get('retryable'); }, isConfirmable: function isConfirmable() { return false; }, isPositionable: function isPositionable() { return false; }, toJSON: function toJSON() { return _$1.extend(_$1.pick(this.attributes, 'file_name', 'rights', 'parent_file_id', 'parent_file_model_type', 'content_type', 'file_size'), { configuration: this.configuration.toJSON() }); }, cancelUpload: function cancelUpload() { if (this.get('state') === 'uploading') { this.trigger('uploadCancelled'); this.destroy(); } }, uploadFailed: function uploadFailed() { this.set('state', 'uploading_failed'); this.unset('uploading_progress'); this.trigger('uploadFailed'); }, publish: function publish() { this.save({}, { url: this.url() + '/publish' }); } }); var UploadableFile = ReusableFile.extend({ stages: function stages() { return [{ name: 'uploading', activeStates: ['uploading'], failedStates: ['uploading_failed'] }].concat(_$1.result(this, 'processingStages')); }, processingStages: [], readyState: 'uploaded' }); var EncodedFile = UploadableFile.extend({ processingStages: function processingStages() { var stages = []; if (state.config.confirmEncodingJobs) { stages.push({ name: 'fetching_meta_data', activeStates: ['waiting_for_meta_data', 'fetching_meta_data'], failedStates: ['fetching_meta_data_failed'] }); } stages.push({ name: 'encoding', actionRequiredStates: ['waiting_for_confirmation'], activeStates: ['waiting_for_encoding', 'encoding'], failedStates: ['fetching_meta_data_failed', 'encoding_failed'] }); return stages; }, readyState: 'encoded', isConfirmable: function isConfirmable() { return this.get('state') === 'waiting_for_confirmation'; }, isPositionable: function isPositionable() { return false; } }); var VideoFile = EncodedFile.extend({ getBackgroundPositioningImageUrl: function getBackgroundPositioningImageUrl() { return this.get('poster_url'); }, isPositionable: function isPositionable() { return this.isReady(); } }); var WidgetConfigurationFileSelectionHandler = function WidgetConfigurationFileSelectionHandler(options) { var widget = state.entry.widgets.get(options.id); this.call = function (file) { widget.configuration.setReference(options.attributeName, file); }; this.getReferer = function () { return '/widgets/' + widget.id; }; }; editor$1.registerFileSelectionHandler('widgetConfiguration', WidgetConfigurationFileSelectionHandler); var EncodingConfirmation = Backbone.Model.extend({ paramRoot: 'encoding_confirmation', initialize: function initialize() { this.videoFiles = new Backbone.Collection(); this.audioFiles = new Backbone.Collection(); this.updateEmpty(); this.watchCollections(); }, watchCollections: function watchCollections() { this.listenTo(this.videoFiles, 'add remove', this.check); this.listenTo(this.audioFiles, 'add remove', this.check); this.listenTo(this.videoFiles, 'reset', this.updateEmpty); this.listenTo(this.audioFiles, 'reset', this.updateEmpty); }, check: function check() { var model = this; model.updateEmpty(); model.set('checking', true); model.save({}, { url: model.url() + '/check', success: function success() { model.set('checking', false); }, error: function error() { model.set('checking', false); } }); }, saveAndReset: function saveAndReset() { var model = this; model.save({}, { success: function success() { model.set('summary_html', ''); model.videoFiles.reset(); model.audioFiles.reset(); } }); }, updateEmpty: function updateEmpty() { this.set('empty', this.videoFiles.length === 0 && this.audioFiles.length === 0); }, url: function url() { return '/editor/entries/' + state.entry.get('id') + '/encoding_confirmations'; }, toJSON: function toJSON() { return { video_file_ids: this.videoFiles.pluck('id'), audio_file_ids: this.audioFiles.pluck('id') }; } }); EncodingConfirmation.createWithPreselection = function (options) { var model = new EncodingConfirmation(); if (options.fileId) { if (options.fileType === 'video_file') { model.videoFiles.add(state.videoFiles.get(options.fileId)); } else { model.audioFiles.add(state.audioFiles.get(options.fileId)); } } return model; }; var Theme = Backbone.Model.extend({ title: function title() { return I18n$1.t('pageflow.' + this.get('name') + '_theme.name'); }, thumbnailUrl: function thumbnailUrl() { return this.get('preview_thumbnail_url'); }, hasHomeButton: function hasHomeButton() { return this.get('home_button'); }, hasOverviewButton: function hasOverviewButton() { return this.get('overview_button'); }, supportsEmphasizedPages: function supportsEmphasizedPages() { return this.get('emphasized_pages'); }, supportsScrollIndicatorModes: function supportsScrollIndicatorModes() { return this.get('scroll_indicator_modes'); } }); var WidgetConfiguration = Configuration.extend({ i18nKey: 'pageflow/widget', defaults: {} }); var AudioFile = EncodedFile.extend({ thumbnailPictogram: 'audio', getSources: function getSources(attribute) { if (this.isReady()) { return this.get('sources') ? this.get('sources') : ''; } return ''; } }); var EntryMetadataConfiguration = Configuration.extend({ modelName: 'entry_metadata_configuration', i18nKey: 'pageflow/entry_metadata_configuration', defaults: {} }); var EntryMetadata = Configuration.extend({ modelName: 'entry', i18nKey: 'pageflow/entry', defaults: {}, initialize: function initialize(attributes, options) { Configuration.prototype.initialize.apply(this, attributes, options); this.configuration = new EntryMetadataConfiguration(_$1.clone(attributes.configuration) || {}); this.listenTo(this.configuration, 'change', function () { this.trigger('change'); this.parent.save(); }); } }); var StorylineConfiguration = Configuration.extend({ modelName: 'storyline', i18nKey: 'pageflow/storyline', defaults: {}, initialize: function initialize() { this.listenTo(this, 'change:main', function (model, value) { if (value) { this.unset('parent_page_perma_id'); } }); } }); var TextTrackFile = UploadableFile.extend({ defaults: { configuration: { kind: 'captions' } }, processingStages: [{ name: 'processing', activeStates: ['processing'], failedStates: ['processing_failed'] }], readyState: 'processed', initialize: function initialize(attributes, options) { ReusableFile.prototype.initialize.apply(this, arguments); if (this.isNew() && !this.configuration.get('srclang')) { this.configuration.set('srclang', this.extractLanguageCodeFromFilename()); } }, displayLabel: function displayLabel() { return this.configuration.get('label') || this.inferredLabel() || I18n$1.t('pageflow.editor.text_track_files.label_missing'); }, inferredLabel: function inferredLabel() { var srclang = this.configuration.get('srclang'); if (srclang) { return I18n$1.t('pageflow.languages.' + srclang, { defaultValue: '' }); } }, extractLanguageCodeFromFilename: function extractLanguageCodeFromFilename() { var matches = /\S+\.([a-z]{2})_[A-Z]{2}\.[a-z]+/.exec(this.get('file_name')); return matches && matches[1]; } }); TextTrackFile.displayLabelBinding = 'srclang'; var StorylineOrdering = function StorylineOrdering(storylines, pages) { var storylinesByParent; this.watch = function () { storylines.on('add change:configuration', function () { this.sort(); }, this); pages.on('change:position change:chapter_id', function () { this.sort(); }, this); }; this.sort = function (options) { prepare(); visit(storylinesWithoutParent(), 1, 0); storylines.sort(options); }; function visit(storylines, offset, level) { return _$1(storylines).reduce(function (position, storyline, index) { storyline.set('position', position); storyline.set('level', level); return visit(children(storyline), position + 1, level + 1); }, offset); } function storylinesWithoutParent() { return storylinesByParent[-1]; } function children(storyline) { return storylinesByParent[storyline.cid] || []; } function prepare() { storylinesByParent = _$1(groupStorylinesByParentStoryline()).reduce(function (result, storylines, key) { result[key] = storylines.sort(compareStorylines); return result; }, {}); } function groupStorylinesByParentStoryline() { return storylines.groupBy(function (storyline) { var parentPage = getParentPage(storyline); return parentPage && parentPage.chapter ? parentPage.chapter.storyline.cid : -1; }); } function compareStorylines(storylineA, storylineB) { return compareByMainFlag(storylineA, storylineB) || compareByParentPagePosition(storylineA, storylineB) || compareByLane(storylineA, storylineB) || compareByRow(storylineA, storylineB) || compareByTitle(storylineA, storylineB); } function compareByMainFlag(storylineA, storylineB) { return compare(storylineA.isMain() ? -1 : 1, storylineB.isMain() ? -1 : 1); } function compareByParentPagePosition(storylineA, storylineB) { return compare(getParentPagePosition(storylineA), getParentPagePosition(storylineB)); } function compareByLane(storylineA, storylineB) { return compare(storylineA.lane(), storylineB.lane()); } function compareByRow(storylineA, storylineB) { return compare(storylineA.row(), storylineB.row()); } function compareByTitle(storylineA, storylineB) { return compare(storylineA.title(), storylineB.title()); } function compare(a, b) { if (a > b) { return 1; } else if (a < b) { return -1; } else { return 0; } } function getParentPagePosition(storyline) { var parentPage = getParentPage(storyline); return parentPage && parentPage.get('position'); } function getParentPage(storyline) { return pages.getByPermaId(storyline.parentPagePermaId()); } }; var PageConfigurationFileSelectionHandler = function PageConfigurationFileSelectionHandler(options) { var page = state.pages.get(options.id); this.call = function (file) { page.configuration.setReference(options.attributeName, file); }; this.getReferer = function () { return '/pages/' + page.id + '/' + (options.returnToTab || 'files'); }; }; editor$1.registerFileSelectionHandler('pageConfiguration', PageConfigurationFileSelectionHandler); var ImageFile = ReusableFile.extend({ stages: [{ name: 'uploading', activeStates: ['uploading'], failedStates: ['uploading_failed'] }, { name: 'processing', activeStates: ['processing'], finishedStates: ['processed'], failedStates: ['processing_failed'] }], readyState: 'processed', getBackgroundPositioningImageUrl: function getBackgroundPositioningImageUrl() { return this.get('url'); }, isPositionable: function isPositionable() { return this.isReady(); } }); var EntryMetadataFileSelectionHandler = function EntryMetadataFileSelectionHandler(options) { this.call = function (file) { state.entry.metadata.setReference(options.attributeName, file); }; this.getReferer = function () { return '/meta_data/' + (options.returnToTab || 'general'); }; }; editor$1.registerFileSelectionHandler('entryMetadata', EntryMetadataFileSelectionHandler); var EntryPublication = Backbone.Model.extend({ paramRoot: 'entry_publication', quota: function quota() { return new Backbone.Model(this.get('quota') || {}); }, check: function check() { var model = this; this.set('checking', true); this.save({}, { url: this.url() + '/check', success: function success() { model.set('checking', false); }, error: function error() { model.set('checking', false); } }); }, publish: function publish(attributes) { return this.save(attributes, { success: function success(model) { state.entry.parse(model.get('entry')); }, error: function error(model, xhr) { model.set(xhr.responseJSON); } }); }, url: function url() { return '/editor/entries/' + state.entry.get('id') + '/entry_publications'; } }); var ChapterScaffold = Scaffold.extend({ build: function build() { this.chapter = this.parent.buildChapter(this.options.chapterAttributes); this.page = this.chapter.buildPage(); return this.chapter; }, load: function load(response) { this.chapter.set(response.chapter); this.page.set(response.page); } }); // https://github.com/jashkenas/backbone/issues/2601 function BaseObject$1(options) { this.initialize.apply(this, arguments); } _$1.extend(BaseObject$1.prototype, Backbone.Events, { initialize: function initialize(options) {} }); // The self-propagating extend function that Backbone classes use. BaseObject$1.extend = Backbone.Model.extend; var EntryData = BaseObject$1.extend({ getThemingOption: function getThemingOption(name) { throw 'Not implemented'; }, getFile: function getFile(collectionName, id) { throw 'Not implemented'; }, getPageConfiguration: function getPageConfiguration(permaId) { throw 'Not implemented'; }, getPagePosition: function getPagePosition(permaId) { throw 'Not implemented'; }, getChapterConfiguration: function getChapterConfiguration(id) { throw 'Not implemented'; }, getStorylineConfiguration: function getStorylineConfiguration(id) { throw 'Not implemented'; }, getChapterIdByPagePermaId: function getChapterIdByPagePermaId(permaId) { throw 'Not implemented'; }, getStorylineIdByChapterId: function getStorylineIdByChapterId(permaId) { throw 'Not implemented'; }, getChapterPagePermaIds: function getChapterPagePermaIds(id) { throw 'Not implemented'; }, getParentPagePermaIdByPagePermaId: function getParentPagePermaIdByPagePermaId(permaId) { var storylineId = this.getStorylineIdByPagePermaId(permaId); return this.getParentPagePermaId(storylineId); }, getStorylineIdByPagePermaId: function getStorylineIdByPagePermaId(permaId) { var chapterId = this.getChapterIdByPagePermaId(permaId); return this.getStorylineIdByChapterId(chapterId); }, getParentStorylineId: function getParentStorylineId(storylineId) { var parentPagePermaId = this.getParentPagePermaId(storylineId); return parentPagePermaId && this.getStorylineIdByPagePermaId(parentPagePermaId); }, getParentChapterId: function getParentChapterId(chapterId) { var storylineId = this.getStorylineIdByChapterId(chapterId); var pagePermaId = this.getParentPagePermaId(storylineId); return pagePermaId && this.getChapterIdByPagePermaId(pagePermaId); }, getParentPagePermaId: function getParentPagePermaId(storylineId) { return this.getStorylineConfiguration(storylineId).parent_page_perma_id; }, getStorylineLevel: function getStorylineLevel(storylineId) { var parentStorylineId = this.getParentStorylineId(storylineId); if (parentStorylineId) { return this.getStorylineLevel(parentStorylineId) + 1; } else { return 0; } } }); var PreviewEntryData = EntryData.extend({ initialize: function initialize(options) { this.entry = options.entry; this.storylines = options.storylines; this.chapters = options.chapters; this.pages = options.pages; }, getThemingOption: function getThemingOption(name) { return this.entry.getTheme().get(name); }, getFile: function getFile(collectionName, permaId) { var file = this.entry.getFileCollection(collectionName).getByPermaId(permaId); return file && file.attributes; }, getStorylineConfiguration: function getStorylineConfiguration(id) { var storyline = this.storylines.get(id); return storyline ? storyline.configuration.attributes : {}; }, getChapterConfiguration: function getChapterConfiguration(id) { var chapter = this.chapters.get(id); return chapter ? chapter.configuration.attributes : {}; }, getChapterPagePermaIds: function getChapterPagePermaIds(id) { var chapter = this.chapters.get(id); return chapter ? chapter.pages.pluck('perma_id') : []; }, getStorylineIdByChapterId: function getStorylineIdByChapterId(id) { var chapter = this.chapters.get(id); return chapter && chapter.get('storyline_id'); }, getChapterIdByPagePermaId: function getChapterIdByPagePermaId(permaId) { var page = this.pages.getByPermaId(permaId); return page && page.get('chapter_id'); }, getPageConfiguration: function getPageConfiguration(permaId) { var page = this.pages.getByPermaId(permaId); return page ? page.configuration.attributes : {}; }, getPagePosition: function getPagePosition(permaId) { return this.pages.indexOf(this.pages.getByPermaId(permaId)); } }); var EditLockContainer = Backbone.Model.extend({ initialize: function initialize() { this.storageKey = 'pageflow.edit_lock.' + state.entry.id; }, acquire: function acquire(options) { options = options || {}; var container = this; var lock = new EditLock({ id: options.force ? null : sessionStorage[this.storageKey], force: options.force }); lock.save(null, { polling: !!options.polling, success: function success(lock) { sessionStorage[container.storageKey] = lock.id; container.lock = lock; container.trigger('acquired'); container.startPolling(); } }); }, startPolling: function startPolling() { if (!this.pollingInteval) { this.pollingInteval = setInterval(_$1.bind(function () { this.acquire({ polling: true }); }, this), state.config.editLockPollingIntervalInSeconds * 1000); } }, stopPolling: function stopPolling() { if (this.pollingInteval) { clearInterval(this.pollingInteval); this.pollingInteval = null; } }, watchForErrors: function watchForErrors() { var container = this; $(document).ajaxSend(function (event, xhr) { if (container.lock) { xhr.setRequestHeader("X-Edit-Lock", container.lock.id); } }); $(document).ajaxError(function (event, xhr, settings) { switch (xhr.status) { case 409: container.handleConflict(xhr, settings); break; case 401: case 422: container.handleUnauthenticated(); break; default: container.handleError(); } }); }, release: function release() { if (this.lock) { var promise = this.lock.destroy(); delete sessionStorage[this.storageKey]; this.lock = null; return promise; } }, handleConflict: function handleConflict(xhr, settings) { this.lock = null; this.trigger('locked', xhr.responseJSON || {}, { context: settings.url.match(/\/edit_lock/) && !settings.polling ? 'acquire' : 'other' }); this.stopPolling(); }, handleUnauthenticated: function handleUnauthenticated() { this.stopPolling(); this.trigger('unauthenticated'); }, handleError: function handleError() {} }); var Theming = Backbone.Model.extend({ modelName: 'theming', i18nKey: 'pageflow/theming', collectionName: 'themings' }); var ChapterConfiguration = Configuration.extend({ modelName: 'chapter', i18nKey: 'pageflow/chapter', defaults: {} }); var Widget = Backbone.Model.extend({ paramRoot: 'widget', i18nKey: 'pageflow/widget', initialize: function initialize(attributes, options) { this.widgetTypes = options.widgetTypes; this.configuration = new WidgetConfiguration(this.get('configuration') || {}); this.configuration.parent = this; this.listenTo(this.configuration, 'change', function () { this.trigger('change:configuration', this); }); }, widgetType: function widgetType() { return this.get('type_name') && this.widgetTypes.findByName(this.get('type_name')); }, hasConfiguration: function hasConfiguration() { return !!(this.widgetType() && this.widgetType().hasConfiguration()); }, role: function role() { return this.id; }, urlRoot: function urlRoot() { return this.collection.url(); }, toJSON: function toJSON() { return { role: this.role(), type_name: this.get('type_name'), configuration: this.configuration.toJSON() }; } }); var StorylineTransitiveChildPages = function StorylineTransitiveChildPages(storyline, storylines, pages) { var isTranstiveChildStoryline; this.contain = function (page) { if (!isTranstiveChildStoryline) { search(); } return !!isTranstiveChildStoryline[page.chapter.storyline.id]; }; function search() { isTranstiveChildStoryline = storylines.reduce(function (memo, other) { var current = other; while (current) { if (current === storyline || memo[current.id]) { memo[other.id] = true; return memo; } current = parentStoryline(current); } return memo; }, {}); } function parentStoryline(storyline) { var parentPage = pages.getByPermaId(storyline.parentPagePermaId()); return parentPage && parentPage.chapter && parentPage.chapter.storyline; } }; var FileUploader = BaseObject.extend({ initialize: function initialize(options) { this.fileTypes = options.fileTypes; this.entry = options.entry; this.deferreds = []; }, add: function add(upload, options) { options = options || {}; var editor = options.editor || editor$1; var fileType = this.fileTypes.findByUpload(upload); var file = new fileType.model({ state: 'uploadable', file_name: upload.name, content_type: upload.type, file_size: upload.size }, { fileType: fileType }); var setTargetFile = editor.nextUploadTargetFile; if (setTargetFile) { if (fileType.topLevelType || !setTargetFile.fileType().nestedFileTypes.contains(fileType)) { throw new InvalidNestedTypeError(upload, { editor: editor, fileType: fileType }); } file.set({ parent_file_id: setTargetFile.get('id'), parent_file_model_type: setTargetFile.fileType().typeName }); } else if (!fileType.topLevelType) { throw new NestedTypeError(upload, { fileType: fileType, fileTypes: this.fileTypes }); } this.entry.getFileCollection(fileType).add(file); var deferred = new $.Deferred(); if (setTargetFile) { deferred.resolve(); } else { this.deferreds.push(deferred); if (this.deferreds.length == 1) { this.trigger('new:batch'); } } return deferred.promise().then(function () { file.set('state', 'uploading'); return file; }, function () { file.destroy(); }); }, submit: function submit() { _$1(this.deferreds).invoke('resolve'); this.deferreds = []; }, abort: function abort() { _$1(this.deferreds).invoke('reject'); this.deferreds = []; } }); var orderedCollection = { initialize: function initialize() { if (this.autoConsolidatePositions !== false) { this.listenTo(this, 'remove', function () { this.consolidatePositions(); this.saveOrder(); }); } }, consolidatePositions: function consolidatePositions() { this.each(function (item, index) { item.set('position', index); }); }, saveOrder: function saveOrder() { var parentModel = this.parentModel; var collection = this; if (collection.isEmpty()) { return $.Deferred().resolve().promise(); } return Backbone.sync('update', parentModel, { url: collection.url() + '/order', attrs: { ids: collection.pluck('id') }, success: function success(response) { parentModel.trigger('sync', parentModel, response, {}); parentModel.trigger('sync:order', parentModel, response, {}); }, error: function error(jqXHR, textStatus, errorThrown) { editor$1.failures.add(new OrderingFailure(parentModel, collection)); } }); } }; var ChapterPagesCollection = SubsetCollection.extend({ mixins: [orderedCollection], constructor: function constructor(options) { var chapter = options.chapter; SubsetCollection.prototype.constructor.call(this, { parent: options.pages, parentModel: chapter, filter: function filter(item) { return !chapter.isNew() && item.get('chapter_id') === chapter.id; }, comparator: function comparator(item) { return item.get('position'); } }); this.each(function (page) { page.chapter = chapter; }); this.listenTo(this, 'add', function (model) { model.chapter = chapter; model.set('chapter_id', chapter.id); editor$1.trigger('add:page', model); }); this.listenTo(this, 'remove', function (model) { model.chapter = null; }); this.listenTo(chapter, 'destroy', function () { this.clear(); }); } }); var Chapter = Backbone.Model.extend({ modelName: 'chapter', paramRoot: 'chapter', i18nKey: 'pageflow/chapter', mixins: [failureTracking, delayedDestroying], initialize: function initialize(attributes, options) { this.pages = new ChapterPagesCollection({ pages: options.pages || state.pages, chapter: this }); this.listenTo(this, 'change:title', function () { this.save(); }); this.configuration = new ChapterConfiguration(this.get('configuration') || {}); this.listenTo(this.configuration, 'change', function () { this.save(); this.trigger('change:configuration', this); }); return attributes; }, urlRoot: function urlRoot() { return this.isNew() ? this.collection.url() : '/chapters'; }, storylinePosition: function storylinePosition() { return this.storyline && this.storyline.get('position') || -1; }, addPage: function addPage(attributes) { var page = this.buildPage(attributes); page.save(); return page; }, buildPage: function buildPage(attributes) { var defaults = { chapter_id: this.id, position: this.pages.length }; return this.pages.addAndReturnModel(_$1.extend(defaults, attributes)); }, toJSON: function toJSON() { return _$1.extend(_$1.clone(this.attributes), { configuration: this.configuration.toJSON() }); }, destroy: function destroy() { this.destroyWithDelay(); } }); var StorylineChaptersCollection = SubsetCollection.extend({ mixins: [orderedCollection], constructor: function constructor(options) { var storyline = options.storyline; SubsetCollection.prototype.constructor.call(this, { parent: options.chapters, parentModel: storyline, filter: function filter(item) { return !storyline.isNew() && item.get('storyline_id') === storyline.id; }, comparator: function comparator(item) { return item.get('position'); } }); this.each(function (chapter) { chapter.storyline = storyline; }); this.listenTo(this, 'add', function (model) { model.storyline = storyline; model.set('storyline_id', storyline.id); editor$1.trigger('add:chapter', model); }); this.listenTo(this, 'remove', function (model) { model.storyline = null; }); } }); var Storyline = Backbone.Model.extend({ modelName: 'storyline', paramRoot: 'storyline', i18nKey: 'pageflow/storyline', mixins: [failureTracking, delayedDestroying], initialize: function initialize(attributes, options) { this.chapters = new StorylineChaptersCollection({ chapters: options.chapters || state.chapters, storyline: this }); this.configuration = new StorylineConfiguration(this.get('configuration') || {}); this.listenTo(this.configuration, 'change', function () { if (!this.isNew()) { this.save(); } this.trigger('change:configuration', this); }); this.listenTo(this.configuration, 'change:main', function (model, value) { this.trigger('change:main', this, value); }); }, urlRoot: function urlRoot() { return this.isNew() ? this.collection.url() : '/storylines'; }, displayTitle: function displayTitle() { return _$1([this.title() || !this.isMain() && I18n$1.t('pageflow.storylines.untitled'), this.isMain() && I18n$1.t('pageflow.storylines.main')]).compact().join(' - '); }, title: function title() { return this.configuration.get('title'); }, isMain: function isMain() { return !!this.configuration.get('main'); }, lane: function lane() { return this.configuration.get('lane'); }, row: function row() { return this.configuration.get('row'); }, parentPagePermaId: function parentPagePermaId() { return this.configuration.get('parent_page_perma_id'); }, parentPage: function parentPage() { return state.pages.getByPermaId(this.parentPagePermaId()); }, transitiveChildPages: function transitiveChildPages() { return new StorylineTransitiveChildPages(this, state.storylines, state.pages); }, addChapter: function addChapter(attributes) { var chapter = this.buildChapter(attributes); chapter.save(); return chapter; }, buildChapter: function buildChapter(attributes) { var defaults = { storyline_id: this.id, title: '', position: this.chapters.length }; return this.chapters.addAndReturnModel(_$1.extend(defaults, attributes)); }, scaffoldChapter: function scaffoldChapter(options) { var scaffold = new ChapterScaffold(this, options); scaffold.create(); return scaffold; }, toJSON: function toJSON() { return { configuration: this.configuration.toJSON() }; }, destroy: function destroy() { this.destroyWithDelay(); } }); var PageLink = Backbone.Model.extend({ mixins: [transientReferences], i18nKey: 'pageflow/page_link', targetPage: function targetPage() { return state.pages.getByPermaId(this.get('target_page_id')); }, label: function label() { return this.get('label'); }, editPath: function editPath() { return '/page_links/' + this.id; }, getPageId: function getPageId() { return this.collection.page.id; }, toSerializedJSON: function toSerializedJSON() { return _$1.omit(this.attributes, 'highlighted', 'position'); }, highlight: function highlight() { this.set('highlighted', true); }, resetHighlight: function resetHighlight() { this.unset('highlighted'); }, remove: function remove() { this.collection.remove(this); } }); var PageLinkFileSelectionHandler = function PageLinkFileSelectionHandler(options) { var page = state.pages.getByPermaId(options.id.split(':')[0]); var pageLink = page.pageLinks().get(options.id); this.call = function (file) { pageLink.setReference(options.attributeName, file); }; this.getReferer = function () { return '/page_links/' + pageLink.id; }; }; editor$1.registerFileSelectionHandler('pageLink', PageLinkFileSelectionHandler); /** * Mixins for models with a nested configuration model. * * Triggers events on the parent model of the form * `change:configuration` and `change:configuration:`, when * the configuration changes. * * @param {Object} [options] * @param {Function} [options.configurationModel] - * Backbone model to use for nested configuration model. * @param {Boolean} [options.autoSave] - * Save model when configuration changes. * @param {Boolean|Array} [options.includeAttributesInJSON] - * Include all or specific attributes of the parent model in the * data returned by `toJSON` besides the `configuration` property. * @returns {Object} - Mixin to be included in model. * * @example * * import {configurationContainer} from 'pageflow/editor'; * * const Section = Backbone.Model.extend({ * mixins: [configurationContainer({autoSave: true})] * }); * * const section = new Section({configuration: {some: 'value'}}); * section.configuration.get('some') // => 'value'; */ function configurationContainer() { var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, configurationModel = _ref.configurationModel, autoSave = _ref.autoSave, includeAttributesInJSON = _ref.includeAttributesInJSON; configurationModel = configurationModel || Configuration.extend({ defaults: {} }); return { initialize: function initialize() { this.configuration = new configurationModel(this.get('configuration')); this.configuration.parent = this; this.listenTo(this.configuration, 'change', function () { if (!this.isNew() && (!this.isDestroying || !this.isDestroying()) && (!this.isDestroyed || !this.isDestroyed()) && autoSave) { this.save(); } this.trigger('change:configuration', this); _$1.chain(this.configuration.changed).keys().each(function (name) { this.trigger('change:configuration:' + name, this, this.configuration.get(name)); }, this); }); }, toJSON: function toJSON() { var attributes = {}; if (includeAttributesInJSON === true) { attributes = _$1.clone(this.attributes); } else if (includeAttributesInJSON) { attributes = _$1.pick(this.attributes, includeAttributesInJSON); } return _$1.extend(attributes, { configuration: this.configuration.toJSON() }); } }; } var persistedPromise = { persisted: function persisted() { var model = this; this._persistedDeferred = this._persistedDeferred || $.Deferred(function (deferred) { if (model.isNew()) { model.once('change:id', deferred.resolve); } else { deferred.resolve(); } }); return this._persistedDeferred.promise(); } }; Cocktail.mixin(Backbone.Model, persistedPromise); var filesCountWatcher = { watchFileCollection: function watchFileCollection(name, collection) { this.watchedFileCollectionNames = this.watchedFileCollectionNames || []; this.watchedFileCollectionNames.push(name); this.listenTo(collection, 'change:state', function (model) { this.updateFilesCounts(name, collection); }); this.listenTo(collection, 'add', function () { this.updateFilesCounts(name, collection); }); this.listenTo(collection, 'remove', function () { this.updateFilesCounts(name, collection); }); this.updateFilesCounts(name, collection); }, updateFilesCounts: function updateFilesCounts(name, collection) { this.updateFilesCount('uploading', name, collection, function (file) { return file.isUploading(); }); this.updateFilesCount('confirmable', name, collection, function (file) { return file.isConfirmable(); }); this.updateFilesCount('pending', name, collection, function (file) { return file.isPending(); }); }, updateFilesCount: function updateFilesCount(trait, name, collection, filter) { this.set(trait + '_' + name + '_count', collection.filter(filter).length); this.set(trait + '_files_count', _$1.reduce(this.watchedFileCollectionNames, function (sum, name) { return sum + this.get(trait + '_' + name + '_count'); }, 0, this)); } }; var fileWithType = {}; var polling = { togglePolling: function togglePolling(enabled) { if (enabled) { this.startPolling(); } else { this.stopPolling(); } }, startPolling: function startPolling() { if (!this.pollingInterval) { this.pollingInterval = setInterval(_$1.bind(function () { this.fetch(); }, this), 1000); } }, stopPolling: function stopPolling() { if (this.pollingInterval) { clearInterval(this.pollingInterval); this.pollingInterval = null; } } }; var Entry = Backbone.Model.extend({ paramRoot: 'entry', urlRoot: '/editor/entries', modelName: 'entry', i18nKey: 'pageflow/entry', collectionName: 'entries', mixins: [filesCountWatcher, polling, failureTracking], initialize: function initialize(attributes, options) { options = options || {}; this.metadata = new EntryMetadata(this.get('metadata') || {}); this.metadata.parent = this; this.themes = options.themes || state.themes; this.files = options.files || state.files; this.fileTypes = options.fileTypes || editor$1.fileTypes; this.storylines = options.storylines || state.storylines; this.storylines.parentModel = this; this.chapters = options.chapters || state.chapters; this.chapters.parentModel = this; this.pages = state.pages; this.widgets = options.widgets; this.imageFiles = state.imageFiles; this.videoFiles = state.videoFiles; this.audioFiles = state.audioFiles; this.fileTypes.each(function (fileType) { this.watchFileCollection(fileType.collectionName, this.getFileCollection(fileType)); }, this); this.listenTo(this.storylines, 'sort', function () { this.pages.sort(); }); this.listenTo(this.chapters, 'sort', function () { this.pages.sort(); }); this.listenTo(this.metadata, 'change', function () { this.trigger('change:metadata'); this.save(); }); this.listenTo(this.metadata, 'change:locale', function () { this.once('sync', function () { // No other way of updating page templates used in // EntryPreviewView at the moment. location.reload(); }); }); }, getTheme: function getTheme() { return this.themes.findByName(this.metadata.get('theme_name')); }, addStoryline: function addStoryline(attributes) { var storyline = this.buildStoryline(attributes); storyline.save(); return storyline; }, buildStoryline: function buildStoryline(attributes) { var defaults = { title: '' }; return this.storylines.addAndReturnModel(_$1.extend(defaults, attributes)); }, scaffoldStoryline: function scaffoldStoryline(options) { var scaffold = new StorylineScaffold(this, options); scaffold.create(); return scaffold; }, addChapterInNewStoryline: function addChapterInNewStoryline(options) { return this.scaffoldStoryline(_$1.extend({ depth: 'chapter' }, options)).chapter; }, addPageInNewStoryline: function addPageInNewStoryline(options) { return this.scaffoldStoryline(_$1.extend({ depth: 'page' }, options)).page; }, reuseFile: function reuseFile(otherEntry, file) { var entry = this; FileReuse.submit(otherEntry, file, { entry: entry, success: function success(model, response) { entry._setFiles(response, { merge: false, remove: false }); entry.trigger('use:files'); } }); }, getFileCollection: function getFileCollection(fileTypeOrFileTypeName) { return this.files[fileTypeOrFileTypeName.collectionName || fileTypeOrFileTypeName]; }, pollForPendingFiles: function pollForPendingFiles() { this.listenTo(this, 'change:pending_files_count', function (model, value) { this.togglePolling(value > 0); }); this.togglePolling(this.get('pending_files_count') > 0); }, parse: function parse(response, options) { if (response) { this.set(_$1.pick(response, 'published', 'published_until', 'password_protected')); this._setFiles(response, { add: false, remove: false, applyConfigurationUpdaters: true }); } return response; }, _setFiles: function _setFiles(response, options) { this.fileTypes.each(function (fileType) { var filesAttributes = response[fileType.collectionName]; // Temporary solution until rights attributes is moved to // configuration hash. If we are polling, prevent overwriting // the rights attribute. if (options.merge !== false) { filesAttributes = _$1.map(filesAttributes, function (fileAttributes) { return _$1.omit(fileAttributes, 'rights'); }); } this.getFileCollection(fileType).set(filesAttributes, _$1.extend({ fileType: fileType }, options)); delete response[fileType.collectionName]; }, this); }, toJSON: function toJSON() { var metadataJSON = this.metadata.toJSON(); var configJSON = this.metadata.configuration.toJSON(); metadataJSON.configuration = configJSON; return metadataJSON; } }); var AuthenticationProvider = BaseObject.extend({ authenticate: function authenticate(parent, provider) { this.authenticationPopup('/auth/' + provider, 800, 600); this.authParent = parent; }, authenticationPopup: function authenticationPopup(linkUrl, width, height) { var sep = linkUrl.indexOf('?') !== -1 ? '&' : '?', url = linkUrl + sep + 'popup=true', left = (screen.width - width) / 2 - 16, top = (screen.height - height) / 2 - 50, windowFeatures = 'menubar=no,toolbar=no,status=no,width=' + width + ',height=' + height + ',left=' + left + ',top=' + top; return window.open(url, 'authPopup', windowFeatures); }, authenticateCallback: function authenticateCallback() { this.authParent.authenticateCallback(); } }); var authenticationProvider = new AuthenticationProvider(); var FileImport = Backbone.Model.extend({ modelName: 'file_import', action: 'search', url: function url() { var slug = this.get('currentEntry').get('slug'); return '/editor/entries/' + slug + '/file_import/' + this.importer.key + '/' + this.action; }, initialize: function initialize(options) { this.importer = options.importer; this.set('selectedFiles', []); this.set('currentEntry', options.currentEntry); this.authenticationInterval = setInterval(this.authenticate.bind(this), 2000); }, authenticate: function authenticate() { if (!this.popUped) { if (this.importer.authenticationRequired) { authenticationProvider.authenticate(this, this.importer.authenticationProvider); this.popUped = true; } else { this.authenticateCallback(); } } }, authenticateCallback: function authenticateCallback() { clearInterval(this.authenticationInterval); this.set('isAuthenticated', true); this.importer.authenticationRequired = false; this.popUped = false; }, createFileImportDialogView: function createFileImportDialogView() { return this.importer.createFileImportDialogView(this); }, select: function select(options) { if (options instanceof Backbone.Model) { this.get('selectedFiles').push(options); this.trigger('change'); } }, unselect: function unselect(options) { var index = this.get('selectedFiles').indexOf(options); this.get('selectedFiles').splice(index, 1); this.trigger('change'); }, clearSelections: function clearSelections() { this.set('selectedFiles', []); }, search: function search(query) { this.action = 'search/?query=' + query; return this.fetchData(); }, fetchData: function fetchData(options) { return this.fetch(options).then(function (data) { if (data && data.data) { return data.data; } }); }, getFilesMetaData: function getFilesMetaData(options) { this.action = 'files_meta_data'; var selectedFiles = this.get('selectedFiles'); for (var i = 0; i < selectedFiles.length; i++) { selectedFiles[i] = selectedFiles[i].toJSON(); } return this.fetch({ data: { files: selectedFiles }, postData: true, type: 'POST' }).then(function (data) { if (data && data.data) { return data.data; } else { return undefined; } }); }, cancelImport: function cancelImport(collectionName) { var selections = state.files[collectionName].uploadable(); selections.each(function (selection) { selection.destroy(); }); selections.clear(); }, startImportJob: function startImportJob(collectionName) { this.action = 'start_import_job'; var selections = state.files[collectionName].uploadable(); var jsonSelections = selections.toJSON(); jsonSelections.forEach(function (selection, index) { selection['url'] = selections.at(index).get('source_url'); }); var currentEntry = this.get('currentEntry'); this.fetch({ data: { collection: collectionName, files: jsonSelections }, postData: true, type: 'POST' }).then(function (data) { if (data && data.data) { var files = data.data; if (files && files.length > 0) { files.forEach(function (file) { var localFile = selections.find(function (cFile) { return cFile.get('attachment_on_s3_file_name') == file.file_name && cFile.get('source_url') == file.source_url && cFile.get('state') == 'uploadable'; }); if (localFile) { state.files[collectionName].remove(localFile); var fileType = editor$1.fileTypes.findByUpload(file); var file = new fileType.model(file, { fileType: fileType }); currentEntry.getFileCollection(fileType).add(file); file.set('state', 'uploading'); } }); } } }); } }); var ChaptersCollection = Backbone.Collection.extend({ model: Chapter, url: '/chapters', comparator: function comparator(chapter) { return chapter.get('position'); } }); /** * A Backbone collection that is automatically updated to only * contain models with a foreign key matching the id of a parent * model. * * @param {Object} options * @param {Backbone.Model} options.parentModel - * Model whose id is compared to foreign keys. * @param {Backbone.Collection} options.parent - * Collection to filter items with matching foreign key from. * @param {String} options.foreignKeyAttribute - * Attribute to compare to id of parent model. * @param {String} options.parentReferenceAttribute - * Set reference to parent model on models in collection. * * @since 15.1 */ var ForeignKeySubsetCollection = SubsetCollection.extend({ mixins: [orderedCollection], constructor: function constructor(options) { var parent = options.parent; var parentModel = options.parentModel; SubsetCollection.prototype.constructor.call(this, { parent: parent, parentModel: parentModel, filter: function filter(item) { return !parentModel.isNew() && item.get(options.foreignKeyAttribute) === parentModel.id; }, comparator: function comparator(item) { return item.get('position'); } }); this.listenTo(this, 'add', function (model) { if (options.parentReferenceAttribute) { model[options.parentReferenceAttribute] = parentModel; } model.set(options.foreignKeyAttribute, parentModel.id); }); this.listenTo(parentModel, 'destroy', function () { this.clear(); }); if (options.parentReferenceAttribute) { this.each(function (model) { return model[options.parentReferenceAttribute] = parentModel; }); this.listenTo(this, 'remove', function (model) { model[options.parentReferenceAttribute] = null; }); } } }); var PageLinksCollection = Backbone.Collection.extend({ model: PageLink, initialize: function initialize(models, options) { this.configuration = options.configuration; this.page = options.configuration.page; this.load(); this.listenTo(this, 'add remove change', this.save); this.listenTo(this.configuration, 'change:page_links', this.load); }, addLink: function addLink(targetPageId) { this.addWithPosition(this.defaultPosition(), targetPageId); }, canAddLink: function canAddLink(targetPageId) { return true; }, updateLink: function updateLink(link, targetPageId) { link.set('target_page_id', targetPageId); }, removeLink: function removeLink(link) { this.remove(link); }, addWithPosition: function addWithPosition(position, targetPageId) { this.add(this.pageLinkAttributes(position, targetPageId)); }, removeByPosition: function removeByPosition(position) { this.remove(this.findByPosition(position)); }, findByPosition: function findByPosition(position) { return this.findWhere({ position: position }); }, load: function load() { this.set(this.pageLinksAttributes()); }, save: function save() { this.configuration.set('page_links', this.map(function (pageLink) { return pageLink.toSerializedJSON(); })); }, defaultPosition: function defaultPosition() { return Math.max(0, _$1.max(this.map(function (pageLink) { return pageLink.get('position'); }))) + 1; }, pageLinksAttributes: function pageLinksAttributes() { return this.configuration.get('page_links') || []; }, pageLinkAttributes: function pageLinkAttributes(position, targetPageId, id) { return { id: id || this.getUniqueId(), target_page_id: targetPageId, position: position }; }, /** @private */ getUniqueId: function getUniqueId() { var maxId = Math.max(0, _$1.max(this.map(function (pageLink) { return parseInt(pageLink.id.split(':').pop(), 10); }))); return this.configuration.page.get('perma_id') + ':' + (maxId + 1); } }); var OtherEntriesCollection = Backbone.Collection.extend({ model: OtherEntry, url: '/editor/entries', initialize: function initialize(models, options) { options = options || {}; this.excludeEntry = options.excludeEntry; }, // override parse method to exclude the entry being edited. This is the collection // of the "other" entries, after all. parse: function parse(response) { var excludeEntry = this.getExcludeEntry(), filteredResponse = _$1.filter(response, function (entry) { return entry.id != excludeEntry.id; }); return Backbone.Collection.prototype.parse.call(this, filteredResponse); }, getExcludeEntry: function getExcludeEntry() { return this.excludeEntry || state.entry; } }); var StorylinesCollection = Backbone.Collection.extend({ autoConsolidatePositions: false, mixins: [orderedCollection], model: Storyline, url: function url() { return '/entries/' + state.entry.get('id') + '/storylines'; }, initialize: function initialize() { this.listenTo(this, 'change:main', function (model, value) { if (value) { this.each(function (storyline) { if (storyline.isMain() && storyline !== model) { storyline.configuration.unset('main'); } }); } }); }, main: function main() { return this.find(function (storyline) { return storyline.configuration.get('main'); }) || this.first(); }, comparator: function comparator(chapter) { return chapter.get('position'); } }); var OrderedPageLinksCollection = PageLinksCollection.extend({ comparator: 'position', saveOrder: function saveOrder() { this.save(); } }); var PagesCollection = Backbone.Collection.extend({ model: Page, url: '/pages', comparator: function comparator(pageA, pageB) { if (pageA.storylinePosition() > pageB.storylinePosition()) { return 1; } else if (pageA.storylinePosition() < pageB.storylinePosition()) { return -1; } else if (pageA.chapterPosition() > pageB.chapterPosition()) { return 1; } else if (pageA.chapterPosition() < pageB.chapterPosition()) { return -1; } else if (pageA.get('position') > pageB.get('position')) { return 1; } else if (pageA.get('position') < pageB.get('position')) { return -1; } else { return 0; } }, getByPermaId: function getByPermaId(permaId) { return this.findWhere({ perma_id: parseInt(permaId, 10) }); }, persisted: function persisted() { if (!this._persisted) { this._persisted = new SubsetCollection({ parent: this, sortOnParentSort: true, filter: function filter(page) { return !page.isNew(); } }); this.listenTo(this, 'change:id', function (model) { setTimeout(_$1.bind(function () { this._persisted.add(model); }, this), 0); }); } return this._persisted; } }); var ThemesCollection = Backbone.Collection.extend({ model: Theme, findByName: function findByName(name) { var theme = this.findWhere({ name: name }); if (!theme) { throw new Error('Found no theme by name ' + name); } return theme; } }); var WidgetsCollection = Backbone.Collection.extend({ model: Widget, initialize: function initialize() { this.listenTo(this, 'change:type_name change:configuration', function () { this.batchSave(); }); }, url: function url() { return '/editor/subjects/entries/' + this.subject.id + '/widgets'; }, batchSave: function batchSave(options) { var subject = this.subject; return Backbone.sync('patch', subject, _$1.extend(options || {}, { url: this.url() + '/batch', attrs: { widgets: this.map(function (widget) { return widget.toJSON(); }) }, success: function success(response) { subject.trigger('sync:widgets', subject, response, {}); } })); } }); var addAndReturnModel = { // Backbone's add does not return the added model. push returns the // model but does not trigger sort. addAndReturnModel: function addAndReturnModel(model, options) { model = this._prepareModel(model, options); this.add(model, options); return model; } }; Cocktail.mixin(Backbone.Collection, addAndReturnModel); var SidebarRouter = Marionette.AppRouter.extend({ appRoutes: { 'page_links/:id': 'pageLink', 'pages/:id': 'page', 'pages/:id/:tab': 'page', 'chapters/:id': 'chapter', 'storylines/:id': 'storyline', 'widgets/:id': 'widget', 'files/:collectionName?handler=:handler&payload=:payload&filter=:filter': 'files', 'files/:collectionName?handler=:handler&payload=:payload': 'files', 'files/:collectionName': 'files', 'files': 'files', 'confirmable_files?type=:type&id=:id': 'confirmableFiles', 'confirmable_files': 'confirmableFiles', 'meta_data': 'metaData', 'meta_data/:tab': 'metaData', 'publish': 'publish', '?storyline=:id': 'index', '.*': 'index' } }); function template$h(data) { var __t, __p = ''; __p += '' + ((__t = ( I18n.t('pageflow.editor.templates.back_button_decorator.outline') )) == null ? '' : __t) + '\n
    \n'; return __p } var BackButtonDecoratorView = Marionette.Layout.extend({ template: template$h, className: 'back_button_decorator', events: { 'click a.back': 'goBack' }, regions: { outlet: '.outlet' }, onRender: function onRender() { this.outlet.show(this.options.view); }, goBack: function goBack() { editor$1.navigate('/', { trigger: true }); } }); function template$i(data) { var __t, __p = ''; __p += '\n\n\n\n
    \n \n
    \n'; return __p } var ConfirmableFileItemView = Marionette.ItemView.extend({ tagName: 'li', template: template$i, ui: { fileName: '.file_name', duration: '.duration', label: 'label', checkBox: 'input', removeButton: '.remove' }, events: { 'click .remove': 'destroy', 'change input': 'updateSelection' }, onRender: function onRender() { this.ui.label.attr('for', this.cid); this.ui.checkBox.attr('id', this.cid); this.ui.checkBox.prop('checked', this.options.selectedFiles.contains(this.model)); this.ui.fileName.text(this.model.get('file_name') || '(Unbekannt)'); this.ui.duration.text(this.model.get('duration') || '-'); }, destroy: function destroy() { if (confirm("Datei wirklich wirklich löschen?")) { this.model.destroy(); } }, updateSelection: function updateSelection() { if (this.ui.checkBox.is(':checked')) { this.options.selectedFiles.add(this.model); } else { this.options.selectedFiles.remove(this.model); } } }); function template$j(data) { var __t, __p = ''; __p += '
    \n

    \n ' + ((__t = ( I18n.t('pageflow.editor.templates.confirm_encoding.all_released') )) == null ? '' : __t) + '\n

    \n

    \n ' + ((__t = ( I18n.t('pageflow.editor.templates.confirm_encoding.link_to_progress', { link: ''+I18n.t('pageflow.editor.templates.confirm_encoding.manage_files')+''}) )) == null ? '' : __t) + '\n

    \n
    \n\n
    \n

    ' + ((__t = ( I18n.t('pageflow.editor.templates.confirm_encoding.videos_tab') )) == null ? '' : __t) + '

    \n
    \n\n
    \n

    ' + ((__t = ( I18n.t('pageflow.editor.templates.confirm_encoding.audios_tab') )) == null ? '' : __t) + '

    \n
    \n\n
    \n
    \n\n'; return __p } var ConfirmEncodingView = Marionette.ItemView.extend({ template: template$j, className: 'confirm_encoding', ui: { blankSlate: '.blank_slate', videoFilesPanel: '.video_files_panel', audioFilesPanel: '.audio_files_panel', summary: '.summary', confirmButton: 'button' }, events: { 'click button': function clickButton() { this.model.saveAndReset(); } }, initialize: function initialize() { this.confirmableVideoFiles = state.videoFiles.confirmable(); this.confirmableAudioFiles = state.audioFiles.confirmable(); }, onRender: function onRender() { this.listenTo(this.model, 'change', this.updateSummary); this.listenTo(this.confirmableAudioFiles, 'add remove', this.updateBlankSlate); this.listenTo(this.confirmableVideoFiles, 'add remove', this.updateBlankSlate); this.ui.videoFilesPanel.append(this.subview(new CollectionView({ tagName: 'ul', className: 'confirmable_files', collection: this.confirmableVideoFiles, itemViewConstructor: ConfirmableFileItemView, itemViewOptions: { selectedFiles: this.model.videoFiles } })).el); this.ui.audioFilesPanel.append(this.subview(new CollectionView({ tagName: 'ul', className: 'confirmable_files', collection: this.confirmableAudioFiles, itemViewConstructor: ConfirmableFileItemView, itemViewOptions: { selectedFiles: this.model.audioFiles } })).el); this.update(); }, update: function update() { this.updateBlankSlate(); this.updateSummary(); }, updateBlankSlate: function updateBlankSlate() { this.ui.blankSlate.toggle(!this.confirmableVideoFiles.length && !this.confirmableAudioFiles.length); this.ui.videoFilesPanel.toggle(!!this.confirmableVideoFiles.length); this.ui.audioFilesPanel.toggle(!!this.confirmableAudioFiles.length); }, updateSummary: function updateSummary(enabled) { this.ui.summary.html(this.model.get('summary_html')); this.ui.confirmButton.toggleClass('checking', !!this.model.get('checking')); if (this.model.get('empty') || this.model.get('exceeding') || this.model.get('checking')) { this.ui.confirmButton.attr('disabled', true); } else { this.ui.confirmButton.removeAttr('disabled'); } } }); ConfirmEncodingView.create = function (options) { return new BackButtonDecoratorView({ view: new ConfirmEncodingView(options) }); }; /** * Mixin for Marionette Views that sets css class names according to * life cycle events of its model. * * @param {Object} options * @param {Object} options.classNames * @param {String} options.classNames.creating - * Class name to add to root element while model is still being created. * @param {String} options.classNames.destroying - * Class name to add to root element while model is being destroyed. * @param {String} options.classNames.failed - * Class name to add to root element while model is in failed state. * Model needs to include {@link failureTracking} mixin. * @param {String} options.classNames.failureMessage - * Class name of the element that shall be updated with the failure * message. Model needs to include {@link failureTracking} mixin. * @param {String} options.classNames.retryButton - * Class name of the element that shall act as a retry button. */ function modelLifecycleTrackingView(_ref) { var classNames = _ref.classNames; return { events: _defineProperty({}, "click .".concat(classNames.retryButton), function click() { editor$1.failures.retry(); return false; }), initialize: function initialize() { var _this = this; if (classNames.creating) { this.listenTo(this.model, 'change:id', function () { this.$el.removeClass(classNames.creating); }); } if (classNames.destroying) { this.listenTo(this.model, 'destroying', function () { this.$el.addClass(classNames.destroying); }); this.listenTo(this.model, 'error', function () { this.$el.removeClass(classNames.destroying); }); } if (classNames.failed || classNames.failureMessage) { this.listenTo(this.model, 'change:failed', function () { return _this.updateFailIndicator(); }); } }, render: function render() { if (this.model.isNew()) { this.$el.addClass(classNames.creating); } if (this.model.isDestroying && this.model.isDestroying()) { this.$el.addClass(classNames.destroying); } this.updateFailIndicator(); }, updateFailIndicator: function updateFailIndicator() { if (classNames.failed) { this.$el.toggleClass(classNames.failed, this.model.isFailed()); } if (classNames.failureMessage) { this.$el.find(".".concat(classNames.failureMessage)).text(this.model.getFailureMessage()); } } }; } var failureIndicatingView = modelLifecycleTrackingView({ classNames: { failed: 'failed', failureMessage: 'failure .message', retryButton: 'retry' } }); function template$k(data) { var __t, __p = ''; __p += '' + ((__t = ( I18n.t('pageflow.editor.templates.edit_chapter.outline') )) == null ? '' : __t) + '\n' + ((__t = ( I18n.t('pageflow.editor.templates.edit_chapter.destroy') )) == null ? '' : __t) + '\n\n
    \n

    ' + ((__t = ( I18n.t('pageflow.editor.templates.edit_chapter.save_error') )) == null ? '' : __t) + '

    \n

    \n ' + ((__t = ( I18n.t('pageflow.editor.templates.edit_chapter.retry') )) == null ? '' : __t) + '\n
    \n\n
    '; return __p } var EditChapterView = Marionette.Layout.extend({ template: template$k, className: 'edit_chapter', mixins: [failureIndicatingView], regions: { formContainer: '.form_container' }, events: { 'click a.back': 'goBack', 'click a.destroy': 'destroy' }, onRender: function onRender() { var configurationEditor = new ConfigurationEditorView({ model: this.model.configuration }); this.configure(configurationEditor); this.formContainer.show(configurationEditor); }, configure: function configure(configurationEditor) { var view = this; configurationEditor.tab('general', function () { this.input('title', TextInputView, { model: view.model }); if (pageflow.features.isEnabled('chapter_hierachy')) { this.input('display_parent_page_button', CheckBoxInputView); } }); }, destroy: function destroy() { if (confirm(I18n$1.t('pageflow.editor.views.edit_chapter_view.confirm_destroy'))) { this.model.destroy(); this.goBack(); } }, goBack: function goBack() { editor$1.navigate('/', { trigger: true }); } }); function template$l(data) { var __t, __p = ''; __p += '' + ((__t = ( I18n.t('pageflow.editor.templates.edit_entry.close') )) == null ? '' : __t) + '\n\n ' + ((__t = ( I18n.t('pageflow.editor.templates.edit_entry.publish') )) == null ? '' : __t) + '\n\n\n\n\n
    \n'; return __p } var EditEntryView = Marionette.Layout.extend({ template: template$l, mixins: [failureIndicatingView, tooltipContainer], ui: { publishButton: 'a.publish', publicationStateButton: 'a.publication_state', menu: '.menu' }, regions: { outlineRegion: '.edit_entry_outline_region' }, events: { 'click a.close': function clickAClose() { $.when(state.editLock.release()).then(function () { window.location = '/admin/entries/' + state.entry.id; }); }, 'click a.publish': function clickAPublish() { if (!this.ui.publishButton.hasClass('disabled')) { editor$1.navigate('/publish', { trigger: true }); } return false; }, 'click .menu a': function clickMenuA(event) { editor$1.navigate($(event.target).data('path'), { trigger: true }); return false; } }, onRender: function onRender() { this._addMenuItems(); this._updatePublishButton(); this.outlineRegion.show(new editor$1.entryType.outlineView({ entry: state.entry, navigatable: true, editable: true, displayInNavigationHint: true, rememberLastSelection: true, storylineId: this.options.storylineId })); }, _updatePublishButton: function _updatePublishButton() { var disabled = !this.model.get('publishable'); this.ui.publishButton.toggleClass('disabled', disabled); if (disabled) { this.ui.publishButton.attr('data-tooltip', 'pageflow.editor.views.edit_entry_view.cannot_publish'); } else { this.ui.publishButton.removeAttr('data-tooltip'); } }, _addMenuItems: function _addMenuItems() { var view = this; _$1.each(editor$1.mainMenuItems, function (options) { var item = $('
  • '); var link = item.find('a'); if (options.path) { link.data('path', options.path); } link.text(I18n$1.t(options.translationKey)); if (options.click) { $(link).click(options.click); } view.ui.menu.append(item); }); } }); function template$m(data) { var __t, __p = ''; __p += '
    \n
    \n\n'; return __p } var WidgetItemView = Marionette.Layout.extend({ template: template$m, tagName: 'li', className: 'widget_item', regions: { widgetTypeContainer: '.widget_type' }, ui: { role: 'h2' }, modelEvents: { 'change:type_name': 'update' }, events: { 'click .settings': function clickSettings() { editor$1.navigate('/widgets/' + this.model.role(), { trigger: true }); return false; } }, onRender: function onRender() { var widgetTypes = this.options.widgetTypes.findAllByRole(this.model.role()) || []; var isOptional = this.options.widgetTypes.isOptional(this.model.role()); this.widgetTypeContainer.show(new SelectInputView({ model: this.model, propertyName: 'type_name', label: I18n$1.t('pageflow.widgets.roles.' + this.model.role()), collection: widgetTypes, valueProperty: 'name', translationKeyProperty: 'translationKey', includeBlank: isOptional || !this.model.get('type_name') })); this.$el.toggleClass('is_hidden', widgetTypes.length <= 1 && !this.model.hasConfiguration() && !isOptional); this.update(); }, update: function update() { this.$el.toggleClass('has_settings', this.model.hasConfiguration()); } }); function template$n(data) { var __p = ''; __p += '
      \n
    \n'; return __p } var EditWidgetsView = Marionette.Layout.extend({ template: template$n, ui: { widgets: '.widgets' }, onRender: function onRender() { this.subview(new CollectionView({ el: this.ui.widgets, collection: this.model.widgets, itemViewConstructor: WidgetItemView, itemViewOptions: { widgetTypes: this.options.widgetTypes } }).render()); } }); function template$o(data) { var __p = ''; __p += '
    \n
    \n'; return __p } var BackgroundPositioningPreviewView = Marionette.ItemView.extend({ template: template$o, className: 'preview', modelEvents: { change: 'update' }, ui: { image: '.image', label: '.label' }, onRender: function onRender() { this.update(); }, update: function update() { var ratio = this.options.ratio; var max = this.options.maxSize; var width = ratio > 1 ? max : max * ratio; var height = ratio > 1 ? max / ratio : max; this.ui.image.css({ width: width + 'px', height: height + 'px', backgroundImage: this.imageValue(), backgroundPosition: this.model.getFilePosition(this.options.propertyName, 'x') + '% ' + this.model.getFilePosition(this.options.propertyName, 'y') + '%' }); this.ui.label.text(this.options.label); }, imageValue: function imageValue() { var file = this.model.getReference(this.options.propertyName, this.options.filesCollection); return file ? 'url("' + file.getBackgroundPositioningImageUrl() + '")' : 'none'; } }); function template$p(data) { var __p = ''; __p += '
    \n
    \n
    \n
    \n
    \n
    \n \n %\n
    \n
    \n \n %\n
    \n
    \n'; return __p } var BackgroundPositioningSlidersView = Marionette.ItemView.extend({ template: template$p, className: '', ui: { container: '.container', sliderHorizontal: '.horizontal.slider', sliderVertical: '.vertical.slider', inputHorizontal: '.percent.horizontal input', inputVertical: '.percent.vertical input' }, events: { 'mousedown img': function mousedownImg(event) { var view = this; view.saveFromEvent(event); function onMove(event) { view.saveFromEvent(event); } function onUp() { $('.background_positioning.dialog').off('mousemove', onMove).off('mouseup', onUp); } $('.background_positioning.dialog').on('mousemove', onMove).on('mouseup', onUp); }, 'dragstart img': function dragstartImg(event) { event.preventDefault(); } }, modelEvents: { change: 'update' }, onRender: function onRender() { var view = this; var file = this.model.getReference(this.options.propertyName, this.options.filesCollection), image = $('').attr('src', file.getBackgroundPositioningImageUrl()); this.ui.container.append(image); this.ui.sliderVertical.slider({ orientation: 'vertical', change: function change(event, ui) { view.save('y', 100 - ui.value); }, slide: function slide(event, ui) { view.save('y', 100 - ui.value); } }); this.ui.sliderHorizontal.slider({ orientation: 'horizontal', change: function change(event, ui) { view.save('x', ui.value); }, slide: function slide(event, ui) { view.save('x', ui.value); } }); this.ui.inputVertical.on('change', function () { view.save('y', $(this).val()); }); this.ui.inputHorizontal.on('change', function () { view.save('x', $(this).val()); }); this.update(); }, update: function update() { var x = this.model.getFilePosition(this.options.propertyName, 'x'); var y = this.model.getFilePosition(this.options.propertyName, 'y'); this.ui.sliderVertical.slider('value', 100 - y); this.ui.sliderHorizontal.slider('value', x); this.ui.inputVertical.val(y); this.ui.inputHorizontal.val(x); }, saveFromEvent: function saveFromEvent(event) { var x = event.pageX - this.ui.container.offset().left; var y = event.pageY - this.ui.container.offset().top; this.save('x', Math.round(x / this.ui.container.width() * 100)); this.save('y', Math.round(y / this.ui.container.width() * 100)); }, save: function save(coord, value) { this.model.setFilePosition(this.options.propertyName, coord, Math.min(100, Math.max(0, value))); } }); function template$q(data) { var __t, __p = ''; __p += '
    \n

    ' + ((__t = ( I18n.t('pageflow.editor.templates.background_positioning.title') )) == null ? '' : __t) + '

    \n

    ' + ((__t = ( I18n.t('pageflow.editor.templates.background_positioning.help') )) == null ? '' : __t) + '

    \n\n
    \n
    \n\n

    ' + ((__t = ( I18n.t('pageflow.editor.templates.background_positioning.preview_title') )) == null ? '' : __t) + '

    \n
    \n
    \n
    \n
    \n\n \n
    \n'; return __p } var BackgroundPositioningView = Marionette.ItemView.extend({ template: template$q, className: 'background_positioning dialog', mixins: [dialogView], ui: { previews: '.previews > div', wrapper: '.wrapper' }, previews: { ratio16to9: 16 / 9, ratio16to9Portrait: 9 / 16, ratio4to3: 4 / 3, ratio4to3Portrait: 3 / 4, banner: 5 / 1 }, events: { 'click .save': function clickSave() { this.save(); this.close(); } }, initialize: function initialize() { this.transientModel = this.model.clone(); }, onRender: function onRender() { this.ui.wrapper.append(this.subview(new BackgroundPositioningSlidersView({ model: this.transientModel, propertyName: this.options.propertyName, filesCollection: this.options.filesCollection })).el); this.createPreviews(); }, save: function save() { this.model.setFilePositions(this.options.propertyName, this.transientModel.getFilePosition(this.options.propertyName, 'x'), this.transientModel.getFilePosition(this.options.propertyName, 'y')); }, createPreviews: function createPreviews() { var view = this; _$1.each(view.previews, function (ratio, name) { view.ui.previews.append(view.subview(new BackgroundPositioningPreviewView({ model: view.transientModel, propertyName: view.options.propertyName, filesCollection: view.options.filesCollection, ratio: ratio, maxSize: 200, label: I18n$1.t('pageflow.editor.templates.background_positioning.previews.' + name) })).el); }); } }); BackgroundPositioningView.open = function (options) { app.dialogRegion.show(new BackgroundPositioningView(options)); }; function template$r(data) { var __p = ''; __p += '
    \n\n'; return __p } var DropDownButtonItemView = Marionette.ItemView.extend({ template: template$r, tagName: 'li', className: 'drop_down_button_item', ui: { link: '> a', label: '> .label' }, events: { 'click > a': function clickA() { if (!this.model.get('disabled')) { this.model.selected(); } return false; } }, modelEvents: { change: 'update' }, onRender: function onRender() { this.update(); if (this.model.get('items')) { this.appendSubview(new this.options.listView({ items: this.model.get('items') })); } }, update: function update() { this.ui.link.text(this.model.get('label')); this.ui.label.text(this.model.get('label')); this.$el.toggleClass('is_selectable', !!this.model.selected); this.$el.toggleClass('is_disabled', !!this.model.get('disabled')); this.$el.toggleClass('is_checked', !!this.model.get('checked')); this.$el.data('name', this.model.get('name')); } }); var DropDownButtonItemListView = function DropDownButtonItemListView(options) { return new CollectionView({ tagName: 'ul', className: 'drop_down_button_items', collection: options.items, itemViewConstructor: DropDownButtonItemView, itemViewOptions: { listView: DropDownButtonItemListView } }); }; function template$s(data) { var __p = ''; __p += '\n\n\n'; return __p } /** * A button that displays a drop down menu on hover. * * @param {Object} options * * @param {String} options.label * Button text. * * @param {Backbone.Collection} options.items * Collection of menu items. See below for supported attributes. * * ## Item Models * * The following model attributes can be used to control the * appearance of a menu item: * * - `name` - A name for the menu item which is not displayed. * - `label` - Used as menu item label. * - `disabled` - Make the menu item inactive. * - `checked` - Display a check mark in front of the item * - `items` - A Backbone collection of nested menu items. * * If the menu item model provdised a `selected` method, it is called * when the menu item is clicked. * * @class */ var DropDownButtonView = Marionette.ItemView.extend({ template: template$s, className: 'drop_down_button', ui: { button: '> button', menu: '.drop_down_button_menu' }, events: { 'mouseenter': function mouseenter() { this.positionMenu(); this.showMenu(); }, 'mouseleave': function mouseleave() { this.scheduleHideMenu(); } }, onRender: function onRender() { var view = this; this.ui.button.toggleClass('has_icon_and_text', !!this.options.label); this.ui.button.toggleClass('has_icon_only', !this.options.label); this.ui.button.text(this.options.label); this.ui.menu.append(this.subview(new DropDownButtonItemListView({ items: this.options.items })).el); this.ui.menu.on({ 'mouseenter': function mouseenter() { view.showMenu(); }, 'mouseleave': function mouseleave() { view.scheduleHideMenu(); } }); this.ui.menu.appendTo('#editor_menu_container'); }, onClose: function onClose() { this.ui.menu.remove(); }, positionMenu: function positionMenu() { var offset = this.$el.offset(); this.ui.menu.css({ top: offset.top + this.$el.height(), left: offset.left }); }, showMenu: function showMenu() { this.ensureOnlyOneDropDownButtonShowsMenu(); clearTimeout(this.hideMenuTimeout); this.ui.menu.addClass('is_visible'); }, ensureOnlyOneDropDownButtonShowsMenu: function ensureOnlyOneDropDownButtonShowsMenu() { if (DropDownButtonView.currentlyShowingMenu) { DropDownButtonView.currentlyShowingMenu.hideMenu(); } DropDownButtonView.currentlyShowingMenu = this; }, hideMenu: function hideMenu() { clearTimeout(this.hideMenuTimeout); if (!this.isClosed) { this.ui.menu.removeClass('is_visible'); } }, scheduleHideMenu: function scheduleHideMenu() { this.hideMenuTimeout = setTimeout(_$1.bind(this.hideMenu, this), 300); } }); function template$t(data) { var __p = ''; __p += '
    \n'; return __p } var FileThumbnailView = Marionette.ItemView.extend({ className: 'file_thumbnail', template: template$t, modelEvents: { 'change:state': 'update' }, ui: { pictogram: '.pictogram' }, onRender: function onRender() { this.update(); }, update: function update() { if (this.model) { var stage = this.model.currentStage(); if (stage) { this.setStageClassName(stage.get('name')); this.ui.pictogram.toggleClass('action_required', stage.get('action_required')); this.ui.pictogram.toggleClass('failed', stage.get('failed')); } else { this.ui.pictogram.removeClass(this.model.stages.pluck('name').join(' ')); } this.ui.pictogram.addClass(this.model.thumbnailPictogram); this.$el.css('background-image', this._imageUrl() ? 'url(' + this._imageUrl() + ')' : ''); this.$el.removeClass('empty').toggleClass('always_picogram', !!this.model.thumbnailPictogram).toggleClass('ready', this.model.isReady()); } else { this.$el.css('background-image', ''); this.$el.removeClass('ready'); this.ui.pictogram.addClass('empty'); } }, setStageClassName: function setStageClassName(name) { if (!this.$el.hasClass(name)) { this.ui.pictogram.removeClass('empty'); this.ui.pictogram.removeClass(this.model.stages.pluck('name').join(' ')); this.ui.pictogram.addClass(name); } }, _imageUrl: function _imageUrl() { return this.model.get(this.options.imageUrlPropertyName || 'thumbnail_url'); } }); function template$u(data) { var __t, __p = ''; __p += '\n
    \n
    \n\n\n\n'; return __p } /** * Input view to reference a file. * * @class */ var FileInputView = Marionette.ItemView.extend({ mixins: [inputView], template: template$u, className: 'file_input', ui: { fileName: '.file_name', thumbnail: '.file_thumbnail' }, events: { 'click .choose': function clickChoose() { editor$1.selectFile({ name: this.options.collection.name, filter: this.options.filter }, this.options.fileSelectionHandler || 'pageConfiguration', _$1.extend({ id: this.model.getRoutableId ? this.model.getRoutableId() : this.model.id, attributeName: this.options.propertyName, returnToTab: this.options.parentTab }, this.options.fileSelectionHandlerOptions || {})); return false; }, 'click .unset': function clickUnset() { this.model.unsetReference(this.options.propertyName); return false; } }, initialize: function initialize() { this.options = _$1.extend({ positioning: true, textTrackFiles: state.textTrackFiles }, this.options); if (typeof this.options.collection === 'string') { this.options.collection = state.entry.getFileCollection(editor$1.fileTypes.findByCollectionName(this.options.collection)); } this.textTrackMenuItems = new Backbone.Collection(); }, onRender: function onRender() { this.update(); this.listenTo(this.model, 'change:' + this.options.propertyName, this.update); var dropDownMenuItems = this._dropDownMenuItems(); if (dropDownMenuItems.length) { this.appendSubview(new DropDownButtonView({ items: dropDownMenuItems })); } }, update: function update() { var file = this._getFile(); this._listenToNestedTextTrackFiles(file); this.$el.toggleClass('is_unset', !file); this.ui.fileName.text(file ? file.get('file_name') : I18n$1.t('pageflow.ui.views.inputs.file_input_view.none')); this.subview(new FileThumbnailView({ el: this.ui.thumbnail, model: file })); }, _dropDownMenuItems: function _dropDownMenuItems() { var file = this._getFile(file); var items = new Backbone.Collection(); if (this.options.defaultTextTrackFilePropertyName && file) { items.add({ name: 'default_text_track', label: I18n$1.t('pageflow.editor.views.inputs.file_input.default_text_track'), items: this.textTrackMenuItems }); } if (this.options.positioning && file && file.isPositionable()) { items.add(new FileInputView.EditBackgroundPositioningMenuItem({ name: 'edit_background_positioning', label: I18n$1.t('pageflow.editor.views.inputs.file_input.edit_background_positioning') }, { inputModel: this.model, propertyName: this.options.propertyName, filesCollection: this.options.collection })); } if (file) { items.add(new FileInputView.EditFileSettingsMenuItem({ name: 'edit_file_settings', label: I18n$1.t('pageflow.editor.views.inputs.file_input.edit_file_settings') }, { file: file })); } return items; }, _listenToNestedTextTrackFiles: function _listenToNestedTextTrackFiles(file) { if (this.textTrackFiles) { this.stopListening(this.textTrackFiles); this.textTrackFiles = null; } if (file && this.options.defaultTextTrackFilePropertyName) { this.textTrackFiles = file.nestedFiles(this.options.textTrackFiles); this.listenTo(this.textTrackFiles, 'add remove', this._updateTextTrackMenuItems); this._updateTextTrackMenuItems(); } }, _updateTextTrackMenuItems: function update() { var models = [null].concat(this.textTrackFiles.toArray()); this.textTrackMenuItems.set(models.map(function (textTrackFile) { return new FileInputView.DefaultTextTrackFileMenuItem({}, { textTrackFiles: this.textTrackFiles, textTrackFile: textTrackFile, inputModel: this.model, propertyName: this.options.defaultTextTrackFilePropertyName }); }, this)); }, _getFile: function _getFile() { return this.model.getReference(this.options.propertyName, this.options.collection); } }); FileInputView.EditBackgroundPositioningMenuItem = Backbone.Model.extend({ initialize: function initialize(attributes, options) { this.options = options; }, selected: function selected() { BackgroundPositioningView.open({ model: this.options.inputModel, propertyName: this.options.propertyName, filesCollection: this.options.filesCollection }); } }); FileInputView.EditFileSettingsMenuItem = Backbone.Model.extend({ initialize: function initialize(attributes, options) { this.options = options; }, selected: function selected() { FileSettingsDialogView.open({ model: this.options.file }); } }); FileInputView.DefaultTextTrackFileMenuItem = Backbone.Model.extend({ initialize: function initialize(attributes, options) { this.options = options; this.listenTo(this.options.inputModel, 'change:' + this.options.propertyName, this.update); if (this.options.textTrackFile) { this.listenTo(this.options.textTrackFile, 'change:configuration', this.update); } this.update(); }, update: function update() { this.set('checked', this.options.textTrackFile == this.getDefaultTextTrackFile()); this.set('name', this.options.textTrackFile ? null : 'no_default_text_track'); this.set('label', this.options.textTrackFile ? this.options.textTrackFile.displayLabel() : this.options.textTrackFiles.length ? I18n$1.t('pageflow.editor.views.inputs.file_input.auto_default_text_track') : I18n$1.t('pageflow.editor.views.inputs.file_input.no_default_text_track')); }, selected: function selected() { if (this.options.textTrackFile) { this.options.inputModel.setReference(this.options.propertyName, this.options.textTrackFile); } else { this.options.inputModel.unsetReference(this.options.propertyName); } }, getDefaultTextTrackFile: function getDefaultTextTrackFile() { return this.options.inputModel.getReference(this.options.propertyName, this.options.textTrackFiles); } }); function template$v(data) { var __p = ''; __p += '
    \n
    \n
    \n
    \n
    \n
    \n
    \n'; return __p } var LoadingView = Marionette.ItemView.extend({ template: template$v, className: 'loading', tagName: 'li' }); var selectableView = { initialize: function initialize() { this.selectionAttribute = this.selectionAttribute || this.model.modelName; this.listenTo(this.options.selection, 'change:' + this.selectionAttribute, function (selection, selectedModel) { this.$el.toggleClass('active', selectedModel === this.model); }); this.$el.toggleClass('active', this.options.selection.get(this.selectionAttribute) === this.model); }, select: function select() { this.options.selection.set(this.selectionAttribute, this.model); }, onClose: function onClose() { if (this.options.selection.get(this.selectionAttribute) === this.model) { this.options.selection.set(this.selectionAttribute, null); } } }; function template$w(data) { var __t, __p = ''; __p += '\n\n

    \n ' + ((__t = ( I18n.t('pageflow.editor.templates.theme.use') )) == null ? '' : __t) + '\n
    \n'; return __p } var ThemeItemView = Marionette.ItemView.extend({ tagName: 'li', template: template$w, className: 'theme_item', mixins: [selectableView], selectionAttribute: 'theme', ui: { themeName: '.theme_name', useButton: '.use_theme', inUseRegion: '.theme_in_use' }, events: { 'click .use_theme': function clickUse_theme() { this.options.onUse(this.model); }, 'mouseenter': 'select', 'click': 'select' }, onRender: function onRender() { this.$el.data('themeName', this.model.get('name')); this.ui.themeName.text(this.model.title()); if (this.inUse()) { this.ui.inUseRegion.text('✓'); } this.ui.useButton.toggle(!this.inUse()); }, inUse: function inUse() { return this.model.get('name') === this.options.themeInUse; } }); function template$x(data) { var __t, __p = ''; __p += '
    \n
    \n
    \n

    ' + ((__t = ( I18n.t('pageflow.editor.templates.change_theme_dialog.header') )) == null ? '' : __t) + '

    \n
    \n
    \n
    \n

    ' + ((__t = ( I18n.t('pageflow.editor.templates.change_theme_dialog.preview_header_prefix') )) == null ? '' : __t) + '\n ' + ((__t = ( I18n.t('pageflow.editor.templates.change_theme_dialog.preview_header_suffix') )) == null ? '' : __t) + '\n

    \n
    \n \n
    \n
    \n
    \n
    \n\n \n
    \n'; return __p } var ChangeThemeDialogView = Marionette.ItemView.extend({ template: template$x, className: 'change_theme dialog editor', mixins: [dialogView], ui: { content: '.content', themesPanel: '.themes_panel', previewPanel: '.preview_panel', previewImageRegion: '.preview_image_region', previewImage: '.preview_image', previewHeaderThemeName: '.preview_header_theme_name' }, initialize: function initialize(options) { this.selection = new Backbone.Model(); var themeInUse = this.options.themes.findByName(this.options.themeInUse); this.selection.set('theme', themeInUse); this.listenTo(this.selection, 'change:theme', function () { if (!this.selection.get('theme')) { this.selection.set('theme', themeInUse); } this.update(); }); }, onRender: function onRender() { var themes = this.options.themes; this.themesView = new CollectionView({ collection: themes, tagName: 'ul', itemViewConstructor: ThemeItemView, itemViewOptions: { selection: this.selection, onUse: this.options.onUse, themes: themes, themeInUse: this.options.themeInUse } }); this.ui.themesPanel.append(this.subview(this.themesView).el); this.ui.previewPanel.append(this.subview(new LoadingView({ tagName: 'div' })).el); this.update(); }, update: function update() { var that = this; var selectedTheme = this.options.themes.findByName(that.selection.get('theme').get('name')); this.ui.previewImage.hide(); this.ui.previewImage.one('load', function () { $(this).show(); }); this.ui.previewImage.attr('src', selectedTheme.get('preview_image_url')); this.ui.previewHeaderThemeName.text(selectedTheme.title()); } }); ChangeThemeDialogView.changeTheme = function (options) { return $.Deferred(function (deferred) { options.onUse = function (theme) { deferred.resolve(theme); view.close(); }; var view = new ChangeThemeDialogView(options); view.on('close', function () { deferred.reject(); }); app.dialogRegion.show(view.render()); }).promise(); }; function template$y(data) { var __p = ''; __p += '\n'; return __p } var StaticThumbnailView = Marionette.ItemView.extend({ template: template$y, className: 'static_thumbnail', modelEvents: { 'change:configuration': 'update' }, onRender: function onRender() { this.update(); }, update: function update() { this.$el.css('background-image', 'url(' + this._imageUrl() + ')'); }, _imageUrl: function _imageUrl() { return this.model.thumbnailUrl(); } }); /** * Base thumbnail view for models supporting a `thumbnailFile` method. * * @class */ var ModelThumbnailView = Marionette.View.extend({ className: 'model_thumbnail', modelEvents: { 'change:configuration': 'update' }, render: function render() { this.update(); return this; }, update: function update() { if (this.model) { if (_$1.isFunction(this.model.thumbnailFile)) { var file = this.model && this.model.thumbnailFile(); if (this.thumbnailView && this.currentFileThumbnail == file) { return; } this.currentFileThumbnail = file; this.newThumbnailView = new FileThumbnailView({ model: file, className: 'thumbnail file_thumbnail', imageUrlPropertyName: this.options.imageUrlPropertyName }); } else { this.newThumbnailView = this.newThumbnailView || new StaticThumbnailView({ model: this.model }); } } if (this.thumbnailView) { this.thumbnailView.close(); } if (this.model) { this.thumbnailView = this.subview(this.newThumbnailView); this.$el.append(this.thumbnailView.el); } } }); function template$z(data) { var __p = ''; __p += '\n
    \n\n\n'; return __p } /** * Base class for input views that reference models. * * @class */ var ReferenceInputView = Marionette.ItemView.extend( /** @lends ReferenceInputView.prototype */ { mixins: [inputView], template: template$z, className: 'reference_input', ui: { title: '.title', chooseButton: '.choose', unsetButton: '.unset', buttons: 'button' }, events: { 'click .choose': function clickChoose() { var view = this; this.chooseValue().then(function (id) { view.model.set(view.options.propertyName, id); }); return false; }, 'click .unset': function clickUnset() { this.model.unset(this.options.propertyName); return false; } }, initialize: function initialize() { this.listenTo(this.model, 'change:' + this.options.propertyName, this.update); }, onRender: function onRender() { this.update(); this.listenTo(this.model, 'change:' + this.options.propertyName, this.update); }, /** * Returns a promise for some identifying attribute. * * Default attribute name is perma_id. If the attribute is named * differently, you can have your specific ReferenceInputView * implement `chooseValue()` accordingly. * * Will be used to set the chosen Model for this View. */ chooseValue: function chooseValue() { return this.choose().then(function (model) { return model.get('perma_id'); }); }, choose: function choose() { throw 'Not implemented: Override ReferenceInputView#choose to return a promise'; }, getTarget: function getTarget(targetId) { throw 'Not implemented: Override ReferenceInputView#getTarget'; }, createThumbnailView: function createThumbnailView(target) { return new ModelThumbnailView({ model: target }); }, update: function update() { if (this.isClosed) { return; } var target = this.getTarget(this.model.get(this.options.propertyName)); this.ui.title.text(target ? target.title() : I18n$1.t('pageflow.editor.views.inputs.reference_input_view.none')); this.ui.unsetButton.toggle(!!target && !this.options.hideUnsetButton); this.ui.unsetButton.attr('title', this.options.unsetButtonTitle || I18n$1.t('pageflow.editor.views.inputs.reference_input_view.unset')); this.ui.chooseButton.attr('title', this.options.chooseButtonTitle || I18n$1.t('pageflow.editor.views.inputs.reference_input_view.choose')); this.updateDisabledAttribute(this.ui.buttons); if (this.thumbnailView) { this.thumbnailView.close(); } this.thumbnailView = this.subview(this.createThumbnailView(target)); this.ui.title.before(this.thumbnailView.el); } }); var ThemeInputView = ReferenceInputView.extend({ options: function options() { return { chooseButtonTitle: I18n$1.t('pageflow.editor.views.inputs.theme_input_view.choose'), hideUnsetButton: true }; }, choose: function choose() { return ChangeThemeDialogView.changeTheme({ model: this.model, themes: this.options.themes, themeInUse: this.model.get(this.options.propertyName) }); }, chooseValue: function chooseValue() { return this.choose().then(function (model) { return model.get('name'); }); }, getTarget: function getTarget(themeName) { return this.options.themes.findByName(themeName); } }); function template$A(data) { var __t, __p = ''; __p += '' + ((__t = ( I18n.t('pageflow.editor.templates.edit_meta_data.outline') )) == null ? '' : __t) + '\n\n
    \n

    ' + ((__t = ( I18n.t('pageflow.editor.templates.edit_meta_data.save_error') )) == null ? '' : __t) + '

    \n

    \n ' + ((__t = ( I18n.t('pageflow.editor.templates.edit_meta_data.retry') )) == null ? '' : __t) + '\n
    \n\n
    \n'; return __p } var EditMetaDataView = Marionette.Layout.extend({ template: template$A, className: 'edit_meta_data', mixins: [failureIndicatingView], regions: { formContainer: '.form_fields' }, events: { 'click a.back': 'goBack' }, onRender: function onRender() { var entry = this.model; var state = this.options.state || {}; var features = this.options.features || {}; var editor = this.options.editor || {}; var configurationEditor = new ConfigurationEditorView({ model: entry.metadata.configuration, tab: this.options.tab, attributeTranslationKeyPrefixes: ['pageflow.entry_types.' + editor.entryType.name + '.editor.entry_metadata_configuration_attributes'] }); configurationEditor.tab('general', function () { this.input('title', TextInputView, { placeholder: entry.get('entry_title'), model: entry.metadata }); this.input('locale', SelectInputView, { values: state.config.availablePublicLocales, texts: _$1.map(state.config.availablePublicLocales, function (locale) { return I18n$1.t('pageflow.public._language', { locale: locale }); }), model: entry.metadata }); this.input('credits', TextAreaInputView, { model: entry.metadata }); this.input('author', TextInputView, { placeholder: state.config.defaultAuthorMetaTag, model: entry.metadata }); this.input('publisher', TextInputView, { placeholder: state.config.defaultPublisherMetaTag, model: entry.metadata }); this.input('keywords', TextInputView, { placeholder: state.config.defaultKeywordsMetaTag, model: entry.metadata }); }); configurationEditor.tab('widgets', function () { editor.entryType.appearanceInputs && editor.entryType.appearanceInputs(this, { entry: entry, theming: state.theming }); entry.widgets && this.view(EditWidgetsView, { model: entry, widgetTypes: editor.widgetTypes }); if (features.isEnabled && features.isEnabled('selectable_themes') && state.themes.length > 1) { this.view(ThemeInputView, { themes: state.themes, propertyName: 'theme_name', model: entry.metadata }); } }); configurationEditor.tab('social', function () { this.input('share_image_id', FileInputView, { collection: state.imageFiles, fileSelectionHandler: 'entryMetadata', model: entry.metadata }); this.input('summary', TextAreaInputView, { disableRichtext: true, disableLinks: true, model: entry.metadata }); this.input('share_url', TextInputView, { placeholder: state.entry.get('pretty_url'), model: entry.metadata }); this.input('share_providers', CheckBoxGroupInputView, { values: state.config.availableShareProviders, translationKeyPrefix: 'activerecord.values.pageflow/entry.share_providers', model: entry.metadata }); }); this.listenTo(entry.metadata, 'change:theme_name', function () { configurationEditor.refresh(); }); this.formContainer.show(configurationEditor); }, goBack: function goBack() { editor.navigate('/', { trigger: true }); } }); function template$B(data) { var __t, __p = ''; __p += '' + ((__t = ( I18n.t('pageflow.editor.templates.edit_page_link.back') )) == null ? '' : __t) + '\n' + ((__t = ( I18n.t('pageflow.editor.templates.edit_page_link.destroy') )) == null ? '' : __t) + '\n
    \n'; return __p } var EditPageLinkView = Marionette.Layout.extend({ template: template$B, regions: { formContainer: '.form_container' }, ui: { backButton: 'a.back' }, events: { 'click a.back': 'goBack', 'click a.destroy': 'destroy' }, onRender: function onRender() { var pageType = this.options.api.pageTypes.findByPage(this.options.page); var configurationEditor = pageType.createPageLinkConfigurationEditorView({ model: this.model, page: this.options.page }); this.formContainer.show(configurationEditor); this.highlight(); }, highlight: function highlight() { this.model.highlight(); this.listenTo(this, 'close', function () { this.model.resetHighlight(); }); }, destroy: function destroy() { if (confirm(I18n$1.t('pageflow.internal_links.editor.views.edit_page_link_view.confirm_destroy'))) { this.model.remove(); this.goBack(); } }, goBack: function goBack() { editor$1.navigate('/pages/' + this.options.page.id + '/links', { trigger: true }); } }); function template$C(data) { var __t, __p = ''; __p += '' + ((__t = ( I18n.t('pageflow.editor.templates.edit_page.outline') )) == null ? '' : __t) + '\n' + ((__t = ( I18n.t('pageflow.editor.templates.edit_page.destroy') )) == null ? '' : __t) + '\n\n
    \n

    ' + ((__t = ( I18n.t('pageflow.editor.templates.edit_page.save_error') )) == null ? '' : __t) + '

    \n

    \n ' + ((__t = ( I18n.t('pageflow.editor.templates.edit_page.retry') )) == null ? '' : __t) + '\n
    \n\n
    \n\n
    '; return __p } var EditPageView = Marionette.Layout.extend({ template: template$C, className: 'edit_page', mixins: [failureIndicatingView], regions: { pageTypeContainer: '.page_type', configurationContainer: '.configuration_container' }, events: { 'click a.back': 'goBack', 'click a.destroy': 'destroy' }, modelEvents: { 'change:template': 'load' }, onRender: function onRender() { this.pageTypeContainer.show(new ExtendedSelectInputView({ model: this.model, propertyName: 'template', collection: this.options.api.pageTypes.pluck('seed'), valueProperty: 'name', translationKeyProperty: 'translation_key', groupTranslationKeyProperty: 'category_translation_key', descriptionTranslationKeyProperty: 'description_translation_key', pictogramClass: 'type_pictogram', helpLinkClicked: function helpLinkClicked(value) { var pageType = this.options.api.pageTypes.findByName(value); app.trigger('toggle-help', pageType.seed.help_entry_translation_key); } })); this.load(); this.model.trigger('edit', this.model); }, onShow: function onShow() { this.configurationEditor.refreshScroller(); }, load: function load() { this.configurationEditor = this.options.api.createPageConfigurationEditorView(this.model, { tab: this.options.tab }); this.configurationContainer.show(this.configurationEditor); }, destroy: function destroy() { if (confirm(I18n$1.t('pageflow.editor.views.edit_page_view.confirm_destroy'))) { this.model.destroy(); this.goBack(); } }, goBack: function goBack() { editor$1.navigate('/', { trigger: true }); } }); var PageLinkInputView = ReferenceInputView.extend({ choose: function choose() { return editor$1.selectPage({ isAllowed: this.options.isAllowed }); }, getTarget: function getTarget(permaId) { return state.pages.getByPermaId(permaId); } }); function template$D(data) { var __t, __p = ''; __p += '' + ((__t = ( I18n.t('pageflow.editor.templates.edit_storyline.outline') )) == null ? '' : __t) + '\n\n ' + ((__t = ( I18n.t('pageflow.editor.templates.edit_storyline.destroy') )) == null ? '' : __t) + '\n\n\n
    \n

    ' + ((__t = ( I18n.t('pageflow.editor.templates.edit_storyline.save_error') )) == null ? '' : __t) + '

    \n

    \n ' + ((__t = ( I18n.t('pageflow.editor.templates.edit_storyline.retry') )) == null ? '' : __t) + '\n
    \n\n
    \n'; return __p } var EditStorylineView = Marionette.Layout.extend({ template: template$D, className: 'edit_storyline', mixins: [failureIndicatingView, tooltipContainer], regions: { formContainer: '.form_container' }, ui: { destroyButton: 'a.destroy' }, events: { 'click a.back': 'goBack', 'click a.destroy': 'destroy' }, onRender: function onRender() { var configurationEditor = new ConfigurationEditorView({ model: this.model.configuration, attributeTranslationKeyPrefixes: ['pageflow.storyline_attributes'] }); this.configure(configurationEditor, this.model.transitiveChildPages()); this.formContainer.show(configurationEditor); this.updateDestroyButton(); }, updateDestroyButton: function updateDestroyButton() { var disabled = this.model.chapters.length > 0; this.ui.destroyButton.toggleClass('disabled', disabled); if (disabled) { this.ui.destroyButton.attr('data-tooltip', 'pageflow.editor.views.edit_storyline_view.cannot_destroy'); } else { this.ui.destroyButton.removeAttr('data-tooltip'); } }, configure: function configure(configurationEditor, storylineChildPages) { configurationEditor.tab('general', function () { this.input('title', TextInputView); this.input('main', CheckBoxInputView, { disabled: true, visibleBinding: 'main' }); this.group('page_transitions', { includeBlank: true }); this.input('main', CheckBoxInputView, { visibleBinding: 'main', visible: function visible(isMain) { return !isMain; } }); this.input('parent_page_perma_id', PageLinkInputView, { visibleBinding: 'main', visible: function visible(isMain) { return !isMain && state.storylines.length > 1; }, isAllowed: function isAllowed(page) { return !storylineChildPages.contain(page); } }); this.input('scroll_successor_id', PageLinkInputView); if (pageflow.features.isEnabled('chapter_hierachy')) { this.input('navigation_bar_mode', SelectInputView, { values: pageflow.ChapterFilter.strategies }); } }); }, destroy: function destroy() { if (this.model.chapters.length) { return; } if (confirm(I18n$1.t('pageflow.editor.views.edit_storyline_view.confirm_destroy'))) { this.model.destroy(); this.goBack(); } }, goBack: function goBack() { editor$1.navigate('/?storyline=' + this.model.id, { trigger: true }); } }); function template$E(data) { var __t, __p = ''; __p += '' + ((__t = ( I18n.t('pageflow.editor.templates.edit_widget.back') )) == null ? '' : __t) + '\n'; return __p } var EditWidgetView = Marionette.ItemView.extend({ template: template$E, className: 'edit_widget', events: { 'click a.back': function clickABack() { editor$1.navigate('/meta_data/widgets', { trigger: true }); } }, initialize: function initialize() { this.model.set('editing', true); }, onClose: function onClose() { Marionette.ItemView.prototype.onClose.call(this); this.model.set('editing', false); }, onRender: function onRender() { var configurationEditor = this.model.widgetType().createConfigurationEditorView({ model: this.model.configuration, tab: this.options.tab }); this.appendSubview(configurationEditor); } }); var loadable = modelLifecycleTrackingView({ classNames: { creating: 'creating', destroying: 'destroying' } }); function template$F(data) { var __p = ''; __p += '\n\n\n'; return __p } var ExplorerFileItemView = Marionette.ItemView.extend({ tagName: 'li', template: template$F, mixins: [loadable, selectableView], selectionAttribute: 'file', ui: { fileName: '.file_name', thumbnail: '.file_thumbnail' }, events: { 'click': function click() { if (!this.$el.hasClass('disabled')) { this.select(); } } }, modelEvents: { 'change': 'update' }, onRender: function onRender() { this.update(); this.subview(new FileThumbnailView({ el: this.ui.thumbnail, model: this.model })); }, update: function update() { if (this.isDisabled()) { this.$el.addClass('disabled'); } this.$el.attr('data-id', this.model.id); this.ui.fileName.text(this.model.get('file_name') || '(Unbekannt)'); }, isDisabled: function isDisabled() { return this.options.disabledIds && _$1.contains(this.options.disabledIds, this.model.get('id')); } }); function template$G(data) { var __p = ''; __p += '\n \n\n'; return __p } var OtherEntryItemView = Marionette.ItemView.extend({ template: template$G, className: 'other_entry_item', tagName: 'li', mixins: [selectableView], ui: { title: '.title' }, events: { 'click': 'select' }, onRender: function onRender() { this.ui.title.text(this.model.titleOrSlug()); } }); function template$H(data) { var __t, __p = ''; __p += ((__t = ( I18n.t('pageflow.editor.templates.other_entries_blank_slate.none_available') )) == null ? '' : __t) + '\n'; return __p } var OtherEntriesCollectionView = Marionette.View.extend({ initialize: function initialize() { this.otherEntries = new OtherEntriesCollection(); this.listenTo(this.otherEntries, 'sync', function () { if (this.otherEntries.length === 1) { this.options.selection.set('entry', this.otherEntries.first()); } }); }, render: function render() { this.subview(new CollectionView({ el: this.el, collection: this.otherEntries, itemViewConstructor: OtherEntryItemView, itemViewOptions: { selection: this.options.selection }, blankSlateViewConstructor: Marionette.ItemView.extend({ template: template$H, tagName: 'li', className: 'blank_slate' }), loadingViewConstructor: LoadingView })); this.otherEntries.fetch(); return this; } }); function template$I(data) { var __t, __p = ''; __p += '
    \n

    ' + ((__t = ( I18n.t('pageflow.editor.templates.files_explorer.reuse_files') )) == null ? '' : __t) + '

    \n\n
    \n
      \n
    \n\n
    \n
    \n
    \n\n \n
    \n'; return __p } function filesGalleryBlankSlateTemplate(data) { var __t, __p = ''; __p += '
  • ' + ((__t = ( I18n.t('pageflow.editor.templates.files_gallery_blank_slate.no_files') )) == null ? '' : __t) + '
  • \n'; return __p } function filesExplorerBlankSlateTemplate(data) { var __t, __p = ''; __p += '
  • ' + ((__t = ( I18n.t('pageflow.editor.templates.files_explorer_blank_slate.choose_hint') )) == null ? '' : __t) + '
  • \n'; return __p } var FilesExplorerView = Marionette.ItemView.extend({ template: template$I, className: 'files_explorer editor dialog', mixins: [dialogView], ui: { entriesPanel: '.entries_panel', filesPanel: '.files_panel', okButton: '.ok' }, events: { 'click .ok': function clickOk() { if (this.options.callback) { this.options.callback(this.selection.get('entry'), this.selection.get('file')); } this.close(); } }, initialize: function initialize() { this.selection = new Backbone.Model(); this.listenTo(this.selection, 'change:entry', function () { this.tabsView.refresh(); }); // check if the OK button should be enabled. this.listenTo(this.selection, 'change', function (selection, options) { this.ui.okButton.prop('disabled', !this.selection.get('file')); }); }, onRender: function onRender() { this.subview(new OtherEntriesCollectionView({ el: this.ui.entriesPanel, selection: this.selection })); this.tabsView = new TabsView({ model: this.model, i18n: 'pageflow.editor.files.tabs', defaultTab: this.options.tabName }); editor$1.fileTypes.each(function (fileType) { if (fileType.topLevelType) { this.tab(fileType); } }, this); this.ui.filesPanel.append(this.subview(this.tabsView).el); this.ui.okButton.prop('disabled', true); }, tab: function tab(fileType) { this.tabsView.tab(fileType.collectionName, _$1.bind(function () { var collection = this._collection(fileType); var disabledIds = state.entry.getFileCollection(fileType).pluck('id'); return new CollectionView({ tagName: 'ul', className: 'files_gallery', collection: collection, itemViewConstructor: ExplorerFileItemView, itemViewOptions: { selection: this.selection, disabledIds: disabledIds }, blankSlateViewConstructor: this._blankSlateConstructor() }); }, this)); }, _collection: function _collection(fileType) { var collection, entry = this.selection.get('entry'); if (entry) { collection = entry.getFileCollection(fileType); collection.fetch(); } else { collection = new Backbone.Collection(); } return collection; }, _blankSlateConstructor: function _blankSlateConstructor() { return Marionette.ItemView.extend({ template: this.selection.get('entry') ? filesGalleryBlankSlateTemplate : filesExplorerBlankSlateTemplate }); } }); FilesExplorerView.open = function (options) { app.dialogRegion.show(new FilesExplorerView(options)); }; function template$J(data) { var __p = ''; __p += '\n'; return __p } var FileMetaDataItemView = Marionette.ItemView.extend({ tagName: 'tr', template: template$J, ui: { label: 'th', value: 'td' }, onRender: function onRender() { this.subview(new this.options.valueView(_$1.extend({ el: this.ui.value, model: this.model, name: this.options.name }, this.options.valueViewOptions || {}))); this.ui.label.text(this.labelText()); }, labelText: function labelText() { return i18nUtils.attributeTranslation(this.options.name, 'label', { prefixes: ['pageflow.editor.files.attributes.' + this.model.fileType().collectionName, 'pageflow.editor.files.common_attributes'], fallbackPrefix: 'activerecord.attributes', fallbackModelI18nKey: this.model.i18nKey }); } }); function template$K(data) { var __p = ''; __p += '

    \n

    \n

    '; return __p } var FileStageItemView = Marionette.ItemView.extend({ tagName: 'li', className: 'file_stage_item', template: template$K, ui: { description: '.description', percent: '.percent', errorMessage: '.error_message' }, modelEvents: { 'change': 'update' }, onRender: function onRender() { this.update(); this.$el.addClass(this.model.get('name')); if (this.options.standAlone) { this.$el.addClass('stand_alone'); } else { this.$el.addClass('indented'); } }, update: function update() { this.ui.description.text(this.model.localizedDescription()); if (typeof this.model.get('progress') === 'number' && this.model.get('active')) { this.ui.percent.text(this.model.get('progress') + '%'); } else { this.ui.percent.text(''); } this.ui.errorMessage.toggle(!!this.model.get('error_message')).text(this._translatedErrorMessage()); this.$el.toggleClass('active', this.model.get('active')); this.$el.toggleClass('finished', this.model.get('finished')); this.$el.toggleClass('failed', this.model.get('failed')); this.$el.toggleClass('action_required', this.model.get('action_required')); }, _translatedErrorMessage: function _translatedErrorMessage() { return this.model.get('error_message') && I18n$1.t(this.model.get('error_message'), { defaultValue: this.model.get('error_message') }); } }); function template$L(data) { var __t, __p = ''; __p += '\n\n\n' + ((__t = ( I18n.t('pageflow.editor.templates.file_item.select') )) == null ? '' : __t) + '\n\n
    \n \n \n \n \n \n
    \n\n
    \n
      \n\n \n
      \n'; return __p } var FileItemView = Marionette.ItemView.extend({ tagName: 'li', template: template$L, mixins: [loadable], ui: { fileName: '.file_name', selectButton: '.select', settingsButton: '.settings', removeButton: '.remove', confirmButton: '.confirm', cancelButton: '.cancel', retryButton: '.retry', thumbnail: '.file_thumbnail', stageItems: '.file_stage_items', metaData: 'tbody.attributes', downloads: 'tbody.downloads', downloadLink: 'a.original' }, events: { 'click .select': function clickSelect() { var result = this.options.selectionHandler.call(this.model); if (result !== false) { editor$1.navigate(this.options.selectionHandler.getReferer(), { trigger: true }); } return false; }, 'click .settings': function clickSettings() { FileSettingsDialogView.open({ model: this.model }); }, 'click .cancel': 'cancel', 'click .confirm': 'confirm', 'click .remove': 'destroy', 'click .retry': 'retry', 'click .file_thumbnail': 'toggleExpanded' }, modelEvents: { 'change': 'update' }, onRender: function onRender() { this.update(); this.subview(new FileThumbnailView({ el: this.ui.thumbnail, model: this.model })); this.subview(new CollectionView({ el: this.ui.stageItems, collection: this.model.stages, itemViewConstructor: FileStageItemView })); _$1.each(this.metaDataViews(), function (view) { this.ui.metaData.append(this.subview(view).el); }, this); }, update: function update() { if (this.isClosed) { return; } this.$el.attr('data-id', this.model.id); this.ui.fileName.text(this.model.get('file_name') || '(Unbekannt)'); this.ui.downloadLink.attr('href', this.model.get('original_url')); this.ui.downloads.toggle(this.model.isUploaded() && !_$1.isEmpty(this.model.get('original_url'))); this.ui.selectButton.toggle(!!this.options.selectionHandler); this.ui.settingsButton.toggle(!this.model.isNew()); this.ui.cancelButton.toggle(this.model.isUploading()); this.ui.confirmButton.toggle(this.model.isConfirmable()); this.ui.removeButton.toggle(!this.model.isUploading()); this.ui.retryButton.toggle(this.model.isRetryable()); this.updateToggleTitle(); }, metaDataViews: function metaDataViews() { var model = this.model; return _$1.map(this.options.metaDataAttributes, function (options) { if (typeof options === 'string') { options = { name: options, valueView: TextFileMetaDataItemValueView }; } return new FileMetaDataItemView(_$1.extend({ model: model }, options)); }); }, toggleExpanded: function toggleExpanded() { this.$el.toggleClass('expanded'); this.updateToggleTitle(); }, updateToggleTitle: function updateToggleTitle() { this.ui.thumbnail.attr('title', this.$el.hasClass('expanded') ? 'Details ausblenden' : 'Details einblenden'); }, destroy: function destroy() { if (confirm("Datei wirklich wirklich löschen?")) { this.model.destroy(); } }, cancel: function cancel() { this.model.cancelUpload(); }, confirm: function confirm() { editor$1.navigate('/confirmable_files?type=' + this.model.modelName + '&id=' + this.model.id, { trigger: true }); }, retry: function retry() { this.model.retry(); } }); function template$M(data) { var __t, __p = ''; __p += '
      \n \n ' + ((__t = ( I18n.t('pageflow.editor.views.filtered_files_view.banner_prefix') )) == null ? '' : __t) + '\n \n \n \n \n
      \n'; return __p } function blankSlateTemplate$1(data) { var __t, __p = ''; __p += '
    • ' + ((__t = ( data.text )) == null ? '' : __t) + '
    • \n'; return __p } var FilteredFilesView = Marionette.ItemView.extend({ template: template$M, className: 'filtered_files', ui: { banner: '.filtered_files-banner', filterName: '.filtered_files-filter_name' }, events: { 'click .filtered_files-reset_filter': function clickFiltered_filesReset_filter() { editor$1.navigate('/files/' + this.options.fileType.collectionName, { trigger: true }); return false; } }, onRender: function onRender() { var entry = this.options.entry; var fileType = this.options.fileType; var collection = entry.getFileCollection(fileType); var blankSlateText = I18n$1.t('pageflow.editor.templates.files_blank_slate.no_files'); if (this.options.filterName) { if (this.filteredCollection) { this.filteredCollection.dispose(); } collection = this.filteredCollection = collection.withFilter(this.options.filterName); blankSlateText = this.filterTranslation('blank_slate'); } this.appendSubview(new CollectionView({ tagName: 'ul', className: 'files expandable', collection: collection, itemViewConstructor: FileItemView, itemViewOptions: { metaDataAttributes: fileType.metaDataAttributes, selectionHandler: this.options.selectionHandler }, blankSlateViewConstructor: Marionette.ItemView.extend({ template: blankSlateTemplate$1, serializeData: function serializeData() { return { text: blankSlateText }; } }) })); this.ui.banner.toggle(!!this.options.filterName); if (this.options.filterName) { this.ui.filterName.text(this.filterTranslation('name')); } }, filterTranslation: function filterTranslation(keyName, options) { var filterName = this.options.filterName; return i18nUtils.findTranslation(['pageflow.editor.files.filters.' + this.options.fileType.collectionName + '.' + filterName + '.' + keyName, 'pageflow.editor.files.common_filters.' + keyName], options); }, onClose: function onClose() { if (this.filteredCollection) { this.filteredCollection.dispose(); } } }); function template$N(data) { var __t, __p = ''; __p += '
      \n

      ' + ((__t = ( I18n.t('pageflow.editor.views.files_view.importer.heading') )) == null ? '' : __t) + '

      \n\n
      \n
        \n
      \n
      \n\n \n
      \n'; return __p } function template$O(data) { var __t, __p = ''; __p += ''; return __p } var ImporterSelectView = Marionette.ItemView.extend({ template: template$O, className: 'importer_select', tagName: 'li', events: { 'click .importer': function clickImporter(event) { this.options.parentView.importerSelected(this.options.importer); } }, initialize: function initialize(options) {}, serializeData: function serializeData() { return { fileImporter: this.options.importer }; } }); var ChooseImporterView = Marionette.ItemView.extend({ template: template$N, className: 'choose_importer editor dialog', mixins: [dialogView], ui: { importersList: '.importers_panel', closeButton: '.close' }, events: { 'click .close': function clickClose() { this.close(); } }, importerSelected: function importerSelected(importer) { if (this.options.callback) { this.options.callback(importer); } this.close(); }, onRender: function onRender() { var self = this; editor$1.fileImporters.values().forEach(function (fileImporter) { var importerSelectView = new ImporterSelectView({ importer: fileImporter, parentView: self }).render(); self.ui.importersList.append(importerSelectView.$el); }); } }); ChooseImporterView.open = function (options) { app.dialogRegion.show(new ChooseImporterView(options).render()); }; function template$P(data) { var __t, __p = ''; __p += '
      \n

      ' + ((__t = ( I18n.t('pageflow.editor.file_importers.'+data.importerKey+'.dialog_label') )) == null ? '' : __t) + '

      \n\n
      \n \n
      \n\n \n
      \n'; return __p } function template$Q(data) { var __t, __p = ''; __p += '
      \n

      ' + ((__t = ( I18n.t('pageflow.editor.templates.confirm_upload.header') )) == null ? '' : __t) + '

      \n

      ' + ((__t = ( I18n.t('pageflow.editor.templates.confirm_upload.hint') )) == null ? '' : __t) + '

      \n\n
      \n
      \n\n
      \n

      ' + ((__t = ( I18n.t('pageflow.editor.templates.confirm_upload.edit_file_header') )) == null ? '' : __t) + '

      \n
      \n
      \n
      \n\n \n
      \n'; return __p } function template$R(data) { var __p = ''; __p += '

      \n'; return __p } var UploadableFilesView = Marionette.ItemView.extend({ template: template$R, className: 'uploadable_files', ui: { header: 'h2' }, initialize: function initialize() { this.uploadableFiles = this.collection.uploadable(); if (!this.options.selection.has('file')) { this.options.selection.set('file', this.uploadableFiles.first()); } }, onRender: function onRender() { this.ui.header.text(I18n$1.t('pageflow.editor.files.tabs.' + this.options.fileType.collectionName)); this.appendSubview(new TableView({ collection: this.uploadableFiles, attributeTranslationKeyPrefixes: ['pageflow.editor.files.attributes.' + this.options.fileType.collectionName, 'pageflow.editor.files.common_attributes'], columns: this.commonColumns().concat(this.fileTypeColumns()), selection: this.options.selection, selectionAttribute: 'file' })); this.listenTo(this.uploadableFiles, 'add remove', this.update); this.update(); }, update: function update() { this.$el.toggleClass('is_empty', this.uploadableFiles.length === 0); }, commonColumns: function commonColumns() { return [{ name: 'file_name', cellView: TextTableCellView }, { name: 'rights', cellView: PresenceTableCellView }]; }, fileTypeColumns: function fileTypeColumns() { return _$1(this.options.fileType.confirmUploadTableColumns).map(function (column) { return _$1.extend({}, column, { configurationAttribute: true }); }); } }); var ConfirmFileImportUploadView = Marionette.Layout.extend({ template: template$Q, className: 'confirm_upload editor dialog', mixins: [dialogView], regions: { selectedFileRegion: '.selected_file_region' }, ui: { filesPanel: '.files_panel' }, events: { 'click .upload': function clickUpload() { this.onImport(); }, 'click .close': function clickClose() { this.closeMe(); } }, getSelectedFiles: function getSelectedFiles() { var files = []; for (var key in state.files) { if (state.files.hasOwnProperty(key)) { var collection = state.files[key]; if (collection.length > 0) { files = files.concat(collection.toJSON()); } } } return files; }, initialize: function initialize() { this.selection = new Backbone.Model(); this.listenTo(this.selection, 'change', this.update); }, onRender: function onRender() { this.options.fileTypes.each(function (fileType) { this.ui.filesPanel.append(this.subview(new UploadableFilesView({ collection: this.options.files[fileType.collectionName], fileType: fileType, selection: this.selection })).el); }, this); this.update(); }, onImport: function onImport() { var cName = this.options.fileImportModel.get('metaData').collection; this.options.fileImportModel.get('importer').startImportJob(cName); this.close(); }, closeMe: function closeMe() { var cName = this.options.fileImportModel.get('metaData').collection; this.options.fileImportModel.get('importer').cancelImport(cName); this.close(); }, update: function update() { var file = this.selection.get('file'); if (file) { this.selectedFileRegion.show(new pageflow.EditFileView({ model: file })); } else { this.selectedFileRegion.close(); } } }); ConfirmFileImportUploadView.open = function (options) { app.dialogRegion.show(new ConfirmFileImportUploadView(options)); }; var FilesImporterView = Marionette.ItemView.extend({ template: template$P, className: 'files_importer editor dialog', mixins: [dialogView], ui: { contentPanel: '.content_panel', spinner: '.lds-spinner', importButton: '.import', closeButton: '.close' }, events: { 'click .import': function clickImport() { this.getMetaData(); } }, initialize: function initialize(options) { this.model = new Backbone.Model({ importerKey: options.importer.key, importer: new FileImport({ importer: options.importer, currentEntry: state.entry }) }); this.listenTo(this.model.get('importer'), "change", function (event) { this.updateImportButton(); if (!this.isInitialized) { this.updateAuthenticationView(); } }); }, updateAuthenticationView: function updateAuthenticationView() { var importer = this.model.get('importer'); if (importer.get('isAuthenticated')) { this.ui.contentPanel.empty(); this.ui.contentPanel.append(this.model.get('importer').createFileImportDialogView().render().el); this.isInitialized = true; } }, updateImportButton: function updateImportButton() { var importer = this.model.get('importer'); this.ui.importButton.prop('disabled', importer.get('selectedFiles').length < 1); }, getMetaData: function getMetaData() { var self = this; this.model.get('importer').getFilesMetaData().then(function (metaData) { if (metaData) { self.model.set('metaData', metaData); // add each selected file meta to state.files for (var i = 0; i < metaData.files.length; i++) { var file = metaData.files[i]; var fileType = editor$1.fileTypes.findByUpload(file); var file = new fileType.model({ state: 'uploadable', file_name: file.name, content_type: file.type, file_size: -1, rights: file.rights, source_url: file.url }, { fileType: fileType }); state.entry.getFileCollection(fileType).add(file); } ConfirmFileImportUploadView.open({ fileTypes: editor$1.fileTypes, fileImportModel: self.model, files: state.files }); } }); this.close(); }, onRender: function onRender() { if (!this.isInitialized) { this.ui.contentPanel.append(this.subview(new LoadingView({ tagName: 'div' })).el); } } }); FilesImporterView.open = function (options) { app.dialogRegion.show(new FilesImporterView(options).render()); }; function template$S(data) { var __t, __p = ''; __p += '\n\n\n'; return __p } var SelectButtonView = Marionette.ItemView.extend({ template: template$S, className: 'select_button', ui: { button: 'button', label: 'button .label', menu: '.dropdown-menu', dropdown: '.dropdown' }, events: { 'click .dropdown-menu li': function clickDropdownMenuLi(e, x) { e.preventDefault(); var index = getClickedIndex(e.target); this.model.get('options')[index].handler(); function getClickedIndex(target) { var $target = $(target), index = parseInt($target.data('index'), 10); if (isNaN(index)) { index = parseInt($target.find('a').data('index'), 10); } return index; } } }, onRender: function onRender() { this.ui.label.text(this.model.get('label')); this.model.get('options').forEach(this.addOption.bind(this)); }, addOption: function addOption(option, index) { this.ui.menu.append('
    • ' + option.label + '
    • '); } }); function template$T(data) { var __t, __p = ''; __p += '' + ((__t = ( I18n.t('pageflow.editor.templates.files.back') )) == null ? '' : __t) + '\n'; return __p } var FilesView = Marionette.ItemView.extend({ template: template$T, className: 'manage_files', events: { 'click a.back': 'goBack', 'file-selected': 'updatePage' }, onRender: function onRender() { var menuOptions = [{ label: I18n$1.t('pageflow.editor.views.files_view.upload'), handler: this.upload.bind(this) }, { label: I18n$1.t('pageflow.editor.views.files_view.reuse'), handler: function handler() { FilesExplorerView.open({ callback: function callback(otherEntry, file) { state.entry.reuseFile(otherEntry, file); } }); } }]; if (editor$1.fileImporters.keys().length > 0) { menuOptions.push({ label: I18n$1.t('pageflow.editor.views.files_view.import'), handler: function handler() { ChooseImporterView.open({ callback: function callback(importer) { FilesImporterView.open({ importer: importer }); } }); } }); } this.addFileModel = new Backbone.Model({ label: I18n$1.t('pageflow.editor.views.files_view.add'), options: menuOptions }); this.$el.append(this.subview(new SelectButtonView({ model: this.addFileModel })).el); this.tabsView = new TabsView({ model: this.model, i18n: 'pageflow.editor.files.tabs', defaultTab: this.options.tabName }); editor$1.fileTypes.each(function (fileType) { if (fileType.topLevelType) { this.tab(fileType); } }, this); this.$el.append(this.subview(this.tabsView).el); }, tab: function tab(fileType) { var selectionMode = this.options.tabName === fileType.collectionName; this.tabsView.tab(fileType.collectionName, _$1.bind(function () { return this.subview(new FilteredFilesView({ entry: state.entry, fileType: fileType, selectionHandler: selectionMode && this.options.selectionHandler, filterName: selectionMode && this.options.filterName })); }, this)); this.listenTo(this.model, 'change:uploading_' + fileType.collectionName + '_count', function (model, value) { this.tabsView.toggleSpinnerOnTab(fileType.collectionName, value > 0); }); }, goBack: function goBack() { if (this.options.selectionHandler) { editor$1.navigate(this.options.selectionHandler.getReferer(), { trigger: true }); } else { editor$1.navigate('/', { trigger: true }); } }, upload: function upload() { app.trigger('request-upload'); } }); function template$U(data) { var __p = ''; __p += '
      \n
      \n
      \n
      \n
      \n
      \n'; return __p } var EntryPublicationQuotaDecoratorView = Marionette.Layout.extend({ template: template$U, className: 'quota_decorator', regions: { outlet: '.outlet' }, ui: { state: '.quota_state', exhaustedMessage: '.exhausted_message' }, modelEvents: { 'change:exceeding change:checking change:quota': 'update' }, onRender: function onRender() { this.model.check(); }, update: function update() { var view = this; if (this.model.get('checking')) { view.ui.state.text(I18n$1.t('pageflow.editor.quotas.loading')); view.ui.exhaustedMessage.hide().html(''); view.outlet.close(); } else { if (view.model.get('exceeding')) { view.ui.state.hide(); view.ui.exhaustedMessage.show().html(view.model.get('exhausted_html')); view.outlet.close(); } else { if (view.model.quota().get('state_description')) { view.ui.state.text(view.model.quota().get('state_description')); view.ui.state.show(); } else { view.ui.state.hide(); } view.outlet.show(view.options.view); } } } }); function template$V(data) { var __t, __p = ''; __p += '
      \n

      ' + ((__t = ( I18n.t('pageflow.editor.templates.publish_entry.files_pending_notice') )) == null ? '' : __t) + '

      \n

      ' + ((__t = ( I18n.t('pageflow.editor.templates.publish_entry.show_files') )) == null ? '' : __t) + '

      \n
      \n\n
      \n
      \n

      ' + ((__t = ( I18n.t('pageflow.editor.templates.publish_entry.published_notice') )) == null ? '' : __t) + '

      \n

      ' + ((__t = ( I18n.t('pageflow.editor.templates.publish_entry.view_revisions') )) == null ? '' : __t) + '

      \n
      \n\n
      \n ' + ((__t = ( I18n.t('pageflow.editor.templates.publish_entry.not_published_notice') )) == null ? '' : __t) + '\n
      \n\n

      ' + ((__t = ( I18n.t('pageflow.editor.templates.publish_entry.publish_current') )) == null ? '' : __t) + '

      \n\n
      \n \n \n
      \n\n
      \n \n \n
      \n\n
      \n \n\n \n
      \n\n
      \n \n \n
      \n\n
      \n \n\n \n\n

      \n ' + ((__t = ( I18n.t('pageflow.editor.templates.publish_entry.already_published_with_password_help') )) == null ? '' : __t) + '\n

      \n

      \n ' + ((__t = ( I18n.t('pageflow.editor.templates.publish_entry.previously_published_with_password_help') )) == null ? '' : __t) + '\n

      \n

      \n ' + ((__t = ( I18n.t('pageflow.editor.templates.publish_entry.already_published_without_password_help') )) == null ? '' : __t) + '\n

      \n
      \n\n
      \n\n
      \n

      ' + ((__t = ( I18n.t('pageflow.editor.templates.publish_entry.publish_success') )) == null ? '' : __t) + '

      \n

      ' + ((__t = ( I18n.t('pageflow.editor.templates.publish_entry.published_url_hint') )) == null ? '' : __t) + '

      \n

      \n
      \n'; return __p } var PublishEntryView = Marionette.ItemView.extend({ template: template$V, className: 'publish_entry', ui: { publishUntilFields: '.publish_until_fields', publishUntilField: 'input[name=publish_until]', publishUntilTimeField: 'input[name=publish_until_time]', publishUntilRadioBox: '#publish_entry_until', publishForeverRadioBox: 'input[value=publish_forever]', passwordProtectedCheckBox: 'input[name=password_protected]', passwordFields: '.password_fields', userNameField: 'input[name=user_name]', passwordField: 'input[name=password]', alreadyPublishedWithPassword: '.already_published_with_password', previouslyPublishedWithPassword: '.previously_published_with_password', alreadyPublishedWithoutPassword: '.already_published_without_password', revisionsLink: '.published.notice a', publishedNotice: '.published.notice', saveButton: 'button.save', successNotice: '.success', successLink: '.success a' }, events: { 'click button.save': 'save', 'click input#publish_entry_forever': 'enablePublishForever', 'click input#publish_entry_until': 'enablePublishUntilFields', 'focus .publish_until_fields input': 'enablePublishUntilFields', 'change .publish_until_fields input': 'checkForm', 'click input#publish_password_protected': 'togglePasswordFields', 'keyup input[name=password]': 'checkForm', 'change input[name=password]': 'checkForm' }, modelEvents: { 'change': 'update', 'change:published': function changePublished(model, value) { if (value) { this.ui.publishedNotice.effect('highlight', { duration: 'slow' }); } } }, onRender: function onRender() { this.ui.publishUntilField.datepicker({ dateFormat: 'dd.mm.yy', constrainInput: true, defaultDate: new Date(), minDate: new Date() }); this.update(); }, update: function update() { this.$el.toggleClass('files_pending', this.model.get('uploading_files_count') > 0 || this.model.get('pending_files_count') > 0); this.$el.toggleClass('published', this.model.get('published')); this.ui.revisionsLink.attr('href', '/admin/entries/' + this.model.id); this.ui.successLink.attr('href', this.model.get('pretty_url')); this.ui.successLink.text(this.model.get('pretty_url')); var publishedUntil = new Date(this.model.get('published_until')); if (publishedUntil > new Date()) { this.ui.publishUntilField.datepicker('setDate', publishedUntil); this.ui.publishUntilTimeField.val(timeStr(publishedUntil)); } else { this.ui.publishUntilField.datepicker('setDate', oneYearFromNow()); } this.ui.userNameField.val(state.account.get('name')); if (this.model.get('password_protected')) { this.ui.passwordProtectedCheckBox.prop('checked', true); this.togglePasswordFields(); } else { this.ui.passwordField.val(this.randomPassword()); } this.ui.alreadyPublishedWithPassword.toggle(this.model.get('published') && this.model.get('password_protected')); this.ui.previouslyPublishedWithPassword.toggle(!this.model.get('published') && this.model.get('password_protected')); this.ui.alreadyPublishedWithoutPassword.toggle(this.model.get('published') && !this.model.get('password_protected')); // Helpers function timeStr(date) { return twoDigits(date.getHours()) + ':' + twoDigits(date.getMinutes()); function twoDigits(val) { return ("0" + val).slice(-2); } } function oneYearFromNow() { var date = new Date(); date.setFullYear(date.getFullYear() + 1); return date; } }, save: function save() { var publishedUntil = null; if (this.$el.hasClass('publishing')) { return; } if (this.ui.publishUntilRadioBox.is(':checked')) { publishedUntil = this.ui.publishUntilField.datepicker('getDate'); setTime(publishedUntil, this.ui.publishUntilTimeField.val()); if (!this.checkPublishUntilTime()) { alert('Bitte legen Sie einen gültigen Depublikationszeitpunkt fest.'); this.ui.publishUntilTimeField.focus(); return; } if (!publishedUntil || !checkDate(publishedUntil)) { alert('Bitte legen Sie ein Depublikationsdatum fest.'); this.ui.publishUntilField.focus(); return; } } var that = this; this.options.entryPublication.publish({ published_until: publishedUntil, password_protected: this.ui.passwordProtectedCheckBox.is(':checked'), password: this.ui.passwordField.val() }).fail(function () { alert('Beim Veröffentlichen ist ein Fehler aufgetreten'); }).always(function () { if (that.isClosed) { return; } that.$el.removeClass('publishing'); that.$el.addClass('succeeded'); that.$('input').removeAttr('disabled'); var publishedMessage = that.options.entryPublication.get('published_message_html'); if (publishedMessage) { that.ui.successNotice.append(publishedMessage); } that.enableSave(); }); this.$el.addClass('publishing'); this.$('input').attr('disabled', '1'); this.disableSave(); // Helpers function setTime(date, time) { date.setHours.apply(date, parseTime(time)); } function parseTime(str) { return str.split(':').map(function (number) { return parseInt(number, 10); }); } function checkDate(date) { if (Object.prototype.toString.call(date) === "[object Date]") { if (isNaN(date.getTime())) { return false; } return true; } return false; } }, enableSave: function enableSave() { this.ui.saveButton.removeAttr('disabled'); }, disableSave: function disableSave() { this.ui.saveButton.attr('disabled', true); }, enablePublishUntilFields: function enablePublishUntilFields() { this.ui.publishForeverRadioBox[0].checked = false; this.ui.publishUntilRadioBox[0].checked = true; this.ui.publishUntilFields.removeClass('disabled'); this.checkForm(); }, disablePublishUntilFields: function disablePublishUntilFields() { this.ui.publishUntilRadioBox[0].checked = false; this.ui.publishUntilFields.addClass('disabled'); this.checkForm(); if (!this.checkPublishUntilTime()) { this.ui.publishUntilTimeField.val('00:00'); } this.ui.publishUntilTimeField.removeClass('invalid'); this.ui.publishUntilField.removeClass('invalid'); }, enablePublishForever: function enablePublishForever() { this.disablePublishUntilFields(); this.ui.publishForeverRadioBox[0].checked = true; this.enableSave(); }, checkForm: function checkForm() { if (_$1.all([this.checkPublishUntil(), this.checkPassword()])) { this.enableSave(); } else { this.disableSave(); } }, checkPublishUntil: function checkPublishUntil() { return this.ui.publishForeverRadioBox.is(':checked') || this.ui.publishUntilRadioBox.is(':checked') && _$1.all([this.checkPublishUntilDate(), this.checkPublishUntilTime()]); }, checkPublishUntilDate: function checkPublishUntilDate() { if (this.ui.publishUntilField.datepicker('getDate')) { this.ui.publishUntilField.removeClass('invalid'); return true; } else { this.ui.publishUntilField.addClass('invalid'); return false; } }, checkPublishUntilTime: function checkPublishUntilTime() { if (!this.ui.publishUntilTimeField.val().match(/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/)) { this.ui.publishUntilTimeField.addClass('invalid'); return false; } this.ui.publishUntilTimeField.removeClass('invalid'); return true; }, togglePasswordFields: function togglePasswordFields() { this.ui.passwordFields.toggleClass('disabled', !this.ui.passwordProtectedCheckBox.is(':checked')); this.checkForm(); }, checkPassword: function checkPassword() { if (this.ui.passwordField.val().length === 0 && !this.model.get('password_protected') && this.ui.passwordProtectedCheckBox.is(':checked')) { this.ui.passwordField.addClass('invalid'); return false; } else { this.ui.passwordField.removeClass('invalid'); return true; } }, randomPassword: function randomPassword() { var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; return _$1(10).times(function () { return possible.charAt(Math.floor(Math.random() * possible.length)); }).join(''); } }); PublishEntryView.create = function (options) { return new BackButtonDecoratorView({ view: new EntryPublicationQuotaDecoratorView({ model: options.entryPublication, view: new PublishEntryView(options) }) }); }; var SidebarController = Marionette.Controller.extend({ initialize: function initialize(options) { this.region = options.region; this.entry = options.entry; }, index: function index(storylineId) { this.region.show(new EditEntryView({ model: this.entry, storylineId: storylineId })); }, files: function files(collectionName, handler, payload, filterName) { this.region.show(new FilesView({ model: this.entry, selectionHandler: handler && editor$1.createFileSelectionHandler(handler, payload), tabName: collectionName, filterName: filterName })); editor$1.setDefaultHelpEntry('pageflow.help_entries.files'); }, confirmableFiles: function confirmableFiles(preselectedFileType, preselectedFileId) { this.region.show(ConfirmEncodingView.create({ model: EncodingConfirmation.createWithPreselection({ fileType: preselectedFileType, fileId: preselectedFileId }) })); }, metaData: function metaData(tab) { this.region.show(new EditMetaDataView({ model: this.entry, tab: tab, state: state, features: pageflow.features, editor: editor$1 })); }, publish: function publish() { this.region.show(PublishEntryView.create({ model: this.entry, entryPublication: new EntryPublication() })); editor$1.setDefaultHelpEntry('pageflow.help_entries.publish'); }, storyline: function storyline(id) { this.region.show(new EditStorylineView({ model: this.entry.storylines.get(id) })); }, chapter: function chapter(id) { this.region.show(new EditChapterView({ model: this.entry.chapters.get(id) })); }, page: function page(id, tab) { var page = this.entry.pages.get(id); this.region.show(new EditPageView({ model: page, api: editor$1, tab: tab })); editor$1.setDefaultHelpEntry(page.pageType().help_entry_translation_key); }, pageLink: function pageLink(linkId) { var pageId = linkId.split(':')[0]; var page = state.pages.getByPermaId(pageId); this.region.show(new EditPageLinkView({ model: page.pageLinks().get(linkId), page: page, api: editor$1 })); }, widget: function widget(id) { this.region.show(new EditWidgetView({ model: this.entry.widgets.get(id) })); } }); var UploaderView = Marionette.View.extend({ el: 'form#upload', initialize: function initialize() { this.listenTo(app, 'request-upload', this.openFileDialog); }, render: function render() { var that = this; this.$el.fileupload({ type: 'POST', paramName: 'file', dataType: 'XML', acceptFileTypes: new RegExp('(\\.|\\/)(bmp|gif|jpe?g|png|ti?f|wmv|mp4|mpg|mov|asf|asx|avi|' + 'm?v|mpeg|qt|3g2|3gp|3ivx|divx|3vx|vob|flv|dvx|xvid|mkv|vtt)$', 'i'), add: function add(event, data) { try { state.fileUploader.add(data.files[0]).then(function (record) { data.record = record; record.save(null, { success: function success() { var directUploadConfig = data.record.get('direct_upload_config'); data.url = directUploadConfig.url; data.formData = directUploadConfig.fields; var xhr = data.submit(); that.listenTo(data.record, 'uploadCancelled', function () { xhr.abort(); }); } }); }); } catch (e) { if (e instanceof UploadError) { app.trigger('error', e); } else { throw e; } } }, progress: function progress(event, data) { data.record.set('uploading_progress', parseInt(data.loaded / data.total * 100, 10)); }, done: function done(event, data) { data.record.unset('uploading_progress'); data.record.publish(); }, fail: function fail(event, data) { if (data.errorThrown !== 'abort') { data.record.uploadFailed(); } }, always: function always(event, data) { that.stopListening(data.record); } }); return this; }, openFileDialog: function openFileDialog() { this.$('input:file').click(); } }); var ScrollingView = Marionette.View.extend({ events: { scroll: function scroll() { if (this._isChapterView()) { this.scrollpos = this.$el.scrollTop(); } } }, initialize: function initialize() { this.scrollpos = 0; this.listenTo(this.options.region, 'show', function () { if (this._isChapterView()) { this.$el.scrollTop(this.scrollpos); } }); }, _isChapterView: function _isChapterView() { return !Backbone.history.getFragment(); } }); function template$W(data) { var __t, __p = ''; __p += '
      \n

      ' + ((__t = ( I18n.t('pageflow.editor.templates.help.title') )) == null ? '' : __t) + '

      \n\n
      \n\n \n
      \n'; return __p } var HelpView = Marionette.ItemView.extend({ template: template$W, className: 'help', ui: { placeholder: '.placeholder', sections: 'section', menuItems: 'li' }, events: { 'click .close': function clickClose() { this.toggle(); }, 'click .expandable > a': function clickExpandableA(event) { $(event.currentTarget).parents('.expandable').toggleClass('expanded'); }, 'click a': function clickA(event) { var link = $(event.currentTarget); if (link.attr('href').indexOf('#') === 0) { this.showSection(link.attr('href').substring(1), { scrollIntoView: !link.parents('nav').length }); } else if (link.attr('href').match(/^http/)) { window.open(link.attr('href'), '_blank'); } return false; }, 'click .box': function clickBox() { return false; }, 'click': function click() { this.toggle(); } }, initialize: function initialize() { this.listenTo(app, 'toggle-help', function (name) { this.toggle(); this.showSection(name || editor$1.defaultHelpEntry || this.defaultHelpEntry(), { scrollIntoView: true }); }); }, onRender: function onRender() { this.ui.placeholder.replaceWith($('#help_entries_seed').html()); this.bindUIElements(); }, toggle: function toggle() { this.$el.toggle(); }, defaultHelpEntry: function defaultHelpEntry() { return this.ui.sections.first().data('name'); }, showSection: function showSection(name, options) { this.ui.menuItems.each(function () { var menuItem = $(this); var active = menuItem.find('a').attr('href') === '#' + name; menuItem.toggleClass('active', active); if (active) { menuItem.parents('.expandable').addClass('expanded'); if (options.scrollIntoView) { menuItem[0].scrollIntoView(); } } }); this.ui.sections.each(function () { var section = $(this); section.toggle(section.data('name') === name); }); } }); var PageThumbnailView = ModelThumbnailView.extend({ className: 'model_thumbnail page_thumbnail' }); function template$X(data) { var __t, __p = ''; __p += '
      \n \n \n
      \n
      \n \n \n
      \n'; return __p } var PageLinkItemView = Marionette.ItemView.extend({ template: template$X, tagName: 'li', className: 'page_link', ui: { thumbnail: '.page_thumbnail', title: '.title', label: '.label', editButton: '.edit', removeButton: '.remove' }, events: { 'click .edit': function clickEdit() { editor$1.navigate(this.model.editPath(), { trigger: true }); return false; }, 'mouseenter': function mouseenter() { this.model.highlight(true); }, 'mouseleave': function mouseleave() { this.model.resetHighlight(false); }, 'click .remove': function clickRemove() { if (confirm(I18n$1.t('pageflow.internal_links.editor.views.edit_page_link_view.confirm_destroy'))) { this.model.remove(); } } }, onRender: function onRender() { var page = this.model.targetPage(); if (page) { this.subview(new PageThumbnailView({ el: this.ui.thumbnail, model: page })); this.$el.addClass(page.get('template')); this.ui.title.text(page.title() || I18n$1.t('pageflow.editor.views.page_link_item_view.unnamed')); } else { this.ui.title.text(I18n$1.t('pageflow.editor.views.page_link_item_view.no_page')); } this.ui.label.text(this.model.label()); this.ui.label.toggle(!!this.model.label()); this.ui.editButton.toggle(!!this.model.editPath()); this.$el.toggleClass('dangling', !page); } }); function template$Y(data) { var __t, __p = ''; __p += '\n\n\n' + ((__t = ( I18n.t('pageflow.editor.templates.page_links.add') )) == null ? '' : __t) + '\n'; return __p } var PageLinksView = Marionette.ItemView.extend({ template: template$Y, className: 'page_links', ui: { links: 'ul.links', addButton: '.add_link' }, events: { 'click .add_link': function clickAdd_link() { var view = this; editor$1.selectPage().then(function (page) { view.model.pageLinks().addLink(page.get('perma_id')); }); return false; } }, onRender: function onRender() { var pageLinks = this.model.pageLinks(); var collectionViewConstructor = pageLinks.saveOrder ? SortableCollectionView : CollectionView; this.subview(new collectionViewConstructor({ el: this.ui.links, collection: pageLinks, itemViewConstructor: PageLinkItemView, itemViewOptions: { pageLinks: pageLinks } })); this.listenTo(pageLinks, 'add remove', function () { this.updateAddButton(pageLinks); }); this.updateAddButton(pageLinks); }, updateAddButton: function updateAddButton(pageLinks) { this.ui.addButton.css('display', pageLinks.canAddLink() ? 'inline-block' : 'none'); } }); function template$Z(data) { var __t, __p = ''; __p += '\n
      \n ' + ((__t = ( I18n.t('pageflow.editor.templates.emulation_mode_button.desktop') )) == null ? '' : __t) + '\n
      \n
      \n ' + ((__t = ( I18n.t('pageflow.editor.templates.emulation_mode_button.phone') )) == null ? '' : __t) + '\n
      \n'; return __p } var EmulationModeButtonView = Marionette.ItemView.extend({ template: template$Z, className: 'emulation_mode_button', ui: { phoneItem: '.emulation_mode_button-phone', desktopItem: '.emulation_mode_button-desktop', phoneDisplay: '.emulation_mode_button-display.emulation_mode_button-phone', desktopDisplay: '.emulation_mode_button-display.emulation_mode_button-desktop' }, events: { 'click .emulation_mode_button-desktop a': function clickEmulation_mode_buttonDesktopA() { this.model.unset('emulation_mode'); }, 'click .emulation_mode_button-phone a': function clickEmulation_mode_buttonPhoneA() { if (!this.model.get('current_page_supports_emulation_mode')) { return; } this.model.set('emulation_mode', 'phone'); } }, modelEvents: { 'change:emulation_mode change:current_page_supports_emulation_mode': 'update' }, onRender: function onRender() { this.update(); }, update: function update() { this.ui.phoneItem.toggleClass('disabled', !this.model.get('current_page_supports_emulation_mode')); this.ui.phoneItem.toggleClass('active', this.model.has('emulation_mode')); this.ui.desktopItem.toggleClass('active', !this.model.has('emulation_mode')); this.ui.phoneDisplay.toggleClass('active', this.model.has('emulation_mode')); this.ui.desktopDisplay.toggleClass('active', !this.model.has('emulation_mode')); } }); function template$_(data) { var __t, __p = ''; __p += ((__t = ( I18n.t('pageflow.editor.templates.help_button.open_help') )) == null ? '' : __t); return __p } var HelpButtonView = Marionette.ItemView.extend({ template: template$_, className: 'help_button', events: { 'click': function click() { app.trigger('toggle-help'); } } }); var SidebarFooterView = Marionette.View.extend({ className: 'sidebar_footer', render: function render() { if (pageflow.features.isEnabled('editor_emulation_mode')) { this.appendSubview(new EmulationModeButtonView({ model: this.model })); } this.appendSubview(new HelpButtonView()); return this; } }); var HelpImageView = Marionette.View.extend({ tagName: 'img', className: 'help_image', render: function render() { this.$el.attr('src', state.editorAssetUrls.help[this.options.imageName]); return this; } }); var InfoBoxView = Marionette.View.extend({ className: 'info_box', render: function render() { this.$el.html(this.options.text); return this; } }); function template$$(data) { var __t, __p = ''; __p += '\n\n\n\n
      \n
      \n\n
      \n \n \n
      \n'; return __p } var ListItemView = Marionette.ItemView.extend({ template: template$$, tagName: 'li', className: 'list_item', ui: { thumbnail: '.list_item_thumbnail', typePictogram: '.list_item_type_pictogram', title: '.list_item_title', description: '.list_item_description', editButton: '.list_item_edit_button', removeButton: '.list_item_remove_button' }, events: { 'click .list_item_edit_button': function clickList_item_edit_button() { this.options.onEdit(this.model); return false; }, 'click .list_item_remove_button': function clickList_item_remove_button() { this.options.onRemove(this.model); return false; }, 'mouseenter': function mouseenter() { if (this.options.highlight) { this.model.highlight(); } }, 'mouseleave': function mouseleave() { if (this.options.highlight) { this.model.resetHighlight(); } } }, modelEvents: { 'change': 'update' }, onRender: function onRender() { this.subview(new ModelThumbnailView({ el: this.ui.thumbnail, model: this.model })); if (this.options.typeName) { this.$el.addClass(this.typeName()); } this.ui.editButton.toggleClass('is_available', !!this.options.onEdit); this.ui.removeButton.toggleClass('is_available', !!this.options.onRemove); this.update(); }, update: function update() { this.ui.typePictogram.attr('title', this.typeDescription()); this.ui.title.text(this.model.title() || I18n$1.t('pageflow.editor.views.page_link_item_view.unnamed')); this.ui.description.text(this.description()); this.ui.description.toggle(!!this.description()); this.$el.toggleClass('is_invalid', !!this.getOptionResult('isInvalid')); }, onClose: function onClose() { if (this.options.highlight) { this.model.resetHighlight(); } }, description: function description() { return this.getOptionResult('description'); }, typeName: function typeName() { return this.getOptionResult('typeName'); }, typeDescription: function typeDescription() { return this.getOptionResult('typeDescription'); }, getOptionResult: function getOptionResult(name) { return typeof this.options[name] === 'function' ? this.options[name](this.model) : this.options[name]; } }); function template$10(data) { var __t, __p = ''; __p += '
      \n

      ' + ((__t = ( I18n.t('pageflow.editor.templates.locked.loading') )) == null ? '' : __t) + '

      \n\n ' + ((__t = ( I18n.t('pageflow.editor.templates.locked.close') )) == null ? '' : __t) + '\n
      \n\n\n\n'; return __p } var LockedView = Marionette.ItemView.extend({ template: template$10, className: 'locked checking', ui: { breakButton: '.break', message: '.error .message' }, events: { 'click .close': 'goBack', 'click .break': 'breakLock' }, modelEvents: { acquired: 'hide', locked: 'show', unauthenticated: 'goBack' }, breakLock: function breakLock() { this.model.acquire({ force: true }); }, goBack: function goBack() { window.location = "/admin/entries/" + state.entry.id; }, show: function show(info, options) { var key = info.error + '.' + options.context; this.ui.message.html(I18n$1.t('pageflow.edit_locks.errors.' + key + '_html', { user_name: info.held_by })); this.ui.message.attr('data-error', key); this.ui.breakButton.text(I18n$1.t('pageflow.edit_locks.break_action.acquire')); this.$el.removeClass('checking'); this.$el.show(); }, hide: function hide() { this.ui.message.attr('data-error', null); this.$el.removeClass('checking'); this.$el.hide(); } }); var EditorView = Backbone.View.extend({ scrollNavigationKeys: _$1.values({ pageUp: 33, pageDown: 34, end: 35, home: 36, left: 37, up: 38, right: 39, down: 40 }), events: { 'click a': function clickA(event) { // prevent default for all links if (!$(event.currentTarget).attr('target') && !$(event.currentTarget).attr('download') && !$(event.currentTarget).attr('href')) { return false; } }, 'keydown sidebar': function keydownSidebar(event) { this.preventScrollingPreviewWhileFocusInSidebar(event); } }, initialize: function initialize() { $(window).on('beforeunload', function (event) { if (state.entry.get('uploading_files_count') > 0) { return I18n$1.t('pageflow.editor.views.editor_views.files_pending_warning'); } }); }, render: function render() { this.$el.layout({ minSize: 300, togglerTip_closed: I18n$1.t('pageflow.editor.views.editor_views.show_editor'), togglerTip_open: I18n$1.t('pageflow.editor.views.editor_views.hide_editor'), resizerTip: I18n$1.t('pageflow.editor.views.editor_views.resize_editor'), enableCursorHotkey: false, fxName: 'none', maskIframesOnResize: true, onresize: function onresize() { app.trigger('resize'); } }); new UploaderView().render(); this.$el.append(new LockedView({ model: state.editLock }).render().el); this.$el.append(new HelpView().render().el); }, preventScrollingPreviewWhileFocusInSidebar: function preventScrollingPreviewWhileFocusInSidebar(event) { if (this.scrollNavigationKeys.indexOf(event.which) >= 0) { event.stopPropagation(); } } }); var BackgroundImageEmbeddedView = Marionette.View.extend({ modelEvents: { 'change': 'update' }, render: function render() { this.update(); return this; }, update: function update() { if (this.options.useInlineStyles !== false) { this.updateInlineStyle(); } else { this.updateClassName(); } if (this.options.dataSizeAttributes) { this.updateDataSizeAttributes(); } }, updateClassName: function updateClassName() { this.$el.addClass('load_image'); var propertyName = this.options.propertyName.call ? this.options.propertyName() : this.options.propertyName; var id = this.model.get(propertyName); var prefix = this.options.backgroundImageClassNamePrefix.call ? this.options.backgroundImageClassNamePrefix() : this.options.backgroundImageClassNamePrefix; prefix = prefix || 'image'; var backgroundImageClassName = id && prefix + '_' + id; if (this.currentBackgroundImageClassName !== backgroundImageClassName) { this.$el.removeClass(this.currentBackgroundImageClassName); this.$el.addClass(backgroundImageClassName); this.currentBackgroundImageClassName = backgroundImageClassName; } }, updateInlineStyle: function updateInlineStyle() { this.$el.css({ backgroundImage: this.imageValue(), backgroundPosition: this.model.getFilePosition(this.options.propertyName, 'x') + '% ' + this.model.getFilePosition(this.options.propertyName, 'y') + '%' }); }, updateDataSizeAttributes: function updateDataSizeAttributes() { var imageFile = this.model.getImageFile(this.options.propertyName); if (imageFile && imageFile.isReady()) { this.$el.attr('data-width', imageFile.get('width')); this.$el.attr('data-height', imageFile.get('height')); } else { this.$el.attr('data-width', '16'); this.$el.attr('data-height', '9'); } this.$el.css({ backgroundPosition: '0 0' }); }, imageValue: function imageValue() { var url = this.model.getImageFileUrl(this.options.propertyName, { styleGroup: this.$el.data('styleGroup') }); return url ? 'url("' + url + '")' : 'none'; } }); var LazyVideoEmbeddedView = Marionette.View.extend({ modelEvents: { 'change': 'update' }, render: function render() { this.videoPlayer = this.$el.data('videoPlayer'); this.videoPlayer.ready(_$1.bind(function () { this.videoPlayer.src(this.model.getVideoFileSources(this.options.propertyName)); }, this)); this.update(); return this; }, update: function update() { if (this.videoPlayer.isPresent() && this.model.hasChanged(this.options.propertyName)) { var paused = this.videoPlayer.paused(); this.videoPlayer.src(this.model.getVideoFileSources(this.options.propertyName)); if (!paused) { this.videoPlayer.play(); } } if (this.options.dataSizeAttributes) { var videoFile = this.model.getVideoFile(this.options.propertyName); if (videoFile && videoFile.isReady()) { this.$el.attr('data-width', videoFile.get('width')); this.$el.attr('data-height', videoFile.get('height')); } else { this.$el.attr('data-width', '16'); this.$el.attr('data-height', '9'); } } } }); function template$11(data) { var __t, __p = ''; __p += '
    • 0' + ((__t = ( I18n.t('pageflow.editor.templates.notification.upload_pending') )) == null ? '' : __t) + '
    • \n
    • 0 ' + ((__t = ( I18n.t('pageflow.editor.templates.notification.save_error') )) == null ? '' : __t) + ' ' + ((__t = ( I18n.t('pageflow.editor.templates.notification.retry') )) == null ? '' : __t) + '
    • \n
    • ' + ((__t = ( I18n.t('pageflow.editor.templates.notification.saving') )) == null ? '' : __t) + '
    • \n
    • ' + ((__t = ( I18n.t('pageflow.editor.templates.notification.saved') )) == null ? '' : __t) + '
    • \n\n
    • \n ' + ((__t = ( I18n.t('pageflow.editor.templates.notification.approve_files', {num_files: '0'}) )) == null ? '' : __t) + '\n ' + ((__t = ( I18n.t('pageflow.editor.templates.notification.show') )) == null ? '' : __t) + '\n
    • \n'; return __p } var NotificationsView = Marionette.ItemView.extend({ className: 'notifications', tagName: 'ul', template: template$11, ui: { failedCount: '.failed .count', uploadingCount: '.uploading .count', confirmableFilesCount: '.confirmable_files .count' }, events: { 'click .retry': function clickRetry() { editor$1.failures.retry(); } }, onRender: function onRender() { this.listenTo(state.entry, 'change:uploading_files_count', this.notifyUploadCount); this.listenTo(state.entry, 'change:confirmable_files_count', this.notifyConfirmableFilesCount); this.listenTo(editor$1.savingRecords, 'add', this.update); this.listenTo(editor$1.savingRecords, 'remove', this.update); this.listenTo(editor$1.failures, 'add', this.update); this.listenTo(editor$1.failures, 'remove', this.update); this.update(); this.notifyConfirmableFilesCount(); }, update: function update() { this.$el.toggleClass('failed', !editor$1.failures.isEmpty()); this.$el.toggleClass('saving', !editor$1.savingRecords.isEmpty()); this.ui.failedCount.text(editor$1.failures.count()); }, notifyUploadCount: function notifyUploadCount(model, uploadCount) { this.$el.toggleClass('uploading', uploadCount > 0); this.ui.uploadingCount.text(uploadCount); }, notifyConfirmableFilesCount: function notifyConfirmableFilesCount() { var confirmableFilesCount = state.entry.get('confirmable_files_count'); this.$el.toggleClass('has_confirmable_files', confirmableFilesCount > 0); this.ui.confirmableFilesCount.text(confirmableFilesCount); } }); var FileProcessingStateDisplayView = Marionette.View.extend({ className: 'file_processing_state_display', mixins: [inputView], initialize: function initialize() { if (typeof this.options.collection === 'string') { this.options.collection = state.entry.getFileCollection(editor$1.fileTypes.findByCollectionName(this.options.collection)); } this.listenTo(this.model, 'change:' + this.options.propertyName, this._update); }, render: function render() { this._update(); return this; }, _update: function _update() { if (this.fileStagesView) { this.stopListening(this.file.unfinishedStages); this.fileStagesView.close(); this.fileStagesView = null; } this.file = this._getFile(); if (this.file) { this.listenTo(this.file.unfinishedStages, 'add remove', this._updateClassNames); this.fileStagesView = new CollectionView({ tagName: 'ul', collection: this.file.unfinishedStages, itemViewConstructor: FileStageItemView, itemViewOptions: { standAlone: true } }); this.appendSubview(this.fileStagesView); } this._updateClassNames(); }, _updateClassNames: function _updateClassNames() { this.$el.toggleClass('file_processing_state_display-empty', !this._hasItems()); }, _hasItems: function _hasItems() { return this.file && this.file.unfinishedStages.length; }, _getFile: function _getFile() { return this.model.getReference(this.options.propertyName, this.options.collection); } }); function template$12(data) { var __p = ''; __p += '

      \n'; return __p } var NestedFilesView = Marionette.ItemView.extend({ template: template$12, className: 'nested_files', ui: { header: 'h2' }, initialize: function initialize() { if (!this.options.selection.has('file')) { this.options.selection.set('file', this.collection.first()); this.options.selection.set('nextFile', this.collection.at(1)); } this.listenTo(this.collection, 'add', this.selectNewFile); this.listenTo(this.collection, 'remove', this.selectNextFileIfSelectionDeleted); this.listenTo(this.options.selection, 'change', this.setNextFile); this.listenTo(this.collection, 'add', this.update); this.listenTo(this.collection, 'remove', this.update); this.listenTo(this.collection, 'request', this.update); this.listenTo(this.collection, 'sync', this.update); }, onRender: function onRender() { this.ui.header.text(this.collection.parentModel.get('file_name')); this.appendSubview(new TableView({ collection: this.collection, attributeTranslationKeyPrefixes: ['pageflow.editor.nested_files.' + this.options.fileType.collectionName], columns: this.columns(this.options.fileType), selection: this.options.selection, selectionAttribute: 'file', blankSlateText: this.options.tableBlankSlateText })); this.update(); }, update: function update() { this.$el.toggleClass('is_empty', this.collection.length === 0); }, columns: function columns(fileType) { var nestedFilesColumns = _$1(fileType.nestedFileTableColumns).map(function (column) { return _$1.extend({}, column, { configurationAttribute: true }); }); nestedFilesColumns.push({ name: 'delete', cellView: DeleteRowTableCellView, cellViewOptions: { toggleDeleteButton: 'isUploading', invertToggleDeleteButton: true } }); return nestedFilesColumns; }, selectNewFile: function selectNewFile(file) { this.options.selection.set('file', file); this.setNextFile(); }, selectNextFileIfSelectionDeleted: function selectNextFileIfSelectionDeleted() { var fileIndex = this.collection.indexOf(this.options.selection.get('file')); if (fileIndex === -1) { var nextFile = this.options.selection.get('nextFile'); this.options.selection.set('file', nextFile); } }, setNextFile: _$1.debounce(function () { var fileIndex = this.collection.indexOf(this.options.selection.get('file')); if (typeof this.collection.at(fileIndex + 1) !== 'undefined') { this.options.selection.set('nextFile', this.collection.at(fileIndex + 1)); } else if (typeof this.collection.at(fileIndex - 1) !== 'undefined') { this.options.selection.set('nextFile', this.collection.at(fileIndex - 1)); } else { this.options.selection.set('nextFile', undefined); } }, 200) }); function template$13(data) { var __t, __p = ''; __p += '
      \n \n\n
      \n

      ' + ((__t = ( I18n.t('pageflow.editor.templates.text_tracks.edit_file_header') )) == null ? '' : __t) + '

      \n
      \n
      \n
      \n
      \n'; return __p } var TextTracksView = Marionette.Layout.extend({ template: template$13, className: 'text_tracks', regions: { selectedFileRegion: '.selected_file_region' }, ui: { filesPanel: '.files_panel', selectedFileHeader: '.selected_file_header' }, events: { 'click a.upload': 'upload' }, initialize: function initialize(options) { this.options = options || {}; this.selection = new Backbone.Model(); this.listenTo(this.selection, 'change', this.update); }, onRender: function onRender() { this.nestedFilesView = new NestedFilesView({ collection: this.model.nestedFiles(this.options.supersetCollection), fileType: editor$1.fileTypes.findByCollectionName('text_track_files'), selection: this.selection, model: this.model, tableBlankSlateText: I18n$1.t('pageflow.editor.nested_files.text_track_files.no_files_blank_slate') }); this.ui.filesPanel.append(this.subview(this.nestedFilesView).el); this.update(); editor$1.setUploadTargetFile(this.model); }, onClose: function onClose() { editor$1.setUploadTargetFile(undefined); }, update: function update() { var selectedFile = this.selection.get('file'); if (selectedFile) { this.selectedFileRegion.show(new EditFileView({ model: selectedFile, attributeTranslationKeyPrefixes: ['pageflow.editor.nested_files.text_track_files'] })); this.ui.selectedFileHeader.toggle(true); } else { this.selectedFileRegion.close(); this.ui.selectedFileHeader.toggle(false); } }, upload: function upload() { app.trigger('request-upload'); } }); var TextTracksFileMetaDataItemValueView = FileMetaDataItemValueView.extend({ initialize: function initialize() { this.textTrackFiles = this.model.nestedFiles(state.textTrackFiles); this.listenTo(this.textTrackFiles, 'add remove change:configuration', this.update); }, getText: function getText() { return this.textTrackFiles.map(function (textTrackFile) { return textTrackFile.displayLabel(); }).join(', '); } }); var DisabledAtmoIndicatorView = Marionette.View.extend({ className: 'disabled_atmo_indicator', events: { 'click': function click() { pageflow.atmo.enable(); } }, initialize: function initialize() { this.listenTo(pageflow.events, 'atmo:disabled', function () { this.$el.show(); }); this.listenTo(pageflow.events, 'atmo:enabled', function () { this.$el.hide(); }); this.$el.toggle(!!pageflow.atmo && pageflow.atmo.disabled); }, render: function render() { this.$el.attr('title', I18n$1.t('pageflow.editor.atmo.disabled')); return this; } }); function template$14(data) { var __p = ''; __p += '\n\n
        \n'; return __p } function blankSlateTemplate$2(data) { var __t, __p = ''; __p += ((__t = ( I18n.t('pageflow.editor.templates.list_blank_slate.text') )) == null ? '' : __t) + '\n'; return __p } /** * A generic list view with items consisting of a thumbnail, text and * possibly some buttons or a navigation arrow. * * Models inside the collection must implement the following methods: * * @param {Backbone.Collection} options.collection * * @param {Object} options * * @param {string} options.label * Text of the label to display above the list. * * @param {boolean} [options.highlight=false] * * @param {boolean} [options.sortable=false] * * @param {string|function} [options.itemDescription] * * @param {string|function} [options.itemTypeName] * * @param {string|function} [options.itemTypeDescription] * * @param {string|function} [options.itemIsInvalid] * * @param {function} [options.onEdit] * * @param {function} [options.onRemove] * * @class */ var ListView = Marionette.ItemView.extend({ template: template$14, className: 'list', ui: { label: '.list_label', items: '.list_items' }, onRender: function onRender() { var collectionViewConstructor = this.options.sortable ? SortableCollectionView : CollectionView; this.subview(new collectionViewConstructor({ el: this.ui.items, collection: this.collection, itemViewConstructor: ListItemView, itemViewOptions: _$1.extend({ description: this.options.itemDescription, typeName: this.options.itemTypeName, typeDescription: this.options.itemTypeDescription, isInvalid: this.options.itemIsInvalid }, _$1(this.options).pick('onEdit', 'onDelete', 'highlight')), blankSlateViewConstructor: Marionette.ItemView.extend({ tagName: 'li', className: 'list_blank_slate', template: blankSlateTemplate$2 }) })); this.ui.label.text(this.options.label); this.$el.toggleClass('with_type_pictogram', !!this.options.itemTypeName); } }); var ConfirmUploadView = Marionette.Layout.extend({ template: template$Q, className: 'confirm_upload editor dialog', mixins: [dialogView], regions: { selectedFileRegion: '.selected_file_region' }, ui: { filesPanel: '.files_panel' }, events: { 'click .upload': function clickUpload() { this.options.fileUploader.submit(); this.close(); } }, initialize: function initialize() { this.selection = new Backbone.Model(); this.listenTo(this.selection, 'change', this.update); }, onRender: function onRender() { this.options.fileTypes.each(function (fileType) { this.ui.filesPanel.append(this.subview(new UploadableFilesView({ collection: this.options.files[fileType.collectionName], fileType: fileType, selection: this.selection })).el); }, this); this.update(); }, onClose: function onClose() { this.options.fileUploader.abort(); }, update: function update() { var file = this.selection.get('file'); if (file) { this.selectedFileRegion.show(new EditFileView({ model: file })); } else { this.selectedFileRegion.close(); } } }); ConfirmUploadView.watch = function (fileUploader, fileTypes, files) { fileUploader.on('new:batch', function () { ConfirmUploadView.open({ fileUploader: fileUploader, fileTypes: fileTypes, files: files }); }); }; ConfirmUploadView.open = function (options) { app.dialogRegion.show(new ConfirmUploadView(options)); }; /** * Base view to edit configuration container models. Extend and * override the `configure` method which receives a {@link * ConfigurationEditorView} to define the tabs and inputs that shall * be displayed. * * Add a `translationKeyPrefix` property to the prototype and define * the following translations: * * * `.tabs`: used as `tabTranslationKeyPrefix` * of the `ConfigurationEditorView`. * * * `.attributes`: used as one of the * `attributeTranslationKeyPrefixes` of the * `ConfigurationEditorView`. * * * `.back` (optional): Back button label. * * * `.destroy` (optional): Destroy button * label. * * * `.confirm_destroy` (optional): Confirm * message displayed before destroying. * * * `.save_error` (optional): Header of the * failure message that is displayed if the model cannot be saved. * * * `.retry` (optional): Label of the retry * button of the failure message. * * @param {Object} options * @param {Backbone.Model} options.model - * Model including the {@link configurationContainer}, * {@link failureTracking} and {@link delayedDestroying} mixins. * * @since 15.1 */ var EditConfigurationView = Marionette.Layout.extend({ className: 'edit_configuration_view', template: function template(_ref) { var t = _ref.t; return "\n ".concat(t('back'), "\n ").concat(t('destroy'), "\n\n
        \n

        ").concat(t('save_error'), "

        \n

        \n ").concat(t('retry'), "\n
        \n\n
        \n "); }, serializeData: function serializeData() { var _this = this; return { t: function t(key) { return _this.t(key); } }; }, mixins: [failureIndicatingView], regions: { configurationContainer: '.configuration_container' }, events: { 'click a.back': 'goBack', 'click a.destroy': 'destroy' }, onRender: function onRender() { var translationKeyPrefix = _$1.result(this, 'translationKeyPrefix'); this.configurationEditor = new ConfigurationEditorView({ tabTranslationKeyPrefix: "".concat(translationKeyPrefix, ".tabs"), attributeTranslationKeyPrefixes: ["".concat(translationKeyPrefix, ".attributes")], model: this.model.configuration }); this.configure(this.configurationEditor); this.configurationContainer.show(this.configurationEditor); }, onShow: function onShow() { this.configurationEditor.refreshScroller(); }, destroy: function destroy() { if (window.confirm(this.t('confirm_destroy'))) { this.model.destroyWithDelay(); this.goBack(); } }, goBack: function goBack() { editor$1.navigate('/', { trigger: true }); }, t: function t(suffix) { var translationKeyPrefix = _$1.result(this, 'translationKeyPrefix'); return I18n$1.t("".concat(translationKeyPrefix, ".").concat(suffix), { defaultValue: I18n$1.t("pageflow.editor.views.edit_configuration.".concat(suffix)) }); } }); ConfigurationEditorView.register('audio', { configure: function configure() { this.tab('general', function () { this.group('general', { supportsTextPositionCenter: true }); this.input('additional_title', TextInputView); this.input('additional_description', TextAreaInputView, { size: 'short' }); }); this.tab('files', function () { this.input('audio_file_id', FileInputView, { collection: state.audioFiles, defaultTextTrackFilePropertyName: 'default_text_track_file_id' }); this.group('background'); this.input('thumbnail_image_id', FileInputView, { collection: state.imageFiles, positioning: false }); }); this.tab('options', function () { if (pageflow.features.isEnabled('waveform_player_controls')) { this.input('audio_player_controls_variant', SelectInputView, { values: ['default', 'waveform'] }); } this.input('waveform_color', ColorInputView, { visibleBinding: 'audio_player_controls_variant', visibleBindingValue: 'waveform', defaultValue: pageflow.theme.mainColor(), swatches: usedWaveformColors() }); this.input('autoplay', CheckBoxInputView); this.group('options', { canPauseAtmo: true }); }); function usedWaveformColors() { return _$1.chain(state.pages.map(function (page) { return page.configuration.get('waveform_color'); })).uniq().compact().value(); } } }); ConfigurationEditorView.register('background_image', { configure: function configure() { this.tab('general', function () { this.group('general', { supportsTextPositionCenter: true }); }); this.tab('files', function () { this.group('background'); this.input('thumbnail_image_id', FileInputView, { collection: state.imageFiles, positioning: false }); }); this.tab('options', function () { this.group('options'); }); } }); ConfigurationEditorView.register('video', { configure: function configure() { this.tab('general', function () { this.group('general', { supportsTextPositionCenter: true }); this.input('additional_title', TextInputView); this.input('additional_description', TextAreaInputView, { size: 'short' }); }); this.tab('files', function () { this.input('video_file_id', FileInputView, { collection: state.videoFiles, positioning: false, defaultTextTrackFilePropertyName: 'default_text_track_file_id' }); this.input('poster_image_id', FileInputView, { collection: state.imageFiles, positioning: false }); this.input('mobile_poster_image_id', FileInputView, { collection: state.imageFiles, positioning: true }); this.input('thumbnail_image_id', FileInputView, { collection: state.imageFiles, positioning: false }); }); this.tab('options', function () { this.input('autoplay', CheckBoxInputView); if (pageflow.features.isEnabled('auto_change_page')) { this.input('auto_change_page_on_ended', CheckBoxInputView); } this.group('options', { canPauseAtmo: true }); }); } }); ConfigurationEditorTabView.groups.define('background', function (options) { options = options || {}; var prefix = options.propertyNamePrefix ? options.propertyNamePrefix + '_' : ''; var backgroundTypeProperty = prefix + 'background_type'; this.input(backgroundTypeProperty, SelectInputView, { values: ['image', 'video'], ensureValueDefined: true }); this.input(prefix + 'background_image_id', FileInputView, { collection: state.imageFiles, visibleBinding: backgroundTypeProperty, visibleBindingValue: 'image', fileSelectionHandlerOptions: options }); this.input(prefix + 'video_file_id', FileInputView, { collection: state.videoFiles, visibleBinding: backgroundTypeProperty, visibleBindingValue: 'video', fileSelectionHandlerOptions: options }); this.input(prefix + 'poster_image_id', FileInputView, { collection: state.imageFiles, visibleBinding: backgroundTypeProperty, visibleBindingValue: 'video', fileSelectionHandlerOptions: options }); this.input(prefix + 'mobile_poster_image_id', FileInputView, { collection: state.imageFiles, visibleBinding: backgroundTypeProperty, visibleBindingValue: 'video', fileSelectionHandlerOptions: options }); }); ConfigurationEditorTabView.groups.define('general', function (options) { this.input('title', TextInputView, { required: true, maxLength: 5000 }); this.input('hide_title', CheckBoxInputView); this.input('tagline', TextInputView, { maxLength: 5000 }); this.input('subtitle', TextInputView, { maxLength: 5000 }); this.input('text', TextAreaInputView, { fragmentLinkInputView: PageLinkInputView, enableLists: true }); this.input('text_position', SelectInputView, { values: options.supportsTextPositionCenter ? Page.textPositions : Page.textPositionsWithoutCenterOption }); this.input('gradient_opacity', SliderInputView); this.input('invert', CheckBoxInputView); }); ConfigurationEditorTabView.groups.define('page_link', function () { this.input('label', TextInputView); this.input('target_page_id', PageLinkInputView); this.group('page_transitions', { includeBlank: true }); }); ConfigurationEditorTabView.groups.define('page_transitions', function (options) { var inputOptions = { translationKeyPrefix: 'pageflow.page_transitions', blankTranslationKey: 'pageflow.page_transitions.default', values: pageflow.pageTransitions.names() }; if (pageflow.navigationDirection.isHorizontalOnPhone()) { inputOptions.additionalInlineHelpText = I18n$1.t('pageflow.editor.phone_horizontal_slideshow_mode.page_transitions_inline_help'); } this.input(options.propertyName || 'page_transition', SelectInputView, _$1.extend(inputOptions, options)); }); ConfigurationEditorTabView.groups.define('options', function (options) { var theme = state.entry.getTheme(); this.input('display_in_navigation', CheckBoxInputView); if (theme.supportsEmphasizedPages()) { this.input('emphasize_in_navigation', CheckBoxInputView); } this.group('page_transitions', { propertyName: 'transition' }); if (pageflow.features.isEnabled('delayed_text_fade_in')) { this.input('delayed_text_fade_in', SelectInputView, { values: Page.delayedTextFadeIn }); } this.input('description', TextAreaInputView, { size: 'short', disableLinks: true }); this.input('atmo_audio_file_id', FileInputView, { collection: state.audioFiles }); if (options.canPauseAtmo) { this.input('atmo_during_playback', SelectInputView, { values: pageflow.Atmo.duringPlaybackModes }); } if (theme.supportsScrollIndicatorModes()) { this.input('scroll_indicator_mode', SelectInputView, { values: Page.scrollIndicatorModes }); this.input('scroll_indicator_orientation', SelectInputView, { values: Page.scrollIndicatorOrientations }); } }); editor$1.widgetTypes.register('classic_loading_spinner', { configurationEditorView: ConfigurationEditorView.extend({ configure: function configure() { this.tab('loading_spinner', function () { this.view(InfoBoxView, { text: I18n$1.t('pageflow.editor.classic_loading_spinner.widget_type_info_box_text') }); }); } }) }); editor$1.widgetTypes.registerRole('cookie_notice', { isOptional: true }); editor$1.widgetTypes.register('cookie_notice_bar', { configurationEditorView: ConfigurationEditorView.extend({ configure: function configure() { this.tab('cookie_notice_bar', function () { this.view(InfoBoxView, { text: I18n$1.t('pageflow.editor.cookie_notice_bar.widget_type_info_box_text') }); }); } }) }); editor$1.widgetTypes.register('media_loading_spinner', { configurationEditorView: ConfigurationEditorView.extend({ configure: function configure() { this.tab('loading_spinner', function () { this.view(InfoBoxView, { text: I18n$1.t('pageflow.editor.media_loading_spinner.widget_type_info_box_text') }); this.input('custom_background_image_id', FileInputView, { collection: 'image_files', fileSelectionHandler: 'widgetConfiguration' }); this.input('invert', CheckBoxInputView); this.input('remove_logo', CheckBoxInputView); this.input('blur_strength', SliderInputView); }); } }) }); editor$1.widgetTypes.register('phone_horizontal_slideshow_mode', { configurationEditorView: ConfigurationEditorView.extend({ configure: function configure() { this.tab('phone_horizontal_slideshow_mode', function () { this.view(InfoBoxView, { text: I18n$1.t('pageflow.editor.phone_horizontal_slideshow_mode.widget_type_info_box_text') }); this.view(HelpImageView, { imageName: 'phone_horizontal_slideshow_mode' }); }); } }) }); editor$1.widgetTypes.register('title_loading_spinner', { configurationEditorView: ConfigurationEditorView.extend({ configure: function configure() { this.tab('loading_spinner', function () { this.view(InfoBoxView, { text: I18n$1.t('pageflow.editor.title_loading_spinner.widget_type_info_box_text') }); this.input('title', TextInputView, { placeholder: state.entry.metadata.get('title') || state.entry.get('entry_title') }); this.input('subtitle', TextInputView); this.input('custom_background_image_id', FileInputView, { collection: 'image_files', fileSelectionHandler: 'widgetConfiguration' }); this.input('invert', CheckBoxInputView); this.input('remove_logo', CheckBoxInputView); this.input('blur_strength', SliderInputView); }); } }) }); app.addInitializer(function (options) { state.config = options.config; }); app.addInitializer(function (options) { state.editorAssetUrls = options.asset_urls; }); app.addInitializer(function (options) { state.seed = options.common; }); app.addInitializer(function (options) { pageflow.features.enable('editor', options.entry.enabled_feature_names); }); app.addInitializer(function (options) { pageflow.Audio.setup({ getSources: function getSources(audioFileId) { var file = state.audioFiles.getByPermaId(audioFileId); return file ? file.getSources() : ''; } }); }); app.addInitializer(function () { Backbone.history.on('route', function () { editor$1.applyDefaultHelpEntry(); }); }); app.addInitializer(function (options) { var textTracksMetaDataAttribute = { name: 'text_tracks', valueView: TextTracksFileMetaDataItemValueView, valueViewOptions: { settingsDialogTabLink: 'text_tracks' } }; var textTracksSettingsDialogTab = { name: 'text_tracks', view: TextTracksView, viewOptions: { supersetCollection: function supersetCollection() { return state.textTrackFiles; } } }; var altMetaDataAttribute = { name: 'alt', valueView: TextFileMetaDataItemValueView, valueViewOptions: { fromConfiguration: true, settingsDialogTabLink: 'general' } }; editor$1.fileTypes.register('image_files', { model: ImageFile, metaDataAttributes: ['dimensions', altMetaDataAttribute], matchUpload: /^image/, configurationEditorInputs: [{ name: 'alt', inputView: TextInputView }] }); editor$1.fileTypes.register('video_files', { model: VideoFile, metaDataAttributes: ['format', 'dimensions', 'duration', textTracksMetaDataAttribute, altMetaDataAttribute], matchUpload: /^video/, configurationEditorInputs: [{ name: 'alt', inputView: TextInputView }], settingsDialogTabs: [textTracksSettingsDialogTab] }); editor$1.fileTypes.register('audio_files', { model: AudioFile, metaDataAttributes: ['format', 'duration', textTracksMetaDataAttribute, altMetaDataAttribute], matchUpload: /^audio/, configurationEditorInputs: [{ name: 'alt', inputView: TextInputView }], settingsDialogTabs: [textTracksSettingsDialogTab] }); editor$1.fileTypes.register('text_track_files', { model: TextTrackFile, matchUpload: function matchUpload(upload) { return upload.name.match(/\.vtt$/) || upload.name.match(/\.srt$/); }, skipUploadConfirmation: true, configurationEditorInputs: [{ name: 'label', inputView: TextInputView, inputViewOptions: { placeholder: function placeholder(configuration) { var textTrackFile = configuration.parent; return textTrackFile.inferredLabel(); }, placeholderBinding: TextTrackFile.displayLabelBinding } }, { name: 'kind', inputView: SelectInputView, inputViewOptions: { values: state.config.availableTextTrackKinds, translationKeyPrefix: 'pageflow.config.text_track_kind' } }, { name: 'srclang', inputView: TextInputView, inputViewOptions: { required: true } }], nestedFileTableColumns: [{ name: 'label', cellView: TextTableCellView, value: function value(textTrackFile) { return textTrackFile.displayLabel(); }, contentBinding: TextTrackFile.displayLabelBinding }, { name: 'srclang', cellView: TextTableCellView, "default": I18n$1.t('pageflow.editor.text_track_files.srclang_missing') }, { name: 'kind', cellView: IconTableCellView, cellViewOptions: { icons: state.config.availableTextTrackKinds } }], nestedFilesOrder: { comparator: function comparator(textTrackFile) { return textTrackFile.displayLabel().toLowerCase(); }, binding: 'label' } }); editor$1.fileTypes.setup(options.config.fileTypes); }); app.addInitializer(function (options) { editor$1.widgetTypes.registerRole('navigation', { isOptional: true }); editor$1.widgetTypes.setup(options.widget_types); }); app.addInitializer(function (options) { state.files = FilesCollection.createForFileTypes(editor$1.fileTypes, options.files); state.imageFiles = state.files.image_files; state.videoFiles = state.files.video_files; state.audioFiles = state.files.audio_files; state.textTrackFiles = state.files.text_track_files; var widgets = new WidgetsCollection(options.widgets, { widgetTypes: editor$1.widgetTypes }); state.themes = new ThemesCollection(options.themes); state.pages = new PagesCollection(options.pages); state.chapters = new ChaptersCollection(options.chapters); state.storylines = new StorylinesCollection(options.storylines); state.entry = editor$1.createEntryModel(options, { widgets: widgets }); state.theming = new Theming(options.theming); state.account = new Backbone.Model(options.account); widgets.subject = state.entry; state.entryData = new PreviewEntryData({ entry: state.entry, storylines: state.storylines, chapters: state.chapters, pages: state.pages }); state.storylineOrdering = new StorylineOrdering(state.storylines, state.pages); state.storylineOrdering.sort({ silent: true }); state.storylineOrdering.watch(); state.pages.sort(); state.storylines.on('sort', _$1.debounce(function () { state.storylines.saveOrder(); }, 100)); editor$1.failures.watch(state.entry); editor$1.failures.watch(state.pages); editor$1.failures.watch(state.chapters); editor$1.savingRecords.watch(state.pages); editor$1.savingRecords.watch(state.chapters); pageflow.events.trigger('seed:loaded'); }); app.addInitializer(function (options) { state.fileUploader = new FileUploader({ entry: state.entry, fileTypes: editor$1.fileTypes }); ConfirmUploadView.watch(state.fileUploader, editor$1.fileTypes, state.files); }); app.addInitializer(function (options) { editor$1.pageTypes.setup(options.page_types); }); app.addInitializer(function (options) { var KEY_A = 65; var KEY_X = 88; $(document).on('keydown', function (event) { if (event.altKey && event.which === KEY_A) { if (pageflow.atmo.disabled) { pageflow.atmo.enable(); } else { pageflow.atmo.disable(); } } else if (event.altKey && event.which === KEY_X) { editor$1.navigate('pages/' + pageflow.slides.currentPage().data('id'), { trigger: true }); } }); }); app.addInitializer(function (options) { editor$1.fileImporters.setup(options.config.fileImporters); }); app.addInitializer(function (options) { state.editLock = new EditLockContainer(); state.editLock.watchForErrors(); state.editLock.acquire(); }); app.addInitializer(function (options) { state.entry.pollForPendingFiles(); }); app.addInitializer(function (options) { state.entry.on('change:pending_files_count', function (model, value) { if (value < state.entry.previous('pending_files_count')) { pageflow.stylesheet.reload('entry'); } }); state.entry.on('use:files', function () { pageflow.stylesheet.reload('entry'); }); state.entry.metadata.on('change:theme_name', function () { var theme = state.entry.getTheme(); pageflow.stylesheet.update('theme', theme.get('stylesheet_path')); }); }); app.addInitializer(function () { _$1.each(editor$1.sideBarRoutings, function (options) { new options.router({ controller: new options.controller({ region: app.sidebarRegion, entry: state.entry }) }); }); editor$1.router = new SidebarRouter({ controller: new SidebarController({ region: app.sidebarRegion, entry: state.entry }) }); window.editor = editor$1.router; }); app.addInitializer(function () { app.on('error', function (e) { if (e.message) { alert(e.message); } else { alert(I18n$1.t(e.name, { scope: 'pageflow.editor.errors', defaultValue: I18n$1.t('pageflow.editor.errors.unknown') })); } }); }); app.addInitializer(function () /* args */ { var context = this; var args = arguments; _$1.each(editor$1.initializers, function (fn) { fn.call(context, args); }); }); app.addInitializer(function (options) { new EditorView({ el: $('body') }).render(); new ScrollingView({ el: $('sidebar .scrolling'), region: app.sidebarRegion }).render(); app.previewRegion.show(new editor$1.entryType.previewView({ model: state.entry })); app.indicatorsRegion.show(new DisabledAtmoIndicatorView()); app.notificationsRegion.show(new NotificationsView()); app.sidebarFooterRegion.show(new SidebarFooterView({ model: state.entry })); Backbone.history.start({ root: options.root }); }); app.addRegions({ previewRegion: '#entry_preview', mainRegion: '#main_content', indicatorsRegion: '#editor_indicators', sidebarRegion: 'sidebar .container', dialogRegion: '.dialog_container', notificationsRegion: 'sidebar .notifications_container', sidebarFooterRegion: 'sidebar .sidebar_footer_container' }); exports.AudioFile = AudioFile; exports.BackButtonDecoratorView = BackButtonDecoratorView; exports.BackgroundImageEmbeddedView = BackgroundImageEmbeddedView; exports.BackgroundPositioningPreviewView = BackgroundPositioningPreviewView; exports.BackgroundPositioningSlidersView = BackgroundPositioningSlidersView; exports.BackgroundPositioningView = BackgroundPositioningView; exports.ChangeThemeDialogView = ChangeThemeDialogView; exports.Chapter = Chapter; exports.ChapterConfiguration = ChapterConfiguration; exports.ChapterPagesCollection = ChapterPagesCollection; exports.ChapterScaffold = ChapterScaffold; exports.ChaptersCollection = ChaptersCollection; exports.CheckBoxGroupInputView = CheckBoxGroupInputView; exports.CheckBoxInputView = CheckBoxInputView; exports.ChooseImporterView = ChooseImporterView; exports.CollectionView = CollectionView; exports.ColorInputView = ColorInputView; exports.Configuration = Configuration; exports.ConfigurationEditorTabView = ConfigurationEditorTabView; exports.ConfigurationEditorView = ConfigurationEditorView; exports.ConfirmEncodingView = ConfirmEncodingView; exports.ConfirmFileImportUploadView = ConfirmFileImportUploadView; exports.ConfirmUploadView = ConfirmUploadView; exports.ConfirmableFileItemView = ConfirmableFileItemView; exports.DeleteRowTableCellView = DeleteRowTableCellView; exports.DisabledAtmoIndicatorView = DisabledAtmoIndicatorView; exports.DropDownButtonItemListView = DropDownButtonItemListView; exports.DropDownButtonItemView = DropDownButtonItemView; exports.DropDownButtonView = DropDownButtonView; exports.EditChapterView = EditChapterView; exports.EditConfigurationView = EditConfigurationView; exports.EditEntryView = EditEntryView; exports.EditFileView = EditFileView; exports.EditLock = EditLock; exports.EditLockContainer = EditLockContainer; exports.EditMetaDataView = EditMetaDataView; exports.EditPageLinkView = EditPageLinkView; exports.EditPageView = EditPageView; exports.EditStorylineView = EditStorylineView; exports.EditWidgetView = EditWidgetView; exports.EditWidgetsView = EditWidgetsView; exports.EditorApi = EditorApi; exports.EditorView = EditorView; exports.EmulationModeButtonView = EmulationModeButtonView; exports.EncodedFile = EncodedFile; exports.EncodingConfirmation = EncodingConfirmation; exports.Entry = Entry; exports.EntryMetadata = EntryMetadata; exports.EntryMetadataFileSelectionHandler = EntryMetadataFileSelectionHandler; exports.EntryPublication = EntryPublication; exports.EntryPublicationQuotaDecoratorView = EntryPublicationQuotaDecoratorView; exports.EnumTableCellView = EnumTableCellView; exports.ExplorerFileItemView = ExplorerFileItemView; exports.ExtendedSelectInputView = ExtendedSelectInputView; exports.Failure = Failure; exports.FileConfiguration = FileConfiguration; exports.FileImport = FileImport; exports.FileInputView = FileInputView; exports.FileItemView = FileItemView; exports.FileMetaDataItemValueView = FileMetaDataItemValueView; exports.FileMetaDataItemView = FileMetaDataItemView; exports.FileProcessingStateDisplayView = FileProcessingStateDisplayView; exports.FileReuse = FileReuse; exports.FileSettingsDialogView = FileSettingsDialogView; exports.FileStage = FileStage; exports.FileStageItemView = FileStageItemView; exports.FileThumbnailView = FileThumbnailView; exports.FileTypes = FileTypes; exports.FileTypesCollection = FileTypesCollection; exports.FileUploader = FileUploader; exports.FilesCollection = FilesCollection; exports.FilesExplorerView = FilesExplorerView; exports.FilesImporterView = FilesImporterView; exports.FilesView = FilesView; exports.FilteredFilesView = FilteredFilesView; exports.ForeignKeySubsetCollection = ForeignKeySubsetCollection; exports.HelpButtonView = HelpButtonView; exports.HelpImageView = HelpImageView; exports.HelpView = HelpView; exports.IconTableCellView = IconTableCellView; exports.ImageFile = ImageFile; exports.InfoBoxView = InfoBoxView; exports.InvalidNestedTypeError = InvalidNestedTypeError; exports.JsonInputView = JsonInputView; exports.LazyVideoEmbeddedView = LazyVideoEmbeddedView; exports.ListItemView = ListItemView; exports.ListView = ListView; exports.LoadingView = LoadingView; exports.LockedView = LockedView; exports.ModelThumbnailView = ModelThumbnailView; exports.NestedFilesCollection = NestedFilesCollection; exports.NestedFilesView = NestedFilesView; exports.NestedTypeError = NestedTypeError; exports.NotificationsView = NotificationsView; exports.Object = BaseObject; exports.OrderedPageLinksCollection = OrderedPageLinksCollection; exports.OtherEntriesCollection = OtherEntriesCollection; exports.OtherEntriesCollectionView = OtherEntriesCollectionView; exports.OtherEntry = OtherEntry; exports.OtherEntryItemView = OtherEntryItemView; exports.Page = Page; exports.PageConfigurationFileSelectionHandler = PageConfigurationFileSelectionHandler; exports.PageLink = PageLink; exports.PageLinkConfigurationEditorView = PageLinkConfigurationEditorView; exports.PageLinkFileSelectionHandler = PageLinkFileSelectionHandler; exports.PageLinkInputView = PageLinkInputView; exports.PageLinkItemView = PageLinkItemView; exports.PageLinksCollection = PageLinksCollection; exports.PageLinksView = PageLinksView; exports.PageThumbnailView = PageThumbnailView; exports.PagesCollection = PagesCollection; exports.PresenceTableCellView = PresenceTableCellView; exports.PreviewEntryData = PreviewEntryData; exports.ProxyUrlInputView = ProxyUrlInputView; exports.PublishEntryView = PublishEntryView; exports.ReferenceInputView = ReferenceInputView; exports.ReusableFile = ReusableFile; exports.Scaffold = Scaffold; exports.ScrollingView = ScrollingView; exports.SelectButtonView = SelectButtonView; exports.SelectInputView = SelectInputView; exports.SidebarController = SidebarController; exports.SidebarFooterView = SidebarFooterView; exports.SidebarRouter = SidebarRouter; exports.SliderInputView = SliderInputView; exports.SortableCollectionView = SortableCollectionView; exports.StaticThumbnailView = StaticThumbnailView; exports.Storyline = Storyline; exports.StorylineChaptersCollection = StorylineChaptersCollection; exports.StorylineConfiguration = StorylineConfiguration; exports.StorylineOrdering = StorylineOrdering; exports.StorylineScaffold = StorylineScaffold; exports.StorylineTransitiveChildPages = StorylineTransitiveChildPages; exports.StorylinesCollection = StorylinesCollection; exports.SubsetCollection = SubsetCollection; exports.TableCellView = TableCellView; exports.TableHeaderCellView = TableHeaderCellView; exports.TableRowView = TableRowView; exports.TableView = TableView; exports.TabsView = TabsView; exports.TextAreaInputView = TextAreaInputView; exports.TextFileMetaDataItemValueView = TextFileMetaDataItemValueView; exports.TextInputView = TextInputView; exports.TextTableCellView = TextTableCellView; exports.TextTrackFile = TextTrackFile; exports.TextTracksFileMetaDataItemValueView = TextTracksFileMetaDataItemValueView; exports.TextTracksView = TextTracksView; exports.Theme = Theme; exports.ThemeInputView = ThemeInputView; exports.ThemeItemView = ThemeItemView; exports.ThemesCollection = ThemesCollection; exports.Theming = Theming; exports.TooltipView = TooltipView; exports.UnmatchedUploadError = UnmatchedUploadError; exports.UploadError = UploadError; exports.UploadableFile = UploadableFile; exports.UploadableFilesView = UploadableFilesView; exports.UploaderView = UploaderView; exports.UrlDisplayView = UrlDisplayView; exports.UrlInputView = UrlInputView; exports.VideoFile = VideoFile; exports.Widget = Widget; exports.WidgetConfiguration = WidgetConfiguration; exports.WidgetConfigurationFileSelectionHandler = WidgetConfigurationFileSelectionHandler; exports.WidgetItemView = WidgetItemView; exports.WidgetTypes = WidgetTypes; exports.WidgetsCollection = WidgetsCollection; exports.addAndReturnModel = addAndReturnModel; exports.app = app; exports.authenticationProvider = authenticationProvider; exports.configurationContainer = configurationContainer; exports.cssModulesUtils = cssModulesUtils; exports.delayedDestroying = delayedDestroying; exports.dialogView = dialogView; exports.editor = editor$1; exports.entryTypeEditorControllerUrls = entryTypeEditorControllerUrls; exports.failureIndicatingView = failureIndicatingView; exports.failureTracking = failureTracking; exports.fileWithType = fileWithType; exports.filesCountWatcher = filesCountWatcher; exports.formDataUtils = formDataUtils; exports.i18nUtils = i18nUtils; exports.inputView = inputView; exports.inputWithPlaceholderText = inputWithPlaceholderText; exports.loadable = loadable; exports.modelLifecycleTrackingView = modelLifecycleTrackingView; exports.orderedCollection = orderedCollection; exports.persistedPromise = persistedPromise; exports.polling = polling; exports.retryable = retryable; exports.selectableView = selectableView; exports.stageProvider = stageProvider; exports.startEditor = startEditor; exports.state = state; exports.stylesheet = stylesheet; exports.subviewContainer = subviewContainer; exports.tooltipContainer = tooltipContainer; exports.transientReferences = transientReferences; exports.validFileTypeTranslationList = validFileTypeTranslationList; return exports; }({}, Backbone, _, Backbone.Marionette, jQuery, I18n, Backbone.ChildViewContainer, IScroll, jQuery, wysihtml5, Cocktail));