From b0290dec32423f0e64e4200e6cb1e100dfec3c77 Mon Sep 17 00:00:00 2001 From: Tim Fischbach Date: Fri, 29 Sep 2023 11:58:06 +0200 Subject: [PATCH] Build --- app/assets/javascripts/pageflow/dist/ui.js | 3340 ++++ .../javascripts/pageflow_paged/dist/editor.js | 12935 ++++++++++++++++ .../pageflow_paged/dist/frontend.js | 9722 ++++++++++++ .../pageflow_paged/dist/react-client.js | 22 + .../pageflow_paged/dist/react-server.js | 19 + .../package/contentElements-editor.js | 1126 ++ .../package/contentElements-frontend.css | 1 + .../package/contentElements-frontend.js | 2222 +++ entry_types/scrolled/package/editor.js | 4777 ++++++ .../scrolled/package/frontend-server.js | 225 + .../EditableInlineText.module-fa9e3aff.js | 5309 +++++++ .../frontend/PhonePlatformContext-10a1d600.js | 32 + .../ToggleFullscreenCornerButton-727cce0d.js | 107 + .../package/frontend/Viewer-169e14ca.js | 154 + .../package/frontend/Viewer-ee1aa590.js | 341 + .../package/frontend/Wavesurfer-7d9cf1b7.js | 333 + .../package/frontend/arrowRight-92a34ccc.js | 77 + .../package/frontend/components-4a09bfa3.js | 2852 ++++ .../package/frontend/createSuper-d0f30da3.js | 115 + .../package/frontend/i18n-ddd92820.js | 1091 ++ .../package/frontend/index-02378634.js | 118 + .../scrolled/package/frontend/index.css | 1 + .../scrolled/package/frontend/index.js | 2078 +++ .../useContentElementEditorState-63045393.js | 52 + entry_types/scrolled/package/testHelpers.js | 473 + .../scrolled/package/widgets/consentBar.css | 1 + .../scrolled/package/widgets/consentBar.js | 426 + .../package/widgets/defaultNavigation.css | 9 + .../package/widgets/defaultNavigation.js | 622 + package/editor.js | 8371 ++++++++++ package/frontend.js | 2954 ++++ package/testHelpers.js | 786 + package/ui.js | 3295 ++++ 33 files changed, 63986 insertions(+) create mode 100644 app/assets/javascripts/pageflow/dist/ui.js create mode 100644 entry_types/paged/app/assets/javascripts/pageflow_paged/dist/editor.js create mode 100644 entry_types/paged/app/assets/javascripts/pageflow_paged/dist/frontend.js create mode 100644 entry_types/paged/app/assets/javascripts/pageflow_paged/dist/react-client.js create mode 100644 entry_types/paged/app/assets/javascripts/pageflow_paged/dist/react-server.js create mode 100644 entry_types/scrolled/package/contentElements-editor.js create mode 100644 entry_types/scrolled/package/contentElements-frontend.css create mode 100644 entry_types/scrolled/package/contentElements-frontend.js create mode 100644 entry_types/scrolled/package/editor.js create mode 100644 entry_types/scrolled/package/frontend-server.js create mode 100644 entry_types/scrolled/package/frontend/EditableInlineText.module-fa9e3aff.js create mode 100644 entry_types/scrolled/package/frontend/PhonePlatformContext-10a1d600.js create mode 100644 entry_types/scrolled/package/frontend/ToggleFullscreenCornerButton-727cce0d.js create mode 100644 entry_types/scrolled/package/frontend/Viewer-169e14ca.js create mode 100644 entry_types/scrolled/package/frontend/Viewer-ee1aa590.js create mode 100644 entry_types/scrolled/package/frontend/Wavesurfer-7d9cf1b7.js create mode 100644 entry_types/scrolled/package/frontend/arrowRight-92a34ccc.js create mode 100644 entry_types/scrolled/package/frontend/components-4a09bfa3.js create mode 100644 entry_types/scrolled/package/frontend/createSuper-d0f30da3.js create mode 100644 entry_types/scrolled/package/frontend/i18n-ddd92820.js create mode 100644 entry_types/scrolled/package/frontend/index-02378634.js create mode 100644 entry_types/scrolled/package/frontend/index.css create mode 100644 entry_types/scrolled/package/frontend/index.js create mode 100644 entry_types/scrolled/package/frontend/useContentElementEditorState-63045393.js create mode 100644 entry_types/scrolled/package/testHelpers.js create mode 100644 entry_types/scrolled/package/widgets/consentBar.css create mode 100644 entry_types/scrolled/package/widgets/consentBar.js create mode 100644 entry_types/scrolled/package/widgets/defaultNavigation.css create mode 100644 entry_types/scrolled/package/widgets/defaultNavigation.js create mode 100644 package/editor.js create mode 100644 package/frontend.js create mode 100644 package/testHelpers.js create mode 100644 package/ui.js diff --git a/app/assets/javascripts/pageflow/dist/ui.js b/app/assets/javascripts/pageflow/dist/ui.js new file mode 100644 index 0000000000..444cd73a7b --- /dev/null +++ b/app/assets/javascripts/pageflow/dist/ui.js @@ -0,0 +1,3340 @@ +this.pageflow = this.pageflow || {}; +this.pageflow._uiGlobalInterop = (function (exports, Marionette, _, $, I18n$1, Backbone, ChildViewContainer, IScroll, jquery_minicolors, wysihtml5, jqueryUi, Cocktail) { + 'use strict'; + + Marionette = Marionette && Marionette.hasOwnProperty('default') ? Marionette['default'] : Marionette; + _ = _ && _.hasOwnProperty('default') ? _['default'] : _; + $ = $ && $.hasOwnProperty('default') ? $['default'] : $; + I18n$1 = I18n$1 && I18n$1.hasOwnProperty('default') ? I18n$1['default'] : I18n$1; + Backbone = Backbone && Backbone.hasOwnProperty('default') ? Backbone['default'] : Backbone; + 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; + + /*global JST*/ + + Marionette.Renderer.render = function (template, data) { + if (_.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(_(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 _.chain(keys).reverse().reduce(function (result, key) { + var unescapedTranslation = I18n$1.t(key, _.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 _(keys).detect(function (key) { + return I18n$1.t(key, { + defaultValue: missing + }) !== missing; + }) || _.first(keys); + } + function translationKeysWithSuffix(keys, suffix) { + return _.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 (typeof Symbol === "undefined" || !(Symbol.iterator in Object(arr))) 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 _arrayLikeToArray(arr, len) { + if (len == null || len > arr.length) len = arr.length; + + for (var i = 0, arr2 = new Array(len); i < len; i++) { + arr2[i] = arr[i]; + } + + return arr2; + } + + function _unsupportedIterableToArray(o, minLen) { + if (!o) return; + if (typeof o === "string") return _arrayLikeToArray(o, minLen); + var n = Object.prototype.toString.call(o).slice(8, -1); + if (n === "Object" && o.constructor) n = o.constructor.name; + if (n === "Map" || n === "Set") return Array.from(n); + if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); + } + + function _nonIterableRest() { + throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); + } + + function _slicedToArray(arr, i) { + return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(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.events(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); + } + + _.extend(BaseObject.prototype, Backbone.Events, { + initialize: function initialize(options) {} + }); // The self-propagating extend function that Backbone classes use. + + + BaseObject.extend = Backbone.Model.extend; + + var serverSideValidation = { + initialize: function initialize() { + var _this = this; + + this.validationErrors = {}; + this.listenTo(this, 'error', function (model, request) { + if (request.status === 422) { + _this.validationErrors = JSON.parse(request.responseText).errors; + + _this.trigger('invalid'); + } + }); + this.listenTo(this, 'sync', function () { + _this.validationErrors = {}; + }); + } + }; + + 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(_.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: _.bind(function (event, ui) { + if (ui.item.parent().is(this.el)) { + this.updateOrder(); + } + }, this), + receive: _.bind(function (event, ui) { + var view = ui.item.data('view'); + this.reindexPositions(); + this.itemViews.add(view); + this.collection.add(view.model); + }, this), + remove: _.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, _.extend({ + placeholderModel: this.options.placeholderModel, + propertyName: propertyName, + attributeTranslationKeyPrefixes: this.options.attributeTranslationKeyPrefixes + }, options || {})); + }, + view: function view(_view, options) { + this.inputs.add(new _view(_.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() { + _.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 (_.include(this.tabNames, this.options.defaultTab)) { + return this.options.defaultTab; + } else { + return _.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 = _.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, callbackOrOptions, callback) { + callback = callback || callbackOrOptions; + var options = callback ? callbackOrOptions : {}; + this.tabsView.tab(name, _.bind(function () { + var tabView = new ConfigurationEditorTabView({ + model: options.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; + } + }); + + _.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 _(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.options.column.headerText || 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() { + _(this.options.columns).each(function (column) { + this.appendSubview(new column.cellView(_.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; + + _(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(_.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'); + this.$el.toggleClass('align_top_center', options.align === 'top center'); + + if (options.align === 'bottom right' || options.align === 'bottom left') { + offsetTop = 10; + offsetLeft = 0; + } else if (options.align === 'top center') { + 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); + } + }); + + 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; + } + + 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 _objectSpread2(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; + } + + var attributeBinding = { + setupBooleanAttributeBinding: function setupBooleanAttributeBinding(optionName, updateMethod) { + this.setupAttributeBinding(optionName, updateMethod, Boolean); + }, + getBooleanAttributBoundOption: function getBooleanAttributBoundOption(optionName) { + return this.getAttributeBoundOption(optionName, Boolean); + }, + setupAttributeBinding: function setupAttributeBinding(optionName, updateMethod) { + var _this = this; + + var normalize = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : function (value) { + return value; + }; + var binding = this.options["".concat(optionName, "Binding")]; + var view = this; + + if (binding) { + _.flatten([binding]).forEach(function (attribute) { + _this.listenTo(_this.model, 'change:' + attribute, update); + }); + } + + update(); + + function update() { + updateMethod.call(view, view.getAttributeBoundOption(optionName, normalize)); + } + }, + getAttributeBoundOption: function getAttributeBoundOption(optionName) { + var _this2 = this; + + var normalize = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function (value) { + return value; + }; + var binding = this.options["".concat(optionName, "Binding")]; + var bindingValueOptionName = "".concat(optionName, "BindingValue"); + var value = Array.isArray(binding) ? binding.map(function (attribute) { + return _this2.model.get(attribute); + }) : this.model.get(binding); + + if (bindingValueOptionName in this.options) { + return value === this.options[bindingValueOptionName]; + } else if (typeof this.options[optionName] === 'function') { + return normalize(this.options[optionName](value)); + } else if (optionName in this.options) { + return normalize(this.options[optionName]); + } else if (binding) { + return normalize(value); + } + } + }; + + /** + * 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 {string|string[]} [options.disabledBinding] + * Name of an attribute to control whether the input is disabled. If + * the `disabled` and `disabledBinding` options are not set, + * input will be disabled whenever this attribute has a truthy value. + * When multiple attribute names are passed, the function passed to + * the `disabled` option will receive an array of values in the same + * order. + * + * @param {function|boolean} [options.disabled] + * Render input as disabled. A Function taking the value of the + * `disabledBinding` attribute as parameter. Input will be disabled + * only if function returns `true`. + * + * @param {any} [options.disabledBindingValue] + * Input will be disabled whenever the value of the `disabledBinding` + * attribute equals the value of this option. + * + * @param {string|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. + * When multiple attribute names are passed, the function passed to + * the `visible` option will receive an array of values in the same + * order. + * + * @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 = _objectSpread2(_objectSpread2({}, attributeBinding), {}, { + ui: { + label: 'label', + 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, _.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.updateInlineHelp(); + this.setLabelFor(); + this.setupBooleanAttributeBinding('disabled', this.updateDisabled); + this.setupBooleanAttributeBinding('visible', this.updateVisible); + }, + + /** + * 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' + })); + }, + updateInlineHelp: function updateInlineHelp() { + this.ui.inlineHelp.html(this.inlineHelpText()); + + if (!this.inlineHelpText()) { + this.ui.inlineHelp.hide(); + } + }, + + /** + * 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.isDisabled()) { + keys = translationKeysWithSuffix(keys, 'disabled'); + } + + return _.compact([findTranslation(keys, { + defaultValue: '', + html: true + }), this.options.additionalInlineHelpText]).join(' '); + }, + setLabelFor: function setLabelFor() { + if (this.ui.input && this.ui.label.length === 1 && !this.ui.input.attr('id')) { + var id = 'input_' + this.model.modelName + '_' + this.options.propertyName; + this.ui.input.attr('id', id); + this.ui.label.attr('for', id); + } + }, + isDisabled: function isDisabled() { + return this.getBooleanAttributBoundOption('disabled'); + }, + updateDisabled: function updateDisabled() { + this.$el.toggleClass('input-disabled', !!this.isDisabled()); + this.updateInlineHelp(); + + if (this.ui.input) { + this.updateDisabledAttribute(this.ui.input); + } + }, + updateDisabledAttribute: function updateDisabledAttribute(element) { + if (this.isDisabled()) { + element.attr('disabled', true); + } else { + element.removeAttr('disabled'); + } + }, + updateVisible: function updateVisible() { + this.$el.toggleClass('hidden_via_binding', this.getBooleanAttributBoundOption('visible') === false); + } + }); + + 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 = _.map(this.options.values, function (value) { + return translationKeyPrefix + '.' + value; + }, this); + } + + this.options.texts = _.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() { + _.each(this.options.values, function (value, index) { + var option = '
    ' + '
    '; + this.ui.container.append($(option)); + }, this); + }, + save: function save() { + var configured = {}; + + _.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) { + _.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() && !_.isEmpty(url)); + this.ui.link.attr('href', url); + } + }); + + function _classCallCheck(instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } + } + + function _defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); + } + } + + function _createClass(Constructor, protoProps, staticProps) { + if (protoProps) _defineProperties(Constructor.prototype, protoProps); + if (staticProps) _defineProperties(Constructor, staticProps); + return Constructor; + } + + /** + * Input view for a number. + * + * See {@link inputView} for further options. + * + * @param {Object} [options] + * + * @param {string} [options.locale] + * Locale used to fomat and parse numbers. + * + * @class + */ + + var NumberInputView = Marionette.ItemView.extend({ + mixins: [inputView], + template: function template() { + return "\n \n \n "; + }, + ui: { + input: 'input' + }, + events: { + 'change': 'onChange' + }, + initialize: function initialize() { + this.parser = new NumberParser(this.options.locale); + }, + onRender: function onRender() { + this.load(); + this.listenTo(this.model, 'change:' + this.options.propertyName, this.load); + }, + onChange: function onChange() { + this.save(); + this.load(); + }, + onClose: function onClose() { + this.save(); + }, + save: function save() { + var inputValue = this.ui.input.val(); + this.model.set(this.options.propertyName, this.parser.parse(inputValue) || 0); + }, + load: function load() { + var input = this.ui.input; + var value = this.model.get(this.options.propertyName) || 0; + input.val(value.toLocaleString(this.options.locale, { + useGrouping: false + })); + }, + 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', ''); + } + }); + + var NumberParser = /*#__PURE__*/function () { + function NumberParser(locale) { + _classCallCheck(this, NumberParser); + + var format = new Intl.NumberFormat(locale); + var parts = format.formatToParts(12345.6); + var numerals = Array.from({ + length: 10 + }).map(function (_, i) { + return format.format(i); + }); + var index = new Map(numerals.map(function (d, i) { + return [d, i]; + })); + this._group = new RegExp("[".concat(parts.find(function (d) { + return d.type === "group"; + }).value, "]"), "g"); + this._decimal = new RegExp("[".concat(parts.find(function (d) { + return d.type === "decimal"; + }).value, "]")); + this._numeral = new RegExp("[".concat(numerals.join(""), "]"), "g"); + + this._index = function (d) { + return index.get(d); + }; + } + + _createClass(NumberParser, [{ + key: "parse", + value: function parse(string) { + string = string.trim().replace(this._group, "").replace(this._decimal, ".").replace(this._numeral, this._index); + return string ? +string : NaN; + } + }]); + + return NumberParser; + }(); + + /** + * 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); + } + }, + updateDisabled: function updateDisabled() { + this.updatePlaceholder(); + }, + updatePlaceholder: function updatePlaceholder() { + this.ui.input.attr('placeholder', this.placeholderText()); + }, + placeholderText: function placeholderText() { + if (!this.isDisabled() || !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(); + } + } + + return ''; + }, + placeholderModelValue: function placeholderModelValue() { + return this.options.placeholderModel && this.options.placeholderModel.get(this.options.propertyName); + } + }; + + var viewWithValidationErrorMessages = { + onRender: function onRender() { + this.listenTo(this.model, 'invalid sync', this.updateValidationErrorMessages); + this.updateValidationErrorMessages(); + }, + updateValidationErrorMessages: function updateValidationErrorMessages() { + var _this = this; + + var errors = this.model.validationErrors && this.model.validationErrors[this.options.propertyName] || []; + + if (errors.length) { + this.validationErrorList = this.validationErrorList || $('
      ').appendTo(this.el); + this.validationErrorList.html(''); + errors.forEach(function (error) { + return _this.validationErrorList.append("
    • ".concat(error, "
    • ")); + }); + this.$el.addClass('invalid'); + } else if (this.validationErrorList) { + this.validationErrorList.remove(); + this.validationErrorList = null; + this.$el.removeClass('invalid'); + } + } + }; + + 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, viewWithValidationErrorMessages], + 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: _.bind(function (color) { + this._saving = true; + + if (color === this.defaultValue()) { + this.model.unset(this.options.propertyName); + } else { + this.model.set(this.options.propertyName, color); + } + + this._saving = false; + }, 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.resetSwatchesInStoredSettings(); + this.ui.input.minicolors('settings', { + defaultValue: this.defaultValue(), + swatches: this.getSwatches() + }); + this.load(); + }, + // see https://github.com/claviska/jquery-minicolors/issues/287 + resetSwatchesInStoredSettings: function resetSwatchesInStoredSettings() { + var settings = this.ui.input.data('minicolors-settings'); + + if (settings) { + delete settings.swatches; + this.ui.input.data('minicolors-settings', settings); + } + }, + load: function load() { + if (!this._saving) { + 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 _.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. + * + * @param {function} [options.optionDisabled] + * Receives value and has to return boolean indicating whether + * option is disabled. + * + * @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 = _.pluck(this.options.collection, this.options.valueProperty); + + if (this.options.textProperty) { + this.options.texts = _.pluck(this.options.collection, this.options.textProperty); + } else if (this.options.translationKeyProperty) { + this.options.translationKeys = _.pluck(this.options.collection, this.options.translationKeyProperty); + } + + if (this.options.groupProperty) { + this.options.groups = _.pluck(this.options.collection, this.options.groupProperty); + } else if (this.options.groupTranslationKeyProperty) { + this.options.groupTanslationKeys = _.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 = _.map(this.options.values, function (value) { + return translationKeyPrefix + '.' + value; + }, this); + } + + this.options.texts = _.map(this.options.translationKeys, function (key) { + return I18n$1.t(key); + }); + } + + if (!this.options.groups) { + this.options.groups = _.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() { + _.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 (this.options.optionDisabled && this.options.optionDisabled(value)) { + option.setAttribute('disabled', true); + } + + 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 + '"]:not([disabled])').length) { + this.ui.select.val(value); + } else { + this.ui.select.val(this.ui.select.find('option:not([disabled]):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 = _.pluck(this.options.collection, this.options.descriptionProperty); + } else if (this.options.descriptionTranslationKeyProperty) { + this.options.descriptionTanslationKeys = _.pluck(this.options.collection, this.options.descriptionTranslationKeyProperty); + } + } + + if (!this.options.descriptions) { + this.options.descriptions = _.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', _.bind(this.save, this)); + this.editor.on('aftercommand:composer', _.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', _.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(''); + 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', _.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 _this = this; + + this.validate().then(function () { + return _this.save(); + }, function () { + return _this.saveDisplayProperty(); + }); + }, + saveDisplayProperty: function saveDisplayProperty() { + this.model.unset(this.options.propertyName, { + silent: true + }); + this.model.set(this.options.displayPropertyName, this.ui.input.val()); + }, + save: function save() { + var _this2 = this; + + var value = this.ui.input.val(); + $.when(this.transformPropertyValue(value)).then(function (transformedValue) { + var _this2$model$set; + + _this2.model.set((_this2$model$set = {}, _defineProperty(_this2$model$set, _this2.options.displayPropertyName, value), _defineProperty(_this2$model$set, _this2.options.propertyName, transformedValue), _this2$model$set)); + }); + }, + load: function load() { + this.ui.input.val(this.model.has(this.options.displayPropertyName) ? this.model.get(this.options.displayPropertyName) : this.model.get(this.options.propertyName)); + 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; + }, + // Host names used to be expected to include protocols. Remove + // protocols for backwards compatilbity. Since supportedHosts + // is supposed to be overridden in subclasses, we do it in a + // separate method. + supportedHostsWithoutLegacyProtocols: function supportedHostsWithoutLegacyProtocols() { + return _.map(this.supportedHosts(), function (host) { + return host.replace(/^https?:\/\//, ''); + }); + }, + 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') + _.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 _.any(view.supportedHostsWithoutLegacyProtocols(), function (host) { + return url.match(new RegExp('^https?://' + host)); + }); + } + + function displayValidationError(message) { + view.$el.addClass('invalid'); + view.ui.input.attr('aria-invalid', 'true'); + view.ui.validation.removeClass('pending').addClass('failed').html(message).show(); + } + + function displayValidationPending(message) { + view.$el.removeClass('invalid'); + view.ui.input.removeAttr('aria-invalid'); + view.ui.validation.removeClass('failed').addClass('pending').html(message).show(); + } + + function resetValidationError(message) { + view.$el.removeClass('invalid'); + view.ui.input.attr('aria-invalid', 'false'); + 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 _.pluck(this.options.proxies, 'url'); + }, + rewriteUrl: function rewriteUrl(url) { + _.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] + * + * @param {number} [options.defaultValue] + * Defaults value to display if property is not set. + * + * @param {number} [options.minValue=0] + * Value when dragging slider to the very left. + * + * @param {number} [options.maxValue=100] + * Value when dragging slider to the very right. + * + * @param {string} [options.unit="%"] + * Unit to display after value. + * + * @param {function} [options.displayText] + * Function that receives value and returns custom text to display as value. + * + * @param {boolean} [options.saveOnSlide] + * Already update the model while dragging the handle - not only after + * handle has been released. + * + * @class + */ + + var SliderInputView = Marionette.ItemView.extend({ + mixins: [inputView], + className: 'slider_input', + template: template$a, + ui: { + widget: '.slider', + value: '.value' + }, + events: { + 'slidechange': 'save', + 'slide': 'handleSlide' + }, + onRender: function onRender() { + var _this = this; + + this.ui.widget.slider({ + animate: 'fast' + }); + this.setupAttributeBinding('minValue', function (value) { + return _this.updateSliderOption('min', value || 0); + }); + this.setupAttributeBinding('maxValue', function (value) { + return _this.updateSliderOption('max', value || 100); + }); + this.load(); + this.listenTo(this.model, 'change:' + this.options.propertyName, this.load); + }, + updateSliderOption: function updateSliderOption(name, value) { + this.ui.widget.slider('option', name, value); + this.updateText(this.ui.widget.slider('value')); + }, + updateDisabled: function updateDisabled(disabled) { + this.$el.toggleClass('disabled', !!disabled); + + if (disabled) { + this.ui.widget.slider('disable'); + } else { + this.ui.widget.slider('enable'); + } + }, + handleSlide: function handleSlide(event, ui) { + this.updateText(ui.value); + + if (this.options.saveOnSlide) { + this.save(event, ui); + } + }, + save: function save(event, ui) { + this.model.set(this.options.propertyName, ui.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', this.clampValue(value)); + this.updateText(value); + }, + clampValue: function clampValue(value) { + var min = this.ui.widget.slider('option', 'min'); + var max = this.ui.widget.slider('option', 'max'); + return Math.min(max, Math.max(min, value)); + }, + updateText: function updateText(value) { + var unit = 'unit' in this.options ? this.options.unit : '%'; + var text = 'displayText' in this.options ? this.options.displayText(value) : value + unit; + this.ui.value.text(text); + } + }); + + 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. + * + * @param {boolean} [options.displayCheckedIfDisabled=false] + * Ignore the attribute value if the input is disabled and display + * an checked check box. + * + * @param {string} [options.storeInverted] + * Display checked by default and store true in given attribute when + * unchecked. The property name passed to `input` is only used for + * translations. + * + * @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); + }, + updateDisabled: function updateDisabled() { + this.load(); + }, + save: function save() { + if (!this.isDisabled()) { + var value = this.ui.input.is(':checked'); + + if (this.options.storeInverted) { + this.model.set(this.options.storeInverted, !value); + } else { + this.model.set(this.options.propertyName, value); + } + } + }, + load: function load() { + if (!this.isClosed) { + this.ui.input.prop('checked', this.displayValue()); + } + }, + displayValue: function displayValue() { + if (this.isDisabled() && this.options.displayUncheckedIfDisabled) { + return false; + } else if (this.isDisabled() && this.options.displayCheckedIfDisabled) { + return true; + } else if (this.options.storeInverted) { + return !this.model.get(this.options.storeInverted); + } else { + return this.model.get(this.options.propertyName); + } + } + }); + + /** + * Render a separator in a {@link ConfigurationEditorView} tab. + * + * @example + * + * this.view(SeparatorView); + * + * @class + */ + + var SeparatorView = Marionette.View.extend({ + className: 'separator' + }); + + /** + * Render an input that is only a label. Can be used to render + * additional inline help. + * + * See {@link inputView} for further options + * + * @class + */ + + var LabelOnlyView = Marionette.ItemView.extend({ + mixins: [inputView], + template: function template() { + return "\n \n "; + }, + ui: { + label: 'label' + } + }); + + /** + * 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) { + var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, + to = _ref.to; + + return (to || this.$el).append(this.subview(view).el); + }, + onClose: function onClose() { + if (this.subviews) { + this.subviews.call('close'); + } + } + }; + + if (!Marionette.View.prototype.appendSubview) { + Cocktail.mixin(Marionette.View, subviewContainer); + } + + var tooltipContainer = { + events: { + 'mouseover [data-tooltip]': function mouseoverDataTooltip(event) { + if (!this.tooltip.visible) { + var target = $(event.currentTarget); + var key = target.attr('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 if (target.data('tooltipAlign') === 'top center') { + position = { + left: target.position().left + target.outerWidth() / 2, + top: target.position().top + 2 + }; + } 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') + }); + } + }, + 'mouseleave [data-tooltip]': function mouseleaveDataTooltip() { + this.tooltip.hide(); + } + }, + onRender: function onRender() { + this.appendSubview(this.tooltip = new TooltipView()); + } + }; + + exports.CheckBoxGroupInputView = CheckBoxGroupInputView; + exports.CheckBoxInputView = CheckBoxInputView; + exports.CollectionView = CollectionView; + exports.ColorInputView = ColorInputView; + exports.ConfigurationEditorTabView = ConfigurationEditorTabView; + exports.ConfigurationEditorView = ConfigurationEditorView; + exports.DeleteRowTableCellView = DeleteRowTableCellView; + exports.EnumTableCellView = EnumTableCellView; + exports.ExtendedSelectInputView = ExtendedSelectInputView; + exports.IconTableCellView = IconTableCellView; + exports.JsonInputView = JsonInputView; + exports.LabelOnlyView = LabelOnlyView; + exports.NumberInputView = NumberInputView; + exports.Object = BaseObject; + exports.PresenceTableCellView = PresenceTableCellView; + exports.ProxyUrlInputView = ProxyUrlInputView; + exports.SelectInputView = SelectInputView; + exports.SeparatorView = SeparatorView; + exports.SliderInputView = SliderInputView; + exports.SortableCollectionView = SortableCollectionView; + exports.TableCellView = TableCellView; + exports.TableHeaderCellView = TableHeaderCellView; + exports.TableRowView = TableRowView; + exports.TableView = TableView; + exports.TabsView = TabsView; + exports.TextAreaInputView = TextAreaInputView; + exports.TextInputView = TextInputView; + exports.TextTableCellView = TextTableCellView; + exports.TooltipView = TooltipView; + exports.UrlDisplayView = UrlDisplayView; + exports.UrlInputView = UrlInputView; + exports.attributeBinding = attributeBinding; + exports.cssModulesUtils = cssModulesUtils; + exports.i18nUtils = i18nUtils; + exports.inputView = inputView; + exports.inputWithPlaceholderText = inputWithPlaceholderText; + exports.serverSideValidation = serverSideValidation; + exports.subviewContainer = subviewContainer; + exports.tooltipContainer = tooltipContainer; + exports.viewWithValidationErrorMessages = viewWithValidationErrorMessages; + + return exports; + +}({}, Backbone.Marionette, _, jQuery, I18n, Backbone, Backbone.ChildViewContainer, IScroll, jQuery, wysihtml5, jQuery, Cocktail)); diff --git a/entry_types/paged/app/assets/javascripts/pageflow_paged/dist/editor.js b/entry_types/paged/app/assets/javascripts/pageflow_paged/dist/editor.js new file mode 100644 index 0000000000..46ef5c6445 --- /dev/null +++ b/entry_types/paged/app/assets/javascripts/pageflow_paged/dist/editor.js @@ -0,0 +1,12935 @@ +var pageflow_paged = (function (exports, Backbone, _, Marionette, $, I18n$1, ChildViewContainer, IScroll, jquery_minicolors, wysihtml5, jqueryUi, Cocktail, frontend, frontend$1) { + 'use strict'; + + Backbone = Backbone && Backbone.hasOwnProperty('default') ? Backbone['default'] : Backbone; + _ = _ && _.hasOwnProperty('default') ? _['default'] : _; + 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; + + /*global JST*/ + + Marionette.Renderer.render = function (template, data) { + if (_.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(_(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 _.chain(keys).reverse().reduce(function (result, key) { + var unescapedTranslation = I18n$1.t(key, _.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 _(keys).detect(function (key) { + return I18n$1.t(key, { + defaultValue: missing + }) !== missing; + }) || _.first(keys); + } + + function translationKeysWithSuffix(keys, suffix) { + return _.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 (typeof Symbol === "undefined" || !(Symbol.iterator in Object(arr))) 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 _arrayLikeToArray(arr, len) { + if (len == null || len > arr.length) len = arr.length; + + for (var i = 0, arr2 = new Array(len); i < len; i++) { + arr2[i] = arr[i]; + } + + return arr2; + } + + function _unsupportedIterableToArray(o, minLen) { + if (!o) return; + if (typeof o === "string") return _arrayLikeToArray(o, minLen); + var n = Object.prototype.toString.call(o).slice(8, -1); + if (n === "Object" && o.constructor) n = o.constructor.name; + if (n === "Map" || n === "Set") return Array.from(n); + if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); + } + + function _nonIterableRest() { + throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); + } + + function _slicedToArray(arr, i) { + return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(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.events(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); + } + + _.extend(BaseObject.prototype, Backbone.Events, { + initialize: function initialize(options) {} + }); // The self-propagating extend function that Backbone classes use. + + + BaseObject.extend = Backbone.Model.extend; + var serverSideValidation = { + initialize: function initialize() { + var _this = this; + + this.validationErrors = {}; + this.listenTo(this, 'error', function (model, request) { + if (request.status === 422) { + _this.validationErrors = JSON.parse(request.responseText).errors; + + _this.trigger('invalid'); + } + }); + this.listenTo(this, 'sync', function () { + _this.validationErrors = {}; + }); + } + }; + 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(_.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: _.bind(function (event, ui) { + if (ui.item.parent().is(this.el)) { + this.updateOrder(); + } + }, this), + receive: _.bind(function (event, ui) { + var view = ui.item.data('view'); + this.reindexPositions(); + this.itemViews.add(view); + this.collection.add(view.model); + }, this), + remove: _.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, _.extend({ + placeholderModel: this.options.placeholderModel, + propertyName: propertyName, + attributeTranslationKeyPrefixes: this.options.attributeTranslationKeyPrefixes + }, options || {})); + }, + view: function view(_view, options) { + this.inputs.add(new _view(_.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() { + _.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 (_.include(this.tabNames, this.options.defaultTab)) { + return this.options.defaultTab; + } else { + return _.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 = _.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, callbackOrOptions, callback) { + callback = callback || callbackOrOptions; + var options = callback ? callbackOrOptions : {}; + this.tabsView.tab(name, _.bind(function () { + var tabView = new ConfigurationEditorTabView({ + model: options.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; + } + }); + + _.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 _(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.options.column.headerText || 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() { + _(this.options.columns).each(function (column) { + this.appendSubview(new column.cellView(_.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; + + _(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(_.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'); + this.$el.toggleClass('align_top_center', options.align === 'top center'); + + if (options.align === 'bottom right' || options.align === 'bottom left') { + offsetTop = 10; + offsetLeft = 0; + } else if (options.align === 'top center') { + 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); + } + }); + + 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; + } + + 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 _objectSpread2(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; + } + + var attributeBinding = { + setupBooleanAttributeBinding: function setupBooleanAttributeBinding(optionName, updateMethod) { + this.setupAttributeBinding(optionName, updateMethod, Boolean); + }, + getBooleanAttributBoundOption: function getBooleanAttributBoundOption(optionName) { + return this.getAttributeBoundOption(optionName, Boolean); + }, + setupAttributeBinding: function setupAttributeBinding(optionName, updateMethod) { + var _this = this; + + var normalize = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : function (value) { + return value; + }; + var binding = this.options["".concat(optionName, "Binding")]; + var view = this; + + if (binding) { + _.flatten([binding]).forEach(function (attribute) { + _this.listenTo(_this.model, 'change:' + attribute, update); + }); + } + + update(); + + function update() { + updateMethod.call(view, view.getAttributeBoundOption(optionName, normalize)); + } + }, + getAttributeBoundOption: function getAttributeBoundOption(optionName) { + var _this2 = this; + + var normalize = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function (value) { + return value; + }; + var binding = this.options["".concat(optionName, "Binding")]; + var bindingValueOptionName = "".concat(optionName, "BindingValue"); + var value = Array.isArray(binding) ? binding.map(function (attribute) { + return _this2.model.get(attribute); + }) : this.model.get(binding); + + if (bindingValueOptionName in this.options) { + return value === this.options[bindingValueOptionName]; + } else if (typeof this.options[optionName] === 'function') { + return normalize(this.options[optionName](value)); + } else if (optionName in this.options) { + return normalize(this.options[optionName]); + } else if (binding) { + return normalize(value); + } + } + }; + /** + * 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 {string|string[]} [options.disabledBinding] + * Name of an attribute to control whether the input is disabled. If + * the `disabled` and `disabledBinding` options are not set, + * input will be disabled whenever this attribute has a truthy value. + * When multiple attribute names are passed, the function passed to + * the `disabled` option will receive an array of values in the same + * order. + * + * @param {function|boolean} [options.disabled] + * Render input as disabled. A Function taking the value of the + * `disabledBinding` attribute as parameter. Input will be disabled + * only if function returns `true`. + * + * @param {any} [options.disabledBindingValue] + * Input will be disabled whenever the value of the `disabledBinding` + * attribute equals the value of this option. + * + * @param {string|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. + * When multiple attribute names are passed, the function passed to + * the `visible` option will receive an array of values in the same + * order. + * + * @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 = _objectSpread2(_objectSpread2({}, attributeBinding), {}, { + ui: { + label: 'label', + 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, _.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.updateInlineHelp(); + this.setLabelFor(); + this.setupBooleanAttributeBinding('disabled', this.updateDisabled); + this.setupBooleanAttributeBinding('visible', this.updateVisible); + }, + + /** + * 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' + })); + }, + updateInlineHelp: function updateInlineHelp() { + this.ui.inlineHelp.html(this.inlineHelpText()); + + if (!this.inlineHelpText()) { + this.ui.inlineHelp.hide(); + } + }, + + /** + * 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.isDisabled()) { + keys = translationKeysWithSuffix(keys, 'disabled'); + } + + return _.compact([findTranslation(keys, { + defaultValue: '', + html: true + }), this.options.additionalInlineHelpText]).join(' '); + }, + setLabelFor: function setLabelFor() { + if (this.ui.input && this.ui.label.length === 1 && !this.ui.input.attr('id')) { + var id = 'input_' + this.model.modelName + '_' + this.options.propertyName; + this.ui.input.attr('id', id); + this.ui.label.attr('for', id); + } + }, + isDisabled: function isDisabled() { + return this.getBooleanAttributBoundOption('disabled'); + }, + updateDisabled: function updateDisabled() { + this.$el.toggleClass('input-disabled', !!this.isDisabled()); + this.updateInlineHelp(); + + if (this.ui.input) { + this.updateDisabledAttribute(this.ui.input); + } + }, + updateDisabledAttribute: function updateDisabledAttribute(element) { + if (this.isDisabled()) { + element.attr('disabled', true); + } else { + element.removeAttr('disabled'); + } + }, + updateVisible: function updateVisible() { + this.$el.toggleClass('hidden_via_binding', this.getBooleanAttributBoundOption('visible') === false); + } + }); + + 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 = _.map(this.options.values, function (value) { + return translationKeyPrefix + '.' + value; + }, this); + } + + this.options.texts = _.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() { + _.each(this.options.values, function (value, index) { + var option = '
        ' + '
        '; + this.ui.container.append($(option)); + }, this); + }, + save: function save() { + var configured = {}; + + _.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) { + _.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() && !_.isEmpty(url)); + this.ui.link.attr('href', url); + } + }); + + function _classCallCheck(instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } + } + + function _defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); + } + } + + function _createClass(Constructor, protoProps, staticProps) { + if (protoProps) _defineProperties(Constructor.prototype, protoProps); + if (staticProps) _defineProperties(Constructor, staticProps); + return Constructor; + } + /** + * Input view for a number. + * + * See {@link inputView} for further options. + * + * @param {Object} [options] + * + * @param {string} [options.locale] + * Locale used to fomat and parse numbers. + * + * @class + */ + + + var NumberInputView = Marionette.ItemView.extend({ + mixins: [inputView], + template: function template() { + return "\n \n \n "; + }, + ui: { + input: 'input' + }, + events: { + 'change': 'onChange' + }, + initialize: function initialize() { + this.parser = new NumberParser(this.options.locale); + }, + onRender: function onRender() { + this.load(); + this.listenTo(this.model, 'change:' + this.options.propertyName, this.load); + }, + onChange: function onChange() { + this.save(); + this.load(); + }, + onClose: function onClose() { + this.save(); + }, + save: function save() { + var inputValue = this.ui.input.val(); + this.model.set(this.options.propertyName, this.parser.parse(inputValue) || 0); + }, + load: function load() { + var input = this.ui.input; + var value = this.model.get(this.options.propertyName) || 0; + input.val(value.toLocaleString(this.options.locale, { + useGrouping: false + })); + }, + 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', ''); + } + }); + + var NumberParser = /*#__PURE__*/function () { + function NumberParser(locale) { + _classCallCheck(this, NumberParser); + + var format = new Intl.NumberFormat(locale); + var parts = format.formatToParts(12345.6); + var numerals = Array.from({ + length: 10 + }).map(function (_, i) { + return format.format(i); + }); + var index = new Map(numerals.map(function (d, i) { + return [d, i]; + })); + this._group = new RegExp("[".concat(parts.find(function (d) { + return d.type === "group"; + }).value, "]"), "g"); + this._decimal = new RegExp("[".concat(parts.find(function (d) { + return d.type === "decimal"; + }).value, "]")); + this._numeral = new RegExp("[".concat(numerals.join(""), "]"), "g"); + + this._index = function (d) { + return index.get(d); + }; + } + + _createClass(NumberParser, [{ + key: "parse", + value: function parse(string) { + string = string.trim().replace(this._group, "").replace(this._decimal, ".").replace(this._numeral, this._index); + return string ? +string : NaN; + } + }]); + + return NumberParser; + }(); + /** + * 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); + } + }, + updateDisabled: function updateDisabled() { + this.updatePlaceholder(); + }, + updatePlaceholder: function updatePlaceholder() { + this.ui.input.attr('placeholder', this.placeholderText()); + }, + placeholderText: function placeholderText() { + if (!this.isDisabled() || !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(); + } + } + + return ''; + }, + placeholderModelValue: function placeholderModelValue() { + return this.options.placeholderModel && this.options.placeholderModel.get(this.options.propertyName); + } + }; + var viewWithValidationErrorMessages = { + onRender: function onRender() { + this.listenTo(this.model, 'invalid sync', this.updateValidationErrorMessages); + this.updateValidationErrorMessages(); + }, + updateValidationErrorMessages: function updateValidationErrorMessages() { + var _this = this; + + var errors = this.model.validationErrors && this.model.validationErrors[this.options.propertyName] || []; + + if (errors.length) { + this.validationErrorList = this.validationErrorList || $('
          ').appendTo(this.el); + this.validationErrorList.html(''); + errors.forEach(function (error) { + return _this.validationErrorList.append("
        • ".concat(error, "
        • ")); + }); + this.$el.addClass('invalid'); + } else if (this.validationErrorList) { + this.validationErrorList.remove(); + this.validationErrorList = null; + this.$el.removeClass('invalid'); + } + } + }; + + 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, viewWithValidationErrorMessages], + 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: _.bind(function (color) { + this._saving = true; + + if (color === this.defaultValue()) { + this.model.unset(this.options.propertyName); + } else { + this.model.set(this.options.propertyName, color); + } + + this._saving = false; + }, 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.resetSwatchesInStoredSettings(); + this.ui.input.minicolors('settings', { + defaultValue: this.defaultValue(), + swatches: this.getSwatches() + }); + this.load(); + }, + // see https://github.com/claviska/jquery-minicolors/issues/287 + resetSwatchesInStoredSettings: function resetSwatchesInStoredSettings() { + var settings = this.ui.input.data('minicolors-settings'); + + if (settings) { + delete settings.swatches; + this.ui.input.data('minicolors-settings', settings); + } + }, + load: function load() { + if (!this._saving) { + 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 _.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. + * + * @param {function} [options.optionDisabled] + * Receives value and has to return boolean indicating whether + * option is disabled. + * + * @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 = _.pluck(this.options.collection, this.options.valueProperty); + + if (this.options.textProperty) { + this.options.texts = _.pluck(this.options.collection, this.options.textProperty); + } else if (this.options.translationKeyProperty) { + this.options.translationKeys = _.pluck(this.options.collection, this.options.translationKeyProperty); + } + + if (this.options.groupProperty) { + this.options.groups = _.pluck(this.options.collection, this.options.groupProperty); + } else if (this.options.groupTranslationKeyProperty) { + this.options.groupTanslationKeys = _.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 = _.map(this.options.values, function (value) { + return translationKeyPrefix + '.' + value; + }, this); + } + + this.options.texts = _.map(this.options.translationKeys, function (key) { + return I18n$1.t(key); + }); + } + + if (!this.options.groups) { + this.options.groups = _.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() { + _.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 (this.options.optionDisabled && this.options.optionDisabled(value)) { + option.setAttribute('disabled', true); + } + + 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 + '"]:not([disabled])').length) { + this.ui.select.val(value); + } else { + this.ui.select.val(this.ui.select.find('option:not([disabled]):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 = _.pluck(this.options.collection, this.options.descriptionProperty); + } else if (this.options.descriptionTranslationKeyProperty) { + this.options.descriptionTanslationKeys = _.pluck(this.options.collection, this.options.descriptionTranslationKeyProperty); + } + } + + if (!this.options.descriptions) { + this.options.descriptions = _.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', _.bind(this.save, this)); + this.editor.on('aftercommand:composer', _.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', _.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(''); + 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', _.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 _this = this; + + this.validate().then(function () { + return _this.save(); + }, function () { + return _this.saveDisplayProperty(); + }); + }, + saveDisplayProperty: function saveDisplayProperty() { + this.model.unset(this.options.propertyName, { + silent: true + }); + this.model.set(this.options.displayPropertyName, this.ui.input.val()); + }, + save: function save() { + var _this2 = this; + + var value = this.ui.input.val(); + $.when(this.transformPropertyValue(value)).then(function (transformedValue) { + var _this2$model$set; + + _this2.model.set((_this2$model$set = {}, _defineProperty(_this2$model$set, _this2.options.displayPropertyName, value), _defineProperty(_this2$model$set, _this2.options.propertyName, transformedValue), _this2$model$set)); + }); + }, + load: function load() { + this.ui.input.val(this.model.has(this.options.displayPropertyName) ? this.model.get(this.options.displayPropertyName) : this.model.get(this.options.propertyName)); + 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; + }, + // Host names used to be expected to include protocols. Remove + // protocols for backwards compatilbity. Since supportedHosts + // is supposed to be overridden in subclasses, we do it in a + // separate method. + supportedHostsWithoutLegacyProtocols: function supportedHostsWithoutLegacyProtocols() { + return _.map(this.supportedHosts(), function (host) { + return host.replace(/^https?:\/\//, ''); + }); + }, + 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') + _.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 _.any(view.supportedHostsWithoutLegacyProtocols(), function (host) { + return url.match(new RegExp('^https?://' + host)); + }); + } + + function displayValidationError(message) { + view.$el.addClass('invalid'); + view.ui.input.attr('aria-invalid', 'true'); + view.ui.validation.removeClass('pending').addClass('failed').html(message).show(); + } + + function displayValidationPending(message) { + view.$el.removeClass('invalid'); + view.ui.input.removeAttr('aria-invalid'); + view.ui.validation.removeClass('failed').addClass('pending').html(message).show(); + } + + function resetValidationError(message) { + view.$el.removeClass('invalid'); + view.ui.input.attr('aria-invalid', 'false'); + 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 _.pluck(this.options.proxies, 'url'); + }, + rewriteUrl: function rewriteUrl(url) { + _.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] + * + * @param {number} [options.defaultValue] + * Defaults value to display if property is not set. + * + * @param {number} [options.minValue=0] + * Value when dragging slider to the very left. + * + * @param {number} [options.maxValue=100] + * Value when dragging slider to the very right. + * + * @param {string} [options.unit="%"] + * Unit to display after value. + * + * @param {function} [options.displayText] + * Function that receives value and returns custom text to display as value. + * + * @param {boolean} [options.saveOnSlide] + * Already update the model while dragging the handle - not only after + * handle has been released. + * + * @class + */ + + + var SliderInputView = Marionette.ItemView.extend({ + mixins: [inputView], + className: 'slider_input', + template: template$a, + ui: { + widget: '.slider', + value: '.value' + }, + events: { + 'slidechange': 'save', + 'slide': 'handleSlide' + }, + onRender: function onRender() { + var _this = this; + + this.ui.widget.slider({ + animate: 'fast' + }); + this.setupAttributeBinding('minValue', function (value) { + return _this.updateSliderOption('min', value || 0); + }); + this.setupAttributeBinding('maxValue', function (value) { + return _this.updateSliderOption('max', value || 100); + }); + this.load(); + this.listenTo(this.model, 'change:' + this.options.propertyName, this.load); + }, + updateSliderOption: function updateSliderOption(name, value) { + this.ui.widget.slider('option', name, value); + this.updateText(this.ui.widget.slider('value')); + }, + updateDisabled: function updateDisabled(disabled) { + this.$el.toggleClass('disabled', !!disabled); + + if (disabled) { + this.ui.widget.slider('disable'); + } else { + this.ui.widget.slider('enable'); + } + }, + handleSlide: function handleSlide(event, ui) { + this.updateText(ui.value); + + if (this.options.saveOnSlide) { + this.save(event, ui); + } + }, + save: function save(event, ui) { + this.model.set(this.options.propertyName, ui.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', this.clampValue(value)); + this.updateText(value); + }, + clampValue: function clampValue(value) { + var min = this.ui.widget.slider('option', 'min'); + var max = this.ui.widget.slider('option', 'max'); + return Math.min(max, Math.max(min, value)); + }, + updateText: function updateText(value) { + var unit = 'unit' in this.options ? this.options.unit : '%'; + var text = 'displayText' in this.options ? this.options.displayText(value) : value + unit; + this.ui.value.text(text); + } + }); + + 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. + * + * @param {boolean} [options.displayCheckedIfDisabled=false] + * Ignore the attribute value if the input is disabled and display + * an checked check box. + * + * @param {string} [options.storeInverted] + * Display checked by default and store true in given attribute when + * unchecked. The property name passed to `input` is only used for + * translations. + * + * @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); + }, + updateDisabled: function updateDisabled() { + this.load(); + }, + save: function save() { + if (!this.isDisabled()) { + var value = this.ui.input.is(':checked'); + + if (this.options.storeInverted) { + this.model.set(this.options.storeInverted, !value); + } else { + this.model.set(this.options.propertyName, value); + } + } + }, + load: function load() { + if (!this.isClosed) { + this.ui.input.prop('checked', this.displayValue()); + } + }, + displayValue: function displayValue() { + if (this.isDisabled() && this.options.displayUncheckedIfDisabled) { + return false; + } else if (this.isDisabled() && this.options.displayCheckedIfDisabled) { + return true; + } else if (this.options.storeInverted) { + return !this.model.get(this.options.storeInverted); + } else { + return this.model.get(this.options.propertyName); + } + } + }); + /** + * Render a separator in a {@link ConfigurationEditorView} tab. + * + * @example + * + * this.view(SeparatorView); + * + * @class + */ + + var SeparatorView = Marionette.View.extend({ + className: 'separator' + }); + /** + * Render an input that is only a label. Can be used to render + * additional inline help. + * + * See {@link inputView} for further options + * + * @class + */ + + var LabelOnlyView = Marionette.ItemView.extend({ + mixins: [inputView], + template: function template() { + return "\n \n "; + }, + ui: { + label: 'label' + } + }); + /** + * 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) { + var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, + to = _ref.to; + + return (to || this.$el).append(this.subview(view).el); + }, + onClose: function onClose() { + if (this.subviews) { + this.subviews.call('close'); + } + } + }; + + if (!Marionette.View.prototype.appendSubview) { + Cocktail.mixin(Marionette.View, subviewContainer); + } + + var tooltipContainer = { + events: { + 'mouseover [data-tooltip]': function mouseoverDataTooltip(event) { + if (!this.tooltip.visible) { + var target = $(event.currentTarget); + var key = target.attr('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 if (target.data('tooltipAlign') === 'top center') { + position = { + left: target.position().left + target.outerWidth() / 2, + top: target.position().top + 2 + }; + } 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') + }); + } + }, + 'mouseleave [data-tooltip]': function mouseleaveDataTooltip() { + this.tooltip.hide(); + } + }, + onRender: function onRender() { + this.appendSubview(this.tooltip = new TooltipView()); + } + }; + + (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$1(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; + } + + function ownKeys$1(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 _objectSpread2$1(target) { + for (var i = 1; i < arguments.length; i++) { + var source = arguments[i] != null ? arguments[i] : {}; + + if (i % 2) { + ownKeys$1(Object(source), true).forEach(function (key) { + _defineProperty$1(target, key, source[key]); + }); + } else if (Object.getOwnPropertyDescriptors) { + Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); + } else { + ownKeys$1(Object(source)).forEach(function (key) { + Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); + }); + } + } + + return target; + } + + var CommonPageConfigurationTabs = BaseObject.extend({ + initialize: function initialize() { + this.configureFns = {}; + }, + register: function register(name, configureFn) { + this.configureFns[name] = configureFn; + }, + apply: function apply(configurationEditorView) { + _.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() { + _.each(this.failures, function (failure, key) { + this.remove(key); + failure.retry(); + }, this); + }, + isEmpty: function isEmpty() { + return _.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 = _.size(this.failures); + }, + remove: function remove(key) { + delete this.failures[key]; + this.length = _.size(this.failures); + }, + count: function count() { + return this.length; + } + }); + + _.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 validFileTypes.map(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; + 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; + } + }); + + _.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 _[method].apply(_, 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'] + }); + + if (this.options.displayFileName) { + tab.input('file_name', TextInputView, { + model: this.model, + disabled: true + }); + } + + tab.input('rights', TextInputView, { + model: this.model, + placeholder: entry.get('default_file_rights') + }); + + _(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 _.chain(fileType.configurationEditorInputs).map(function (inputs) { + if (_.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$1$1(data) { + var __t, + __p = ''; + + __p += '\n'; + return __p; + } + + var FileSettingsDialogView = Marionette.ItemView.extend({ + template: template$1$1, + className: 'file_settings_dialog editor dialog', + mixins: [dialogView], + ui: { + content: '.content', + header: '.dialog-header' + }, + onRender: function onRender() { + this.ui.header.text(this.model.title()); + this.tabsView = new TabsView({ + model: this.model, + i18n: 'pageflow.editor.files.settings_dialog_tabs', + defaultTab: this.options.tabName + }); + + _.each(this.model.fileType().settingsDialogTabs, function (options) { + this.tabsView.tab(options.name, _.bind(function () { + return this.subview(new options.view(_.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$2$1(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$2$1, + 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 = _(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 _this = this; + + var clientSideConfigs = this.clientSideConfigs; + this._setup = true; + + var configs = _.chain(serverSideConfigs).map(function (serverSideConfig) { + var clientSideConfig = clientSideConfigs[serverSideConfig.collectionName]; + + if (!clientSideConfig) { + throw 'Missing client side config for file type "' + serverSideConfig.collectionName + '"'; + } + + _(_this.clientSideConfigModifications[serverSideConfig.collectionName]).each(function (modification) { + this.lintModification(modification, serverSideConfig.collectionName); + this.applyModification(clientSideConfig, modification); + }, _this); + + return _.extend({}, serverSideConfig, clientSideConfig); + }).sortBy(function (config) { + return config.priority || 10; + }).value(); + + this.collection = new FileTypesCollection(_.map(configs, function (config) { + return new FileType(config); + })); + var those = this; + + _.map(serverSideConfigs, function (serverSideConfig) { + var fileType = those.findByCollectionName(serverSideConfig.collectionName); + fileType.setNestedFileTypes(new FileTypesCollection(_.map(serverSideConfig.nestedFileTypes, function (nestedFileType) { + return those.findByCollectionName(nestedFileType.collectionName); + }))); + }); + }, + lintModification: function lintModification(modification, collectionName) { + var unmodifyableProperties = _.difference(_.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) { + _(this.modifyableProperties).each(function (property) { + target[property] = (target[property] || []).concat(modification[property] || []); + }); + } + }); + + _.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(_.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(_.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 = _.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')); + } + }); + + _.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 _[method].apply(_, 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; + }; + + _.extend(MultiCollection.prototype, { + add: function add(record) { + if (!this.records[record.cid]) { + this.records[record.cid] = record; + this.length = _.keys(this.records).length; + this.trigger('add', record); + } + }, + remove: function remove(record) { + if (this.records[record.cid]) { + delete this.records[record.cid]; + this.length = _.keys(this.records).length; + this.trigger('remove', record); + } + }, + isEmpty: function isEmpty() { + return this.length === 0; + } + }); + + _.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(_.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 = _.keys(serverSideConfigsByRole); + + this._widgetTypesByRole = roles.reduce(_.bind(function (result, role) { + result[role] = serverSideConfigsByRole[role].map(_.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]; + } + }); + /** + * 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. + * @param {function} options.isBrowserSupported + * Checks to see if the browser is supported. + * @param {function} options.browserNotSupportedView + * Backbone view that will be rendered if the browser is not supported. + */ + registerEntryType: function registerEntryType(name, options) { + this.entryType = _objectSpread2$1({ + 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}|{defaultTab: 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 or an object containingn a defaultTab property that controls + * which tab will visible initially, while allowing selecting files of + * any type. + * + * @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.defaultTab ? "".concat(fileType.defaultTab, ":default") : 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(_objectSpread2$1(_objectSpread2$1({}, 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](_objectSpread2$1(_objectSpread2$1({}, payloadJson), {}, { + entry: state.entry + })); + }, + createPageConfigurationEditorView: function createPageConfigurationEditorView(page, options) { + var view = this.pageTypes.findByPage(page).createConfigurationEditorView(_.extend(options, { + model: page.configuration + })); + this.commonPageConfigurationTabs.apply(view); + return view; + }, + ensureBrowserSupport: function ensureBrowserSupport(start) { + if (this.entryType.isBrowserSupported) { + var isBrowserSupported = this.entryType.isBrowserSupported(); + + if (isBrowserSupported) { + start(); + } else { + var browserNotSupportedView = new this.entryType.browserNotSupportedView(); + app.mainRegion.show(browserNotSupportedView); + } + } else { + start(); + } + } + }); + var editor = new EditorApi(); + + var startEditor = function startEditor(options) { + // In Webpack builds, I18n object from the i18n-js module is not + // identical to window.I18n which is provided by the i18n-js gem via + // the asset pipeline. Make translations provided via the asset + // pipeline available in Webpack bundle. + I18n$1.defaultLocale = window.I18n.defaultLocale; + I18n$1.locale = window.I18n.locale; + I18n$1.translations = window.I18n.translations; + $(function () { + editor.ensureBrowserSupport(function () { + Promise.all([$.getJSON('/editor/entries/' + options.entryId + '/seed'), frontend.browser.detectFeatures()]).then(function (result) { + return app.start(result[0]); + }, function () { + return 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.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 _(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() + (_.result(this.parent, 'urlSuffix') || _.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 = _.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, _.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.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', _.bind(callback, this)); + }, + _listenForReady: function _listenForReady(attribute, record) { + if (!record.isReady()) { + this.pendingReferences[attribute] = record; + this.listenTo(record, 'change:state', function (model, value, options) { + if (record.isReady()) { + this._cleanUpReadyListener(attribute); + + this.trigger('change', this, options); + 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 _.map(sources, function (source) { + var clone = _.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 _.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.pageTypes.findByName(this.get('template')); + }, + toJSON: function toJSON() { + return _.extend(_.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.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) { + _(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 ? _.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 = _.result(this, 'stages') || []; + this.stages = new Backbone.Collection(_.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); + + _.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 _.extend(_.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(_.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.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'); + }, + supportsHideLogoOnPages: function supportsHideLogoOnPages() { + return this.get('hide_logo_option'); + } + }); + 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(_.clone(attributes.configuration) || {}); + this.listenTo(this.configuration, 'change', function (model, options) { + this.trigger('change', model, options); + this.parent.save(); + }); + }, + // Pageflow Scrolled only synchronizes saved records to entry state. + isNew: function isNew() { + return false; + } + }); + 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 _(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 = _(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.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.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); + } + }); + 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(_.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 Site = Backbone.Model.extend({ + modelName: 'site', + i18nKey: 'pageflow/site', + collectionName: 'sites' + }); + 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$1 = options.editor || editor; + 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$1.nextUploadTargetFile; + + if (setTargetFile) { + if (fileType.topLevelType || !setTargetFile.fileType().nestedFileTypes.contains(fileType)) { + throw new InvalidNestedTypeError(upload, { + editor: editor$1, + 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() { + _(this.deferreds).invoke('resolve'); + + this.deferreds = []; + }, + abort: function abort() { + _(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.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.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(_.extend(defaults, attributes)); + }, + toJSON: function toJSON() { + return _.extend(_.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.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 _([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(_.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 _.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.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 (model, options) { + if (!this.isNew() && (!this.isDestroying || !this.isDestroying()) && (!this.isDestroyed || !this.isDestroyed()) && autoSave && options.autoSave !== false) { + this.save(); + } + + this.trigger('change:configuration', this, undefined, options); + + _.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 = _.clone(this.attributes); + } else if (includeAttributesInJSON) { + attributes = _.pick(this.attributes, includeAttributesInJSON); + } + + return _.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', _.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(_.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; // In 15.1 `entry.configuration` was turned into a new `Metadata` + // model. Some of the entry type specific data (like + // `home_button_enabled`) was extraced into + // `entry.metadata.configuration`. Attributes like `title` or `locale` + // which used to live in `entry.configuration` now live in + // entry.metadata. Since some plugins (e.g. `pageflow-vr`) depend on + // reading the locale from `entry.configuration`, this `configuration` + // keeps backwards compatibility. + + this.configuration = this.metadata; + this.themes = options.themes || state.themes; + this.files = options.files || state.files; + this.fileTypes = options.fileTypes || editor.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')); + }, + supportsPhoneEmulation: function supportsPhoneEmulation() { + return true; + }, + addStoryline: function addStoryline(attributes) { + var storyline = this.buildStoryline(attributes); + storyline.save(); + return storyline; + }, + buildStoryline: function buildStoryline(attributes) { + var defaults = { + title: '' + }; + return this.storylines.addAndReturnModel(_.extend(defaults, attributes)); + }, + scaffoldStoryline: function scaffoldStoryline(options) { + var scaffold = new StorylineScaffold(this, options); + scaffold.create(); + return scaffold; + }, + addChapterInNewStoryline: function addChapterInNewStoryline(options) { + return this.scaffoldStoryline(_.extend({ + depth: 'chapter' + }, options)).chapter; + }, + addPageInNewStoryline: function addPageInNewStoryline(options) { + return this.scaffoldStoryline(_.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(_.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 = _.map(filesAttributes, function (fileAttributes) { + return _.omit(fileAttributes, 'rights'); + }); + } + + this.getFileCollection(fileType).set(filesAttributes, _.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 fileType = editor.fileTypes.findByCollectionName(collectionName); + var currentEntry = this.get('currentEntry'); + var selections = currentEntry.getFileCollection(fileType).uploadable(); + this.sync('create', this, { + attrs: { + collection: collectionName, + files: selections.toJSON().map(function (item, index) { + return _objectSpread2$1(_objectSpread2$1({}, item), {}, { + url: selections.at(index).get('source_url') + }); + }) + }, + success: function success(items) { + items.forEach(function (item) { + var file = selections.find(function (file) { + return file.get('source_url') == item.source_url; + }); + + if (file) { + file.set(item.attributes); + } + }); + } + }); + } + }); + 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; + this.autoConsolidatePositions = options.autoConsolidatePositions; + this.listenTo(this, 'add', function (model) { + if (options.parentReferenceAttribute) { + model[options.parentReferenceAttribute] = parentModel; + } + + model.set(options.foreignKeyAttribute, parentModel.id); + }); + 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(parentModel, 'destroy dependentDestroy', function () { + this.invoke('trigger', 'dependentDestroy'); + 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, _.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, _.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 = _.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(_.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, _.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: { + '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 _arrayWithHoles$1(arr) { + if (Array.isArray(arr)) return arr; + } + + function _iterableToArrayLimit$1(arr, i) { + if (typeof Symbol === "undefined" || !(Symbol.iterator in Object(arr))) 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 _arrayLikeToArray$1(arr, len) { + if (len == null || len > arr.length) len = arr.length; + + for (var i = 0, arr2 = new Array(len); i < len; i++) { + arr2[i] = arr[i]; + } + + return arr2; + } + + function _unsupportedIterableToArray$1(o, minLen) { + if (!o) return; + if (typeof o === "string") return _arrayLikeToArray$1(o, minLen); + var n = Object.prototype.toString.call(o).slice(8, -1); + if (n === "Object" && o.constructor) n = o.constructor.name; + if (n === "Map" || n === "Set") return Array.from(n); + if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray$1(o, minLen); + } + + function _nonIterableRest$1() { + throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); + } + + function _slicedToArray$1(arr, i) { + return _arrayWithHoles$1(arr) || _iterableToArrayLimit$1(arr, i) || _unsupportedIterableToArray$1(arr, i) || _nonIterableRest$1(); + } + + function template$3$1(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$3$1, + className: 'back_button_decorator', + events: { + 'click a.back': 'goBack' + }, + regions: { + outlet: '.outlet' + }, + onRender: function onRender() { + this.outlet.show(this.options.view); + }, + goBack: function goBack() { + this.options.view.onGoBack && this.options.view.onGoBack(); + editor.navigate('/', { + trigger: true + }); + } + }); + + function template$4$1(data) { + var __t, + __p = ''; + + __p += '\n\n\n\n
          \n \n
          \n'; + return __p; + } + + var ConfirmableFileItemView = Marionette.ItemView.extend({ + tagName: 'li', + template: template$4$1, + 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$5$1(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
          \n\n
          \n \n
          \n\n
          \n \n
          \n\n
          \n
          \n\n'; + return __p; + } + + var ConfirmEncodingView = Marionette.ItemView.extend({ + template: template$5$1, + className: 'confirm_encoding', + ui: { + blankSlate: '.blank_slate', + videoFilesPanel: '.video_files_panel', + audioFilesPanel: '.audio_files_panel', + summary: '.summary', + intro: '.intro', + 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.intro.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.intro.html(this.model.get('intro_html')); + 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$1({}, "click .".concat(classNames.retryButton), function click() { + editor.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$6$1(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$6$1, + 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.navigate('/publish', { + trigger: true + }); + } + + return false; + }, + 'click .menu a': function clickMenuA(event) { + editor.navigate($(event.target).data('path'), { + trigger: true + }); + return false; + } + }, + onRender: function onRender() { + this._addMenuItems(); + + this._updatePublishButton(); + + this.outlineRegion.show(new editor.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; + + _.each(editor.mainMenuItems, function (options) { + var item = $('
        • '); + var link = item.find('a'); + + if (options.path) { + link.data('path', options.path); + } + + if (options.id) { + link.attr('data-main-menu-item', options.id); + } + + link.text(I18n$1.t(options.translationKey)); + + if (options.click) { + $(link).click(options.click); + } + + view.ui.menu.append(item); + }); + } + }); + + function template$7$1(data) { + var __t, + __p = ''; + + __p += '
          \n
          \n\n'; + return __p; + } + + var WidgetItemView = Marionette.Layout.extend({ + template: template$7$1, + tagName: 'li', + className: 'widget_item', + regions: { + widgetTypeContainer: '.widget_type' + }, + modelEvents: { + 'change:type_name': 'update' + }, + events: { + 'click .settings': function clickSettings() { + editor.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$8$1(data) { + var __p = ''; + __p += '
            \n
          \n'; + return __p; + } + + var EditWidgetsView = Marionette.Layout.extend({ + template: template$8$1, + 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$9$1(data) { + var __p = ''; + __p += '
          \n
          \n'; + return __p; + } + + var BackgroundPositioningPreviewView = Marionette.ItemView.extend({ + template: template$9$1, + 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$a$1(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$a$1, + 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$b$1(data) { + var __t, + __p = ''; + + __p += '
          \n
          \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
          \n'; + return __p; + } + + var BackgroundPositioningView = Marionette.ItemView.extend({ + template: template$b$1, + className: 'background_positioning editor 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; + + _.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$c$1(data) { + var __p = ''; + __p += '
          \n\n'; + return __p; + } + + var DropDownButtonItemView = Marionette.ItemView.extend({ + template: template$c$1, + tagName: 'li', + className: 'drop_down_button_item', + ui: { + link: '> a', + label: '> .label' + }, + events: { + 'click > a': function clickA(event) { + if (!this.model.get('disabled')) { + this.model.selected(); + } + + event.preventDefault(); + } + }, + 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$d$1(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. + * + * @param {boolean} [options.fullWidth] + * Make button and drop down span 100% of available width. + * + * @param {boolean} [options.openOnClick] + * Require click to open menu. By default, menu opens on when the + * mouse enters the button. + * + * @param {String} [options.alignMenu] + * "right" to align menu on the right. Aligned on the left by + * default. + * + * @param {String} [options.buttonClassName] + * CSS class name for button element. + * + * ## 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$d$1, + className: 'drop_down_button', + ui: { + button: '> button', + menu: '.drop_down_button_menu' + }, + events: function events() { + var _ref; + + return _ref = {}, _defineProperty$1(_ref, this.options.openOnClick ? 'click' : 'mouseenter', function () { + this.positionMenu(); + this.showMenu(); + }), _defineProperty$1(_ref, 'mouseleave', function mouseleave() { + this.scheduleHideMenu(); + }), _ref; + }, + onRender: function onRender() { + var view = this; + this.$el.toggleClass('full_width', !!this.options.fullWidth); + this.ui.button.toggleClass('has_icon_and_text', !!this.options.label); + this.ui.button.toggleClass('has_icon_only', !this.options.label); + this.ui.button.toggleClass('ellipsis_icon', !!this.options.ellipsisIcon); + this.ui.button.text(this.options.label); + this.ui.button.addClass(this.options.buttonClassName); + 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(); + }, + 'click': function click() { + view.hideMenu(); + } + }); + 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: this.options.alignMenu === 'right' ? offset.left + this.$el.width() - this.ui.menu.outerWidth() : offset.left, + width: this.options.fullWidth ? this.$el.width() : null + }); + }, + showMenu: function showMenu() { + this.ensureOnlyOneDropDownButtonShowsMenu(); + clearTimeout(this.hideMenuTimeout); + this.ui.menu.addClass('is_visible'); + this.ui.button.addClass('hover'); + }, + ensureOnlyOneDropDownButtonShowsMenu: function ensureOnlyOneDropDownButtonShowsMenu() { + if (DropDownButtonView.currentlyShowingMenu) { + DropDownButtonView.currentlyShowingMenu.hideMenu(); + } + + DropDownButtonView.currentlyShowingMenu = this; + }, + hideMenu: function hideMenu() { + clearTimeout(this.hideMenuTimeout); + + if (!this.isClosed) { + this.ui.button.removeClass('hover'); + this.ui.menu.removeClass('is_visible'); + } + }, + scheduleHideMenu: function scheduleHideMenu() { + this.hideMenuTimeout = setTimeout(_.bind(this.hideMenu, this), 300); + } + }); + + function template$e$1(data) { + var __p = ''; + __p += '
          \n'; + return __p; + } + + var FileThumbnailView = Marionette.ItemView.extend({ + className: 'file_thumbnail', + template: template$e$1, + 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'); + } + }); + /** + * Input view to reference a file. + * + * @class + */ + + var FileInputView = Marionette.ItemView.extend({ + mixins: [inputView], + template: function template() { + return "\n \n
          \n
          \n\n \n \n \n \n "); + }, + className: 'file_input', + ui: { + fileName: '.file_name', + thumbnail: '.file_thumbnail' + }, + events: { + 'click .choose': function clickChoose() { + editor.selectFile({ + name: this.options.collection.name, + filter: this.options.filter + }, this.options.fileSelectionHandler || 'pageConfiguration', _.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 = _.extend({ + positioning: true, + textTrackFiles: state.textTrackFiles + }, this.options); + + if (typeof this.options.collection === 'string') { + this.options.collection = state.entry.getFileCollection(editor.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, + ellipsisIcon: true, + openOnClick: true + })); + } + }, + 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 _this = this; + + 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) { + _.each(this.options.dropDownMenuItems, function (item) { + items.add(new FileInputView.CustomMenuItem({ + name: item.name, + label: item.label + }, { + inputModel: _this.model, + propertyName: _this.options.propertyName, + file: file, + selected: item.selected + })); + }); + + 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.CustomMenuItem = Backbone.Model.extend({ + initialize: function initialize(attributes, options) { + this.options = options; + }, + selected: function selected() { + this.options.selected({ + inputModel: this.options.inputModel, + propertyName: this.options.propertyName, + file: this.options.file + }); + } + }); + 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$f(data) { + var __p = ''; + __p += '
          \n
          \n
          \n
          \n
          \n
          \n
          \n'; + return __p; + } + + var LoadingView = Marionette.ItemView.extend({ + template: template$f, + 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$g(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$g, + 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$h(data) { + var __t, + __p = ''; + + __p += '
          \n

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

          \n
          \n
          \n
          \n
          \n

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

          \n
          \n \n
          \n
          \n
          \n \n
          \n'; + return __p; + } + + var ChangeThemeDialogView = Marionette.ItemView.extend({ + template: template$h, + className: 'change_theme dialog editor', + mixins: [dialogView], + ui: { + 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$i(data) { + var __p = ''; + __p += '\n'; + return __p; + } + + var StaticThumbnailView = Marionette.ItemView.extend({ + template: template$i, + 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 (_.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$j(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$j, + 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$k(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$k, + 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: _.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, + site: state.site + }); + 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() { + this.options.editor.navigate('/', { + trigger: true + }); + } + }); + + function template$l(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$l, + className: 'edit_widget', + events: { + 'click a.back': function clickABack() { + editor.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$m(data) { + var __p = ''; + __p += '\n\n\n'; + return __p; + } + + var ExplorerFileItemView = Marionette.ItemView.extend({ + tagName: 'li', + template: template$m, + 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 && _.contains(this.options.disabledIds, this.model.get('id')); + } + }); + + function template$n(data) { + var __p = ''; + __p += '\n \n\n'; + return __p; + } + + var OtherEntryItemView = Marionette.ItemView.extend({ + template: template$n, + 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$o(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$o, + tagName: 'li', + className: 'blank_slate' + }), + loadingViewConstructor: LoadingView + })); + this.otherEntries.fetch(); + return this; + } + }); + + function template$p(data) { + var __t, + __p = ''; + + __p += '
          \n

          \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
          \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$p, + 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.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, _.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$q(data) { + var __p = ''; + __p += '\n'; + return __p; + } + + var FileMetaDataItemView = Marionette.ItemView.extend({ + tagName: 'tr', + template: template$q, + ui: { + label: 'th', + value: 'td' + }, + onRender: function onRender() { + this.subview(new this.options.valueView(_.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$r(data) { + var __p = ''; + __p += '

          \n

          \n

          '; + return __p; + } + + var FileStageItemView = Marionette.ItemView.extend({ + tagName: 'li', + className: 'file_stage_item', + template: template$r, + 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$s(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$s, + 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.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 + })); + + _.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() && !_.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 _.map(this.options.metaDataAttributes, function (options) { + if (typeof options === 'string') { + options = { + name: options, + valueView: TextFileMetaDataItemValueView + }; + } + + return new FileMetaDataItemView(_.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.navigate('/confirmable_files?type=' + this.model.modelName + '&id=' + this.model.id, { + trigger: true + }); + }, + retry: function retry() { + this.model.retry(); + } + }); + + function template$t(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$t, + className: 'filtered_files', + ui: { + banner: '.filtered_files-banner', + filterName: '.filtered_files-filter_name' + }, + events: { + 'click .filtered_files-reset_filter': function clickFiltered_filesReset_filter() { + editor.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$u(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$v(data) { + var __t, + __p = ''; + + __p += ''; + return __p; + } + + var ImporterSelectView = Marionette.ItemView.extend({ + template: template$v, + 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$u, + 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.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$w(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$x(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
            \n

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

            \n
            \n
            \n
            \n
            \n\n \n
            \n'; + return __p; + } + + var UploadableFilesView = Marionette.View.extend({ + className: 'uploadable_files', + initialize: function initialize() { + this.uploadableFiles = this.collection.uploadable(); + + if (!this.options.selection.has('file')) { + this.options.selection.set('file', this.uploadableFiles.first()); + } + }, + render: function render() { + this.appendSubview(new TableView({ + collection: this.uploadableFiles, + attributeTranslationKeyPrefixes: ['pageflow.editor.files.attributes.' + this.options.fileType.collectionName, 'pageflow.editor.files.common_attributes'], + columns: this.commonColumns({ + fileTypeDisplayName: I18n$1.t('pageflow.editor.files.tabs.' + this.options.fileType.collectionName) + }).concat(this.fileTypeColumns()), + selection: this.options.selection, + selectionAttribute: 'file' + })); + this.listenTo(this.uploadableFiles, 'add remove', this.update); + this.update(); + return this; + }, + update: function update() { + this.$el.toggleClass('is_empty', this.uploadableFiles.length === 0); + }, + commonColumns: function commonColumns(options) { + return [{ + name: 'file_name', + headerText: options.fileTypeDisplayName, + cellView: TextTableCellView + }, { + name: 'rights', + cellView: PresenceTableCellView + }]; + }, + fileTypeColumns: function fileTypeColumns() { + return _(this.options.fileType.confirmUploadTableColumns).map(function (column) { + return _.extend({}, column, { + configurationAttribute: true + }); + }); + } + }); + var ConfirmFileImportUploadView = Marionette.Layout.extend({ + template: template$x, + 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 = []; + + _.each(state.files, function (collection) { + 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 EditFileView({ + model: file + })); + } else { + this.selectedFileRegion.close(); + } + } + }); + + ConfirmFileImportUploadView.open = function (options) { + app.dialogRegion.show(new ConfirmFileImportUploadView(options)); + }; + + var FilesImporterView = Marionette.ItemView.extend({ + template: template$w, + 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.fileTypes.findByUpload(file); + 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.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$y(data) { + var __t, + __p = ''; + + __p += '\n\n\n'; + return __p; + } + + var SelectButtonView = Marionette.ItemView.extend({ + template: template$y, + 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$z(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$z, + 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.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.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.allowSelectingAny || this.options.tabName === fileType.collectionName; + this.tabsView.tab(fileType.collectionName, _.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.navigate(this.options.selectionHandler.getReferer(), { + trigger: true + }); + } else { + editor.navigate('/', { + trigger: true + }); + } + }, + upload: function upload() { + app.trigger('request-upload'); + } + }); + + function template$A(data) { + var __p = ''; + __p += '
            \n
            \n
            \n
            \n
            \n
            \n'; + return __p; + } + + var EntryPublicationQuotaDecoratorView = Marionette.Layout.extend({ + template: template$A, + 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$B(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 \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$B, + 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', this.defaultPublishedUntilDate()); + } + + this.ui.userNameField.val(this.options.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); + } + } + }, + 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; + } + }, + defaultPublishedUntilDate: function defaultPublishedUntilDate() { + var date = new Date(); + date.setMonth(date.getMonth() + this.options.config.defaultPublishedUntilDurationInMonths); + return date; + }, + 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 (_.all([this.checkPublishUntil(), this.checkPassword()])) { + this.enableSave(); + } else { + this.disableSave(); + } + }, + checkPublishUntil: function checkPublishUntil() { + return this.ui.publishForeverRadioBox.is(':checked') || this.ui.publishUntilRadioBox.is(':checked') && _.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 _(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) { + var _split = (collectionName || '').split(':'), + _split2 = _slicedToArray$1(_split, 2), + tabName = _split2[0], + suffix = _split2[1]; + + this.region.show(new FilesView({ + model: this.entry, + selectionHandler: handler && editor.createFileSelectionHandler(handler, payload), + tabName: tabName, + allowSelectingAny: suffix === 'default', + filterName: filterName + })); + editor.setDefaultHelpEntry('pageflow.help_entries.files'); + }, + confirmableFiles: function confirmableFiles(preselectedFileType, preselectedFileId) { + var model = EncodingConfirmation.createWithPreselection({ + fileType: preselectedFileType, + fileId: preselectedFileId + }); + model.check(); + this.region.show(ConfirmEncodingView.create({ + model: model + })); + }, + metaData: function metaData(tab) { + this.region.show(new EditMetaDataView({ + model: this.entry, + tab: tab, + state: state, + features: frontend.features, + editor: editor + })); + }, + publish: function publish() { + this.region.show(PublishEntryView.create({ + model: this.entry, + account: state.account, + entryPublication: new EntryPublication(), + config: state.config + })); + editor.setDefaultHelpEntry('pageflow.help_entries.publish'); + }, + 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$C(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$C, + 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.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$D(data) { + var __t, + __p = ''; + + __p += '
            \n \n \n
            \n
            \n \n \n
            \n'; + return __p; + } + + var PageLinkItemView = Marionette.ItemView.extend({ + template: template$D, + tagName: 'li', + className: 'page_link', + ui: { + thumbnail: '.page_thumbnail', + title: '.title', + label: '.label', + editButton: '.edit', + removeButton: '.remove' + }, + events: { + 'click .edit': function clickEdit() { + editor.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$E(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$E, + className: 'page_links', + ui: { + links: 'ul.links', + addButton: '.add_link' + }, + events: { + 'click .add_link': function clickAdd_link() { + var view = this; + editor.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$F(data) { + var __p = ''; + __p += '
            \n
            \n
            \n
            \n
            \n
            \n'; + return __p; + } + + var EmulationModeButtonView = Marionette.ItemView.extend({ + template: template$F, + className: 'emulation_mode_button', + mixins: [tooltipContainer], + ui: { + wrapper: '.emulation_mode_button-wrapper', + desktopIcon: '.emulation_mode_button-desktop_icon', + phoneIcon: '.emulation_mode_button-phone_icon' + }, + events: { + 'click': function click() { + if (this.model.get('emulation_mode_disabled')) { + return; + } + + if (this.model.has('emulation_mode')) { + this.model.unset('emulation_mode'); + } else { + this.model.set('emulation_mode', 'phone'); + } + } + }, + modelEvents: { + 'change:emulation_mode change:emulation_mode_disabled': 'update' + }, + onRender: function onRender() { + this.update(); + }, + update: function update() { + this.$el.toggleClass('disabled', !!this.model.get('emulation_mode_disabled')); + this.$el.toggleClass('active', this.model.has('emulation_mode')); + this.ui.wrapper.attr('data-tooltip', this.model.get('emulation_mode_disabled') ? 'pageflow.editor.templates.emulation_mode_button.disabled_hint' : 'pageflow.editor.templates.emulation_mode_button.tooltip'); + } + }); + + function template$G(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$G, + className: 'help_button', + events: { + 'click': function click() { + app.trigger('toggle-help'); + } + } + }); + var SidebarFooterView = Marionette.View.extend({ + className: 'sidebar_footer', + render: function render() { + if (this.model.supportsPhoneEmulation()) { + 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', + mixins: [attributeBinding], + initialize: function initialize() { + this.setupBooleanAttributeBinding('visible', this.updateVisible); + }, + updateVisible: function updateVisible() { + this.$el.toggleClass('hidden_via_binding', this.getBooleanAttributBoundOption('visible') === false); + }, + render: function render() { + this.$el.addClass(this.options.level); + this.$el.html(this.options.text); + return this; + } + }); + + function template$H(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$H, + 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$I(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$I, + 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({ + 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; + } + } + }, + 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); + } + }); + 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(_.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$J(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$J, + ui: { + failedCount: '.failed .count', + uploadingCount: '.uploading .count', + confirmableFilesCount: '.confirmable_files .count' + }, + events: { + 'click .retry': function clickRetry() { + editor.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.savingRecords, 'add', this.update); + this.listenTo(editor.savingRecords, 'remove', this.update); + this.listenTo(editor.failures, 'add', this.update); + this.listenTo(editor.failures, 'remove', this.update); + this.update(); + this.notifyConfirmableFilesCount(); + }, + update: function update() { + this.$el.toggleClass('failed', !editor.failures.isEmpty()); + this.$el.toggleClass('saving', !editor.savingRecords.isEmpty()); + this.ui.failedCount.text(editor.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.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); + } + }); + var NestedFilesView = Marionette.View.extend({ + className: 'nested_files', + 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); + }, + render: function render() { + 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(); + return this; + }, + update: function update() { + this.$el.toggleClass('is_empty', this.collection.length === 0); + }, + columns: function columns(fileType) { + var nestedFilesColumns = _(fileType.nestedFileTableColumns).map(function (column) { + return _.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: _.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$K(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$K, + 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.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.setUploadTargetFile(this.model); + }, + onClose: function onClose() { + editor.setUploadTargetFile(undefined); + }, + update: function update() { + var selectedFile = this.selection.get('file'); + + if (selectedFile) { + this.selectedFileRegion.show(new EditFileView({ + model: selectedFile, + displayFileName: true, + 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(', '); + } + }); + + function template$L(data) { + var __p = ''; + __p += '\n\n
              \n'; + return __p; + } + + function blankSlateTemplate$1$1(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$L, + 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: _.extend({ + description: this.options.itemDescription, + typeName: this.options.itemTypeName, + typeDescription: this.options.itemTypeDescription, + isInvalid: this.options.itemIsInvalid + }, _(this.options).pick('onEdit', 'onRemove', 'highlight')), + blankSlateViewConstructor: Marionette.ItemView.extend({ + tagName: 'li', + className: 'list_blank_slate', + template: blankSlateTemplate$1$1 + }) + })); + this.ui.label.text(this.options.label); + this.$el.toggleClass('with_type_pictogram', !!this.options.itemTypeName); + } + }); + var ConfirmUploadView = Marionette.Layout.extend({ + template: template$x, + 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. + * + * Override the `destroyModel` method to customize destroy behavior. + * Calls `destroyWithDelay` by default. + * + * @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 = _.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'))) { + if (this.destroyModel() !== false) { + this.goBack(); + } + } + }, + destroyModel: function destroyModel() { + this.model.destroyWithDelay(); + }, + goBack: function goBack() { + editor.navigate('/', { + trigger: true + }); + }, + t: function t(suffix) { + var translationKeyPrefix = _.result(this, 'translationKeyPrefix'); + + return I18n$1.t("".concat(translationKeyPrefix, ".").concat(suffix), { + defaultValue: I18n$1.t("pageflow.editor.views.edit_configuration.".concat(suffix)) + }); + } + }); + editor.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.widgetTypes.register('consent_bar', { + configurationEditorView: ConfigurationEditorView.extend({ + configure: function configure() { + this.tab('consent_bar', function () { + this.view(InfoBoxView, { + text: I18n$1.t('pageflow.editor.consent_bar.widget_type_info_box_text') + }); + }); + } + }) + }); + editor.widgetTypes.registerRole('cookie_notice', { + isOptional: true + }); + editor.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.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); + this.input('animation_duration', SliderInputView, { + minValue: 1, + maxValue: 7, + defaultValue: 7, + unit: 's' + }); + }); + } + }) + }); + editor.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.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); + this.input('animation_duration', SliderInputView, { + minValue: 1, + maxValue: 7, + defaultValue: 7, + unit: 's' + }); + }); + } + }) + }); + 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) { + frontend.features.enable('editor', options.entry.enabled_feature_names); + }); + app.addInitializer(function (options) { + frontend.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.applyDefaultHelpEntry(); + }); + }); + var OtherFile = UploadableFile.extend({ + thumbnailPictogram: 'other' + }); + 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.fileTypes.register('image_files', { + model: ImageFile, + metaDataAttributes: ['dimensions', altMetaDataAttribute], + matchUpload: /^image/, + configurationEditorInputs: [{ + name: 'alt', + inputView: TextInputView + }] + }); + editor.fileTypes.register('video_files', { + model: VideoFile, + metaDataAttributes: ['format', 'dimensions', 'duration', textTracksMetaDataAttribute, altMetaDataAttribute], + matchUpload: /^video/, + configurationEditorInputs: [{ + name: 'alt', + inputView: TextInputView + }], + settingsDialogTabs: [textTracksSettingsDialogTab] + }); + editor.fileTypes.register('audio_files', { + model: AudioFile, + metaDataAttributes: ['format', 'duration', textTracksMetaDataAttribute, altMetaDataAttribute], + matchUpload: /^audio/, + configurationEditorInputs: [{ + name: 'alt', + inputView: TextInputView + }], + settingsDialogTabs: [textTracksSettingsDialogTab] + }); + editor.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.fileTypes.register('other_files', { + model: OtherFile, + metaDataAttributes: [altMetaDataAttribute], + matchUpload: function matchUpload() { + return true; + }, + priority: 100, + configurationEditorInputs: [{ + name: 'alt', + inputView: TextInputView + }] + }); + editor.fileTypes.setup(options.config.fileTypes); + }); + app.addInitializer(function (options) { + editor.widgetTypes.registerRole('navigation', { + isOptional: true + }); + editor.widgetTypes.setup(options.widget_types); + }); + app.addInitializer(function (options) { + state.files = FilesCollection.createForFileTypes(editor.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.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.createEntryModel(options, { + widgets: widgets + }); + state.site = new Site(options.site); + state.account = new Backbone.Model(options.account); + widgets.subject = state.entry; + state.storylineOrdering = new StorylineOrdering(state.storylines, state.pages); + state.storylineOrdering.sort({ + silent: true + }); + state.storylineOrdering.watch(); + state.pages.sort(); + state.storylines.on('sort', _.debounce(function () { + state.storylines.saveOrder(); + }, 100)); + editor.failures.watch(state.entry); + editor.failures.watch(state.pages); + editor.failures.watch(state.chapters); + editor.savingRecords.watch(state.pages); + editor.savingRecords.watch(state.chapters); + frontend.events.trigger('seed:loaded'); + }); + app.addInitializer(function (options) { + state.fileUploader = new FileUploader({ + entry: state.entry, + fileTypes: editor.fileTypes + }); + ConfirmUploadView.watch(state.fileUploader, editor.fileTypes, state.files); + }); + app.addInitializer(function (options) { + editor.pageTypes.setup(options.page_types); + }); + app.addInitializer(function (options) { + editor.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 () { + _.each(editor.sideBarRoutings, function (options) { + new options.router({ + controller: new options.controller({ + region: app.sidebarRegion, + entry: state.entry + }) + }); + }); + + editor.router = new SidebarRouter({ + controller: new SidebarController({ + region: app.sidebarRegion, + entry: state.entry + }) + }); + window.editor = editor.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 () { + var context = this; + var args = arguments; + + _.each(editor.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.entryType.previewView({ + model: state.entry + })); + 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' + }); + + var PreviewEntryData = frontend$1.EntryData.extend({ + initialize: function initialize(options) { + this.entry = options.entry; + this.storylines = options.storylines; + this.chapters = options.chapters; + this.pages = options.pages; + }, + getSiteOption: function getSiteOption(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 : {}; + }, + getChapterPosition: function getChapterPosition(id) { + var chapter = this.chapters.get(id); + return this.chapters.indexOf(chapter); + }, + 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 PagedEntry = Entry.extend({ + setupFromEntryTypeSeed: function setupFromEntryTypeSeed(seed, state) { + state.entryData = new PreviewEntryData({ + entry: this, + storylines: state.storylines, + chapters: state.chapters, + pages: state.pages + }); + }, + supportsPhoneEmulation: function supportsPhoneEmulation() { + return frontend.features.isEnabled('editor_emulation_mode'); + } + }); + + function template$M(data) { + var __t, __p = ''; + __p += '\n \n \n \n \n\n'; + return __p + } + + var PageItemView = Marionette.ItemView.extend({ + tagName: 'li', + template: template$M, + ui: { + title: '.title', + pictogram: '.type_pictogram', + pageThumbnail: '.page_thumbnail' + }, + modelEvents: { + 'change:title': 'update', + 'change:active': 'update' + }, + onRender: function onRender() { + this.subview(new PageThumbnailView({ + el: this.ui.pageThumbnail, + model: this.model + })); + this.update(); + }, + update: function update() { + this.$el.attr('data-id', this.model.id); + this.$el.attr('data-perma-id', this.model.get('perma_id')); + this.$el.attr('title', this._getItemTitle()); + this.$el.toggleClass('active', this.model.get('active')); + this.$el.toggleClass('disabled', !!(this.options.isDisabled && this.options.isDisabled(this.model))); + this.$el.toggleClass('hide_in_navigation', !this.model.configuration.get('display_in_navigation')); + this.$el.removeClass(editor.pageTypes.pluck('name').join(' ')).addClass(this.model.get('template')); + this.ui.pictogram.attr('title', this._getPictogramTitle()); + this.ui.title.text(this._getTitle()); + }, + _getPictogramTitle: function _getPictogramTitle() { + return I18n$1.t(this.model.pageType().translationKey()) + ' Seite'; + }, + _getItemTitle: function _getItemTitle() { + if (this.options.displayInNavigationHint && !this.model.configuration.get('display_in_navigation')) { + return 'nicht in Navigationsleiste'; + } + + return ''; + }, + _getTitle: function _getTitle() { + var result = this.model.title() || I18n$1.t('pageflow.editor.views.page_item_view.unnamed'); + + if (this.options.displayInNavigationHint && !this.model.configuration.get('display_in_navigation')) { + return "(".concat(result, ")"); + } + + return result; + } + }); + + var NavigatablePageItemView = PageItemView.extend({ + mixins: [loadable, failureIndicatingView], + className: 'draggable', + events: { + 'click': function click() { + if (!this.model.isNew() && !this.model.isDestroying()) { + editor.navigate('/pages/' + this.model.get('id'), { + trigger: true + }); + } + + return false; + } + } + }); + + function template$N(data) { + var __t, __p = ''; + __p += '\n \n \n \n \n\n\n
                \n\n' + + ((__t = ( I18n.t('pageflow.editor.templates.chapter_item.new_page') )) == null ? '' : __t) + + '\n'; + return __p + } + + var ChapterItemView = Marionette.ItemView.extend({ + tagName: 'li', + template: template$N, + ui: { + title: '> a > .title', + number: '> a > .number', + pages: 'ul.pages' + }, + modelEvents: { + change: 'update' + }, + onRender: function onRender() { + var collectionView = this.options.sortable ? SortableCollectionView : CollectionView; + this.subview(new collectionView({ + el: this.ui.pages, + collection: this.model.pages, + itemViewConstructor: this.options.pageItemView || NavigatablePageItemView, + itemViewOptions: this.options.pageItemViewOptions, + connectWith: 'ul.pages' + })); + this.update(); + }, + update: function update() { + this.ui.title.text(this.model.get('title') || I18n$1.t('pageflow.editor.views.chapter_item_view.unnamed')); + this.ui.number.text(I18n$1.t('pageflow.editor.views.chapter_item_view.chapter') + ' ' + (this.model.get('position') + 1)); + } + }); + + var NavigatableChapterItemView = ChapterItemView.extend({ + mixins: [loadable, failureIndicatingView], + className: 'draggable', + events: { + 'click a.add_page': function clickAAdd_page() { + this.model.addPage(); + }, + 'click a.edit_chapter': function clickAEdit_chapter() { + if (!this.model.isNew() && !this.model.isDestroying()) { + editor.navigate('/chapters/' + this.model.get('id'), { + trigger: true + }); + } + + return false; + } + } + }); + + function template$O(data) { + var __t, __p = ''; + __p += '\n
                  \n\n' + + ((__t = ( I18n.t('pageflow.editor.templates.storyline_outline.new_chapter') )) == null ? '' : __t) + + '\n'; + return __p + } + + var StorylineOutlineView = Marionette.Layout.extend({ + template: template$O, + className: 'storyline_outline', + ui: { + chapters: 'ul.storyline_outline_chapters' + }, + events: { + 'click a.add_chapter': function clickAAdd_chapter() { + this.model.scaffoldChapter(); + } + }, + onRender: function onRender() { + this.ui.chapters.toggleClass('navigatable', !!this.options.navigatable); + var collectionView = this.options.sortable ? SortableCollectionView : CollectionView; + new collectionView({ + el: this.ui.chapters, + collection: this.model.chapters, + itemViewConstructor: this.options.navigatable ? NavigatableChapterItemView : ChapterItemView, + itemViewOptions: { + sortable: this.options.sortable, + pageItemView: this.options.navigatable ? NavigatablePageItemView : PageItemView, + pageItemViewOptions: _.extend({ + displayInNavigationHint: this.options.displayInNavigationHint + }, this.options.pageItemViewOptions || {}) + } + }).render(); + } + }); + + function template$P(data) { + var __t, __p = ''; + __p += '
                  \n
                  \n \n \n
                  \n\n
                  \n'; + return __p + } + + var StorylinePickerView = Marionette.Layout.extend({ + template: template$P, + className: 'storyline_picker', + regions: { + selectRegion: '.storyline_picker_select_region', + mainRegion: '.storyline_picker_main_region' + }, + ui: { + storylines: '.storyline_picker_storylines' + }, + events: { + 'click .add_storyline': function clickAdd_storyline() { + var storyline = this.options.entry.scaffoldStoryline({ + depth: 'page' + }).storyline; + this.listenToOnce(storyline, 'sync', function () { + this.updateSelect(); + this.model.set('storyline_id', storyline.id); + }); + return false; + }, + 'click .edit_storyline': function clickEdit_storyline() { + editor.navigate('storylines/' + this.model.get('storyline_id'), { + trigger: true + }); + return false; + } + }, + initialize: function initialize() { + this.model = new Backbone.Model({ + storyline_id: this.defaultStorylineId() + }); + this.listenTo(this.options.entry.storylines, 'add sort remove', this.updateSelect); + this.listenTo(this.model, 'change', this.load); + }, + onRender: function onRender() { + this.$el.toggleClass('editable', !!this.options.editable); + this.ui.storylines.toggle(!!frontend.features.isEnabled('storylines')); + this.updateSelect(); + this.load(); + }, + updateSelect: function updateSelect() { + var storylines = this.options.entry.storylines; + this.selectRegion.show(new SelectInputView({ + model: this.model, + label: I18n$1.t('pageflow.editor.views.storylines_picker_view.label'), + propertyName: 'storyline_id', + values: storylines.pluck('id'), + texts: storylines.map(function (storyline) { + return this.indentation(storyline) + storyline.displayTitle(); + }, this), + groups: storylines.reduce(function (result, storyline) { + if (storyline.isMain() || storyline.parentPage()) { + result.push(_.last(result)); + } else { + result.push(I18n$1.t('pageflow.editor.views.storylines_picker_view.without_parent_page')); + } + + return result; + }, []) + })); + }, + load: function load() { + var storyline = this.options.entry.storylines.get(this.model.get('storyline_id')); + this.saveRememberedStorylineId(storyline.id); + this.mainRegion.show(new StorylineOutlineView({ + model: storyline, + navigatable: this.options.navigatable, + sortable: this.options.editable, + chapterItemView: this.options.chapterItemView, + pageItemView: this.options.pageItemView, + pageItemViewOptions: this.options.pageItemViewOptions, + displayInNavigationHint: this.options.displayInNavigationHint + })); + }, + defaultStorylineId: function defaultStorylineId() { + var storyline = this.options.entry.storylines.get(this.options.storylineId) || this.options.entry.storylines.get(this.rememberedStorylineId()) || this.options.entry.storylines.first(); + return storyline.id; + }, + rememberedStorylineId: function rememberedStorylineId() { + if (this.options.rememberLastSelection) { + return StorylinePickerView._rememberedStorylineId; + } + }, + saveRememberedStorylineId: function saveRememberedStorylineId(id) { + if (this.options.rememberLastSelection) { + StorylinePickerView._rememberedStorylineId = id; + } + }, + indentation: function indentation(storyline) { + return _(storyline.get('level') || 0).times(function () { + return "\xA0\xA0\xA0"; + }).join(''); + } + }); + + var EntryOutlineView = function EntryOutlineView(options) { + return new StorylinePickerView(_.extend({ + navigatable: true, + editable: true, + displayInNavigationHint: true, + rememberLastSelection: true + }, options)); + }; + + function template$Q(data) { + var __t, __p = ''; + __p += '

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

                  \n

                  ' + + ((__t = ( I18n.t('pageflow.editor.blank_entry.intro') )) == null ? '' : __t) + + '

                  \n
                    \n
                  1. ' + + ((__t = ( I18n.t('pageflow.editor.blank_entry.create_chapter') )) == null ? '' : __t) + + '
                  2. \n
                  3. ' + + ((__t = ( I18n.t('pageflow.editor.blank_entry.create_page') )) == null ? '' : __t) + + '
                  4. \n
                  5. ' + + ((__t = ( I18n.t('pageflow.editor.blank_entry.edit_page') )) == null ? '' : __t) + + '
                  6. \n
                  \n

                  ' + + ((__t = ( I18n.t('pageflow.editor.blank_entry.outro') )) == null ? '' : __t) + + '

                  \n'; + return __p + } + + var BlankEntryView = Marionette.ItemView.extend({ + template: template$Q, + className: 'blank_entry' + }); + + var PagePreviewView = Marionette.View.extend({ + tagName: 'section', + className: 'page', + modelEvents: { + 'change:template': 'updateTemplate', + 'change:configuration': 'update', + 'change:position': 'updatePositionClassNames', + 'change:id': function changeId() { + this.$el.attr('data-id', this.model.id); + this.$el.attr('data-perma-id', this.model.get('perma_id')); + this.$el.attr('id', this.model.get('perma_id')); + } + }, + events: { + pageactivate: function pageactivate() { + this.model.set('active', true); + }, + pagedeactivate: function pagedeactivate() { + this.model.set('active', false); + } + }, + render: function render() { + this.$el.html(this.pageTemplate()); + this.$el.attr('data-id', this.model.id); + this.$el.attr('data-perma-id', this.model.get('perma_id')); + this.$el.attr('id', this.model.get('perma_id')); + this.$el.attr('data-chapter-id', this.model.get('chapter_id')); + this.$el.data('template', this.model.get('template')); + this.$el.data('configuration', this.model.get('configuration')); + this.$el.on('pageenhanced', _.bind(function () { + this.update(); + this.initEmbeddedViews(); + this.$el.page('reactivate'); + }, this)); + return this; + }, + onClose: function onClose() { + this.$el.page('cleanup'); + }, + updateTemplate: function updateTemplate() { + this.$el.page('cleanup'); + this.$el.html(this.pageTemplate()); + this.$el.data('template', this.model.get('template')); + setTimeout(_.bind(function () { + this.$el.page('reinit'); + }, this), 0); + }, + update: function update() { + this.$el.page('update', this.model.configuration); + frontend.events.trigger('page:update', this.model); + this.refreshScroller(); + this.updatePositionClassNames(); + }, + updatePositionClassNames: function updatePositionClassNames() { + this.$el.toggleClass('chapter_beginning', this.model.isChapterBeginning()); + this.$el.toggleClass('first_page', this.model.isFirstPage()); + }, + pageTypeHooks: function pageTypeHooks() { + return frontend$1.pageType.get(this.model.get('template')); + }, + pageTemplate: function pageTemplate() { + return this._unescape($('script[data-template="' + this.model.get('template') + '_page"]').html()); + }, + refreshScroller: function refreshScroller() { + this.$el.page('refreshScroller'); + }, + initEmbeddedViews: function initEmbeddedViews() { + var view = this; + + if (view.embeddedViews) { + view.embeddedViews.call('close'); + } + + view.embeddedViews = new ChildViewContainer(); + + _.each(view.embeddedViewDefinitions(), function (item, selector) { + view.$(selector).each(function () { + view.embeddedViews.add(new item.view(_.extend(item.options || {}, { + el: this, + model: view.model.configuration, + container: view + })).render()); + }); + }); + }, + embeddedViewDefinitions: function embeddedViewDefinitions() { + return _.extend({}, this.pageTypeHooks().embeddedEditorViews() || {}, this.model.pageType().embeddedViews()); + }, + _unescape: function _unescape(text) { + return $('
                  ').html(text).text(); + } + }); + + function template$R(data) { + var __t, __p = ''; + __p += '
                  \n
                  \n
                  \n\n
                  \n
                  \n\n'; + return __p + } + + var EntryPreviewView = Marionette.ItemView.extend({ + template: template$R, + className: 'entry_preview', + ui: { + container: '> .container', + header: '> .container > .header', + entry: '> .container > .entry', + overview: '> .container > .overview', + navigationDisabledHint: '.navigation_disabled_hint' + }, + initialize: function initialize() { + this.pages = this.model.pages.persisted(); + this.widgets = $(); + this.debouncedFetchWidgets = _.debounce(this.fetchWidgets, 200); + }, + onRender: function onRender() { + this.pageViews = this.subview(new CollectionView({ + el: this.ui.entry, + collection: this.pages, + itemViewConstructor: PagePreviewView, + blankSlateViewConstructor: BlankEntryView + })); + this.ui.entry.append($('#indicators_seed > *')); + this.update(); + this.listenTo(this.model, 'sync:order sync:widgets', this.update); + this.listenTo(this.model, 'change:metadata', function () { + this.model.once('sync', this.update, this); + }); + this.listenTo(this.model, 'change:emulation_mode', this.updateEmulationMode); + this.listenTo(this.model.storylines, 'sync', this.update); + this.listenTo(this.model.chapters, 'sync', this.update); + this.listenTo(this.model.pages, 'sync', this.update); + this.listenTo(this.model.audioFiles, 'sync', this.update); + this.listenTo(this.model.imageFiles, 'sync', this.update); + this.listenTo(this.model.videoFiles, 'sync', this.update); + this.listenTo(frontend.events, 'page:changing', function (event) { + if (this.model.get('emulation_mode')) { + this.ui.navigationDisabledHint.css('opacity', 1); + clearTimeout(this.navigationDisabledHintTimeout); + this.navigationDisabledHintTimeout = setTimeout(_.bind(function () { + this.ui.navigationDisabledHint.css('opacity', 0); + }, this), 2000); + event.cancel(); + } + }); + this.listenTo(frontend.events, 'page:change', function (page) { + this.updateEmulationModeSupport(page.getPermaId()); + }); + }, + onShow: function onShow() { + var slideshow = this.slideshow = frontend$1.Slideshow.setup({ + element: this.ui.entry, + enabledFeatureNames: this.model.get('enabled_feature_names'), + simulateHistory: true + }); + frontend$1.delayedStart.perform(); + this.listenTo(this.pages, 'add', function () { + slideshow.update(); + }); + this.listenTo(this.pages, 'remove', function () { + slideshow.update(); + }); + this.listenTo(this.pages, 'edit', function (model) { + if (this.lastEditedPage != model) { + this.model.unset('emulation_mode'); + } + + this.lastEditedPage = model; + slideshow.goTo(this.pageViews.itemViews.findByModel(model).$el); + }); + this.listenTo(app, 'resize', function () { + slideshow.triggerResizeHooks(); + this.updateSimulatedMediaQueryClasses(); + }); + this.listenTo(this.model.pages, 'change:template', function () { + this.updateEmulationModeSupport(slideshow.currentPagePermaId()); + }); + this.updateSimulatedMediaQueryClasses(); + }, + updateEmulationModeSupport: function updateEmulationModeSupport(permaId) { + var model = this.model.pages.getByPermaId(permaId); + this.model.set('emulation_mode_disabled', !model || !model.pageType().supportsPhoneEmulation()); + }, + updateSimulatedMediaQueryClasses: function updateSimulatedMediaQueryClasses() { + var width = this.ui.container.width(); + var height = this.ui.container.height(); + var portrait = width < height; + $('html').toggleClass('simulate_mobile', width <= 900).toggleClass('simulate_phone', portrait && width <= 500 || !portrait && height <= 500).toggleClass('simulate_desktop', portrait && width > 500 || !portrait && height > 500).toggleClass('simulate_narrow_desktop', width <= 1200).toggleClass('simulate_wide_desktop', width > 1600).toggleClass('simulate_pad_portrait', width <= 768 && portrait).toggleClass('simulate_phone_portrait', width <= 500 && portrait); + }, + update: function update() { + this.debouncedFetchWidgets(); + this.$el.toggleClass('emphasize_chapter_beginning', !!this.model.metadata.get('emphasize_chapter_beginning')); + }, + fetchWidgets: function fetchWidgets() { + var view = this; + $.ajax(this.model.url() + '/paged/partials').success(function (response) { + var partials = $('
                  ').html(response); + view.ui.header.replaceWith(partials.find('> .header')); + view.ui.overview.replaceWith(partials.find('> .overview')); + view.bindUIElements(); + view.updateWidgets(partials); + view.ui.header.header({ + slideshow: view.slideshow + }); + view.ui.overview.overview(); + }); + }, + updateWidgets: function updateWidgets(partials) { + var widgets = partials.find('[data-widget]'); + this.updatePresentWidgetsCssClasses(widgets); + this.widgets.remove(); + this.widgets = widgets; + this.ui.entry.before(this.widgets); + frontend$1.widgetTypes.enhance(this.$el); + }, + updatePresentWidgetsCssClasses: function updatePresentWidgetsCssClasses(newWidgets) { + var previousClasses = this.widgetNames(this.widgets); + var newClasses = this.widgetNames(newWidgets); + + var removedClasses = _.difference(previousClasses, newClasses); + + var addedClasses = _.difference(newClasses, previousClasses); + + this.$el.addClass('widgets_present'); + this.$el.removeClass(removedClasses.join(' ')); + this.$el.addClass(addedClasses.join(' ')); + + if (removedClasses.length || addedClasses.length) { + frontend.events.trigger('widgets:update'); + } + }, + widgetNames: function widgetNames(widgets) { + return widgets.map(function () { + return 'widget_' + $(this).data('widget') + '_present'; + }).get(); + }, + updateEmulationMode: function updateEmulationMode() { + if (this.model.previous('emulation_mode')) { + this.$el.removeClass(this.emulationModeClassName(this.model.previous('emulation_mode'))); + } + + if (this.model.get('emulation_mode')) { + this.$el.addClass(this.emulationModeClassName(this.model.get('emulation_mode'))); + } + + app.trigger('resize'); + }, + emulationModeClassName: function emulationModeClassName(mode) { + return 'emulation_mode_' + mode; + } + }); + + var appearanceInputs = function appearanceInputs(tabView, options) { + var entry = options.entry; + var theme = entry.getTheme(); + var site = options.site; + tabView.input('manual_start', CheckBoxInputView); + tabView.input('emphasize_chapter_beginning', CheckBoxInputView); + tabView.input('emphasize_new_pages', CheckBoxInputView); + tabView.input('home_button_enabled', CheckBoxInputView, { + disabled: !theme.hasHomeButton(), + displayUncheckedIfDisabled: true + }); + tabView.input('overview_button_enabled', CheckBoxInputView, { + disabled: !theme.hasOverviewButton(), + displayUncheckedIfDisabled: true + }); + + if (theme.hasHomeButton()) { + tabView.input('home_url', TextInputView, { + placeholder: site.get('pretty_url'), + visibleBinding: 'home_button_enabled' + }); + } + }; + + function template$S(data) { + var __t, __p = ''; + __p += '
                  \n

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

                  \n\n
                  \n
                  \n
                  \n
                  \n\n \n
                  \n'; + return __p + } + + var PageSelectionView = Marionette.ItemView.extend({ + template: template$S, + className: 'page_selection dialog editor', + mixins: [dialogView], + ui: { + storylines: '.storyline_picker', + chapters: '.chapters' + }, + events: { + 'click ul.pages li': function clickUlPagesLi(event) { + this.options.onSelect(this.model.pages.get($(event.currentTarget).data('id'))); + this.close(); + } + }, + onRender: function onRender() { + var options = this.options; + this.subview(new StorylinePickerView({ + el: this.ui.storylines, + entry: this.model, + pageItemViewOptions: { + isDisabled: function isDisabled(page) { + return options.isAllowed && !options.isAllowed(page); + } + } + })); + } + }); + + PageSelectionView.selectPage = function (options) { + return $.Deferred(function (deferred) { + var view = new PageSelectionView({ + model: options.entry, + onSelect: deferred.resolve, + isAllowed: options && options.isAllowed + }); + view.on('close', function () { + deferred.reject(); + }); + app.dialogRegion.show(view.render()); + }).promise(); + }; + + editor.pageSelectionView = PageSelectionView; + + var SideBarRouter = Marionette.AppRouter.extend({ + appRoutes: { + 'page_links/:id': 'pageLink', + 'pages/:id': 'page', + 'pages/:id/:tab': 'page', + 'chapters/:id': 'chapter', + 'storylines/:id': 'storyline' + } + }); + + function template$T(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$T, + 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 (frontend.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.navigate('/', { + trigger: true + }); + } + }); + + function template$U(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$U, + 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.navigate('/pages/' + this.options.page.id + '/links', { + trigger: true + }); + } + }); + + function template$V(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$V, + 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() { + var _this = this; + + 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.navigate('/', { + trigger: true + }); + } + }); + + var state$1 = window.pageflow || {}; + + var PageLinkInputView = ReferenceInputView.extend({ + choose: function choose() { + return editor.selectPage({ + isAllowed: this.options.isAllowed + }); + }, + getTarget: function getTarget(permaId) { + return state$1.pages.getByPermaId(permaId); + } + }); + + function template$W(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$W, + 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$1.storylines.length > 1; + }, + isAllowed: function isAllowed(page) { + return !storylineChildPages.contain(page); + } + }); + this.input('scroll_successor_id', PageLinkInputView); + + if (frontend.features.isEnabled('chapter_hierachy')) { + this.input('navigation_bar_mode', SelectInputView, { + values: frontend$1.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.navigate('/?storyline=' + this.model.id, { + trigger: true + }); + } + }); + + var SideBarController = Marionette.Controller.extend({ + initialize: function initialize(options) { + this.region = options.region; + this.entry = options.entry; + }, + 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, + tab: tab + })); + editor.setDefaultHelpEntry(page.pageType().help_entry_translation_key); + }, + pageLink: function pageLink(linkId) { + var pageId = linkId.split(':')[0]; + var page = this.entry.pages.getByPermaId(pageId); + this.region.show(new EditPageLinkView({ + model: page.pageLinks().get(linkId), + page: page, + api: editor + })); + } + }); + + 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$1.audioFiles, + defaultTextTrackFilePropertyName: 'default_text_track_file_id' + }); + this.group('background'); + this.input('thumbnail_image_id', FileInputView, { + collection: state$1.imageFiles, + positioning: false + }); + }); + this.tab('options', function () { + if (frontend.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: frontend$1.theme.mainColor(), + swatches: usedWaveformColors() + }); + this.input('autoplay', CheckBoxInputView); + this.group('options', { + canPauseAtmo: true + }); + }); + + function usedWaveformColors() { + return _.chain(state$1.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$1.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$1.videoFiles, + positioning: false, + defaultTextTrackFilePropertyName: 'default_text_track_file_id' + }); + this.input('poster_image_id', FileInputView, { + collection: state$1.imageFiles, + positioning: false + }); + this.input('mobile_poster_image_id', FileInputView, { + collection: state$1.imageFiles, + positioning: true + }); + this.input('thumbnail_image_id', FileInputView, { + collection: state$1.imageFiles, + positioning: false + }); + }); + this.tab('options', function () { + this.input('autoplay', CheckBoxInputView); + this.input('smart_contain', CheckBoxInputView, { + storeInverted: 'contain' + }); + + if (frontend.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$1.imageFiles, + visibleBinding: backgroundTypeProperty, + visibleBindingValue: 'image', + fileSelectionHandlerOptions: options + }); + this.input(prefix + 'video_file_id', FileInputView, { + collection: state$1.videoFiles, + visibleBinding: backgroundTypeProperty, + visibleBindingValue: 'video', + fileSelectionHandlerOptions: options + }); + this.input(prefix + 'poster_image_id', FileInputView, { + collection: state$1.imageFiles, + visibleBinding: backgroundTypeProperty, + visibleBindingValue: 'video', + fileSelectionHandlerOptions: options + }); + this.input(prefix + 'mobile_poster_image_id', FileInputView, { + collection: state$1.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: frontend$1.pageTransitions.names() + }; + + if (frontend$1.navigationDirection.isHorizontalOnPhone()) { + inputOptions.additionalInlineHelpText = I18n$1.t('pageflow.editor.phone_horizontal_slideshow_mode.page_transitions_inline_help'); + } + + this.input(options.propertyName || 'page_transition', SelectInputView, _.extend(inputOptions, options)); + }); + + ConfigurationEditorTabView.groups.define('options', function (options) { + var theme = state$1.entry.getTheme(); + this.input('display_in_navigation', CheckBoxInputView); + + if (theme.supportsEmphasizedPages()) { + this.input('emphasize_in_navigation', CheckBoxInputView); + } + + this.group('page_transitions', { + propertyName: 'transition' + }); + + if (frontend.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$1.audioFiles + }); + + if (theme.supportsHideLogoOnPages()) { + this.input('hide_logo', CheckBoxInputView); + } + + if (options.canPauseAtmo) { + this.input('atmo_during_playback', SelectInputView, { + values: frontend$1.Atmo.duringPlaybackModes + }); + } + + if (theme.supportsScrollIndicatorModes()) { + this.input('scroll_indicator_mode', SelectInputView, { + values: Page.scrollIndicatorModes + }); + this.input('scroll_indicator_orientation', SelectInputView, { + values: Page.scrollIndicatorOrientations + }); + } + }); + + editor.addInitializer(function () { + var scrollNavigationKeys = _.values({ + pageUp: 33, + pageDown: 34, + end: 35, + home: 36, + left: 37, + up: 38, + right: 39, + down: 40 + }); + + $('sidebar').on('keydown', function (event) { + if (scrollNavigationKeys.indexOf(event.which) >= 0) { + event.stopPropagation(); + } + }); + }); + + editor.addInitializer(function (options) { + var KEY_A = 65; + var KEY_X = 88; + $(document).on('keydown', function (event) { + if (event.altKey && event.which === KEY_A) { + if (state$1.atmo.disabled) { + state$1.atmo.enable(); + } else { + state$1.atmo.disable(); + } + } else if (event.altKey && event.which === KEY_X) { + editor.navigate('pages/' + state$1.slides.currentPage().data('id'), { + trigger: true + }); + } + }); + }); + + var DisabledAtmoIndicatorView = Marionette.View.extend({ + className: 'disabled_atmo_indicator', + events: { + 'click': function click() { + state$1.atmo.enable(); + } + }, + initialize: function initialize() { + this.listenTo(frontend.events, 'atmo:disabled', function () { + this.$el.show(); + }); + this.listenTo(frontend.events, 'atmo:enabled', function () { + this.$el.hide(); + }); + this.$el.toggle(state$1.atmo.disabled); + }, + render: function render() { + this.$el.attr('title', I18n$1.t('pageflow.editor.atmo.disabled')); + return this; + } + }); + + // pageflow/editora's boot initializer. DisabledAtmoIndicatorView + // depends on state.atmo`which is only available after the entry + // preview has been created. + + app.addInitializer(function (options) { + app.indicatorsRegion.show(new DisabledAtmoIndicatorView()); + }); + + editor.addInitializer(function (options) { + state$1.entry.on('change:pending_files_count', function (model, value) { + if (value < state$1.entry.previous('pending_files_count')) { + stylesheet.reload('entry'); + } + }); + state$1.entry.on('use:files', function () { + stylesheet.reload('entry'); + }); + state$1.entry.metadata.on('change:theme_name', function () { + var theme = state$1.entry.getTheme(); + stylesheet.update('theme', theme.get('stylesheet_path')); + }); + }); + + editor.registerEntryType('paged', { + entryModel: PagedEntry, + previewView: EntryPreviewView, + outlineView: EntryOutlineView, + appearanceInputs: appearanceInputs + }); + editor.registerSideBarRouting({ + router: SideBarRouter, + controller: SideBarController + }); + + 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.DropDownButtonItemListView = DropDownButtonItemListView; + exports.DropDownButtonItemView = DropDownButtonItemView; + exports.DropDownButtonView = DropDownButtonView; + exports.EditConfigurationView = EditConfigurationView; + exports.EditEntryView = EditEntryView; + exports.EditFileView = EditFileView; + exports.EditLock = EditLock; + exports.EditLockContainer = EditLockContainer; + exports.EditMetaDataView = EditMetaDataView; + 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.LabelOnlyView = LabelOnlyView; + 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.NumberInputView = NumberInputView; + 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.SeparatorView = SeparatorView; + exports.SidebarController = SidebarController; + exports.SidebarFooterView = SidebarFooterView; + exports.SidebarRouter = SidebarRouter; + exports.Site = Site; + 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.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.attributeBinding = attributeBinding; + exports.authenticationProvider = authenticationProvider; + exports.configurationContainer = configurationContainer; + exports.cssModulesUtils = cssModulesUtils; + exports.delayedDestroying = delayedDestroying; + exports.dialogView = dialogView; + exports.editor = editor; + 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.serverSideValidation = serverSideValidation; + exports.stageProvider = stageProvider; + exports.startEditor = startEditor; + exports.state = state; + exports.stylesheet = stylesheet; + exports.subviewContainer = subviewContainer; + exports.tooltipContainer = tooltipContainer; + exports.transientReferences = transientReferences; + exports.validFileTypeTranslationList = validFileTypeTranslationList; + exports.viewWithValidationErrorMessages = viewWithValidationErrorMessages; + + return exports; + +}({}, Backbone, _, Backbone.Marionette, jQuery, I18n, Backbone.ChildViewContainer, IScroll, jQuery, wysihtml5, jQuery, Cocktail, pageflow, pageflow)); diff --git a/entry_types/paged/app/assets/javascripts/pageflow_paged/dist/frontend.js b/entry_types/paged/app/assets/javascripts/pageflow_paged/dist/frontend.js new file mode 100644 index 0000000000..47b32b2e0e --- /dev/null +++ b/entry_types/paged/app/assets/javascripts/pageflow_paged/dist/frontend.js @@ -0,0 +1,9722 @@ +this.pageflow_paged = this.pageflow_paged || {}; +this.pageflow_paged.frontend = (function (exports, jqueryUi, $, Backbone, _, VideoJS, IScroll) { + 'use strict'; + + $ = $ && $.hasOwnProperty('default') ? $['default'] : $; + Backbone = Backbone && Backbone.hasOwnProperty('default') ? Backbone['default'] : Backbone; + _ = _ && _.hasOwnProperty('default') ? _['default'] : _; + VideoJS = VideoJS && VideoJS.hasOwnProperty('default') ? VideoJS['default'] : VideoJS; + IScroll = IScroll && IScroll.hasOwnProperty('default') ? IScroll['default'] : IScroll; + + $.fn.updateTitle = function () { + if (!this.data('title')) { + this.data('title', this.attr('title')); + } + + if (this.hasClass('active')) { + this.attr('title', this.data('activeTitle')); + } else { + this.attr('title', this.data('title')); + } + }; + + $.fn.loadLazyImages = function () { + this.find('img[data-src]').each(function () { + var img = $(this); + + if (!img.attr('src')) { + img.attr('src', img.data('src')); + } + }); + }; + + // https://github.com/jashkenas/backbone/issues/2601 + + function BaseObject(options) { + this.initialize.apply(this, arguments); + } + + _.extend(BaseObject.prototype, Backbone.Events, { + initialize: function initialize(options) {} + }); // The self-propagating extend function that Backbone classes use. + + + BaseObject.extend = Backbone.Model.extend; + + var EntryData = BaseObject.extend({ + getSiteOption: function getSiteOption(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'; + }, + getChapterPosition: function getChapterPosition(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; + } + }, + getPageAnalyticsData: function getPageAnalyticsData(permaId) { + var chapterId = this.getChapterIdByPagePermaId(permaId); + return { + chapterIndex: this.getChapterPosition(chapterId), + chapterTitle: this.getChapterConfiguration(chapterId)['title'], + title: this.getPageConfiguration(permaId)['title'], + index: this.getPagePosition(permaId) + }; + } + }); + + var SeedEntryData = EntryData.extend({ + initialize: function initialize(options) { + this.theme = options.theme; + this.files = _(_.keys(options.files || {})).reduce(function (memo, collectionName) { + memo[collectionName] = _(options.files[collectionName]).reduce(function (result, file) { + result[file.perma_id] = file; + return result; + }, {}); + return memo; + }, {}); + this.storylineConfigurations = _(options.storylines).reduce(function (memo, storyline) { + memo[storyline.id] = storyline.configuration; + return memo; + }, {}); + this.storylineIdsByChapterIds = _(options.chapters).reduce(function (memo, chapter) { + memo[chapter.id] = chapter.storyline_id; + return memo; + }, {}); + this.chapterConfigurations = _.reduce(options.chapters, function (memo, chapter) { + memo[chapter.id] = chapter.configuration; + return memo; + }, {}); + this.chapterPositions = _.reduce(options.chapters, function (memo, chapter, index) { + memo[chapter.id] = index; + return memo; + }, {}); + this.chapterPagePermaIds = _(options.pages).reduce(function (memo, page) { + memo[page.chapter_id] = memo[page.chapter_id] || []; + memo[page.chapter_id].push(page.perma_id); + return memo; + }, {}); + this.chapterIdsByPagePermaIds = _(options.pages).reduce(function (memo, page) { + memo[page.perma_id] = page.chapter_id; + return memo; + }, {}); + this.pageConfigurations = _.reduce(options.pages, function (memo, page) { + memo[page.perma_id] = page.configuration; + return memo; + }, {}); + this.pagePositions = _.reduce(options.pages, function (memo, page, index) { + memo[page.perma_id] = index; + return memo; + }, {}); + }, + getSiteOption: function getSiteOption(name) { + return this.theme[name]; + }, + getFile: function getFile(collectionName, permaId) { + return this.files[collectionName][permaId]; + }, + getChapterConfiguration: function getChapterConfiguration(id) { + return this.chapterConfigurations[id] || {}; + }, + getChapterPosition: function getChapterPosition(id) { + return this.chapterPositions[id]; + }, + getChapterPagePermaIds: function getChapterPagePermaIds(id) { + return this.chapterPagePermaIds[id]; + }, + getPageConfiguration: function getPageConfiguration(permaId) { + return this.pageConfigurations[permaId] || {}; + }, + getPagePosition: function getPagePosition(permaId) { + return this.pagePositions[permaId]; + }, + getChapterIdByPagePermaId: function getChapterIdByPagePermaId(permaId) { + return this.chapterIdsByPagePermaIds[permaId]; + }, + getStorylineConfiguration: function getStorylineConfiguration(id) { + return this.storylineConfigurations[id] || {}; + }, + getStorylineIdByChapterId: function getStorylineIdByChapterId(id) { + return this.storylineIdsByChapterIds[id]; + } + }); + + var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; + + function createCommonjsModule(fn, module) { + return module = { exports: {} }, fn(module, module.exports), module.exports; + } + + var check = function (it) { + return it && it.Math == Math && it; + }; + + // https://github.com/zloirock/core-js/issues/86#issuecomment-115759028 + var global_1 = + // eslint-disable-next-line no-undef + check(typeof globalThis == 'object' && globalThis) || + check(typeof window == 'object' && window) || + check(typeof self == 'object' && self) || + check(typeof commonjsGlobal == 'object' && commonjsGlobal) || + // eslint-disable-next-line no-new-func + Function('return this')(); + + var fails = function (exec) { + try { + return !!exec(); + } catch (error) { + return true; + } + }; + + // Thank's IE8 for his funny defineProperty + var descriptors = !fails(function () { + return Object.defineProperty({}, 1, { get: function () { return 7; } })[1] != 7; + }); + + var nativePropertyIsEnumerable = {}.propertyIsEnumerable; + var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; + + // Nashorn ~ JDK8 bug + var NASHORN_BUG = getOwnPropertyDescriptor && !nativePropertyIsEnumerable.call({ 1: 2 }, 1); + + // `Object.prototype.propertyIsEnumerable` method implementation + // https://tc39.github.io/ecma262/#sec-object.prototype.propertyisenumerable + var f = NASHORN_BUG ? function propertyIsEnumerable(V) { + var descriptor = getOwnPropertyDescriptor(this, V); + return !!descriptor && descriptor.enumerable; + } : nativePropertyIsEnumerable; + + var objectPropertyIsEnumerable = { + f: f + }; + + var createPropertyDescriptor = function (bitmap, value) { + return { + enumerable: !(bitmap & 1), + configurable: !(bitmap & 2), + writable: !(bitmap & 4), + value: value + }; + }; + + var toString = {}.toString; + + var classofRaw = function (it) { + return toString.call(it).slice(8, -1); + }; + + var split = ''.split; + + // fallback for non-array-like ES3 and non-enumerable old V8 strings + var indexedObject = fails(function () { + // throws an error in rhino, see https://github.com/mozilla/rhino/issues/346 + // eslint-disable-next-line no-prototype-builtins + return !Object('z').propertyIsEnumerable(0); + }) ? function (it) { + return classofRaw(it) == 'String' ? split.call(it, '') : Object(it); + } : Object; + + // `RequireObjectCoercible` abstract operation + // https://tc39.github.io/ecma262/#sec-requireobjectcoercible + var requireObjectCoercible = function (it) { + if (it == undefined) throw TypeError("Can't call method on " + it); + return it; + }; + + // toObject with fallback for non-array-like ES3 strings + + + + var toIndexedObject = function (it) { + return indexedObject(requireObjectCoercible(it)); + }; + + var isObject = function (it) { + return typeof it === 'object' ? it !== null : typeof it === 'function'; + }; + + // `ToPrimitive` abstract operation + // https://tc39.github.io/ecma262/#sec-toprimitive + // instead of the ES6 spec version, we didn't implement @@toPrimitive case + // and the second argument - flag - preferred type is a string + var toPrimitive = function (input, PREFERRED_STRING) { + if (!isObject(input)) return input; + var fn, val; + if (PREFERRED_STRING && typeof (fn = input.toString) == 'function' && !isObject(val = fn.call(input))) return val; + if (typeof (fn = input.valueOf) == 'function' && !isObject(val = fn.call(input))) return val; + if (!PREFERRED_STRING && typeof (fn = input.toString) == 'function' && !isObject(val = fn.call(input))) return val; + throw TypeError("Can't convert object to primitive value"); + }; + + var hasOwnProperty = {}.hasOwnProperty; + + var has = function (it, key) { + return hasOwnProperty.call(it, key); + }; + + var document$1 = global_1.document; + // typeof document.createElement is 'object' in old IE + var EXISTS = isObject(document$1) && isObject(document$1.createElement); + + var documentCreateElement = function (it) { + return EXISTS ? document$1.createElement(it) : {}; + }; + + // Thank's IE8 for his funny defineProperty + var ie8DomDefine = !descriptors && !fails(function () { + return Object.defineProperty(documentCreateElement('div'), 'a', { + get: function () { return 7; } + }).a != 7; + }); + + var nativeGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; + + // `Object.getOwnPropertyDescriptor` method + // https://tc39.github.io/ecma262/#sec-object.getownpropertydescriptor + var f$1 = descriptors ? nativeGetOwnPropertyDescriptor : function getOwnPropertyDescriptor(O, P) { + O = toIndexedObject(O); + P = toPrimitive(P, true); + if (ie8DomDefine) try { + return nativeGetOwnPropertyDescriptor(O, P); + } catch (error) { /* empty */ } + if (has(O, P)) return createPropertyDescriptor(!objectPropertyIsEnumerable.f.call(O, P), O[P]); + }; + + var objectGetOwnPropertyDescriptor = { + f: f$1 + }; + + var anObject = function (it) { + if (!isObject(it)) { + throw TypeError(String(it) + ' is not an object'); + } return it; + }; + + var nativeDefineProperty = Object.defineProperty; + + // `Object.defineProperty` method + // https://tc39.github.io/ecma262/#sec-object.defineproperty + var f$2 = descriptors ? nativeDefineProperty : function defineProperty(O, P, Attributes) { + anObject(O); + P = toPrimitive(P, true); + anObject(Attributes); + if (ie8DomDefine) try { + return nativeDefineProperty(O, P, Attributes); + } catch (error) { /* empty */ } + if ('get' in Attributes || 'set' in Attributes) throw TypeError('Accessors not supported'); + if ('value' in Attributes) O[P] = Attributes.value; + return O; + }; + + var objectDefineProperty = { + f: f$2 + }; + + var createNonEnumerableProperty = descriptors ? function (object, key, value) { + return objectDefineProperty.f(object, key, createPropertyDescriptor(1, value)); + } : function (object, key, value) { + object[key] = value; + return object; + }; + + var setGlobal = function (key, value) { + try { + createNonEnumerableProperty(global_1, key, value); + } catch (error) { + global_1[key] = value; + } return value; + }; + + var SHARED = '__core-js_shared__'; + var store = global_1[SHARED] || setGlobal(SHARED, {}); + + var sharedStore = store; + + var functionToString = Function.toString; + + // this helper broken in `3.4.1-3.4.4`, so we can't use `shared` helper + if (typeof sharedStore.inspectSource != 'function') { + sharedStore.inspectSource = function (it) { + return functionToString.call(it); + }; + } + + var inspectSource = sharedStore.inspectSource; + + var WeakMap = global_1.WeakMap; + + var nativeWeakMap = typeof WeakMap === 'function' && /native code/.test(inspectSource(WeakMap)); + + var shared = createCommonjsModule(function (module) { + (module.exports = function (key, value) { + return sharedStore[key] || (sharedStore[key] = value !== undefined ? value : {}); + })('versions', []).push({ + version: '3.6.5', + mode: 'global', + copyright: '© 2020 Denis Pushkarev (zloirock.ru)' + }); + }); + + var id = 0; + var postfix = Math.random(); + + var uid = function (key) { + return 'Symbol(' + String(key === undefined ? '' : key) + ')_' + (++id + postfix).toString(36); + }; + + var keys = shared('keys'); + + var sharedKey = function (key) { + return keys[key] || (keys[key] = uid(key)); + }; + + var hiddenKeys = {}; + + var WeakMap$1 = global_1.WeakMap; + var set, get, has$1; + + var enforce = function (it) { + return has$1(it) ? get(it) : set(it, {}); + }; + + var getterFor = function (TYPE) { + return function (it) { + var state; + if (!isObject(it) || (state = get(it)).type !== TYPE) { + throw TypeError('Incompatible receiver, ' + TYPE + ' required'); + } return state; + }; + }; + + if (nativeWeakMap) { + var store$1 = new WeakMap$1(); + var wmget = store$1.get; + var wmhas = store$1.has; + var wmset = store$1.set; + set = function (it, metadata) { + wmset.call(store$1, it, metadata); + return metadata; + }; + get = function (it) { + return wmget.call(store$1, it) || {}; + }; + has$1 = function (it) { + return wmhas.call(store$1, it); + }; + } else { + var STATE = sharedKey('state'); + hiddenKeys[STATE] = true; + set = function (it, metadata) { + createNonEnumerableProperty(it, STATE, metadata); + return metadata; + }; + get = function (it) { + return has(it, STATE) ? it[STATE] : {}; + }; + has$1 = function (it) { + return has(it, STATE); + }; + } + + var internalState = { + set: set, + get: get, + has: has$1, + enforce: enforce, + getterFor: getterFor + }; + + var redefine = createCommonjsModule(function (module) { + var getInternalState = internalState.get; + var enforceInternalState = internalState.enforce; + var TEMPLATE = String(String).split('String'); + + (module.exports = function (O, key, value, options) { + var unsafe = options ? !!options.unsafe : false; + var simple = options ? !!options.enumerable : false; + var noTargetGet = options ? !!options.noTargetGet : false; + if (typeof value == 'function') { + if (typeof key == 'string' && !has(value, 'name')) createNonEnumerableProperty(value, 'name', key); + enforceInternalState(value).source = TEMPLATE.join(typeof key == 'string' ? key : ''); + } + if (O === global_1) { + if (simple) O[key] = value; + else setGlobal(key, value); + return; + } else if (!unsafe) { + delete O[key]; + } else if (!noTargetGet && O[key]) { + simple = true; + } + if (simple) O[key] = value; + else createNonEnumerableProperty(O, key, value); + // add fake Function#toString for correct work wrapped methods / constructors with methods like LoDash isNative + })(Function.prototype, 'toString', function toString() { + return typeof this == 'function' && getInternalState(this).source || inspectSource(this); + }); + }); + + var path = global_1; + + var aFunction = function (variable) { + return typeof variable == 'function' ? variable : undefined; + }; + + var getBuiltIn = function (namespace, method) { + return arguments.length < 2 ? aFunction(path[namespace]) || aFunction(global_1[namespace]) + : path[namespace] && path[namespace][method] || global_1[namespace] && global_1[namespace][method]; + }; + + var ceil = Math.ceil; + var floor = Math.floor; + + // `ToInteger` abstract operation + // https://tc39.github.io/ecma262/#sec-tointeger + var toInteger = function (argument) { + return isNaN(argument = +argument) ? 0 : (argument > 0 ? floor : ceil)(argument); + }; + + var min = Math.min; + + // `ToLength` abstract operation + // https://tc39.github.io/ecma262/#sec-tolength + var toLength = function (argument) { + return argument > 0 ? min(toInteger(argument), 0x1FFFFFFFFFFFFF) : 0; // 2 ** 53 - 1 == 9007199254740991 + }; + + var max = Math.max; + var min$1 = Math.min; + + // Helper for a popular repeating case of the spec: + // Let integer be ? ToInteger(index). + // If integer < 0, let result be max((length + integer), 0); else let result be min(integer, length). + var toAbsoluteIndex = function (index, length) { + var integer = toInteger(index); + return integer < 0 ? max(integer + length, 0) : min$1(integer, length); + }; + + // `Array.prototype.{ indexOf, includes }` methods implementation + var createMethod = function (IS_INCLUDES) { + return function ($this, el, fromIndex) { + var O = toIndexedObject($this); + var length = toLength(O.length); + var index = toAbsoluteIndex(fromIndex, length); + var value; + // Array#includes uses SameValueZero equality algorithm + // eslint-disable-next-line no-self-compare + if (IS_INCLUDES && el != el) while (length > index) { + value = O[index++]; + // eslint-disable-next-line no-self-compare + if (value != value) return true; + // Array#indexOf ignores holes, Array#includes - not + } else for (;length > index; index++) { + if ((IS_INCLUDES || index in O) && O[index] === el) return IS_INCLUDES || index || 0; + } return !IS_INCLUDES && -1; + }; + }; + + var arrayIncludes = { + // `Array.prototype.includes` method + // https://tc39.github.io/ecma262/#sec-array.prototype.includes + includes: createMethod(true), + // `Array.prototype.indexOf` method + // https://tc39.github.io/ecma262/#sec-array.prototype.indexof + indexOf: createMethod(false) + }; + + var indexOf = arrayIncludes.indexOf; + + + var objectKeysInternal = function (object, names) { + var O = toIndexedObject(object); + var i = 0; + var result = []; + var key; + for (key in O) !has(hiddenKeys, key) && has(O, key) && result.push(key); + // Don't enum bug & hidden keys + while (names.length > i) if (has(O, key = names[i++])) { + ~indexOf(result, key) || result.push(key); + } + return result; + }; + + // IE8- don't enum bug keys + var enumBugKeys = [ + 'constructor', + 'hasOwnProperty', + 'isPrototypeOf', + 'propertyIsEnumerable', + 'toLocaleString', + 'toString', + 'valueOf' + ]; + + var hiddenKeys$1 = enumBugKeys.concat('length', 'prototype'); + + // `Object.getOwnPropertyNames` method + // https://tc39.github.io/ecma262/#sec-object.getownpropertynames + var f$3 = Object.getOwnPropertyNames || function getOwnPropertyNames(O) { + return objectKeysInternal(O, hiddenKeys$1); + }; + + var objectGetOwnPropertyNames = { + f: f$3 + }; + + var f$4 = Object.getOwnPropertySymbols; + + var objectGetOwnPropertySymbols = { + f: f$4 + }; + + // all object keys, includes non-enumerable and symbols + var ownKeys = getBuiltIn('Reflect', 'ownKeys') || function ownKeys(it) { + var keys = objectGetOwnPropertyNames.f(anObject(it)); + var getOwnPropertySymbols = objectGetOwnPropertySymbols.f; + return getOwnPropertySymbols ? keys.concat(getOwnPropertySymbols(it)) : keys; + }; + + var copyConstructorProperties = function (target, source) { + var keys = ownKeys(source); + var defineProperty = objectDefineProperty.f; + var getOwnPropertyDescriptor = objectGetOwnPropertyDescriptor.f; + for (var i = 0; i < keys.length; i++) { + var key = keys[i]; + if (!has(target, key)) defineProperty(target, key, getOwnPropertyDescriptor(source, key)); + } + }; + + var replacement = /#|\.prototype\./; + + var isForced = function (feature, detection) { + var value = data[normalize(feature)]; + return value == POLYFILL ? true + : value == NATIVE ? false + : typeof detection == 'function' ? fails(detection) + : !!detection; + }; + + var normalize = isForced.normalize = function (string) { + return String(string).replace(replacement, '.').toLowerCase(); + }; + + var data = isForced.data = {}; + var NATIVE = isForced.NATIVE = 'N'; + var POLYFILL = isForced.POLYFILL = 'P'; + + var isForced_1 = isForced; + + var getOwnPropertyDescriptor$1 = objectGetOwnPropertyDescriptor.f; + + + + + + + /* + options.target - name of the target object + options.global - target is the global object + options.stat - export as static methods of target + options.proto - export as prototype methods of target + options.real - real prototype method for the `pure` version + options.forced - export even if the native feature is available + options.bind - bind methods to the target, required for the `pure` version + options.wrap - wrap constructors to preventing global pollution, required for the `pure` version + options.unsafe - use the simple assignment of property instead of delete + defineProperty + options.sham - add a flag to not completely full polyfills + options.enumerable - export as enumerable property + options.noTargetGet - prevent calling a getter on target + */ + var _export = function (options, source) { + var TARGET = options.target; + var GLOBAL = options.global; + var STATIC = options.stat; + var FORCED, target, key, targetProperty, sourceProperty, descriptor; + if (GLOBAL) { + target = global_1; + } else if (STATIC) { + target = global_1[TARGET] || setGlobal(TARGET, {}); + } else { + target = (global_1[TARGET] || {}).prototype; + } + if (target) for (key in source) { + sourceProperty = source[key]; + if (options.noTargetGet) { + descriptor = getOwnPropertyDescriptor$1(target, key); + targetProperty = descriptor && descriptor.value; + } else targetProperty = target[key]; + FORCED = isForced_1(GLOBAL ? key : TARGET + (STATIC ? '.' : '#') + key, options.forced); + // contained in target + if (!FORCED && targetProperty !== undefined) { + if (typeof sourceProperty === typeof targetProperty) continue; + copyConstructorProperties(sourceProperty, targetProperty); + } + // add a flag to not completely full polyfills + if (options.sham || (targetProperty && targetProperty.sham)) { + createNonEnumerableProperty(sourceProperty, 'sham', true); + } + // extend global + redefine(target, key, sourceProperty, options); + } + }; + + var aFunction$1 = function (it) { + if (typeof it != 'function') { + throw TypeError(String(it) + ' is not a function'); + } return it; + }; + + // optional / simple context binding + var functionBindContext = function (fn, that, length) { + aFunction$1(fn); + if (that === undefined) return fn; + switch (length) { + case 0: return function () { + return fn.call(that); + }; + case 1: return function (a) { + return fn.call(that, a); + }; + case 2: return function (a, b) { + return fn.call(that, a, b); + }; + case 3: return function (a, b, c) { + return fn.call(that, a, b, c); + }; + } + return function (/* ...args */) { + return fn.apply(that, arguments); + }; + }; + + // `ToObject` abstract operation + // https://tc39.github.io/ecma262/#sec-toobject + var toObject = function (argument) { + return Object(requireObjectCoercible(argument)); + }; + + // call something on iterator step with safe closing on error + var callWithSafeIterationClosing = function (iterator, fn, value, ENTRIES) { + try { + return ENTRIES ? fn(anObject(value)[0], value[1]) : fn(value); + // 7.4.6 IteratorClose(iterator, completion) + } catch (error) { + var returnMethod = iterator['return']; + if (returnMethod !== undefined) anObject(returnMethod.call(iterator)); + throw error; + } + }; + + var nativeSymbol = !!Object.getOwnPropertySymbols && !fails(function () { + // Chrome 38 Symbol has incorrect toString conversion + // eslint-disable-next-line no-undef + return !String(Symbol()); + }); + + var useSymbolAsUid = nativeSymbol + // eslint-disable-next-line no-undef + && !Symbol.sham + // eslint-disable-next-line no-undef + && typeof Symbol.iterator == 'symbol'; + + var WellKnownSymbolsStore = shared('wks'); + var Symbol$1 = global_1.Symbol; + var createWellKnownSymbol = useSymbolAsUid ? Symbol$1 : Symbol$1 && Symbol$1.withoutSetter || uid; + + var wellKnownSymbol = function (name) { + if (!has(WellKnownSymbolsStore, name)) { + if (nativeSymbol && has(Symbol$1, name)) WellKnownSymbolsStore[name] = Symbol$1[name]; + else WellKnownSymbolsStore[name] = createWellKnownSymbol('Symbol.' + name); + } return WellKnownSymbolsStore[name]; + }; + + var iterators = {}; + + var ITERATOR = wellKnownSymbol('iterator'); + var ArrayPrototype = Array.prototype; + + // check on default Array iterator + var isArrayIteratorMethod = function (it) { + return it !== undefined && (iterators.Array === it || ArrayPrototype[ITERATOR] === it); + }; + + var createProperty = function (object, key, value) { + var propertyKey = toPrimitive(key); + if (propertyKey in object) objectDefineProperty.f(object, propertyKey, createPropertyDescriptor(0, value)); + else object[propertyKey] = value; + }; + + var TO_STRING_TAG = wellKnownSymbol('toStringTag'); + var test = {}; + + test[TO_STRING_TAG] = 'z'; + + var toStringTagSupport = String(test) === '[object z]'; + + var TO_STRING_TAG$1 = wellKnownSymbol('toStringTag'); + // ES3 wrong here + var CORRECT_ARGUMENTS = classofRaw(function () { return arguments; }()) == 'Arguments'; + + // fallback for IE11 Script Access Denied error + var tryGet = function (it, key) { + try { + return it[key]; + } catch (error) { /* empty */ } + }; + + // getting tag from ES6+ `Object.prototype.toString` + var classof = toStringTagSupport ? classofRaw : function (it) { + var O, tag, result; + return it === undefined ? 'Undefined' : it === null ? 'Null' + // @@toStringTag case + : typeof (tag = tryGet(O = Object(it), TO_STRING_TAG$1)) == 'string' ? tag + // builtinTag case + : CORRECT_ARGUMENTS ? classofRaw(O) + // ES3 arguments fallback + : (result = classofRaw(O)) == 'Object' && typeof O.callee == 'function' ? 'Arguments' : result; + }; + + var ITERATOR$1 = wellKnownSymbol('iterator'); + + var getIteratorMethod = function (it) { + if (it != undefined) return it[ITERATOR$1] + || it['@@iterator'] + || iterators[classof(it)]; + }; + + // `Array.from` method implementation + // https://tc39.github.io/ecma262/#sec-array.from + var arrayFrom = function from(arrayLike /* , mapfn = undefined, thisArg = undefined */) { + var O = toObject(arrayLike); + var C = typeof this == 'function' ? this : Array; + var argumentsLength = arguments.length; + var mapfn = argumentsLength > 1 ? arguments[1] : undefined; + var mapping = mapfn !== undefined; + var iteratorMethod = getIteratorMethod(O); + var index = 0; + var length, result, step, iterator, next, value; + if (mapping) mapfn = functionBindContext(mapfn, argumentsLength > 2 ? arguments[2] : undefined, 2); + // if the target is not iterable or it's an array with the default iterator - use a simple case + if (iteratorMethod != undefined && !(C == Array && isArrayIteratorMethod(iteratorMethod))) { + iterator = iteratorMethod.call(O); + next = iterator.next; + result = new C(); + for (;!(step = next.call(iterator)).done; index++) { + value = mapping ? callWithSafeIterationClosing(iterator, mapfn, [step.value, index], true) : step.value; + createProperty(result, index, value); + } + } else { + length = toLength(O.length); + result = new C(length); + for (;length > index; index++) { + value = mapping ? mapfn(O[index], index) : O[index]; + createProperty(result, index, value); + } + } + result.length = index; + return result; + }; + + var ITERATOR$2 = wellKnownSymbol('iterator'); + var SAFE_CLOSING = false; + + try { + var called = 0; + var iteratorWithReturn = { + next: function () { + return { done: !!called++ }; + }, + 'return': function () { + SAFE_CLOSING = true; + } + }; + iteratorWithReturn[ITERATOR$2] = function () { + return this; + }; + // eslint-disable-next-line no-throw-literal + Array.from(iteratorWithReturn, function () { throw 2; }); + } catch (error) { /* empty */ } + + var checkCorrectnessOfIteration = function (exec, SKIP_CLOSING) { + if (!SKIP_CLOSING && !SAFE_CLOSING) return false; + var ITERATION_SUPPORT = false; + try { + var object = {}; + object[ITERATOR$2] = function () { + return { + next: function () { + return { done: ITERATION_SUPPORT = true }; + } + }; + }; + exec(object); + } catch (error) { /* empty */ } + return ITERATION_SUPPORT; + }; + + var INCORRECT_ITERATION = !checkCorrectnessOfIteration(function (iterable) { + Array.from(iterable); + }); + + // `Array.from` method + // https://tc39.github.io/ecma262/#sec-array.from + _export({ target: 'Array', stat: true, forced: INCORRECT_ITERATION }, { + from: arrayFrom + }); + + // `Object.keys` method + // https://tc39.github.io/ecma262/#sec-object.keys + var objectKeys = Object.keys || function keys(O) { + return objectKeysInternal(O, enumBugKeys); + }; + + // `Object.defineProperties` method + // https://tc39.github.io/ecma262/#sec-object.defineproperties + var objectDefineProperties = descriptors ? Object.defineProperties : function defineProperties(O, Properties) { + anObject(O); + var keys = objectKeys(Properties); + var length = keys.length; + var index = 0; + var key; + while (length > index) objectDefineProperty.f(O, key = keys[index++], Properties[key]); + return O; + }; + + var html = getBuiltIn('document', 'documentElement'); + + var GT = '>'; + var LT = '<'; + var PROTOTYPE = 'prototype'; + var SCRIPT = 'script'; + var IE_PROTO = sharedKey('IE_PROTO'); + + var EmptyConstructor = function () { /* empty */ }; + + var scriptTag = function (content) { + return LT + SCRIPT + GT + content + LT + '/' + SCRIPT + GT; + }; + + // Create object with fake `null` prototype: use ActiveX Object with cleared prototype + var NullProtoObjectViaActiveX = function (activeXDocument) { + activeXDocument.write(scriptTag('')); + activeXDocument.close(); + var temp = activeXDocument.parentWindow.Object; + activeXDocument = null; // avoid memory leak + return temp; + }; + + // Create object with fake `null` prototype: use iframe Object with cleared prototype + var NullProtoObjectViaIFrame = function () { + // Thrash, waste and sodomy: IE GC bug + var iframe = documentCreateElement('iframe'); + var JS = 'java' + SCRIPT + ':'; + var iframeDocument; + iframe.style.display = 'none'; + html.appendChild(iframe); + // https://github.com/zloirock/core-js/issues/475 + iframe.src = String(JS); + iframeDocument = iframe.contentWindow.document; + iframeDocument.open(); + iframeDocument.write(scriptTag('document.F=Object')); + iframeDocument.close(); + return iframeDocument.F; + }; + + // Check for document.domain and active x support + // No need to use active x approach when document.domain is not set + // see https://github.com/es-shims/es5-shim/issues/150 + // variation of https://github.com/kitcambridge/es5-shim/commit/4f738ac066346 + // avoid IE GC bug + var activeXDocument; + var NullProtoObject = function () { + try { + /* global ActiveXObject */ + activeXDocument = document.domain && new ActiveXObject('htmlfile'); + } catch (error) { /* ignore */ } + NullProtoObject = activeXDocument ? NullProtoObjectViaActiveX(activeXDocument) : NullProtoObjectViaIFrame(); + var length = enumBugKeys.length; + while (length--) delete NullProtoObject[PROTOTYPE][enumBugKeys[length]]; + return NullProtoObject(); + }; + + hiddenKeys[IE_PROTO] = true; + + // `Object.create` method + // https://tc39.github.io/ecma262/#sec-object.create + var objectCreate = Object.create || function create(O, Properties) { + var result; + if (O !== null) { + EmptyConstructor[PROTOTYPE] = anObject(O); + result = new EmptyConstructor(); + EmptyConstructor[PROTOTYPE] = null; + // add "__proto__" for Object.getPrototypeOf polyfill + result[IE_PROTO] = O; + } else result = NullProtoObject(); + return Properties === undefined ? result : objectDefineProperties(result, Properties); + }; + + var UNSCOPABLES = wellKnownSymbol('unscopables'); + var ArrayPrototype$1 = Array.prototype; + + // Array.prototype[@@unscopables] + // https://tc39.github.io/ecma262/#sec-array.prototype-@@unscopables + if (ArrayPrototype$1[UNSCOPABLES] == undefined) { + objectDefineProperty.f(ArrayPrototype$1, UNSCOPABLES, { + configurable: true, + value: objectCreate(null) + }); + } + + // add a key to Array.prototype[@@unscopables] + var addToUnscopables = function (key) { + ArrayPrototype$1[UNSCOPABLES][key] = true; + }; + + var defineProperty = Object.defineProperty; + var cache = {}; + + var thrower = function (it) { throw it; }; + + var arrayMethodUsesToLength = function (METHOD_NAME, options) { + if (has(cache, METHOD_NAME)) return cache[METHOD_NAME]; + if (!options) options = {}; + var method = [][METHOD_NAME]; + var ACCESSORS = has(options, 'ACCESSORS') ? options.ACCESSORS : false; + var argument0 = has(options, 0) ? options[0] : thrower; + var argument1 = has(options, 1) ? options[1] : undefined; + + return cache[METHOD_NAME] = !!method && !fails(function () { + if (ACCESSORS && !descriptors) return true; + var O = { length: -1 }; + + if (ACCESSORS) defineProperty(O, 1, { enumerable: true, get: thrower }); + else O[1] = 1; + + method.call(O, argument0, argument1); + }); + }; + + var $includes = arrayIncludes.includes; + + + + var USES_TO_LENGTH = arrayMethodUsesToLength('indexOf', { ACCESSORS: true, 1: 0 }); + + // `Array.prototype.includes` method + // https://tc39.github.io/ecma262/#sec-array.prototype.includes + _export({ target: 'Array', proto: true, forced: !USES_TO_LENGTH }, { + includes: function includes(el /* , fromIndex = 0 */) { + return $includes(this, el, arguments.length > 1 ? arguments[1] : undefined); + } + }); + + // https://tc39.github.io/ecma262/#sec-array.prototype-@@unscopables + addToUnscopables('includes'); + + var nativeAssign = Object.assign; + var defineProperty$1 = Object.defineProperty; + + // `Object.assign` method + // https://tc39.github.io/ecma262/#sec-object.assign + var objectAssign = !nativeAssign || fails(function () { + // should have correct order of operations (Edge bug) + if (descriptors && nativeAssign({ b: 1 }, nativeAssign(defineProperty$1({}, 'a', { + enumerable: true, + get: function () { + defineProperty$1(this, 'b', { + value: 3, + enumerable: false + }); + } + }), { b: 2 })).b !== 1) return true; + // should work with symbols and should have deterministic property order (V8 bug) + var A = {}; + var B = {}; + // eslint-disable-next-line no-undef + var symbol = Symbol(); + var alphabet = 'abcdefghijklmnopqrst'; + A[symbol] = 7; + alphabet.split('').forEach(function (chr) { B[chr] = chr; }); + return nativeAssign({}, A)[symbol] != 7 || objectKeys(nativeAssign({}, B)).join('') != alphabet; + }) ? function assign(target, source) { // eslint-disable-line no-unused-vars + var T = toObject(target); + var argumentsLength = arguments.length; + var index = 1; + var getOwnPropertySymbols = objectGetOwnPropertySymbols.f; + var propertyIsEnumerable = objectPropertyIsEnumerable.f; + while (argumentsLength > index) { + var S = indexedObject(arguments[index++]); + var keys = getOwnPropertySymbols ? objectKeys(S).concat(getOwnPropertySymbols(S)) : objectKeys(S); + var length = keys.length; + var j = 0; + var key; + while (length > j) { + key = keys[j++]; + if (!descriptors || propertyIsEnumerable.call(S, key)) T[key] = S[key]; + } + } return T; + } : nativeAssign; + + // `Object.assign` method + // https://tc39.github.io/ecma262/#sec-object.assign + _export({ target: 'Object', stat: true, forced: Object.assign !== objectAssign }, { + assign: objectAssign + }); + + var propertyIsEnumerable = objectPropertyIsEnumerable.f; + + // `Object.{ entries, values }` methods implementation + var createMethod$1 = function (TO_ENTRIES) { + return function (it) { + var O = toIndexedObject(it); + var keys = objectKeys(O); + var length = keys.length; + var i = 0; + var result = []; + var key; + while (length > i) { + key = keys[i++]; + if (!descriptors || propertyIsEnumerable.call(O, key)) { + result.push(TO_ENTRIES ? [key, O[key]] : O[key]); + } + } + return result; + }; + }; + + var objectToArray = { + // `Object.entries` method + // https://tc39.github.io/ecma262/#sec-object.entries + entries: createMethod$1(true), + // `Object.values` method + // https://tc39.github.io/ecma262/#sec-object.values + values: createMethod$1(false) + }; + + var $entries = objectToArray.entries; + + // `Object.entries` method + // https://tc39.github.io/ecma262/#sec-object.entries + _export({ target: 'Object', stat: true }, { + entries: function entries(O) { + return $entries(O); + } + }); + + var FAILS_ON_PRIMITIVES = fails(function () { objectKeys(1); }); + + // `Object.keys` method + // https://tc39.github.io/ecma262/#sec-object.keys + _export({ target: 'Object', stat: true, forced: FAILS_ON_PRIMITIVES }, { + keys: function keys(it) { + return objectKeys(toObject(it)); + } + }); + + // `Object.prototype.toString` method implementation + // https://tc39.github.io/ecma262/#sec-object.prototype.tostring + var objectToString = toStringTagSupport ? {}.toString : function toString() { + return '[object ' + classof(this) + ']'; + }; + + // `Object.prototype.toString` method + // https://tc39.github.io/ecma262/#sec-object.prototype.tostring + if (!toStringTagSupport) { + redefine(Object.prototype, 'toString', objectToString, { unsafe: true }); + } + + var nativePromiseConstructor = global_1.Promise; + + var redefineAll = function (target, src, options) { + for (var key in src) redefine(target, key, src[key], options); + return target; + }; + + var defineProperty$2 = objectDefineProperty.f; + + + + var TO_STRING_TAG$2 = wellKnownSymbol('toStringTag'); + + var setToStringTag = function (it, TAG, STATIC) { + if (it && !has(it = STATIC ? it : it.prototype, TO_STRING_TAG$2)) { + defineProperty$2(it, TO_STRING_TAG$2, { configurable: true, value: TAG }); + } + }; + + var SPECIES = wellKnownSymbol('species'); + + var setSpecies = function (CONSTRUCTOR_NAME) { + var Constructor = getBuiltIn(CONSTRUCTOR_NAME); + var defineProperty = objectDefineProperty.f; + + if (descriptors && Constructor && !Constructor[SPECIES]) { + defineProperty(Constructor, SPECIES, { + configurable: true, + get: function () { return this; } + }); + } + }; + + var anInstance = function (it, Constructor, name) { + if (!(it instanceof Constructor)) { + throw TypeError('Incorrect ' + (name ? name + ' ' : '') + 'invocation'); + } return it; + }; + + var iterate_1 = createCommonjsModule(function (module) { + var Result = function (stopped, result) { + this.stopped = stopped; + this.result = result; + }; + + var iterate = module.exports = function (iterable, fn, that, AS_ENTRIES, IS_ITERATOR) { + var boundFunction = functionBindContext(fn, that, AS_ENTRIES ? 2 : 1); + var iterator, iterFn, index, length, result, next, step; + + if (IS_ITERATOR) { + iterator = iterable; + } else { + iterFn = getIteratorMethod(iterable); + if (typeof iterFn != 'function') throw TypeError('Target is not iterable'); + // optimisation for array iterators + if (isArrayIteratorMethod(iterFn)) { + for (index = 0, length = toLength(iterable.length); length > index; index++) { + result = AS_ENTRIES + ? boundFunction(anObject(step = iterable[index])[0], step[1]) + : boundFunction(iterable[index]); + if (result && result instanceof Result) return result; + } return new Result(false); + } + iterator = iterFn.call(iterable); + } + + next = iterator.next; + while (!(step = next.call(iterator)).done) { + result = callWithSafeIterationClosing(iterator, boundFunction, step.value, AS_ENTRIES); + if (typeof result == 'object' && result && result instanceof Result) return result; + } return new Result(false); + }; + + iterate.stop = function (result) { + return new Result(true, result); + }; + }); + + var SPECIES$1 = wellKnownSymbol('species'); + + // `SpeciesConstructor` abstract operation + // https://tc39.github.io/ecma262/#sec-speciesconstructor + var speciesConstructor = function (O, defaultConstructor) { + var C = anObject(O).constructor; + var S; + return C === undefined || (S = anObject(C)[SPECIES$1]) == undefined ? defaultConstructor : aFunction$1(S); + }; + + var engineUserAgent = getBuiltIn('navigator', 'userAgent') || ''; + + var engineIsIos = /(iphone|ipod|ipad).*applewebkit/i.test(engineUserAgent); + + var location$1 = global_1.location; + var set$1 = global_1.setImmediate; + var clear = global_1.clearImmediate; + var process = global_1.process; + var MessageChannel = global_1.MessageChannel; + var Dispatch = global_1.Dispatch; + var counter = 0; + var queue = {}; + var ONREADYSTATECHANGE = 'onreadystatechange'; + var defer, channel, port; + + var run = function (id) { + // eslint-disable-next-line no-prototype-builtins + if (queue.hasOwnProperty(id)) { + var fn = queue[id]; + delete queue[id]; + fn(); + } + }; + + var runner = function (id) { + return function () { + run(id); + }; + }; + + var listener = function (event) { + run(event.data); + }; + + var post = function (id) { + // old engines have not location.origin + global_1.postMessage(id + '', location$1.protocol + '//' + location$1.host); + }; + + // Node.js 0.9+ & IE10+ has setImmediate, otherwise: + if (!set$1 || !clear) { + set$1 = function setImmediate(fn) { + var args = []; + var i = 1; + while (arguments.length > i) args.push(arguments[i++]); + queue[++counter] = function () { + // eslint-disable-next-line no-new-func + (typeof fn == 'function' ? fn : Function(fn)).apply(undefined, args); + }; + defer(counter); + return counter; + }; + clear = function clearImmediate(id) { + delete queue[id]; + }; + // Node.js 0.8- + if (classofRaw(process) == 'process') { + defer = function (id) { + process.nextTick(runner(id)); + }; + // Sphere (JS game engine) Dispatch API + } else if (Dispatch && Dispatch.now) { + defer = function (id) { + Dispatch.now(runner(id)); + }; + // Browsers with MessageChannel, includes WebWorkers + // except iOS - https://github.com/zloirock/core-js/issues/624 + } else if (MessageChannel && !engineIsIos) { + channel = new MessageChannel(); + port = channel.port2; + channel.port1.onmessage = listener; + defer = functionBindContext(port.postMessage, port, 1); + // Browsers with postMessage, skip WebWorkers + // IE8 has postMessage, but it's sync & typeof its postMessage is 'object' + } else if ( + global_1.addEventListener && + typeof postMessage == 'function' && + !global_1.importScripts && + !fails(post) && + location$1.protocol !== 'file:' + ) { + defer = post; + global_1.addEventListener('message', listener, false); + // IE8- + } else if (ONREADYSTATECHANGE in documentCreateElement('script')) { + defer = function (id) { + html.appendChild(documentCreateElement('script'))[ONREADYSTATECHANGE] = function () { + html.removeChild(this); + run(id); + }; + }; + // Rest old browsers + } else { + defer = function (id) { + setTimeout(runner(id), 0); + }; + } + } + + var task = { + set: set$1, + clear: clear + }; + + var getOwnPropertyDescriptor$2 = objectGetOwnPropertyDescriptor.f; + + var macrotask = task.set; + + + var MutationObserver = global_1.MutationObserver || global_1.WebKitMutationObserver; + var process$1 = global_1.process; + var Promise$1 = global_1.Promise; + var IS_NODE = classofRaw(process$1) == 'process'; + // Node.js 11 shows ExperimentalWarning on getting `queueMicrotask` + var queueMicrotaskDescriptor = getOwnPropertyDescriptor$2(global_1, 'queueMicrotask'); + var queueMicrotask = queueMicrotaskDescriptor && queueMicrotaskDescriptor.value; + + var flush, head, last, notify, toggle, node, promise, then; + + // modern engines have queueMicrotask method + if (!queueMicrotask) { + flush = function () { + var parent, fn; + if (IS_NODE && (parent = process$1.domain)) parent.exit(); + while (head) { + fn = head.fn; + head = head.next; + try { + fn(); + } catch (error) { + if (head) notify(); + else last = undefined; + throw error; + } + } last = undefined; + if (parent) parent.enter(); + }; + + // Node.js + if (IS_NODE) { + notify = function () { + process$1.nextTick(flush); + }; + // browsers with MutationObserver, except iOS - https://github.com/zloirock/core-js/issues/339 + } else if (MutationObserver && !engineIsIos) { + toggle = true; + node = document.createTextNode(''); + new MutationObserver(flush).observe(node, { characterData: true }); + notify = function () { + node.data = toggle = !toggle; + }; + // environments with maybe non-completely correct, but existent Promise + } else if (Promise$1 && Promise$1.resolve) { + // Promise.resolve without an argument throws an error in LG WebOS 2 + promise = Promise$1.resolve(undefined); + then = promise.then; + notify = function () { + then.call(promise, flush); + }; + // for other environments - macrotask based on: + // - setImmediate + // - MessageChannel + // - window.postMessag + // - onreadystatechange + // - setTimeout + } else { + notify = function () { + // strange IE + webpack dev server bug - use .call(global) + macrotask.call(global_1, flush); + }; + } + } + + var microtask = queueMicrotask || function (fn) { + var task = { fn: fn, next: undefined }; + if (last) last.next = task; + if (!head) { + head = task; + notify(); + } last = task; + }; + + var PromiseCapability = function (C) { + var resolve, reject; + this.promise = new C(function ($$resolve, $$reject) { + if (resolve !== undefined || reject !== undefined) throw TypeError('Bad Promise constructor'); + resolve = $$resolve; + reject = $$reject; + }); + this.resolve = aFunction$1(resolve); + this.reject = aFunction$1(reject); + }; + + // 25.4.1.5 NewPromiseCapability(C) + var f$5 = function (C) { + return new PromiseCapability(C); + }; + + var newPromiseCapability = { + f: f$5 + }; + + var promiseResolve = function (C, x) { + anObject(C); + if (isObject(x) && x.constructor === C) return x; + var promiseCapability = newPromiseCapability.f(C); + var resolve = promiseCapability.resolve; + resolve(x); + return promiseCapability.promise; + }; + + var hostReportErrors = function (a, b) { + var console = global_1.console; + if (console && console.error) { + arguments.length === 1 ? console.error(a) : console.error(a, b); + } + }; + + var perform = function (exec) { + try { + return { error: false, value: exec() }; + } catch (error) { + return { error: true, value: error }; + } + }; + + var process$2 = global_1.process; + var versions = process$2 && process$2.versions; + var v8 = versions && versions.v8; + var match, version; + + if (v8) { + match = v8.split('.'); + version = match[0] + match[1]; + } else if (engineUserAgent) { + match = engineUserAgent.match(/Edge\/(\d+)/); + if (!match || match[1] >= 74) { + match = engineUserAgent.match(/Chrome\/(\d+)/); + if (match) version = match[1]; + } + } + + var engineV8Version = version && +version; + + var task$1 = task.set; + + + + + + + + + + + var SPECIES$2 = wellKnownSymbol('species'); + var PROMISE = 'Promise'; + var getInternalState = internalState.get; + var setInternalState = internalState.set; + var getInternalPromiseState = internalState.getterFor(PROMISE); + var PromiseConstructor = nativePromiseConstructor; + var TypeError$1 = global_1.TypeError; + var document$2 = global_1.document; + var process$3 = global_1.process; + var $fetch = getBuiltIn('fetch'); + var newPromiseCapability$1 = newPromiseCapability.f; + var newGenericPromiseCapability = newPromiseCapability$1; + var IS_NODE$1 = classofRaw(process$3) == 'process'; + var DISPATCH_EVENT = !!(document$2 && document$2.createEvent && global_1.dispatchEvent); + var UNHANDLED_REJECTION = 'unhandledrejection'; + var REJECTION_HANDLED = 'rejectionhandled'; + var PENDING = 0; + var FULFILLED = 1; + var REJECTED = 2; + var HANDLED = 1; + var UNHANDLED = 2; + var Internal, OwnPromiseCapability, PromiseWrapper, nativeThen; + + var FORCED = isForced_1(PROMISE, function () { + var GLOBAL_CORE_JS_PROMISE = inspectSource(PromiseConstructor) !== String(PromiseConstructor); + if (!GLOBAL_CORE_JS_PROMISE) { + // V8 6.6 (Node 10 and Chrome 66) have a bug with resolving custom thenables + // https://bugs.chromium.org/p/chromium/issues/detail?id=830565 + // We can't detect it synchronously, so just check versions + if (engineV8Version === 66) return true; + // Unhandled rejections tracking support, NodeJS Promise without it fails @@species test + if (!IS_NODE$1 && typeof PromiseRejectionEvent != 'function') return true; + } + // We can't use @@species feature detection in V8 since it causes + // deoptimization and performance degradation + // https://github.com/zloirock/core-js/issues/679 + if (engineV8Version >= 51 && /native code/.test(PromiseConstructor)) return false; + // Detect correctness of subclassing with @@species support + var promise = PromiseConstructor.resolve(1); + var FakePromise = function (exec) { + exec(function () { /* empty */ }, function () { /* empty */ }); + }; + var constructor = promise.constructor = {}; + constructor[SPECIES$2] = FakePromise; + return !(promise.then(function () { /* empty */ }) instanceof FakePromise); + }); + + var INCORRECT_ITERATION$1 = FORCED || !checkCorrectnessOfIteration(function (iterable) { + PromiseConstructor.all(iterable)['catch'](function () { /* empty */ }); + }); + + // helpers + var isThenable = function (it) { + var then; + return isObject(it) && typeof (then = it.then) == 'function' ? then : false; + }; + + var notify$1 = function (promise, state, isReject) { + if (state.notified) return; + state.notified = true; + var chain = state.reactions; + microtask(function () { + var value = state.value; + var ok = state.state == FULFILLED; + var index = 0; + // variable length - can't use forEach + while (chain.length > index) { + var reaction = chain[index++]; + var handler = ok ? reaction.ok : reaction.fail; + var resolve = reaction.resolve; + var reject = reaction.reject; + var domain = reaction.domain; + var result, then, exited; + try { + if (handler) { + if (!ok) { + if (state.rejection === UNHANDLED) onHandleUnhandled(promise, state); + state.rejection = HANDLED; + } + if (handler === true) result = value; + else { + if (domain) domain.enter(); + result = handler(value); // can throw + if (domain) { + domain.exit(); + exited = true; + } + } + if (result === reaction.promise) { + reject(TypeError$1('Promise-chain cycle')); + } else if (then = isThenable(result)) { + then.call(result, resolve, reject); + } else resolve(result); + } else reject(value); + } catch (error) { + if (domain && !exited) domain.exit(); + reject(error); + } + } + state.reactions = []; + state.notified = false; + if (isReject && !state.rejection) onUnhandled(promise, state); + }); + }; + + var dispatchEvent = function (name, promise, reason) { + var event, handler; + if (DISPATCH_EVENT) { + event = document$2.createEvent('Event'); + event.promise = promise; + event.reason = reason; + event.initEvent(name, false, true); + global_1.dispatchEvent(event); + } else event = { promise: promise, reason: reason }; + if (handler = global_1['on' + name]) handler(event); + else if (name === UNHANDLED_REJECTION) hostReportErrors('Unhandled promise rejection', reason); + }; + + var onUnhandled = function (promise, state) { + task$1.call(global_1, function () { + var value = state.value; + var IS_UNHANDLED = isUnhandled(state); + var result; + if (IS_UNHANDLED) { + result = perform(function () { + if (IS_NODE$1) { + process$3.emit('unhandledRejection', value, promise); + } else dispatchEvent(UNHANDLED_REJECTION, promise, value); + }); + // Browsers should not trigger `rejectionHandled` event if it was handled here, NodeJS - should + state.rejection = IS_NODE$1 || isUnhandled(state) ? UNHANDLED : HANDLED; + if (result.error) throw result.value; + } + }); + }; + + var isUnhandled = function (state) { + return state.rejection !== HANDLED && !state.parent; + }; + + var onHandleUnhandled = function (promise, state) { + task$1.call(global_1, function () { + if (IS_NODE$1) { + process$3.emit('rejectionHandled', promise); + } else dispatchEvent(REJECTION_HANDLED, promise, state.value); + }); + }; + + var bind = function (fn, promise, state, unwrap) { + return function (value) { + fn(promise, state, value, unwrap); + }; + }; + + var internalReject = function (promise, state, value, unwrap) { + if (state.done) return; + state.done = true; + if (unwrap) state = unwrap; + state.value = value; + state.state = REJECTED; + notify$1(promise, state, true); + }; + + var internalResolve = function (promise, state, value, unwrap) { + if (state.done) return; + state.done = true; + if (unwrap) state = unwrap; + try { + if (promise === value) throw TypeError$1("Promise can't be resolved itself"); + var then = isThenable(value); + if (then) { + microtask(function () { + var wrapper = { done: false }; + try { + then.call(value, + bind(internalResolve, promise, wrapper, state), + bind(internalReject, promise, wrapper, state) + ); + } catch (error) { + internalReject(promise, wrapper, error, state); + } + }); + } else { + state.value = value; + state.state = FULFILLED; + notify$1(promise, state, false); + } + } catch (error) { + internalReject(promise, { done: false }, error, state); + } + }; + + // constructor polyfill + if (FORCED) { + // 25.4.3.1 Promise(executor) + PromiseConstructor = function Promise(executor) { + anInstance(this, PromiseConstructor, PROMISE); + aFunction$1(executor); + Internal.call(this); + var state = getInternalState(this); + try { + executor(bind(internalResolve, this, state), bind(internalReject, this, state)); + } catch (error) { + internalReject(this, state, error); + } + }; + // eslint-disable-next-line no-unused-vars + Internal = function Promise(executor) { + setInternalState(this, { + type: PROMISE, + done: false, + notified: false, + parent: false, + reactions: [], + rejection: false, + state: PENDING, + value: undefined + }); + }; + Internal.prototype = redefineAll(PromiseConstructor.prototype, { + // `Promise.prototype.then` method + // https://tc39.github.io/ecma262/#sec-promise.prototype.then + then: function then(onFulfilled, onRejected) { + var state = getInternalPromiseState(this); + var reaction = newPromiseCapability$1(speciesConstructor(this, PromiseConstructor)); + reaction.ok = typeof onFulfilled == 'function' ? onFulfilled : true; + reaction.fail = typeof onRejected == 'function' && onRejected; + reaction.domain = IS_NODE$1 ? process$3.domain : undefined; + state.parent = true; + state.reactions.push(reaction); + if (state.state != PENDING) notify$1(this, state, false); + return reaction.promise; + }, + // `Promise.prototype.catch` method + // https://tc39.github.io/ecma262/#sec-promise.prototype.catch + 'catch': function (onRejected) { + return this.then(undefined, onRejected); + } + }); + OwnPromiseCapability = function () { + var promise = new Internal(); + var state = getInternalState(promise); + this.promise = promise; + this.resolve = bind(internalResolve, promise, state); + this.reject = bind(internalReject, promise, state); + }; + newPromiseCapability.f = newPromiseCapability$1 = function (C) { + return C === PromiseConstructor || C === PromiseWrapper + ? new OwnPromiseCapability(C) + : newGenericPromiseCapability(C); + }; + + if ( typeof nativePromiseConstructor == 'function') { + nativeThen = nativePromiseConstructor.prototype.then; + + // wrap native Promise#then for native async functions + redefine(nativePromiseConstructor.prototype, 'then', function then(onFulfilled, onRejected) { + var that = this; + return new PromiseConstructor(function (resolve, reject) { + nativeThen.call(that, resolve, reject); + }).then(onFulfilled, onRejected); + // https://github.com/zloirock/core-js/issues/640 + }, { unsafe: true }); + + // wrap fetch result + if (typeof $fetch == 'function') _export({ global: true, enumerable: true, forced: true }, { + // eslint-disable-next-line no-unused-vars + fetch: function fetch(input /* , init */) { + return promiseResolve(PromiseConstructor, $fetch.apply(global_1, arguments)); + } + }); + } + } + + _export({ global: true, wrap: true, forced: FORCED }, { + Promise: PromiseConstructor + }); + + setToStringTag(PromiseConstructor, PROMISE, false); + setSpecies(PROMISE); + + PromiseWrapper = getBuiltIn(PROMISE); + + // statics + _export({ target: PROMISE, stat: true, forced: FORCED }, { + // `Promise.reject` method + // https://tc39.github.io/ecma262/#sec-promise.reject + reject: function reject(r) { + var capability = newPromiseCapability$1(this); + capability.reject.call(undefined, r); + return capability.promise; + } + }); + + _export({ target: PROMISE, stat: true, forced: FORCED }, { + // `Promise.resolve` method + // https://tc39.github.io/ecma262/#sec-promise.resolve + resolve: function resolve(x) { + return promiseResolve( this, x); + } + }); + + _export({ target: PROMISE, stat: true, forced: INCORRECT_ITERATION$1 }, { + // `Promise.all` method + // https://tc39.github.io/ecma262/#sec-promise.all + all: function all(iterable) { + var C = this; + var capability = newPromiseCapability$1(C); + var resolve = capability.resolve; + var reject = capability.reject; + var result = perform(function () { + var $promiseResolve = aFunction$1(C.resolve); + var values = []; + var counter = 0; + var remaining = 1; + iterate_1(iterable, function (promise) { + var index = counter++; + var alreadyCalled = false; + values.push(undefined); + remaining++; + $promiseResolve.call(C, promise).then(function (value) { + if (alreadyCalled) return; + alreadyCalled = true; + values[index] = value; + --remaining || resolve(values); + }, reject); + }); + --remaining || resolve(values); + }); + if (result.error) reject(result.value); + return capability.promise; + }, + // `Promise.race` method + // https://tc39.github.io/ecma262/#sec-promise.race + race: function race(iterable) { + var C = this; + var capability = newPromiseCapability$1(C); + var reject = capability.reject; + var result = perform(function () { + var $promiseResolve = aFunction$1(C.resolve); + iterate_1(iterable, function (promise) { + $promiseResolve.call(C, promise).then(capability.resolve, reject); + }); + }); + if (result.error) reject(result.value); + return capability.promise; + } + }); + + // Safari bug https://bugs.webkit.org/show_bug.cgi?id=200829 + var NON_GENERIC = !!nativePromiseConstructor && fails(function () { + nativePromiseConstructor.prototype['finally'].call({ then: function () { /* empty */ } }, function () { /* empty */ }); + }); + + // `Promise.prototype.finally` method + // https://tc39.github.io/ecma262/#sec-promise.prototype.finally + _export({ target: 'Promise', proto: true, real: true, forced: NON_GENERIC }, { + 'finally': function (onFinally) { + var C = speciesConstructor(this, getBuiltIn('Promise')); + var isFunction = typeof onFinally == 'function'; + return this.then( + isFunction ? function (x) { + return promiseResolve(C, onFinally()).then(function () { return x; }); + } : onFinally, + isFunction ? function (e) { + return promiseResolve(C, onFinally()).then(function () { throw e; }); + } : onFinally + ); + } + }); + + // patch native Promise.prototype for native async functions + if ( typeof nativePromiseConstructor == 'function' && !nativePromiseConstructor.prototype['finally']) { + redefine(nativePromiseConstructor.prototype, 'finally', getBuiltIn('Promise').prototype['finally']); + } + + // `String.prototype.{ codePointAt, at }` methods implementation + var createMethod$2 = function (CONVERT_TO_STRING) { + return function ($this, pos) { + var S = String(requireObjectCoercible($this)); + var position = toInteger(pos); + var size = S.length; + var first, second; + if (position < 0 || position >= size) return CONVERT_TO_STRING ? '' : undefined; + first = S.charCodeAt(position); + return first < 0xD800 || first > 0xDBFF || position + 1 === size + || (second = S.charCodeAt(position + 1)) < 0xDC00 || second > 0xDFFF + ? CONVERT_TO_STRING ? S.charAt(position) : first + : CONVERT_TO_STRING ? S.slice(position, position + 2) : (first - 0xD800 << 10) + (second - 0xDC00) + 0x10000; + }; + }; + + var stringMultibyte = { + // `String.prototype.codePointAt` method + // https://tc39.github.io/ecma262/#sec-string.prototype.codepointat + codeAt: createMethod$2(false), + // `String.prototype.at` method + // https://github.com/mathiasbynens/String.prototype.at + charAt: createMethod$2(true) + }; + + var correctPrototypeGetter = !fails(function () { + function F() { /* empty */ } + F.prototype.constructor = null; + return Object.getPrototypeOf(new F()) !== F.prototype; + }); + + var IE_PROTO$1 = sharedKey('IE_PROTO'); + var ObjectPrototype = Object.prototype; + + // `Object.getPrototypeOf` method + // https://tc39.github.io/ecma262/#sec-object.getprototypeof + var objectGetPrototypeOf = correctPrototypeGetter ? Object.getPrototypeOf : function (O) { + O = toObject(O); + if (has(O, IE_PROTO$1)) return O[IE_PROTO$1]; + if (typeof O.constructor == 'function' && O instanceof O.constructor) { + return O.constructor.prototype; + } return O instanceof Object ? ObjectPrototype : null; + }; + + var ITERATOR$3 = wellKnownSymbol('iterator'); + var BUGGY_SAFARI_ITERATORS = false; + + var returnThis = function () { return this; }; + + // `%IteratorPrototype%` object + // https://tc39.github.io/ecma262/#sec-%iteratorprototype%-object + var IteratorPrototype, PrototypeOfArrayIteratorPrototype, arrayIterator; + + if ([].keys) { + arrayIterator = [].keys(); + // Safari 8 has buggy iterators w/o `next` + if (!('next' in arrayIterator)) BUGGY_SAFARI_ITERATORS = true; + else { + PrototypeOfArrayIteratorPrototype = objectGetPrototypeOf(objectGetPrototypeOf(arrayIterator)); + if (PrototypeOfArrayIteratorPrototype !== Object.prototype) IteratorPrototype = PrototypeOfArrayIteratorPrototype; + } + } + + if (IteratorPrototype == undefined) IteratorPrototype = {}; + + // 25.1.2.1.1 %IteratorPrototype%[@@iterator]() + if ( !has(IteratorPrototype, ITERATOR$3)) { + createNonEnumerableProperty(IteratorPrototype, ITERATOR$3, returnThis); + } + + var iteratorsCore = { + IteratorPrototype: IteratorPrototype, + BUGGY_SAFARI_ITERATORS: BUGGY_SAFARI_ITERATORS + }; + + var IteratorPrototype$1 = iteratorsCore.IteratorPrototype; + + + + + + var returnThis$1 = function () { return this; }; + + var createIteratorConstructor = function (IteratorConstructor, NAME, next) { + var TO_STRING_TAG = NAME + ' Iterator'; + IteratorConstructor.prototype = objectCreate(IteratorPrototype$1, { next: createPropertyDescriptor(1, next) }); + setToStringTag(IteratorConstructor, TO_STRING_TAG, false); + iterators[TO_STRING_TAG] = returnThis$1; + return IteratorConstructor; + }; + + var aPossiblePrototype = function (it) { + if (!isObject(it) && it !== null) { + throw TypeError("Can't set " + String(it) + ' as a prototype'); + } return it; + }; + + // `Object.setPrototypeOf` method + // https://tc39.github.io/ecma262/#sec-object.setprototypeof + // Works with __proto__ only. Old v8 can't work with null proto objects. + /* eslint-disable no-proto */ + var objectSetPrototypeOf = Object.setPrototypeOf || ('__proto__' in {} ? function () { + var CORRECT_SETTER = false; + var test = {}; + var setter; + try { + setter = Object.getOwnPropertyDescriptor(Object.prototype, '__proto__').set; + setter.call(test, []); + CORRECT_SETTER = test instanceof Array; + } catch (error) { /* empty */ } + return function setPrototypeOf(O, proto) { + anObject(O); + aPossiblePrototype(proto); + if (CORRECT_SETTER) setter.call(O, proto); + else O.__proto__ = proto; + return O; + }; + }() : undefined); + + var IteratorPrototype$2 = iteratorsCore.IteratorPrototype; + var BUGGY_SAFARI_ITERATORS$1 = iteratorsCore.BUGGY_SAFARI_ITERATORS; + var ITERATOR$4 = wellKnownSymbol('iterator'); + var KEYS = 'keys'; + var VALUES = 'values'; + var ENTRIES = 'entries'; + + var returnThis$2 = function () { return this; }; + + var defineIterator = function (Iterable, NAME, IteratorConstructor, next, DEFAULT, IS_SET, FORCED) { + createIteratorConstructor(IteratorConstructor, NAME, next); + + var getIterationMethod = function (KIND) { + if (KIND === DEFAULT && defaultIterator) return defaultIterator; + if (!BUGGY_SAFARI_ITERATORS$1 && KIND in IterablePrototype) return IterablePrototype[KIND]; + switch (KIND) { + case KEYS: return function keys() { return new IteratorConstructor(this, KIND); }; + case VALUES: return function values() { return new IteratorConstructor(this, KIND); }; + case ENTRIES: return function entries() { return new IteratorConstructor(this, KIND); }; + } return function () { return new IteratorConstructor(this); }; + }; + + var TO_STRING_TAG = NAME + ' Iterator'; + var INCORRECT_VALUES_NAME = false; + var IterablePrototype = Iterable.prototype; + var nativeIterator = IterablePrototype[ITERATOR$4] + || IterablePrototype['@@iterator'] + || DEFAULT && IterablePrototype[DEFAULT]; + var defaultIterator = !BUGGY_SAFARI_ITERATORS$1 && nativeIterator || getIterationMethod(DEFAULT); + var anyNativeIterator = NAME == 'Array' ? IterablePrototype.entries || nativeIterator : nativeIterator; + var CurrentIteratorPrototype, methods, KEY; + + // fix native + if (anyNativeIterator) { + CurrentIteratorPrototype = objectGetPrototypeOf(anyNativeIterator.call(new Iterable())); + if (IteratorPrototype$2 !== Object.prototype && CurrentIteratorPrototype.next) { + if ( objectGetPrototypeOf(CurrentIteratorPrototype) !== IteratorPrototype$2) { + if (objectSetPrototypeOf) { + objectSetPrototypeOf(CurrentIteratorPrototype, IteratorPrototype$2); + } else if (typeof CurrentIteratorPrototype[ITERATOR$4] != 'function') { + createNonEnumerableProperty(CurrentIteratorPrototype, ITERATOR$4, returnThis$2); + } + } + // Set @@toStringTag to native iterators + setToStringTag(CurrentIteratorPrototype, TO_STRING_TAG, true); + } + } + + // fix Array#{values, @@iterator}.name in V8 / FF + if (DEFAULT == VALUES && nativeIterator && nativeIterator.name !== VALUES) { + INCORRECT_VALUES_NAME = true; + defaultIterator = function values() { return nativeIterator.call(this); }; + } + + // define iterator + if ( IterablePrototype[ITERATOR$4] !== defaultIterator) { + createNonEnumerableProperty(IterablePrototype, ITERATOR$4, defaultIterator); + } + iterators[NAME] = defaultIterator; + + // export additional methods + if (DEFAULT) { + methods = { + values: getIterationMethod(VALUES), + keys: IS_SET ? defaultIterator : getIterationMethod(KEYS), + entries: getIterationMethod(ENTRIES) + }; + if (FORCED) for (KEY in methods) { + if (BUGGY_SAFARI_ITERATORS$1 || INCORRECT_VALUES_NAME || !(KEY in IterablePrototype)) { + redefine(IterablePrototype, KEY, methods[KEY]); + } + } else _export({ target: NAME, proto: true, forced: BUGGY_SAFARI_ITERATORS$1 || INCORRECT_VALUES_NAME }, methods); + } + + return methods; + }; + + var charAt = stringMultibyte.charAt; + + + + var STRING_ITERATOR = 'String Iterator'; + var setInternalState$1 = internalState.set; + var getInternalState$1 = internalState.getterFor(STRING_ITERATOR); + + // `String.prototype[@@iterator]` method + // https://tc39.github.io/ecma262/#sec-string.prototype-@@iterator + defineIterator(String, 'String', function (iterated) { + setInternalState$1(this, { + type: STRING_ITERATOR, + string: String(iterated), + index: 0 + }); + // `%StringIteratorPrototype%.next` method + // https://tc39.github.io/ecma262/#sec-%stringiteratorprototype%.next + }, function next() { + var state = getInternalState$1(this); + var string = state.string; + var index = state.index; + var point; + if (index >= string.length) return { value: undefined, done: true }; + point = charAt(string, index); + state.index += point.length; + return { value: point, done: false }; + }); + + var setInternalState$2 = internalState.set; + var getInternalAggregateErrorState = internalState.getterFor('AggregateError'); + + var $AggregateError = function AggregateError(errors, message) { + var that = this; + if (!(that instanceof $AggregateError)) return new $AggregateError(errors, message); + if (objectSetPrototypeOf) { + that = objectSetPrototypeOf(new Error(message), objectGetPrototypeOf(that)); + } + var errorsArray = []; + iterate_1(errors, errorsArray.push, errorsArray); + if (descriptors) setInternalState$2(that, { errors: errorsArray, type: 'AggregateError' }); + else that.errors = errorsArray; + if (message !== undefined) createNonEnumerableProperty(that, 'message', String(message)); + return that; + }; + + $AggregateError.prototype = objectCreate(Error.prototype, { + constructor: createPropertyDescriptor(5, $AggregateError), + message: createPropertyDescriptor(5, ''), + name: createPropertyDescriptor(5, 'AggregateError') + }); + + if (descriptors) objectDefineProperty.f($AggregateError.prototype, 'errors', { + get: function () { + return getInternalAggregateErrorState(this).errors; + }, + configurable: true + }); + + _export({ global: true }, { + AggregateError: $AggregateError + }); + + // `Promise.allSettled` method + // https://github.com/tc39/proposal-promise-allSettled + _export({ target: 'Promise', stat: true }, { + allSettled: function allSettled(iterable) { + var C = this; + var capability = newPromiseCapability.f(C); + var resolve = capability.resolve; + var reject = capability.reject; + var result = perform(function () { + var promiseResolve = aFunction$1(C.resolve); + var values = []; + var counter = 0; + var remaining = 1; + iterate_1(iterable, function (promise) { + var index = counter++; + var alreadyCalled = false; + values.push(undefined); + remaining++; + promiseResolve.call(C, promise).then(function (value) { + if (alreadyCalled) return; + alreadyCalled = true; + values[index] = { status: 'fulfilled', value: value }; + --remaining || resolve(values); + }, function (e) { + if (alreadyCalled) return; + alreadyCalled = true; + values[index] = { status: 'rejected', reason: e }; + --remaining || resolve(values); + }); + }); + --remaining || resolve(values); + }); + if (result.error) reject(result.value); + return capability.promise; + } + }); + + var PROMISE_ANY_ERROR = 'No one promise resolved'; + + // `Promise.any` method + // https://github.com/tc39/proposal-promise-any + _export({ target: 'Promise', stat: true }, { + any: function any(iterable) { + var C = this; + var capability = newPromiseCapability.f(C); + var resolve = capability.resolve; + var reject = capability.reject; + var result = perform(function () { + var promiseResolve = aFunction$1(C.resolve); + var errors = []; + var counter = 0; + var remaining = 1; + var alreadyResolved = false; + iterate_1(iterable, function (promise) { + var index = counter++; + var alreadyRejected = false; + errors.push(undefined); + remaining++; + promiseResolve.call(C, promise).then(function (value) { + if (alreadyRejected || alreadyResolved) return; + alreadyResolved = true; + resolve(value); + }, function (e) { + if (alreadyRejected || alreadyResolved) return; + alreadyRejected = true; + errors[index] = e; + --remaining || reject(new (getBuiltIn('AggregateError'))(errors, PROMISE_ANY_ERROR)); + }); + }); + --remaining || reject(new (getBuiltIn('AggregateError'))(errors, PROMISE_ANY_ERROR)); + }); + if (result.error) reject(result.value); + return capability.promise; + } + }); + + // `Promise.try` method + // https://github.com/tc39/proposal-promise-try + _export({ target: 'Promise', stat: true }, { + 'try': function (callbackfn) { + var promiseCapability = newPromiseCapability.f(this); + var result = perform(callbackfn); + (result.error ? promiseCapability.reject : promiseCapability.resolve)(result.value); + return promiseCapability.promise; + } + }); + + // iterable DOM collections + // flag - `iterable` interface - 'entries', 'keys', 'values', 'forEach' methods + var domIterables = { + CSSRuleList: 0, + CSSStyleDeclaration: 0, + CSSValueList: 0, + ClientRectList: 0, + DOMRectList: 0, + DOMStringList: 0, + DOMTokenList: 1, + DataTransferItemList: 0, + FileList: 0, + HTMLAllCollection: 0, + HTMLCollection: 0, + HTMLFormElement: 0, + HTMLSelectElement: 0, + MediaList: 0, + MimeTypeArray: 0, + NamedNodeMap: 0, + NodeList: 1, + PaintRequestList: 0, + Plugin: 0, + PluginArray: 0, + SVGLengthList: 0, + SVGNumberList: 0, + SVGPathSegList: 0, + SVGPointList: 0, + SVGStringList: 0, + SVGTransformList: 0, + SourceBufferList: 0, + StyleSheetList: 0, + TextTrackCueList: 0, + TextTrackList: 0, + TouchList: 0 + }; + + var ARRAY_ITERATOR = 'Array Iterator'; + var setInternalState$3 = internalState.set; + var getInternalState$2 = internalState.getterFor(ARRAY_ITERATOR); + + // `Array.prototype.entries` method + // https://tc39.github.io/ecma262/#sec-array.prototype.entries + // `Array.prototype.keys` method + // https://tc39.github.io/ecma262/#sec-array.prototype.keys + // `Array.prototype.values` method + // https://tc39.github.io/ecma262/#sec-array.prototype.values + // `Array.prototype[@@iterator]` method + // https://tc39.github.io/ecma262/#sec-array.prototype-@@iterator + // `CreateArrayIterator` internal method + // https://tc39.github.io/ecma262/#sec-createarrayiterator + var es_array_iterator = defineIterator(Array, 'Array', function (iterated, kind) { + setInternalState$3(this, { + type: ARRAY_ITERATOR, + target: toIndexedObject(iterated), // target + index: 0, // next index + kind: kind // kind + }); + // `%ArrayIteratorPrototype%.next` method + // https://tc39.github.io/ecma262/#sec-%arrayiteratorprototype%.next + }, function () { + var state = getInternalState$2(this); + var target = state.target; + var kind = state.kind; + var index = state.index++; + if (!target || index >= target.length) { + state.target = undefined; + return { value: undefined, done: true }; + } + if (kind == 'keys') return { value: index, done: false }; + if (kind == 'values') return { value: target[index], done: false }; + return { value: [index, target[index]], done: false }; + }, 'values'); + + // argumentsList[@@iterator] is %ArrayProto_values% + // https://tc39.github.io/ecma262/#sec-createunmappedargumentsobject + // https://tc39.github.io/ecma262/#sec-createmappedargumentsobject + iterators.Arguments = iterators.Array; + + // https://tc39.github.io/ecma262/#sec-array.prototype-@@unscopables + addToUnscopables('keys'); + addToUnscopables('values'); + addToUnscopables('entries'); + + var ITERATOR$5 = wellKnownSymbol('iterator'); + var TO_STRING_TAG$3 = wellKnownSymbol('toStringTag'); + var ArrayValues = es_array_iterator.values; + + for (var COLLECTION_NAME in domIterables) { + var Collection = global_1[COLLECTION_NAME]; + var CollectionPrototype = Collection && Collection.prototype; + if (CollectionPrototype) { + // some Chrome versions have non-configurable methods on DOMTokenList + if (CollectionPrototype[ITERATOR$5] !== ArrayValues) try { + createNonEnumerableProperty(CollectionPrototype, ITERATOR$5, ArrayValues); + } catch (error) { + CollectionPrototype[ITERATOR$5] = ArrayValues; + } + if (!CollectionPrototype[TO_STRING_TAG$3]) { + createNonEnumerableProperty(CollectionPrototype, TO_STRING_TAG$3, COLLECTION_NAME); + } + if (domIterables[COLLECTION_NAME]) for (var METHOD_NAME in es_array_iterator) { + // some Chrome versions have non-configurable methods on DOMTokenList + if (CollectionPrototype[METHOD_NAME] !== es_array_iterator[METHOD_NAME]) try { + createNonEnumerableProperty(CollectionPrototype, METHOD_NAME, es_array_iterator[METHOD_NAME]); + } catch (error) { + CollectionPrototype[METHOD_NAME] = es_array_iterator[METHOD_NAME]; + } + } + } + } + + /* + * classList.js: Cross-browser full element.classList implementation. + * 1.1.20150312 + * + * By Eli Grey, http://eligrey.com + * License: Dedicated to the public domain. + * See https://github.com/eligrey/classList.js/blob/master/LICENSE.md + */ + + /*global self, document, DOMException */ + + /*! @source http://purl.eligrey.com/github/classList.js/blob/master/classList.js */ + + if ("document" in self) { + + // Full polyfill for browsers with no classList support + // Including IE < Edge missing SVGElement.classList + if (!("classList" in document.createElement("_")) + || document.createElementNS && !("classList" in document.createElementNS("http://www.w3.org/2000/svg","g"))) { + + (function (view) { + + if (!('Element' in view)) return; + + var + classListProp = "classList" + , protoProp = "prototype" + , elemCtrProto = view.Element[protoProp] + , objCtr = Object + , strTrim = String[protoProp].trim || function () { + return this.replace(/^\s+|\s+$/g, ""); + } + , arrIndexOf = Array[protoProp].indexOf || function (item) { + var + i = 0 + , len = this.length + ; + for (; i < len; i++) { + if (i in this && this[i] === item) { + return i; + } + } + return -1; + } + // Vendors: please allow content code to instantiate DOMExceptions + , DOMEx = function (type, message) { + this.name = type; + this.code = DOMException[type]; + this.message = message; + } + , checkTokenAndGetIndex = function (classList, token) { + if (token === "") { + throw new DOMEx( + "SYNTAX_ERR" + , "An invalid or illegal string was specified" + ); + } + if (/\s/.test(token)) { + throw new DOMEx( + "INVALID_CHARACTER_ERR" + , "String contains an invalid character" + ); + } + return arrIndexOf.call(classList, token); + } + , ClassList = function (elem) { + var + trimmedClasses = strTrim.call(elem.getAttribute("class") || "") + , classes = trimmedClasses ? trimmedClasses.split(/\s+/) : [] + , i = 0 + , len = classes.length + ; + for (; i < len; i++) { + this.push(classes[i]); + } + this._updateClassName = function () { + elem.setAttribute("class", this.toString()); + }; + } + , classListProto = ClassList[protoProp] = [] + , classListGetter = function () { + return new ClassList(this); + } + ; + // Most DOMException implementations don't allow calling DOMException's toString() + // on non-DOMExceptions. Error's toString() is sufficient here. + DOMEx[protoProp] = Error[protoProp]; + classListProto.item = function (i) { + return this[i] || null; + }; + classListProto.contains = function (token) { + token += ""; + return checkTokenAndGetIndex(this, token) !== -1; + }; + classListProto.add = function () { + var + tokens = arguments + , i = 0 + , l = tokens.length + , token + , updated = false + ; + do { + token = tokens[i] + ""; + if (checkTokenAndGetIndex(this, token) === -1) { + this.push(token); + updated = true; + } + } + while (++i < l); + + if (updated) { + this._updateClassName(); + } + }; + classListProto.remove = function () { + var + tokens = arguments + , i = 0 + , l = tokens.length + , token + , updated = false + , index + ; + do { + token = tokens[i] + ""; + index = checkTokenAndGetIndex(this, token); + while (index !== -1) { + this.splice(index, 1); + updated = true; + index = checkTokenAndGetIndex(this, token); + } + } + while (++i < l); + + if (updated) { + this._updateClassName(); + } + }; + classListProto.toggle = function (token, force) { + token += ""; + + var + result = this.contains(token) + , method = result ? + force !== true && "remove" + : + force !== false && "add" + ; + + if (method) { + this[method](token); + } + + if (force === true || force === false) { + return force; + } else { + return !result; + } + }; + classListProto.toString = function () { + return this.join(" "); + }; + + if (objCtr.defineProperty) { + var classListPropDesc = { + get: classListGetter + , enumerable: true + , configurable: true + }; + try { + objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc); + } catch (ex) { // IE 8 doesn't support enumerable:true + if (ex.number === -0x7FF5EC54) { + classListPropDesc.enumerable = false; + objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc); + } + } + } else if (objCtr[protoProp].__defineGetter__) { + elemCtrProto.__defineGetter__(classListProp, classListGetter); + } + + }(self)); + + } else { + // There is full or partial native classList support, so just check if we need + // to normalize the add/remove and toggle APIs. + + (function () { + + var testElement = document.createElement("_"); + + testElement.classList.add("c1", "c2"); + + // Polyfill for IE 10/11 and Firefox <26, where classList.add and + // classList.remove exist but support only one argument at a time. + if (!testElement.classList.contains("c2")) { + var createMethod = function(method) { + var original = DOMTokenList.prototype[method]; + + DOMTokenList.prototype[method] = function(token) { + var i, len = arguments.length; + + for (i = 0; i < len; i++) { + token = arguments[i]; + original.call(this, token); + } + }; + }; + createMethod('add'); + createMethod('remove'); + } + + testElement.classList.toggle("c3", false); + + // Polyfill for IE 10 and Firefox <24, where classList.toggle does not + // support the second argument. + if (testElement.classList.contains("c3")) { + var _toggle = DOMTokenList.prototype.toggle; + + DOMTokenList.prototype.toggle = function(token, force) { + if (1 in arguments && !this.contains(token) === !force) { + return force; + } else { + return _toggle.call(this, token); + } + }; + + } + + testElement = null; + }()); + + } + + } + + var backboneEventsStandalone = createCommonjsModule(function (module, exports) { + /** + * Standalone extraction of Backbone.Events, no external dependency required. + * Degrades nicely when Backone/underscore are already available in the current + * global context. + * + * Note that docs suggest to use underscore's `_.extend()` method to add Events + * support to some given object. A `mixin()` method has been added to the Events + * prototype to avoid using underscore for that sole purpose: + * + * var myEventEmitter = BackboneEvents.mixin({}); + * + * Or for a function constructor: + * + * function MyConstructor(){} + * MyConstructor.prototype.foo = function(){} + * BackboneEvents.mixin(MyConstructor.prototype); + * + * (c) 2009-2013 Jeremy Ashkenas, DocumentCloud Inc. + * (c) 2013 Nicolas Perriault + */ + /* global exports:true, define, module */ + (function() { + var nativeForEach = Array.prototype.forEach, + hasOwnProperty = Object.prototype.hasOwnProperty, + slice = Array.prototype.slice, + idCounter = 0; + + // Returns a partial implementation matching the minimal API subset required + // by Backbone.Events + function miniscore() { + return { + keys: Object.keys || function (obj) { + if (typeof obj !== "object" && typeof obj !== "function" || obj === null) { + throw new TypeError("keys() called on a non-object"); + } + var key, keys = []; + for (key in obj) { + if (obj.hasOwnProperty(key)) { + keys[keys.length] = key; + } + } + return keys; + }, + + uniqueId: function(prefix) { + var id = ++idCounter + ''; + return prefix ? prefix + id : id; + }, + + has: function(obj, key) { + return hasOwnProperty.call(obj, key); + }, + + each: function(obj, iterator, context) { + if (obj == null) return; + if (nativeForEach && obj.forEach === nativeForEach) { + obj.forEach(iterator, context); + } else if (obj.length === +obj.length) { + for (var i = 0, l = obj.length; i < l; i++) { + iterator.call(context, obj[i], i, obj); + } + } else { + for (var key in obj) { + if (this.has(obj, key)) { + iterator.call(context, obj[key], key, obj); + } + } + } + }, + + once: function(func) { + var ran = false, memo; + return function() { + if (ran) return memo; + ran = true; + memo = func.apply(this, arguments); + func = null; + return memo; + }; + } + }; + } + + var _ = miniscore(), Events; + + // Backbone.Events + // --------------- + + // A module that can be mixed in to *any object* in order to provide it with + // custom events. You may bind with `on` or remove with `off` callback + // functions to an event; `trigger`-ing an event fires all callbacks in + // succession. + // + // var object = {}; + // _.extend(object, Backbone.Events); + // object.on('expand', function(){ alert('expanded'); }); + // object.trigger('expand'); + // + Events = { + + // Bind an event to a `callback` function. Passing `"all"` will bind + // the callback to all events fired. + on: function(name, callback, context) { + if (!eventsApi(this, 'on', name, [callback, context]) || !callback) return this; + this._events || (this._events = {}); + var events = this._events[name] || (this._events[name] = []); + events.push({callback: callback, context: context, ctx: context || this}); + return this; + }, + + // Bind an event to only be triggered a single time. After the first time + // the callback is invoked, it will be removed. + once: function(name, callback, context) { + if (!eventsApi(this, 'once', name, [callback, context]) || !callback) return this; + var self = this; + var once = _.once(function() { + self.off(name, once); + callback.apply(this, arguments); + }); + once._callback = callback; + return this.on(name, once, context); + }, + + // Remove one or many callbacks. If `context` is null, removes all + // callbacks with that function. If `callback` is null, removes all + // callbacks for the event. If `name` is null, removes all bound + // callbacks for all events. + off: function(name, callback, context) { + var retain, ev, events, names, i, l, j, k; + if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this; + if (!name && !callback && !context) { + this._events = {}; + return this; + } + + names = name ? [name] : _.keys(this._events); + for (i = 0, l = names.length; i < l; i++) { + name = names[i]; + if (events = this._events[name]) { + this._events[name] = retain = []; + if (callback || context) { + for (j = 0, k = events.length; j < k; j++) { + ev = events[j]; + if ((callback && callback !== ev.callback && callback !== ev.callback._callback) || + (context && context !== ev.context)) { + retain.push(ev); + } + } + } + if (!retain.length) delete this._events[name]; + } + } + + return this; + }, + + // Trigger one or many events, firing all bound callbacks. Callbacks are + // passed the same arguments as `trigger` is, apart from the event name + // (unless you're listening on `"all"`, which will cause your callback to + // receive the true name of the event as the first argument). + trigger: function(name) { + if (!this._events) return this; + var args = slice.call(arguments, 1); + if (!eventsApi(this, 'trigger', name, args)) return this; + var events = this._events[name]; + var allEvents = this._events.all; + if (events) triggerEvents(events, args); + if (allEvents) triggerEvents(allEvents, arguments); + return this; + }, + + // Tell this object to stop listening to either specific events ... or + // to every object it's currently listening to. + stopListening: function(obj, name, callback) { + var listeners = this._listeners; + if (!listeners) return this; + var deleteListener = !name && !callback; + if (typeof name === 'object') callback = this; + if (obj) (listeners = {})[obj._listenerId] = obj; + for (var id in listeners) { + listeners[id].off(name, callback, this); + if (deleteListener) delete this._listeners[id]; + } + return this; + } + + }; + + // Regular expression used to split event strings. + var eventSplitter = /\s+/; + + // Implement fancy features of the Events API such as multiple event + // names `"change blur"` and jQuery-style event maps `{change: action}` + // in terms of the existing API. + var eventsApi = function(obj, action, name, rest) { + if (!name) return true; + + // Handle event maps. + if (typeof name === 'object') { + for (var key in name) { + obj[action].apply(obj, [key, name[key]].concat(rest)); + } + return false; + } + + // Handle space separated event names. + if (eventSplitter.test(name)) { + var names = name.split(eventSplitter); + for (var i = 0, l = names.length; i < l; i++) { + obj[action].apply(obj, [names[i]].concat(rest)); + } + return false; + } + + return true; + }; + + // A difficult-to-believe, but optimized internal dispatch function for + // triggering events. Tries to keep the usual cases speedy (most internal + // Backbone events have 3 arguments). + var triggerEvents = function(events, args) { + var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2]; + switch (args.length) { + case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return; + case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return; + case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return; + case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return; + default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args); + } + }; + + var listenMethods = {listenTo: 'on', listenToOnce: 'once'}; + + // Inversion-of-control versions of `on` and `once`. Tell *this* object to + // listen to an event in another object ... keeping track of what it's + // listening to. + _.each(listenMethods, function(implementation, method) { + Events[method] = function(obj, name, callback) { + var listeners = this._listeners || (this._listeners = {}); + var id = obj._listenerId || (obj._listenerId = _.uniqueId('l')); + listeners[id] = obj; + if (typeof name === 'object') callback = this; + obj[implementation](name, callback, this); + return this; + }; + }); + + // Aliases for backwards compatibility. + Events.bind = Events.on; + Events.unbind = Events.off; + + // Mixin utility + Events.mixin = function(proto) { + var exports = ['on', 'once', 'off', 'trigger', 'stopListening', 'listenTo', + 'listenToOnce', 'bind', 'unbind']; + _.each(exports, function(name) { + proto[name] = this[name]; + }, this); + return proto; + }; + + // Export Events as BackboneEvents depending on current context + { + if ( module.exports) { + exports = module.exports = Events; + } + exports.BackboneEvents = Events; + } + })(); + }); + var backboneEventsStandalone_1 = backboneEventsStandalone.BackboneEvents; + + var backboneEventsStandalone$1 = backboneEventsStandalone; + + 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; + } + + function ownKeys$1(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 _objectSpread2(target) { + for (var i = 1; i < arguments.length; i++) { + var source = arguments[i] != null ? arguments[i] : {}; + + if (i % 2) { + ownKeys$1(Object(source), true).forEach(function (key) { + _defineProperty(target, key, source[key]); + }); + } else if (Object.getOwnPropertyDescriptors) { + Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); + } else { + ownKeys$1(Object(source)).forEach(function (key) { + Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); + }); + } + } + + return target; + } + + function _classCallCheck(instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } + } + + function _defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); + } + } + + function _createClass(Constructor, protoProps, staticProps) { + if (protoProps) _defineProperties(Constructor.prototype, protoProps); + if (staticProps) _defineProperties(Constructor, staticProps); + return Constructor; + } + + function _arrayWithHoles(arr) { + if (Array.isArray(arr)) return arr; + } + + function _iterableToArrayLimit(arr, i) { + if (typeof Symbol === "undefined" || !(Symbol.iterator in Object(arr))) 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 _arrayLikeToArray(arr, len) { + if (len == null || len > arr.length) len = arr.length; + + for (var i = 0, arr2 = new Array(len); i < len; i++) { + arr2[i] = arr[i]; + } + + return arr2; + } + + function _unsupportedIterableToArray(o, minLen) { + if (!o) return; + if (typeof o === "string") return _arrayLikeToArray(o, minLen); + var n = Object.prototype.toString.call(o).slice(8, -1); + if (n === "Object" && o.constructor) n = o.constructor.name; + if (n === "Map" || n === "Set") return Array.from(n); + if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); + } + + function _nonIterableRest() { + throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); + } + + function _slicedToArray(arr, i) { + return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); + } + + var Persistence = /*#__PURE__*/function () { + function Persistence(_ref) { + var cookies = _ref.cookies; + + _classCallCheck(this, Persistence); + + this.cookies = cookies; + } + + _createClass(Persistence, [{ + key: "store", + value: function store(vendors, signal) { + var _this = this; + + var vendorsByCookieName = vendors.reduce(function (sorted, vendor) { + var cookieName = vendor.cookieName; + sorted[cookieName] = sorted[cookieName] || []; + sorted[cookieName].push(vendor); + return sorted; + }, {}); + Object.entries(vendorsByCookieName).forEach(function (_ref2) { + var _ref3 = _slicedToArray(_ref2, 2), + cookieName = _ref3[0], + vendors = _ref3[1]; + + var cookieDomain = vendors[0].cookieDomain; + + _this.setCookie(cookieName, JSON.stringify(vendors.reduce(function (result, vendor) { + result[vendor.cookieKey || vendor.name] = signal === 'accepted' ? true : signal === 'denied' ? false : signal[vendor.name]; + return result; + }, {})), cookieDomain); + }); + } + }, { + key: "update", + value: function update(vendor, signal) { + var content = this.cookies.getItem(vendor.cookieName); + var flags = content ? JSON.parse(content) : {}; + this.setCookie(vendor.cookieName, JSON.stringify(_objectSpread2(_objectSpread2({}, flags), {}, _defineProperty({}, vendor.cookieKey || vendor.name, signal))), vendor.cookieDomain); + } + }, { + key: "read", + value: function read(vendor) { + var content = this.cookies.getItem(vendor.cookieName); + var flags = content ? JSON.parse(content) : {}; + var flag = flags[vendor.cookieKey || vendor.name]; + return flag === true ? 'accepted' : flag === false ? 'denied' : 'undecided'; + } + }, { + key: "setCookie", + value: function setCookie(name, value, domain) { + if (domain && !window.location.hostname.match(new RegExp("".concat(domain, "$")))) { + domain = null; + } + + this.cookies.setItem(name, value, { + path: '/', + domain: domain, + expires: Infinity, + // Ensure cookie can be read iframe embed + sameSite: 'None', + secure: true + }); + } + }]); + + return Persistence; + }(); // https://developer.mozilla.org/en-US/docs/Web/API/document.cookie + + + var cookies = { + getItem: function getItem(sKey) { + if (!sKey) { + return null; + } // eslint-disable-next-line no-useless-escape + + + return decodeURIComponent(document.cookie.replace(new RegExp("(?:(?:^|.*;)\\s*" + encodeURIComponent(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=\\s*([^;]*).*$)|^.*$"), "$1")) || null; + }, + setItem: function setItem() { + document.cookie = setItemCookieString.apply(void 0, arguments); + return true; + }, + removeItem: function removeItem(sKey, sPath, sDomain) { + if (!this.hasItem(sKey)) { + return false; + } + + document.cookie = encodeURIComponent(sKey) + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT" + (sDomain ? "; domain=" + sDomain : "") + (sPath ? "; path=" + sPath : ""); + return true; + }, + hasItem: function hasItem(sKey) { + if (!sKey) { + return false; + } // eslint-disable-next-line no-useless-escape + + + return new RegExp("(?:^|;\\s*)" + encodeURIComponent(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=").test(document.cookie); + }, + keys: function keys() { + // eslint-disable-next-line no-useless-escape + var aKeys = document.cookie.replace(/((?:^|\s*;)[^\=]+)(?=;|$)|^\s*|\s*(?:\=[^;]*)?(?:\1|$)/g, "").split(/\s*(?:\=[^;]*)?;\s*/); + + for (var nLen = aKeys.length, nIdx = 0; nIdx < nLen; nIdx++) { + aKeys[nIdx] = decodeURIComponent(aKeys[nIdx]); + } + + return aKeys; + } + }; + + function setItemCookieString(key, value, expiresOrOptions, path, domain, secure) { + if (expiresOrOptions && typeof expiresOrOptions === 'object' && expiresOrOptions.constructor !== Date) { + return setItemCookieStringWithOptions(key, value, expiresOrOptions); + } else { + return setItemCookieStringWithOptions(key, value, { + expires: expiresOrOptions, + path: path, + domain: domain, + secure: secure + }); + } + } + + function setItemCookieStringWithOptions(key, value, _ref) { + var expires = _ref.expires, + path = _ref.path, + domain = _ref.domain, + secure = _ref.secure, + sameSite = _ref.sameSite; + var expiresPart = ""; + + if (expires) { + switch (expires.constructor) { + case Number: + expiresPart = expires === Infinity ? "; expires=Fri, 31 Dec 9999 23:59:59 GMT" : "; max-age=" + expires; + break; + + case String: + expiresPart = "; expires=" + expires; + break; + + case Date: + expiresPart = "; expires=" + expires.toUTCString(); + break; + } + } + + return encodeURIComponent(key) + "=" + encodeURIComponent(value) + expiresPart + (domain ? "; domain=" + domain : "") + (path ? "; path=" + path : "") + (sameSite ? "; SameSite=" + sameSite : "") + (secure ? "; Secure" : ""); + } + + var supportedParadigms = ['external opt-out', 'opt-in', 'lazy opt-in', 'skip']; + + var Consent = /*#__PURE__*/function () { + function Consent(_ref) { + var _this = this; + + var cookies = _ref.cookies, + inEditor = _ref.inEditor; + + _classCallCheck(this, Consent); + + this.requestedPromise = new Promise(function (resolve) { + _this.requestedPromiseResolve = resolve; + }); + this.vendors = []; + this.persistence = new Persistence({ + cookies: cookies + }); + this.emitter = _objectSpread2({}, backboneEventsStandalone$1); + this.inEditor = inEditor; + } + + _createClass(Consent, [{ + key: "registerVendor", + value: function registerVendor(name, _ref2) { + var displayName = _ref2.displayName, + description = _ref2.description, + paradigm = _ref2.paradigm, + cookieName = _ref2.cookieName, + cookieKey = _ref2.cookieKey, + cookieDomain = _ref2.cookieDomain; + + if (this.vendorRegistrationClosed) { + throw new Error("Vendor ".concat(name, " has been registered after ") + 'registration has been closed.'); + } + + if (!name.match(/^[a-z0-9-_]+$/i)) { + throw new Error("Invalid vendor name '".concat(name, "'. ") + 'Only letters, numbers, hyphens and underscores are allowed.'); + } + + if (supportedParadigms.indexOf(paradigm) < 0) { + throw new Error("unknown paradigm ".concat(paradigm)); + } + + this.vendors.push({ + displayName: displayName, + description: description, + name: name, + paradigm: paradigm, + cookieName: cookieName || 'pageflow_consent', + cookieKey: cookieKey, + cookieDomain: cookieDomain + }); + } + }, { + key: "closeVendorRegistration", + value: function closeVendorRegistration() { + var _this2 = this; + + this.vendorRegistrationClosed = true; + + if (!this.getUndecidedOptInVendors().length) { + this.triggerDecisionEvents(); + return; + } + + var vendors = this.getRequestedVendors(); + this.requestedPromiseResolve({ + vendors: this.withState(vendors), + acceptAll: function acceptAll() { + _this2.persistence.store(vendors, 'accepted'); + + _this2.triggerDecisionEvents(); + }, + denyAll: function denyAll() { + _this2.persistence.store(vendors, 'denied'); + + _this2.triggerDecisionEvents(); + }, + save: function save(vendorConsent) { + _this2.persistence.store(vendors, vendorConsent); + + _this2.triggerDecisionEvents(); + } + }); + } + }, { + key: "relevantVendors", + value: function relevantVendors() { + var _this3 = this; + + var _ref3 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, + additionalVendorNames = _ref3.include; + + return this.withState(this.vendors.filter(function (vendor) { + return (additionalVendorNames === null || additionalVendorNames === void 0 ? void 0 : additionalVendorNames.includes(vendor.name)) || vendor.paradigm === 'opt-in' || vendor.paradigm === 'external opt-out' || vendor.paradigm === 'lazy opt-in' && _this3.persistence.read(vendor) !== 'undecided'; + }), { + applyDefaults: true + }); + } + }, { + key: "require", + value: function require(vendorName) { + var _this4 = this; + + if (this.inEditor) { + return Promise.resolve('fulfilled'); + } + + var vendor = this.findVendor(vendorName, 'require consent for'); + + switch (vendor.paradigm) { + case 'opt-in': + case 'lazy opt-in': + if (this.getUndecidedOptInVendors().length) { + return new Promise(function (resolve) { + _this4.emitter.once("".concat(vendor.name, ":accepted"), function () { + return resolve('fulfilled'); + }); + + _this4.emitter.once("".concat(vendor.name, ":denied"), function () { + return resolve('failed'); + }); + }); + } + + if (this.persistence.read(vendor) === 'accepted') { + return Promise.resolve('fulfilled'); + } else { + return Promise.resolve('failed'); + } + + case 'external opt-out': + if (this.persistence.read(vendor) === 'denied') { + return Promise.resolve('failed'); + } + + return Promise.resolve('fulfilled'); + + case 'skip': + return Promise.resolve('fulfilled'); + + default: + // should not be used + return null; + } + } + }, { + key: "requireAccepted", + value: function requireAccepted(vendorName) { + var _this5 = this; + + if (this.inEditor) { + return Promise.resolve('fulfilled'); + } + + var vendor = this.findVendor(vendorName, 'require consent for'); + + if (vendor.paradigm === 'opt-in' || vendor.paradigm === 'lazy opt-in') { + if (this.getUndecidedOptInVendors().length || this.persistence.read(vendor) !== 'accepted') { + return new Promise(function (resolve) { + _this5.emitter.once("".concat(vendor.name, ":accepted"), function () { + return resolve('fulfilled'); + }); + }); + } + + return Promise.resolve('fulfilled'); + } else { + return this.require(vendorName); + } + } + }, { + key: "requested", + value: function requested() { + return this.requestedPromise; + } + }, { + key: "accept", + value: function accept(vendorName) { + var vendor = this.findVendor(vendorName, 'accept'); + this.persistence.update(vendor, true); + this.emitter.trigger("".concat(vendor.name, ":accepted")); + } + }, { + key: "deny", + value: function deny(vendorName) { + var vendor = this.findVendor(vendorName, 'deny'); + this.persistence.update(vendor, false); + } + }, { + key: "getRequestedVendors", + value: function getRequestedVendors() { + return this.vendors.filter(function (vendor) { + return vendor.paradigm !== 'skip'; + }); + } + }, { + key: "getUndecidedOptInVendors", + value: function getUndecidedOptInVendors() { + var _this6 = this; + + return this.vendors.filter(function (vendor) { + return vendor.paradigm === 'opt-in' && _this6.persistence.read(vendor) === 'undecided'; + }); + } + }, { + key: "triggerDecisionEvents", + value: function triggerDecisionEvents() { + var _this7 = this; + + this.vendors.filter(function (vendor) { + return vendor.paradigm !== 'skip'; + }).forEach(function (vendor) { + _this7.emitter.trigger("".concat(vendor.name, ":").concat(_this7.persistence.read(vendor))); + }); + } + }, { + key: "findVendor", + value: function findVendor(vendorName, actionForErrorMessage) { + var vendor = this.vendors.find(function (vendor) { + return vendor.name === vendorName; + }); + + if (!vendor) { + throw new Error("Cannot ".concat(actionForErrorMessage, " unknown vendor \"").concat(vendorName, "\". ") + 'Consider using consent.registerVendor.'); + } + + return vendor; + } + }, { + key: "withState", + value: function withState(vendors) { + var _this8 = this; + + var _ref4 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, + applyDefaults = _ref4.applyDefaults; + + return vendors.map(function (vendor) { + var state = _this8.persistence.read(vendor); + + return _objectSpread2(_objectSpread2({}, vendor), {}, { + state: state === 'undecided' && applyDefaults ? _this8.getDefaultState(vendor) : state + }); + }); + } + }, { + key: "getDefaultState", + value: function getDefaultState(vendor) { + if (vendor.paradigm === 'external opt-out') { + return 'accepted'; + } + + return 'undecided'; + } + }]); + + return Consent; + }(); + + Consent.create = function () { + var inEditor = typeof PAGEFLOW_EDITOR !== 'undefined' && PAGEFLOW_EDITOR; + return new Consent({ + cookies: cookies, + inEditor: inEditor + }); + }; + + var log = function log(text, options) { + if (window.console && (debugMode() || options && options.force)) { + window.console.log(text); + } + }; + + var debugMode = function debugMode() { + return window.location.href.indexOf('debug=true') >= 0; + }; + + var state = typeof window !== 'undefined' && window.pageflow || {}; + var assetUrls = state.assetUrls || {}; + var events = Object.assign({}, backboneEventsStandalone$1); + /** + * Detect browser via user agent. Use only if feature detection is not + * an option. + */ + + var Agent = function Agent(userAgent) { + return { + matchesSilk: function matchesSilk() { + return matches(/\bSilk\b/); + }, + matchesDesktopSafari: function matchesDesktopSafari(options) { + if (options) { + return this.matchesSafari() && !this.matchesMobilePlatform() && matchesMinVersion(/Version\/(\d+)/i, options.minVersion); + } else { + return this.matchesSafari() && !this.matchesMobilePlatform(); + } + }, + matchesDesktopSafari9: function matchesDesktopSafari9() { + return this.matchesSafari9() && !this.matchesMobilePlatform(); + }, + matchesDesktopSafari10: function matchesDesktopSafari10() { + return this.matchesSafari10() && !this.matchesMobilePlatform(); + }, + matchesSafari9: function matchesSafari9() { + return this.matchesSafari() && matches(/Version\/9/i); + }, + matchesSafari10: function matchesSafari10() { + return this.matchesSafari() && matches(/Version\/10/i); + }, + matchesSafari11: function matchesSafari11() { + return this.matchesSafari() && matches(/Version\/11/i); + }, + matchesSafari11AndAbove: function matchesSafari11AndAbove() { + return this.matchesSafari() && matchesMinVersion(/Version\/(\d+)/i, 11); + }, + matchesSafari: function matchesSafari() { + // - Chrome also reports to be a Safari + // - Safari does not report to be a Chrome + // - Edge also reports to be a Safari, but also reports to be Chrome + return matches(/Safari\//i) && !matches(/Chrome/i); + }, + + /** + * Returns true on iOS Safari. + * @return {boolean} + */ + matchesMobileSafari: function matchesMobileSafari() { + var matchers = [/iPod/i, /iPad/i, /iPhone/i]; + return matchers.some(function (matcher) { + return userAgent.match(matcher); + }) && !window.MSStream || //IE exclusion from being detected as an iOS device; + matchesiPadSafari13AndAbove(); + }, + + /** + * Returns true on Android. + * @return {boolean} + */ + matchesAndroid: function matchesAndroid() { + return matches(/Android/i); + }, + + /** + * Returns true on iOS or Android. + * @return {boolean} + */ + matchesMobilePlatform: function matchesMobilePlatform() { + var matchers = [/iPod/i, /iPad/i, /iPhone/i, /Android/i, /Silk/i, /IEMobile/i]; + return matchers.some(function (matcher) { + return userAgent.match(matcher); + }) || matchesiPadSafari13AndAbove(); + }, + + /** + * Returns true on Internet Explorser version 9, 10 and 11. + * @return {boolean} + */ + matchesIEUpTo11: function matchesIEUpTo11() { + return userAgent.match(/Trident\//); + }, + + /** + * Returns true in InApp browser of Facebook app. + * @return {boolean} + */ + matchesFacebookInAppBrowser: function matchesFacebookInAppBrowser() { + return userAgent.match(/FBAN/) && userAgent.match(/FBAV/); + }, + matchesDesktopChrome: function matchesDesktopChrome(options) { + if (options) { + return this.matchesChrome() && !this.matchesMobilePlatform() && matchesMinVersion(/Chrome\/(\d+)/i, options.minVersion); + } else { + return this.matchesChrome() && !this.matchesMobilePlatform(); + } + }, + matchesDesktopFirefox: function matchesDesktopFirefox(options) { + if (options) { + return this.matchesFirefox() && !this.matchesMobilePlatform() && matchesMinVersion(/Firefox\/(\d+)/i, options.minVersion); + } else { + return this.matchesFirefox() && !this.matchesMobilePlatform(); + } + }, + matchesDesktopEdge: function matchesDesktopEdge(options) { + if (options) { + return this.matchesEdge() && !this.matchesMobilePlatform() && matchesMinVersion(/Edg\/(\d+)/i, options.minVersion); + } else { + return this.matchesEdge() && !this.matchesMobilePlatform(); + } + }, + + /** + * Returns true on Google Chrome. + * @return {boolean} + */ + matchesChrome: function matchesChrome() { + // - Edge also reports to be a Chrome + return matches(/Chrome\//i) && !matches(/Edg/i); + }, + + /** + * Returns true on Firefox. + * @return {boolean} + */ + matchesFirefox: function matchesFirefox() { + return matches(/Firefox\//i) && !matches(/Seamonkey/i); + }, + + /** + * Returns true on Microsoft Edge. + * @return {boolean} + */ + matchesEdge: function matchesEdge() { + return matches(/Edg\//i); + } + }; + + function matches(exp) { + return !!userAgent.match(exp); + } + + function matchesMinVersion(exp, version) { + var match = userAgent.match(exp); + return match && match[1] && parseInt(match[1], 10) >= version; + } //After ios13 update, iPad reports the same user string + //as Safari on Dekstop MacOS. + //At the time of this writing there are no other devices + //with multi-touch support other than IOS/iPadOS + //See: https://stackoverflow.com/a/58064481 + + + function matchesiPadSafari13AndAbove() { + return agent.matchesSafari() && navigator.maxTouchPoints > 1 && navigator.platform === 'MacIntel'; + } + }; + + var agent = new Agent(typeof navigator !== 'undefined' ? navigator.userAgent : 'ssr'); + /** + * Browser feature detection. + * + * @since 0.9 + */ + + var browser = function () { + var tests = {}, + results = {}, + featureDetectionComplete = false; + var readyPromiseResolve; + var readyPromise = new Promise(function (resolve, reject) { + readyPromiseResolve = resolve; + }); + return { + off: {}, + on: {}, + unset: {}, + + /** + * Add a feature test. + * + * @param name [String] Name of the feature. Can contain whitespace. + * @param test [Function] A function that either returns `true` or + * `false` or a promise that resolves to `true` or `false`. + * @memberof pageflow.browser + */ + feature: function feature(name, test) { + var s = name.replace(/ /g, '_'); + + this.off[s] = function () { + window.localStorage['override ' + name] = 'off'; + log('Feature off: ' + name, { + force: true + }); + }; + + this.on[s] = function () { + window.localStorage['override ' + name] = 'on'; + log('Feature on: ' + name, { + force: true + }); + }; + + this.unset[s] = function () { + window.localStorage.removeItem('override ' + name); + log('Feature unset: ' + name, { + force: true + }); + }; + + tests[name] = test; + }, + + /** + * Check whether the browser has a specific feature. This method + * may only be called after the `#ready` promise is resolved. + * + * @param name [String] Name of the feature. + * @return [Boolean] + * @memberof pageflow.browser + */ + has: function has(name) { + if (!featureDetectionComplete) { + throw 'Feature detection has not finished yet.'; + } + + if (results[name] === undefined) { + throw 'Unknown feature "' + name + '".'; + } + + return results[name]; + }, + + /** + * A promise that is resolved once feature detection has finished. + * + * @return Promise + * @memberof pageflow.browser + */ + ready: function ready() { + return readyPromise; + }, + + /** @api private */ + detectFeatures: function detectFeatures() { + var promises = {}; + + var asyncHas = function asyncHas(name) { + var runTest = function runTest() { + var value, + underscoredName = name.replace(/ /g, '_'); + + if (debugMode() && location.href.indexOf('&has=' + underscoredName) >= 0) { + value = location.href.indexOf('&has=' + underscoredName + '_on') >= 0; + log('FEATURE OVERRIDDEN ' + name + ': ' + value, { + force: true + }); + return value; + } else if ((debugMode() || window.PAGEFLOW_ALLOW_FEATURE_OVERRIDES) && window.localStorage && typeof window.localStorage['override ' + name] !== 'undefined') { + value = window.localStorage['override ' + name] === 'on'; + log('FEATURE OVERRIDDEN ' + name + ': ' + value, { + force: true + }); + return value; + } else { + return tests[name](asyncHas); + } + }; + + promises[name] = promises[name] || Promise.all([runTest()]).then(function (a) { + return a[0]; + }); + return promises[name]; + }; + + asyncHas.not = function (name) { + return asyncHas(name).then(function (result) { + return !result; + }); + }; + + asyncHas.all = function () { + return Promise.all(arguments).then(function (results) { + return results.every(function (result) { + return result; + }); + }); + }; + + Promise.all(Object.keys(tests).map(function (name) { + return asyncHas(name).then(function (result) { + var cssClassName = name.replace(/ /g, '_'); + document.body.classList.toggle('has_' + cssClassName, !!result); + document.body.classList.toggle('has_no_' + cssClassName, !result); + results[name] = !!result; + }); + })).then(function () { + featureDetectionComplete = true; + readyPromiseResolve(); + }); + return this.ready(); + } + }; + }(); + + browser.feature('autoplay support', function (has) { + return !agent.matchesSafari11AndAbove() && !agent.matchesMobilePlatform(); + }); + browser.feature('css animations', function () { + var prefixes = ['Webkit', 'Moz', 'O', 'ms', 'Khtml'], + elm = document.createElement('div'); + + if (elm.style.animationName !== undefined) { + return true; + } + + for (var i = 0; i < prefixes.length; i++) { + if (elm.style[prefixes[i] + 'AnimationName'] !== undefined) { + return true; + } + } + + return false; + }); // phone which hides parts of the browser viewport. Normally this is + // hidden once the user scrolls, but since there is no native + // scrolling in Pageflow, the bar stays and hides page elements like + // the slim player controls. + + browser.feature('facebook toolbar', function (has) { + return has.all(has('iphone platform'), agent.matchesFacebookInAppBrowser()); + }); + browser.feature('ie', function () { + if (navigator.appName == 'Microsoft Internet Explorer') { + return true; + } else { + return false; + } + }); + browser.feature('ios platform', function () { + return agent.matchesMobileSafari(); + }); + browser.feature('iphone platform', function (has) { + return has.all(has('ios platform'), has('phone platform')); + }); + browser.feature('mobile platform', function () { + return agent.matchesMobilePlatform(); + }); + browser.feature('phone platform', function () { + var matchers = [/iPod/i, /iPad/i, /iPhone/i, /Android/i, /IEMobile/i]; + return matchers.some(function (matcher) { + return navigator.userAgent.match(matcher) && window.innerWidth < 700; + }); + }); + browser.feature('pushstate support', function () { + return window.history && 'pushState' in window.history; + }); + browser.feature('request animation frame support', function () { + return 'requestAnimationFrame' in window || 'web'; + }); + browser.feature('touch support', function () { + return 'ontouchstart' in window || + /* Firefox on android */ + window.DocumentTouch && document instanceof window.DocumentTouch || + /* > 0 on IE touch devices */ + navigator.maxTouchPoints; + }); + browser.feature('rewrite video sources support', function () { + // set from conditionally included script file + return !state.ie9; + }); + browser.feature('stop buffering support', function (has) { + return has.not('mobile platform'); + }); + browser.feature('buffer underrun waiting support', function (has) { + return has.not('mobile platform'); + }); + browser.feature('prebuffering support', function (has) { + return has.not('mobile platform'); + }); + browser.feature('mp4 support only', function (has) { + // - Silk does not play videos with hls source + // - Desktop Safari 9.1 does not loop hls videos + // - Desktop Safari 10 does not loop hls videos on El + // Capitan. Appears to be fixed on Sierra + return agent.matchesSilk() || agent.matchesDesktopSafari9() || agent.matchesDesktopSafari10(); + }); + browser.feature('mse and native hls support', function (has) { + return agent.matchesSafari() && !agent.matchesMobilePlatform(); + }); + browser.feature('native video player', function (has) { + return has('iphone platform'); + }); + browser.feature('volume control support', function (has) { + return has.not('ios platform'); + }); + browser.feature('audio context volume fading support', function () { + return !agent.matchesDesktopSafari(); + }); + browser.agent = agent; + browser.Agent = Agent; + /** + * Let plugins register functions which extend the editor or + * slideshow with certain functionality when a named feature is + * enabled. + * + * @alias pageflow.features + * @since 0.9 + */ + + var Features = /*#__PURE__*/function () { + /** @lends pageflow.features */ + + /** @api private */ + function Features() { + _classCallCheck(this, Features); + + this.registry = {}; + this.enabledFeatureNames = []; + } + /** + * `pageflow.features` has been renamed to `pageflow.browser`. + * @deprecated + */ + + + _createClass(Features, [{ + key: "has", + value: function has() { + return browser.has.apply(browser, arguments); + } + /** + * Register a function to configure a feature when it is active. + * + * @param {String} scope - Name of the scope the passed function + * shall be called in. + * @param name [String] Name of the feature + * @param fn [Function] Function to call when the given feature + * is activate. + */ + + }, { + key: "register", + value: function register(scope, name, fn) { + this.registry[scope] = this.registry[scope] || {}; + this.registry[scope][name] = this.registry[scope][name] || []; + this.registry[scope][name].push(fn); + } + /** + * Check if a feature as been enabled. + * + * @param name [String] + * @return [Boolean] + */ + + }, { + key: "isEnabled", + value: function isEnabled(name) { + return this.enabledFeatureNames.includes(name); + } + /** @api private */ + + }, { + key: "enable", + value: function enable(scope, names) { + var fns = this.registry[scope] || {}; + this.enabledFeatureNames = this.enabledFeatureNames.concat(names); + names.forEach(function (name) { + (fns[name] || []).forEach(function (fn) { + fn(); + }); + }); + } + }]); + + return Features; + }(); + + var features = new Features(); + + var handleFailedPlay = function handleFailedPlay(player, options) { + var originalPlay = player.play; + + player.play = function () { + var result = originalPlay.apply(player, arguments); + + if (result && typeof result["catch"] !== 'undefined') { + return result["catch"](function (e) { + if (e.name === 'NotAllowedError' && options.hasAutoplaySupport) { + if (options.fallbackToMutedAutoplay) { + player.muted(true); + return originalPlay.apply(player, arguments).then(function () { + player.trigger('playmuted'); + }, function () { + player.trigger('playfailed'); + }); + } else { + player.trigger('playfailed'); + } + } else { + log('Caught play exception for video.'); + } + }); + } + + return result; + }; + }; + + var asyncPlay = function asyncPlay(player) { + var originalPlay = player.play; + var originalPause = player.pause; + var intendingToPlay = false; + var intendingToPause = false; + + player.play = function () { + player.intendToPlay(); + return originalPlay.apply(player, arguments); + }; + + player.pause = function () { + player.intendToPause(); + return originalPause.apply(player, arguments); + }; + + player.intendToPlay = function () { + intendingToPlay = true; + intendingToPause = false; + }; + + player.intendToPause = function () { + intendingToPause = true; + intendingToPlay = false; + }; + + player.intendingToPlay = function () { + return intendingToPlay; + }; + + player.intendingToPause = function () { + return intendingToPause; + }; + + player.ifIntendingToPause = function () { + return promiseFromBoolean(intendingToPause); + }; + + player.ifIntendingToPlay = function () { + return promiseFromBoolean(intendingToPlay); + }; + + function promiseFromBoolean(value) { + return new Promise(function (resolve, reject) { + if (value) { + resolve(); + } else { + reject('aborted'); + } + }); + } + }; + + var hooks = function hooks(player, _hooks) { + var originalPlay = player.play; + + player.updateHooks = function (newHooks) { + _hooks = newHooks; + }; + + player.play = function () { + var args = arguments; + player.trigger('beforeplay'); + player.intendToPlay(); + + if (_hooks.before) { + return Promise.all([_hooks.before()]).then(function () { + return player.ifIntendingToPlay().then(function () { + return originalPlay.apply(player, args); + }); + }); + } else { + return originalPlay.apply(player, args); + } + }; + + if (player.afterHookListener) { + player.off('pause', player.afterHookListener); + player.off('ended', player.afterHookListener); + } + + player.afterHookListener = function () { + if (_hooks.after) { + _hooks.after(); + } + }; + + player.on('pause', player.afterHookListener); + player.on('ended', player.afterHookListener); + }; + + var volumeBinding = function volumeBinding(player, settings, options) { + options = options || {}; + var originalPlay = player.play; + var originalPause = player.pause; + var volumeFactor = 'volumeFactor' in options ? options.volumeFactor : 1; + + player.play = function () { + player.intendToPlay(); + player.volume(player.targetVolume()); + listenToVolumeSetting(); + return originalPlay.call(player); + }; + + player.playAndFadeIn = function (duration) { + if (!player.paused() && !player.intendingToPause()) { + return Promise.resolve(); + } + + player.intendToPlay(); + player.volume(0); + return Promise.all([originalPlay.call(player)]).then(function () { + listenToVolumeSetting(); + return player.ifIntendingToPlay().then(function () { + return player.fadeVolume(player.targetVolume(), duration).then(null, function () { + return Promise.resolve(); + }); + }); + }); + }; + + player.pause = function () { + stopListeningToVolumeSetting(); + originalPause.call(player); + }; + + player.fadeOutAndPause = function (duration) { + if (player.paused() && !player.intendingToPlay()) { + return Promise.resolve(); + } + + player.intendToPause(); + stopListeningToVolumeSetting(); + return player.fadeVolume(0, duration).then(function () { + return player.ifIntendingToPause().then(function () { + originalPause.call(player); + }); + }); + }; + + player.changeVolumeFactor = function (factor, duration) { + volumeFactor = factor; + return player.fadeVolume(player.targetVolume(), duration); + }; + + player.targetVolume = function () { + return (options.ignoreVolumeSetting ? 1 : settings.get('volume')) * volumeFactor; + }; + + function listenToVolumeSetting() { + player.on('dispose', stopListeningToVolumeSetting); + settings.on('change:volume', onVolumeChange); + } + + function stopListeningToVolumeSetting() { + player.off('dispose', stopListeningToVolumeSetting); + settings.off('change:volume', onVolumeChange); + } + + function onVolumeChange(model, value) { + player.fadeVolume(player.targetVolume(), 40); + } + }; + /** + * Obtain the globally shared audio context. There can only be a + * limited number of `AudioContext` objects in one page. + * + * @since 12.1 + */ + + + var audioContext = { + /** + * @returns [AudioContext] + * Returns `null` if web audio API is not supported or creating + * the context fails. + */ + get: function get() { + var AudioContext = window.AudioContext || window.webkitAudioContext; + + if (typeof this._audioContext === 'undefined') { + try { + this._audioContext = AudioContext && new AudioContext(); + } catch (e) { + this._audioContext = null; + log('Failed to create AudioContext.', { + force: true + }); + } + } + + return this._audioContext; + } + }; + + var webAudio = function webAudio(player, audioContext) { + var gainNode; + var currentResolve; + var currentTimeout; + var currentValue = 1; + var lastStartTime; + var lastDuration; + var lastStartValue; + var allowedMinValue = 0.000001; + + if (audioContext.state === 'suspended') { + events.on('background_media:unmute', function () { + player.volume(currentValue); + }); + } + + function tryResumeIfSuspended() { + return new Promise(function (resolve, reject) { + if (audioContext.state === 'suspended') { + var maybePromise = audioContext.resume(); + + if (maybePromise && maybePromise.then) { + maybePromise.then(handlePromise); + } else { + setTimeout(handlePromise, 0); + } + } else { + resolve(); + } + + function handlePromise() { + if (audioContext.state === 'suspended') { + reject(); + } else { + resolve(); + } + } + }); + } + + player.volume = function (value) { + if (typeof value !== 'undefined') { + tryResumeIfSuspended().then(function () { + ensureGainNode(); + cancel(); + currentValue = ensureInAllowedRange(value); + gainNode.gain.setValueAtTime(currentValue, audioContext.currentTime); + }, function () { + currentValue = ensureInAllowedRange(value); + }); + } + + return Math.round(currentValue * 100) / 100; + }; + + player.fadeVolume = function (value, duration) { + return tryResumeIfSuspended().then(function () { + ensureGainNode(); + cancel(); + recordFadeStart(duration); + currentValue = ensureInAllowedRange(value); + gainNode.gain.setValueAtTime(lastStartValue, audioContext.currentTime); + gainNode.gain.linearRampToValueAtTime(currentValue, audioContext.currentTime + duration / 1000); + return new Promise(function (resolve, reject) { + currentResolve = resolve; + currentTimeout = setTimeout(resolveCurrent, duration); + }); + }, function () { + currentValue = ensureInAllowedRange(value); + return Promise.resolve(); + }); + }; + + player.one('dispose', cancel); + + function ensureGainNode() { + if (!gainNode) { + gainNode = audioContext.createGain(); + var source = audioContext.createMediaElementSource(player.getMediaElement()); + source.connect(gainNode); + gainNode.connect(audioContext.destination); + } + } + + function resolveCurrent() { + clearTimeout(currentTimeout); + currentResolve('done'); + currentTimeout = null; + currentResolve = null; + } + + function cancel() { + if (currentResolve) { + gainNode.gain.cancelScheduledValues(audioContext.currentTime); + clearTimeout(currentTimeout); + currentResolve('cancelled'); + currentTimeout = null; + currentResolve = null; + updateCurrentValueFromComputedValue(); + } + } + + function recordFadeStart(duration) { + lastStartTime = audioContext.currentTime; + lastStartValue = currentValue; + lastDuration = duration; + } + + function updateCurrentValueFromComputedValue() { + // Firefox 54 on Ubuntu does not provide computed values when gain + // was changed via one of the scheduling methods. Instead + // gain.value always reports 1. Interpolate manually do determine + // how far the fade was performed before cancel was called. + if (gainNode.gain.value == 1) { + var performedDuration = (audioContext.currentTime - lastStartTime) * 1000; + var lastDelta = currentValue - lastStartValue; + var performedFraction = lastDelta > 0 ? performedDuration / lastDuration : 1; + currentValue = ensureInAllowedRange(lastStartValue + performedFraction * lastDelta); + } else { + currentValue = gainNode.gain.value; + } + } + + function ensureInAllowedRange(value) { + return value < allowedMinValue ? allowedMinValue : value; + } + }; + + var noop = function noop(player) { + player.fadeVolume = function (value, duration) { + return Promise.resolve(); + }; + }; + + var interval = function interval(player) { + var originalVolume = player.volume; + var fadeVolumeResolve; + var fadeVolumeInterval; + + player.volume = function (value) { + if (typeof value !== 'undefined') { + cancelFadeVolume(); + } + + return originalVolume.apply(player, arguments); + }; + + player.fadeVolume = function (value, duration) { + cancelFadeVolume(); + return new Promise(function (resolve, reject) { + var resolution = 10; + var startValue = volume(); + var steps = duration / resolution; + var leap = (value - startValue) / steps; + + if (value === startValue) { + resolve(); + } else { + fadeVolumeResolve = resolve; + fadeVolumeInterval = setInterval(function () { + volume(volume() + leap); + + if (volume() >= value && value >= startValue || volume() <= value && value <= startValue) { + resolveFadeVolume(); + } + }, resolution); + } + }); + }; + + player.one('dispose', cancelFadeVolume); + + function volume() { + return originalVolume.apply(player, arguments); + } + + function resolveFadeVolume() { + clearInterval(fadeVolumeInterval); + fadeVolumeResolve('done'); + fadeVolumeInterval = null; + fadeVolumeResolve = null; + } + + function cancelFadeVolume() { + if (fadeVolumeResolve) { + fadeVolumeResolve('cancelled'); + fadeVolumeResolve = null; + } + + if (fadeVolumeInterval) { + clearInterval(fadeVolumeInterval); + fadeVolumeInterval = null; + } + } + }; + + var volumeFading = function volumeFading(player) { + if (!browser.has('volume control support')) { + return noop(player); + } else if (browser.has('audio context volume fading support') && audioContext.get() && player.getMediaElement) { + return webAudio(player, audioContext.get()); + } else { + return interval(player); + } + }; + + volumeFading.interval = interval; + volumeFading.noop = noop; + volumeFading.webAudio = webAudio; + + var loadWaiting = function loadWaiting(player) { + var originalFadeVolume = player.fadeVolume; + + player.fadeVolume = function () { + var args = arguments; + return Promise.all([this.loadedPromise]).then(function () { + return originalFadeVolume.apply(player, args); + }); + }; + }; + + var Settings = /*#__PURE__*/function () { + function Settings() { + _classCallCheck(this, Settings); + + this.attributes = { + volume: 1 + }; + this.initialize(); + } + + _createClass(Settings, [{ + key: "get", + value: function get(attributeName) { + return this.attributes[attributeName]; + } + }, { + key: "set", + value: function set(key, value) { + var attrs; + + if (typeof key === 'object') { + attrs = key; + } else { + (attrs = {})[key] = value; + } + + for (var attr in attrs) { + this.attributes[attr] = attrs[attr]; + this.trigger('change:' + attr); + } + + this.trigger('change'); + } + }, { + key: "toJSON", + value: function toJSON() { + return _objectSpread2({}, this.attributes); + } + }, { + key: "initialize", + value: function initialize() { + var storage = this.getLocalStorage(); + + if (storage) { + if (storage['pageflow.settings']) { + try { + this.set(JSON.parse(storage['pageflow.settings'])); + } catch (e) { + log(e); + } + } + + this.on('change', function () { + storage['pageflow.settings'] = JSON.stringify(this.attributes); + }); + } + } + }, { + key: "getLocalStorage", + value: function getLocalStorage() { + try { + return window.localStorage; + } catch (e) { + // Safari throws SecurityError when accessing window.localStorage + // if cookies/website data are disabled. + return null; + } + } + }]); + + return Settings; + }(); + + Object.assign(Settings.prototype, backboneEventsStandalone$1); + var settings = new Settings(); + var mediaPlayer = { + enhance: function enhance(player, options) { + handleFailedPlay(player, _objectSpread2({ + hasAutoplaySupport: browser.has('autoplay support') + }, options)); + asyncPlay(player); + + if (options.hooks) { + hooks(player, options.hooks); + } + + if (options.volumeFading) { + volumeFading(player); + volumeBinding(player, settings, options); + } + + if (options.loadWaiting) { + loadWaiting(player); + } + } + }; + mediaPlayer.handleFailedPlay = handleFailedPlay; + mediaPlayer.volumeBinding = volumeBinding; + mediaPlayer.volumeFading = volumeFading; + mediaPlayer.loadWaiting = loadWaiting; + mediaPlayer.hooks = hooks; + mediaPlayer.asyncPlay = asyncPlay; // Replacement for Underscore's throttle, because scrolled entries + // don't have Underscore anymore + + function throttle(func, timeFrame) { + var lastTime = 0; + return function (options) { + var now = new Date(); + func = func.bind(this); + + if (now - lastTime >= timeFrame) { + func(options); + lastTime = now; + } + }; + } + + var mediaEvents = function mediaEvents(player, context) { + function triggerMediaEvent(name) { + events.trigger('media:' + name, { + fileName: player.currentSrc, + context: context, + currentTime: player.position, + duration: player.duration, + volume: player.volume(), + bitrate: 128000 + }); + } + + player.on('play', function () { + triggerMediaEvent('play'); + }); + player.on('timeupdate', function () { + triggerMediaEvent('timeupdate'); + }); + player.on('timeupdate', throttle(function () { + triggerMediaEvent('timeupdate_throttled'); + }, 5000)); + player.on('pause', function () { + triggerMediaEvent('pause'); + }); + player.on('ended', function () { + triggerMediaEvent('ended'); + }); + }; // Prevent audio play back when browser enters background on mobile + // device. Use the face that timeupdate events continue to fire while + // intervals no longer executed when the browser is in the background. + + + var pauseInBackground = function pauseInBackground(player) { + var interval; + var lastInterval; + var resolution = 100; + + function startProbeInterval() { + interval = setInterval(function () { + lastInterval = new Date().getTime(); + }, resolution); + } + + function stopProbeInterval() { + clearInterval(interval); + interval = null; + } + + function pauseIfProbeIntervalHalted() { + if (intervalHalted()) { + player.pause(); + } + } + + function intervalHalted() { + return interval && lastInterval < new Date().getTime() - resolution * 5; + } + + player.on('play', startProbeInterval); + player.on('pause', stopProbeInterval); + player.on('ended', stopProbeInterval); + player.on('timeupdate', pauseIfProbeIntervalHalted); + }; + /** + * Calling seek before the media tag is ready causes InvalidState + * exeption. If this happens, we wait for the next progress event and + * retry. We resolve a promise once seeking succeeded. + * + * @api private + */ + + + var seekWithInvalidStateHandling = function seekWithInvalidStateHandling(player) { + var originalSeek = player.seek; + + player.seek = function (time) { + return retryOnProgress(function () { + originalSeek.call(player, time); + }); + }; + + function retryOnProgress(fn) { + var tries = 0; + return new Promise(function (resolve, reject) { + function tryOrWaitForProgress() { + tries += 1; + + if (tries >= 50) { + reject(); + return; + } + + try { + fn(); + resolve(); + } catch (e) { + player.one('progress', tryOrWaitForProgress); + } + } + + tryOrWaitForProgress(); + }); + } + }; + + var rewindMethod = function rewindMethod(player) { + /** + * Seek to beginning of file. If already at the beginning do + * nothing. + * + * @alias pageflow.AudioPlayer#rewind + */ + player.rewind = function () { + if (player.position > 0) { + var result = player.seek(0); + player.trigger('timeupdate', player.position, player.duration); + return result; + } else { + return Promise.resolve(); + } + }; + }; + + var getMediaElementMethod = function getMediaElementMethod(player) { + player.getMediaElement = function () { + return player.audio.audio; + }; + }; + /** + * Playing audio sources + * + * @param {Object[]} sources + * List of sources for audio element. + * + * @param {string} sources[].type + * Mime type of the audio. + * + * @param {string} sources[].src + * Url of the audio. + * + * @class + */ + + + var AudioPlayer = function AudioPlayer(sources, options) { + options = options || {}; + var codecMapping = { + vorbis: 'audio/ogg', + mp4: 'audio/mp4', + mp3: 'audio/mpeg' + }; + var readyResolve; + var readyPromise = new Promise(function (resolve) { + readyResolve = resolve; + }); + var loadedResolve; + var loadedPromise = new Promise(function (resolve) { + loadedResolve = resolve; + }); + var audio = new Audio5js({ + reusedTag: options.tag, + swf_path: assetUrls.audioSwf, + throw_errors: false, + format_time: false, + codecs: options.codecs || ['vorbis', 'mp4', 'mp3'], + ready: readyResolve, + loop: options.loop + }); + audio.readyPromise = readyPromise; + audio.loadedPromise = loadedPromise; + audio.on('load', loadedResolve); + + if (options.mediaEvents) { + mediaEvents(audio, options.context); + } + + if (options.pauseInBackground && browser.has('mobile platform')) { + pauseInBackground(audio); + } + + mediaPlayer.enhance(audio, _objectSpread2({ + loadWaiting: true + }, options)); + seekWithInvalidStateHandling(audio); + rewindMethod(audio); + getMediaElementMethod(audio); + + audio.src = function (sources) { + readyPromise.then(function () { + var source = (sources || []).find(function (source) { + return codecMapping[audio.settings.player.codec] === source.type; + }); + audio.load(source ? source.src : ''); + }); + }; + + var originalLoad = audio.load; + + audio.load = function (src) { + if (!src) { + this.duration = 0; + } + + this.currentSrc = src; + this.position = 0; + this.trigger('timeupdate', this.position, this.duration); + originalLoad.apply(this, arguments); + }; + + var originalSeek = audio.seek; + + audio.seek = function () { + if (this.currentSrc) { + return originalSeek.apply(this, arguments); + } + }; + + var originalPlay = audio.play; + + audio.play = function () { + if (this.currentSrc) { + originalPlay.apply(this, arguments); + } + }; + + audio.paused = function () { + return !audio.playing; + }; + + audio.src(sources); + return audio; + }; + + AudioPlayer.fromAudioTag = function (element, options) { + return new AudioPlayer(element.find('source').map(function () { + return { + src: this.getAttribute('src'), + type: this.getAttribute('type') + }; + }).get(), _objectSpread2({ + tag: element[0] + }, options)); + }; + + AudioPlayer.fromScriptTag = function (element, options) { + var sources = element.length ? JSON.parse(element.text()) : []; + return new AudioPlayer(sources, options); + }; + + var Null = function Null() { + this.playAndFadeIn = function () { + return Promise.resolve(); + }; + + this.fadeOutAndPause = function () { + return Promise.resolve(); + }; + + this.changeVolumeFactor = function () { + return Promise.resolve(); + }; + + this.play = function () {}; + + this.pause = function () {}; + + this.paused = function () { + return true; + }; + + this.seek = function () { + return Promise.resolve(); + }; + + this.rewind = function () { + return Promise.resolve(); + }; + + this.formatTime = function () {}; + + this.one = function (event, handler) { + handler(); + }; + }; + + Object.assign(Null.prototype, backboneEventsStandalone$1); + AudioPlayer.mediaEvents = mediaEvents; + AudioPlayer.Null = Null; + AudioPlayer.seekWithInvalidStateHandling = seekWithInvalidStateHandling; + AudioPlayer.rewindMethod = rewindMethod; + AudioPlayer.pauseInBackground = pauseInBackground; + + function _arrayWithoutHoles(arr) { + if (Array.isArray(arr)) return _arrayLikeToArray(arr); + } + + function _iterableToArray(iter) { + if (typeof Symbol !== "undefined" && Symbol.iterator in Object(iter)) return Array.from(iter); + } + + function _nonIterableSpread() { + throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); + } + + function _toConsumableArray(arr) { + return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); + } + + if (typeof window !== 'undefined') { + window.VIDEOJS_NO_DYNAMIC_STYLE = true; + } + + var filterSources = function filterSources(playerElement) { + if (playerElement.tagName.toLowerCase() !== 'video') { + return playerElement; + } + + var changed = false; + + if (browser.has('mp4 support only')) { + // keep only mp4 source + playerElement.querySelectorAll('source').forEach(function (source) { + if (source.type !== 'video/mp4') { + playerElement.removeChild(source); + } + }); + changed = true; + } else if (browser.has('mse and native hls support')) { + // remove dash source to ensure hls is used + var dashSource = playerElement.querySelector('source[type="application/dash+xml"]'); + + if (dashSource) { + playerElement.removeChild(dashSource); + changed = true; + } + } + + if (changed) { + // the video tags initially in the dom are broken since they "saw" + // the other sources. replace with clone + var clone = playerElement.cloneNode(true); + playerElement.replaceWith(clone); + return clone; + } else { + return playerElement; + } + }; + + var useSlimPlayerControlsDuringPhonePlayback = function useSlimPlayerControlsDuringPhonePlayback(player) { + var originalPlay = player.play; + + player.play = function () { + if (browser.has('phone platform') && !browser.has('native video player')) { + state.widgets.use({ + name: 'slim_player_controls', + insteadOf: 'classic_player_controls' + }, function (restoreWidgets) { + player.one('pause', restoreWidgets); + }); + } + + return originalPlay.apply(this, arguments); + }; + }; + + var prebuffering = function prebuffering(player) { + var prebufferPromiseReject; + + player.isBufferedAhead = function (delta, silent) { + // video.js only gives us one time range starting from 0 here. We + // still ask for the last time range to be on the safe side. + var timeRanges = player.buffered(); + var currentBufferTime = timeRanges.end(timeRanges.length - 1); + var desiredBufferTime = player.currentTime() + delta; + + if (player.duration()) { + desiredBufferTime = Math.min(desiredBufferTime, Math.floor(player.duration())); + } + + var result = currentBufferTime >= desiredBufferTime; + + if (!silent) { + log('buffered ahead ' + delta + ': ' + result + ' (' + currentBufferTime + '/' + desiredBufferTime + ')'); + } + + return result; + }; + + player.prebuffer = function (options) { + options = options || {}; + var delta = options.secondsToBuffer || 10; + var secondsToWait = options.secondsToWait || 3; + var interval = 200; + var maxCount = secondsToWait * 1000 / interval; + var count = 0; + + if (browser.has('prebuffering support')) { + if (!player.isBufferedAhead(delta) && !player.prebufferPromise) { + log('prebuffering video ' + player.src()); + player.prebufferPromise = new Promise(function (resolve, reject) { + prebufferPromiseReject = reject; + wait(); + + function wait() { + setTimeout(function () { + if (!player.prebufferPromise) { + return; + } + + count++; + + if (player.isBufferedAhead(delta) || count > maxCount) { + log('finished prebuffering video ' + player.src()); + resolve(); + player.prebufferPromise = null; + } else { + wait(); + } + }, interval); + } + }); + } + } + + return player.prebufferPromise ? player.prebufferPromise : Promise.resolve(); + }; + + player.abortPrebuffering = function () { + if (player.prebufferPromise) { + log('ABORT prebuffering'); + prebufferPromiseReject('prebuffering aborted'); + player.prebufferPromise = null; + } + }; + + var originalPause = player.pause; + + player.pause = function () { + player.abortPrebuffering(); + return originalPause.apply(this, arguments); + }; + + player.one('dispose', function () { + player.abortPrebuffering(); + }); + }; + + var cueSettingsMethods = function cueSettingsMethods(player) { + /** + * Specify the display position of text tracks. This method can also + * be used to make VideoJS reposition the text tracks after the + * margins of the text track display have been changed (e.g. to + * translate text tracks when player controls are displayed). + * + * To force such an update, the passed string has to differ from the + * previously passed string. You can append a dot and an arbitrary + * string (e.g. `"auto.translated"`), to keep the current setting but + * still force repositioning. + * + * On the other hand, it is possible to change the positioning but + * have VideoJS apply the change only when the next cue is + * displayed. This way we can prevent moving a cue that the user + * might just be reading. Simply append the string `".lazy"` + * (e.g. `"auto.lazy"`). + * + * @param {string} line + * Either `"top"` to move text tracks to the first line or + * `"auto"` to stick with automatic positioning, followed by a tag + * to either force or prevent immediate update. + */ + player.updateCueLineSettings = function (line) { + var components = line.split('.'); + var value = components[0]; + var command = components[1]; + value = value == 'top' ? 1 : value; + var changed = false; + Array.from(player.textTracks()).forEach(function (textTrack) { + if (textTrack.mode == 'showing' && textTrack.cues) { + for (var i = 0; i < textTrack.cues.length; i++) { + if (textTrack.cues[i].line != value) { + textTrack.cues[i].line = value; + changed = true; + } + } + } + }); // Setting `line` does not update display directly, but only when + // the next cue is displayed. This is problematic, when we + // reposition text tracks to prevent overlap with player + // controls. Triggering the event makes VideoJS update positions. + // Ensure display is also updated when the current showing text + // track changed since the last call, i.e. `line` has been changed + // for a cue even though the previous call had the same + // parameters. + + if ((this.prevLine !== line || changed) && command != 'lazy') { + var tech = player.tech({ + IWillNotUseThisInPlugins: true + }); + tech && tech.trigger('texttrackchange'); + } + + this.prevLine = line; + }; + }; + + var getMediaElementMethod$1 = function getMediaElementMethod(player) { + player.getMediaElement = function () { + var tech = player.tech({ + IWillNotUseThisInPlugins: true + }); + return tech && tech.el(); + }; + }; + + var mediaEvents$1 = function mediaEvents(player, context) { + player.updateMediaEventsContext = function (newContext) { + context = newContext; + }; + + function triggerMediaEvent(name) { + if (context) { + events.trigger('media:' + name, { + fileName: player.currentSrc(), + context: context, + currentTime: player.currentTime(), + duration: player.duration(), + volume: player.volume(), + altText: player.getMediaElement().getAttribute('alt'), + bitrate: 3500000 + }); + } + } + + player.on('play', function () { + triggerMediaEvent('play'); + }); + player.on('timeupdate', function () { + triggerMediaEvent('timeupdate'); + }); + player.on('timeupdate', throttle(function () { + triggerMediaEvent('timeupdate_throttled'); + }, 5000)); + player.on('pause', function () { + triggerMediaEvent('pause'); + }); + player.on('ended', function () { + triggerMediaEvent('ended'); + }); + }; + + var bufferUnderrunWaiting = function bufferUnderrunWaiting(player) { + var originalPause = player.pause; + + player.pause = function () { + cancelWaiting(); + originalPause.apply(this, arguments); + }; + + function pauseAndPreloadOnUnderrun() { + if (bufferUnderrun()) { + pauseAndPreload(); + } + } + + function bufferUnderrun() { + return !player.isBufferedAhead(0.1, true) && !player.waitingOnUnderrun && !ignoringUnderruns(); + } + + function pauseAndPreload() { + log('Buffer underrun'); + player.trigger('bufferunderrun'); + player.pause(); + player.waitingOnUnderrun = true; + player.prebuffer({ + secondsToBuffer: 5, + secondsToWait: 5 + }).then(function () { + // do nothing if user aborted waiting by clicking pause + if (player.waitingOnUnderrun) { + player.waitingOnUnderrun = false; + player.trigger('bufferunderruncontinue'); + player.play(); + } + }, function () {}); + } + + function cancelWaiting() { + if (player.waitingOnUnderrun) { + player.ignoreUnderrunsUntil = new Date().getTime() + 5 * 1000; + player.waitingOnUnderrun = false; + player.trigger('bufferunderruncontinue'); + } + } + + function ignoringUnderruns() { + var r = player.ignoreUnderrunsUntil && new Date().getTime() < player.ignoreUnderrunsUntil; + + if (r) { + log('ignoring underrun'); + } + + return r; + } + + function stopListeningForProgress() { + player.off('progress', pauseAndPreloadOnUnderrun); + } + + if (browser.has('buffer underrun waiting support')) { + player.on('play', function () { + player.on('progress', pauseAndPreloadOnUnderrun); + }); + player.on('pause', stopListeningForProgress); + player.on('ended', stopListeningForProgress); + } + }; + + var rewindMethod$1 = function rewindMethod(player) { + /** + * Seek to beginning of file. If already at the beginning do + * nothing. + * + * @alias pageflow.VideoPlayer#rewind + */ + player.rewind = function () { + if (player.currentTime() > 0) { + player.currentTime(0); + player.trigger('timeupdate', player.currentTime(), player.duration()); + return Promise.resolve(); + } else { + return Promise.resolve(); + } + }; + }; + + var VideoPlayer = function VideoPlayer(element, options) { + options = options || {}; + element = filterSources(element); + var player = VideoJS(element, options); + + if (options.useSlimPlayerControlsDuringPhonePlayback) { + useSlimPlayerControlsDuringPhonePlayback(player); + } + + prebuffering(player); + cueSettingsMethods(player); + getMediaElementMethod$1(player); + rewindMethod$1(player); + + if (options.mediaEvents) { + mediaEvents$1(player, options.context); + } + + if (options.bufferUnderrunWaiting) { + bufferUnderrunWaiting(player); + } + + mediaPlayer.enhance(player, options); + return player; + }; + + VideoPlayer.useSlimPlayerControlsDuringPhonePlayback = useSlimPlayerControlsDuringPhonePlayback; + VideoPlayer.prebuffering = prebuffering; + VideoPlayer.filterSources = filterSources; + VideoPlayer.mediaEvents = mediaEvents$1; + VideoPlayer.cueSettingsMethods = cueSettingsMethods; + VideoPlayer.bufferUnderrunWaiting = bufferUnderrunWaiting; + + var createMediaPlayer = function createMediaPlayer(options) { + var isAudio = options.tagName == 'AUDIO'; + var player = new VideoPlayer(options.mediaElement, { + controlBar: false, + loadingSpinner: false, + bigPlayButton: false, + errorDisplay: false, + textTrackSettings: false, + poster: options.poster, + loop: options.loop, + controls: options.controls, + html5: { + nativeCaptions: !isAudio && browser.has('iphone platform'), + // Only used by pageflow-scrolled + vhs: { + useBandwidthFromLocalStorage: true, + usePlayerObjectFit: true + } + }, + bufferUnderrunWaiting: true, + fallbackToMutedAutoplay: !isAudio, + ignoreVolumeSetting: true, + volumeFading: true, + hooks: {}, + mediaEvents: true, + context: null + }); + player.textTrackSettings = { + getValues: function getValues() { + return {}; + } + }; + + player.playOrPlayOnLoad = function () { + if (this.readyState() > 0) { + player.play(); + } else { + player.on('loadedmetadata', player.play); + } + }; + + player.addClass('video-js'); + player.addClass('player'); + return player; + }; + /** + * Copyright 2017 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + + var BLANK_AUDIO_SRC = 'data:audio/wav;base64,UklGRjIAAABXQVZFZm10IBA' + 'AAAABAAEAIlYAAESsAAACABAAZGF0YRAAAAAAAAAAAAAAAAAAAAAAAA=='; + var BLANK_VIDEO_SRC = 'data:video/mp4;base64,AAAAHGZ0eXBNNFYgAAACAG' + 'lzb21pc28yYXZjMQAAAAhmcmVlAAAGF21kYXTeBAAAbGliZmFhYyAxLjI4AABCAJMgBDIARw' + 'AAArEGBf//rdxF6b3m2Ui3lizYINkj7u94MjY0IC0gY29yZSAxNDIgcjIgOTU2YzhkOCAtIE' + 'guMjY0L01QRUctNCBBVkMgY29kZWMgLSBDb3B5bGVmdCAyMDAzLTIwMTQgLSBodHRwOi8vd3' + 'd3LnZpZGVvbGFuLm9yZy94MjY0Lmh0bWwgLSBvcHRpb25zOiBjYWJhYz0wIHJlZj0zIGRlYm' + 'xvY2s9MTowOjAgYW5hbHlzZT0weDE6MHgxMTEgbWU9aGV4IHN1Ym1lPTcgcHN5PTEgcHN5X3' + 'JkPTEuMDA6MC4wMCBtaXhlZF9yZWY9MSBtZV9yYW5nZT0xNiBjaHJvbWFfbWU9MSB0cmVsbG' + 'lzPTEgOHg4ZGN0PTAgY3FtPTAgZGVhZHpvbmU9MjEsMTEgZmFzdF9wc2tpcD0xIGNocm9tYV' + '9xcF9vZmZzZXQ9LTIgdGhyZWFkcz02IGxvb2thaGVhZF90aHJlYWRzPTEgc2xpY2VkX3Rocm' + 'VhZHM9MCBucj0wIGRlY2ltYXRlPTEgaW50ZXJsYWNlZD0wIGJsdXJheV9jb21wYXQ9MCBjb2' + '5zdHJhaW5lZF9pbnRyYT0wIGJmcmFtZXM9MCB3ZWlnaHRwPTAga2V5aW50PTI1MCBrZXlpbn' + 'RfbWluPTI1IHNjZW5lY3V0PTQwIGludHJhX3JlZnJlc2g9MCByY19sb29rYWhlYWQ9NDAgcm' + 'M9Y3JmIG1idHJlZT0xIGNyZj0yMy4wIHFjb21wPTAuNjAgcXBtaW49MCBxcG1heD02OSBxcH' + 'N0ZXA9NCB2YnZfbWF4cmF0ZT03NjggdmJ2X2J1ZnNpemU9MzAwMCBjcmZfbWF4PTAuMCBuYW' + 'xfaHJkPW5vbmUgZmlsbGVyPTAgaXBfcmF0aW89MS40MCBhcT0xOjEuMDAAgAAAAFZliIQL8m' + 'KAAKvMnJycnJycnJycnXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' + 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXiEASZACGQAjgCEASZACGQAjgAAAAAdBmjgX4G' + 'SAIQBJkAIZACOAAAAAB0GaVAX4GSAhAEmQAhkAI4AhAEmQAhkAI4AAAAAGQZpgL8DJIQBJkA' + 'IZACOAIQBJkAIZACOAAAAABkGagC/AySEASZACGQAjgAAAAAZBmqAvwMkhAEmQAhkAI4AhAE' + 'mQAhkAI4AAAAAGQZrAL8DJIQBJkAIZACOAAAAABkGa4C/AySEASZACGQAjgCEASZACGQAjgA' + 'AAAAZBmwAvwMkhAEmQAhkAI4AAAAAGQZsgL8DJIQBJkAIZACOAIQBJkAIZACOAAAAABkGbQC' + '/AySEASZACGQAjgCEASZACGQAjgAAAAAZBm2AvwMkhAEmQAhkAI4AAAAAGQZuAL8DJIQBJkA' + 'IZACOAIQBJkAIZACOAAAAABkGboC/AySEASZACGQAjgAAAAAZBm8AvwMkhAEmQAhkAI4AhAE' + 'mQAhkAI4AAAAAGQZvgL8DJIQBJkAIZACOAAAAABkGaAC/AySEASZACGQAjgCEASZACGQAjgA' + 'AAAAZBmiAvwMkhAEmQAhkAI4AhAEmQAhkAI4AAAAAGQZpAL8DJIQBJkAIZACOAAAAABkGaYC' + '/AySEASZACGQAjgCEASZACGQAjgAAAAAZBmoAvwMkhAEmQAhkAI4AAAAAGQZqgL8DJIQBJkA' + 'IZACOAIQBJkAIZACOAAAAABkGawC/AySEASZACGQAjgAAAAAZBmuAvwMkhAEmQAhkAI4AhAE' + 'mQAhkAI4AAAAAGQZsAL8DJIQBJkAIZACOAAAAABkGbIC/AySEASZACGQAjgCEASZACGQAjgA' + 'AAAAZBm0AvwMkhAEmQAhkAI4AhAEmQAhkAI4AAAAAGQZtgL8DJIQBJkAIZACOAAAAABkGbgC' + 'vAySEASZACGQAjgCEASZACGQAjgAAAAAZBm6AnwMkhAEmQAhkAI4AhAEmQAhkAI4AhAEmQAh' + 'kAI4AhAEmQAhkAI4AAAAhubW9vdgAAAGxtdmhkAAAAAAAAAAAAAAAAAAAD6AAABDcAAQAAAQ' + 'AAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAA' + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAzB0cmFrAAAAXHRraGQAAAADAAAAAAAAAAAAAAABAA' + 'AAAAAAA+kAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAA' + 'BAAAAAALAAAACQAAAAAAAkZWR0cwAAABxlbHN0AAAAAAAAAAEAAAPpAAAAAAABAAAAAAKobW' + 'RpYQAAACBtZGhkAAAAAAAAAAAAAAAAAAB1MAAAdU5VxAAAAAAALWhkbHIAAAAAAAAAAHZpZG' + 'UAAAAAAAAAAAAAAABWaWRlb0hhbmRsZXIAAAACU21pbmYAAAAUdm1oZAAAAAEAAAAAAAAAAA' + 'AAACRkaW5mAAAAHGRyZWYAAAAAAAAAAQAAAAx1cmwgAAAAAQAAAhNzdGJsAAAAr3N0c2QAAA' + 'AAAAAAAQAAAJ9hdmMxAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAALAAkABIAAAASAAAAAAAAA' + 'ABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGP//AAAALWF2Y0MBQsAN/+EAFW' + 'dCwA3ZAsTsBEAAAPpAADqYA8UKkgEABWjLg8sgAAAAHHV1aWRraEDyXyRPxbo5pRvPAyPzAA' + 'AAAAAAABhzdHRzAAAAAAAAAAEAAAAeAAAD6QAAABRzdHNzAAAAAAAAAAEAAAABAAAAHHN0c2' + 'MAAAAAAAAAAQAAAAEAAAABAAAAAQAAAIxzdHN6AAAAAAAAAAAAAAAeAAADDwAAAAsAAAALAA' + 'AACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACgAAAA' + 'oAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAA' + 'AAiHN0Y28AAAAAAAAAHgAAAEYAAANnAAADewAAA5gAAAO0AAADxwAAA+MAAAP2AAAEEgAABC' + 'UAAARBAAAEXQAABHAAAASMAAAEnwAABLsAAATOAAAE6gAABQYAAAUZAAAFNQAABUgAAAVkAA' + 'AFdwAABZMAAAWmAAAFwgAABd4AAAXxAAAGDQAABGh0cmFrAAAAXHRraGQAAAADAAAAAAAAAA' + 'AAAAACAAAAAAAABDcAAAAAAAAAAAAAAAEBAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAA' + 'AAAAAAAABAAAAAAAAAAAAAAAAAAAAkZWR0cwAAABxlbHN0AAAAAAAAAAEAAAQkAAADcAABAA' + 'AAAAPgbWRpYQAAACBtZGhkAAAAAAAAAAAAAAAAAAC7gAAAykBVxAAAAAAALWhkbHIAAAAAAA' + 'AAAHNvdW4AAAAAAAAAAAAAAABTb3VuZEhhbmRsZXIAAAADi21pbmYAAAAQc21oZAAAAAAAAA' + 'AAAAAAJGRpbmYAAAAcZHJlZgAAAAAAAAABAAAADHVybCAAAAABAAADT3N0YmwAAABnc3RzZA' + 'AAAAAAAAABAAAAV21wNGEAAAAAAAAAAQAAAAAAAAAAAAIAEAAAAAC7gAAAAAAAM2VzZHMAAA' + 'AAA4CAgCIAAgAEgICAFEAVBbjYAAu4AAAADcoFgICAAhGQBoCAgAECAAAAIHN0dHMAAAAAAA' + 'AAAgAAADIAAAQAAAAAAQAAAkAAAAFUc3RzYwAAAAAAAAAbAAAAAQAAAAEAAAABAAAAAgAAAA' + 'IAAAABAAAAAwAAAAEAAAABAAAABAAAAAIAAAABAAAABgAAAAEAAAABAAAABwAAAAIAAAABAA' + 'AACAAAAAEAAAABAAAACQAAAAIAAAABAAAACgAAAAEAAAABAAAACwAAAAIAAAABAAAADQAAAA' + 'EAAAABAAAADgAAAAIAAAABAAAADwAAAAEAAAABAAAAEAAAAAIAAAABAAAAEQAAAAEAAAABAA' + 'AAEgAAAAIAAAABAAAAFAAAAAEAAAABAAAAFQAAAAIAAAABAAAAFgAAAAEAAAABAAAAFwAAAA' + 'IAAAABAAAAGAAAAAEAAAABAAAAGQAAAAIAAAABAAAAGgAAAAEAAAABAAAAGwAAAAIAAAABAA' + 'AAHQAAAAEAAAABAAAAHgAAAAIAAAABAAAAHwAAAAQAAAABAAAA4HN0c3oAAAAAAAAAAAAAAD' + 'MAAAAaAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAA' + 'AACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAA' + 'kAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAA' + 'AACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAACMc3RjbwAAAA' + 'AAAAAfAAAALAAAA1UAAANyAAADhgAAA6IAAAO+AAAD0QAAA+0AAAQAAAAEHAAABC8AAARLAA' + 'AEZwAABHoAAASWAAAEqQAABMUAAATYAAAE9AAABRAAAAUjAAAFPwAABVIAAAVuAAAFgQAABZ' + '0AAAWwAAAFzAAABegAAAX7AAAGFwAAAGJ1ZHRhAAAAWm1ldGEAAAAAAAAAIWhkbHIAAAAAAA' + 'AAAG1kaXJhcHBsAAAAAAAAAAAAAAAALWlsc3QAAAAlqXRvbwAAAB1kYXRhAAAAAQAAAABMYX' + 'ZmNTUuMzMuMTAw'; + var blankSources = { + audio: { + src: BLANK_AUDIO_SRC, + type: 'audio/wav' + }, + video: { + src: BLANK_VIDEO_SRC, + type: 'video/mp4' + } + }; + /** @const @enum {string} */ + + var MediaType = { + AUDIO: 'audio', + VIDEO: 'video' + }; + var elId = 0; + /** + * Media pool class handles the pool of Videojs media players + */ + + var MediaPool = /*#__PURE__*/function () { + function MediaPool() { + var _this$mediaFactory_; + + var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : { + playerCount: 4 + }; + + _classCallCheck(this, MediaPool); + + this.playerCount = options.playerCount; + this.allocatedPlayers = {}; + this.unAllocatedPlayers = {}; + this.mediaFactory_ = (_this$mediaFactory_ = {}, _defineProperty(_this$mediaFactory_, MediaType.AUDIO, function () { + var audioEl = document.createElement('audio'); + audioEl.setAttribute('crossorigin', 'anonymous'); + return audioEl; + }), _defineProperty(_this$mediaFactory_, MediaType.VIDEO, function () { + var videoEl = document.createElement('video'); + videoEl.setAttribute('crossorigin', 'anonymous'); + return videoEl; + }), _this$mediaFactory_); + } + + _createClass(MediaPool, [{ + key: "allocatePlayer", + value: function allocatePlayer(_ref) { + var playerType = _ref.playerType, + playerId = _ref.playerId, + playsInline = _ref.playsInline, + mediaEventsContextData = _ref.mediaEventsContextData, + hooks = _ref.hooks, + poster = _ref.poster, + _ref$loop = _ref.loop, + loop = _ref$loop === void 0 ? false : _ref$loop, + _ref$controls = _ref.controls, + controls = _ref$controls === void 0 ? false : _ref$controls, + altText = _ref.altText, + onRelease = _ref.onRelease; + var player = undefined; + + if (!this.unAllocatedPlayers[playerType]) { + this.populateMediaPool_(); + } + + if (this.unAllocatedPlayers[playerType].length == 0) { + this.freeOnePlayer(playerType); + } + + player = this.unAllocatedPlayers[playerType].pop(); + + if (player) { + player.pause(); + player.getMediaElement().loop = loop; + player.getMediaElement().setAttribute('alt', altText); + player.poster(poster); + player.controls(controls); + + if (playsInline) { + player.playsinline(true); + } + + player.updateHooks(hooks || {}); + player.updateMediaEventsContext(mediaEventsContextData); + this.allocatedPlayers[playerType].push(player); + player.playerId = playerId || this.allocatedPlayers[playerType].length; + player.releaseCallback = onRelease; + return player; + } else { + console.log('no player found for allocation'); + } + } + }, { + key: "freeOnePlayer", + value: function freeOnePlayer(type) { + this.unAllocatePlayer(this.allocatedPlayers[type][0]); // free up the first allocated player + } + }, { + key: "unAllocatePlayer", + value: function unAllocatePlayer(player) { + if (player) { + var type = this.getMediaTypeFromEl(player.el()); + this.allocatedPlayers[type] = this.allocatedPlayers[type].filter(function (p) { + return p != player; + }); + player.controls(false); + player.getMediaElement().loop = false; + player.playsinline(false); + player.src(blankSources[type]); + player.poster(''); + clearTextTracks(player); + this.unAllocatedPlayers[type].push(player); + + if (player.releaseCallback) { + player.releaseCallback(); + player.releaseCallback = null; + } + } + } + }, { + key: "blessAll", + value: function blessAll(value) { + var _this = this; + + if (this.unAllocatedPlayers[MediaType.AUDIO] == undefined || this.unAllocatedPlayers[MediaType.VIDEO] == undefined) { + this.populateMediaPool_(); + } + + this.forEachMediaType(function (key) { + _this.allPlayersForType(MediaType[key]).forEach(function (player) { + player.muted(value); + }); + }); + } + }, { + key: "allPlayersForType", + value: function allPlayersForType(type) { + if (this.unAllocatedPlayers[type]) { + return [].concat(_toConsumableArray(this.unAllocatedPlayers[type]), _toConsumableArray(this.allocatedPlayers[type])); + } + + return []; + } + }, { + key: "getMediaTypeFromEl", + value: function getMediaTypeFromEl(mediaElement) { + var tagName = mediaElement.tagName.toLowerCase(); + + if (tagName == 'div') { + tagName = mediaElement.children[0].tagName.toLowerCase(); + } + + return this.getMediaType(tagName); + } + }, { + key: "getMediaType", + value: function getMediaType(tagName) { + switch (tagName) { + case 'audio': + return MediaType.AUDIO; + + case 'video': + return MediaType.VIDEO; + } + } + }, { + key: "forEachMediaType", + value: function forEachMediaType(callbackFn) { + Object.keys(MediaType).forEach(callbackFn.bind(this)); + } + }, { + key: "createPlayer_", + value: function createPlayer_(type, mediaEl) { + mediaEl.setAttribute('pool-element', elId++); + + if (!this.unAllocatedPlayers[type]) { + this.unAllocatedPlayers[type] = []; + this.allocatedPlayers[type] = []; + } + + var player = createMediaPlayer({ + mediaElement: mediaEl, + tagName: type + }); + mediaEl.setAttribute('src', blankSources[type].src); + player.muted(true); + this.unAllocatedPlayers[type].push(player); + return player; + } + }, { + key: "initializeMediaPool_", + value: function initializeMediaPool_(type, mediaElSeed) { + while (this.allPlayersForType(type).length < this.playerCount) { + this.createPlayer_(type, mediaElSeed.cloneNode(true)); + } + } + }, { + key: "populateMediaPool_", + value: function populateMediaPool_() { + var _this2 = this; + + this.forEachMediaType(function (key) { + var type = MediaType[key]; + + var mediaEl = _this2.mediaFactory_[type].call(_this2); + + _this2.initializeMediaPool_(type, mediaEl); + }); + } + }]); + + return MediaPool; + }(); + + function clearTextTracks(player) { + var tracks = player.textTracks(); + var i = tracks.length; + + while (i--) { + player.removeRemoteTextTrack(tracks[i]); + } + } + + var media = { + playerPool: new MediaPool(), + muteState: true, + + get muted() { + return this.muteState; + }, + + mute: function mute(value) { + this.muteState = value; + this.playerPool.blessAll(value); + this.trigger('change:muted', value); + }, + getPlayer: function getPlayer(fileSource, options) { + options.playerType = options.tagName || MediaType.VIDEO; + var player = this.playerPool.allocatePlayer(options); + + if (player) { + player.muted(this.muteState); + player.src(fileSource); + + if (options.textTrackSources) { + options.textTrackSources.forEach(function (track) { + return player.addRemoteTextTrack(track, true); + }); + } + + return player; + } + }, + releasePlayer: function releasePlayer(player) { + if (player) { + this.playerPool.unAllocatePlayer(player); + } + } + }; + Object.assign(media, backboneEventsStandalone$1); + /** + * Play and fade between multiple audio files. + * + * @class + */ + + var MultiPlayer = function MultiPlayer(pool, options) { + if (options.crossFade && options.playFromBeginning) { + throw 'pageflow.Audio.MultiPlayer: The options crossFade and playFromBeginning can not be used together at the moment.'; + } + + var current = new AudioPlayer.Null(); + var currentId = null; + var that = this; + /** + * Continue playback. + */ + + this.resume = function () { + return current.play(); + }; + /** + * Continue playback with fade in. + */ + + + this.resumeAndFadeIn = function () { + return current.playAndFadeIn(options.fadeDuration); + }; + + this.seek = function (position) { + return current.seek(position); + }; + + this.pause = function () { + return current.pause(); + }; + + this.paused = function () { + return current.paused(); + }; + + this.fadeOutAndPause = function () { + return current.fadeOutAndPause(options.fadeDuration); + }; + + this.fadeOutIfPlaying = function () { + if (current.paused()) { + return Promise.resolve(); + } else { + return current.fadeOutAndPause(options.fadeDuration); + } + }; + + this.position = function () { + return current.position; + }; + + this.duration = function () { + return current.duration; + }; + + this.fadeTo = function (id) { + return changeCurrent(id, function (player) { + return player.playAndFadeIn(options.fadeDuration); + }); + }; + + this.play = function (id) { + return changeCurrent(id, function (player) { + return player.play(); + }); + }; + + this.changeVolumeFactor = function (factor) { + return current.changeVolumeFactor(factor, options.fadeDuration); + }; + + this.formatTime = function (time) { + return current.formatTime(time); + }; + + function changeCurrent(id, callback) { + if (!options.playFromBeginning && id === currentId && !current.paused()) { + return Promise.resolve(); + } + + var player = pool.get(id); + currentId = id; + var fadeOutPromise = current.fadeOutAndPause(options.fadeDuration); + + if (current._stopMultiPlayerEventPropagation && current.paused()) { + current._stopMultiPlayerEventPropagation(); + } + + return handleCrossFade(fadeOutPromise).then(function () { + current = player; + startEventPropagation(current, id); + return handlePlayFromBeginning(player).then(function () { + return callback(player); + }); + }); + } + + function handleCrossFade(fadePomise) { + if (options.crossFade) { + return Promise.resolve(); + } else { + return fadePomise; + } + } + + function handlePlayFromBeginning(player) { + if (options.playFromBeginning || options.rewindOnChange) { + return player.rewind(); + } else { + return Promise.resolve(); + } + } + + function startEventPropagation(player, id) { + var playCallback = function playCallback() { + that.trigger('play', { + audioFileId: id + }); + }; + + var pauseCallback = function pauseCallback() { + that.trigger('pause', { + audioFileId: id + }); + + if (currentId !== id) { + player._stopMultiPlayerEventPropagation(); + } + }; + + var timeUpdateCallback = function timeUpdateCallback() { + that.trigger('timeupdate', { + audioFileId: id + }); + }; + + var endedCallback = function endedCallback() { + that.trigger('ended', { + audioFileId: id + }); + }; + + var playFailedCallback = function playFailedCallback() { + that.trigger('playfailed', { + audioFileId: id + }); + }; + + player.on('play', playCallback); + player.on('pause', pauseCallback); + player.on('timeupdate', timeUpdateCallback); + player.on('ended', endedCallback); + player.on('playfailed', playFailedCallback); + + player._stopMultiPlayerEventPropagation = function () { + player.off('play', playCallback); + player.off('pause', pauseCallback); + player.off('timeupdate', timeUpdateCallback); + player.off('ended', endedCallback); + player.off('playfailed', playFailedCallback); + }; + } + }; + + Object.assign(MultiPlayer.prototype, backboneEventsStandalone$1); + + var PlayerSourceIDMap = function PlayerSourceIDMap(media) { + var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, + playerOptions = _ref.playerOptions; + + return { + current: undefined, + previous: undefined, + mapSources: function mapSources(id, sources) { + this[id] = sources; + }, + get: function get(sourceID) { + if (!this[sourceID]) { + return new AudioPlayer.Null(); + } + + if (this.current && this.current.playerId === sourceID) { + return this.current; + } + + if (this.previous && this.previous.playerId === sourceID) { + var holder = this.current; + this.current = this.previous; + this.previous = holder; + } else { + if (this.previous) { + media.releasePlayer(this.previous); + } + + this.previous = this.current; + this.current = media.getPlayer(this[sourceID], _objectSpread2({ + filePermaId: sourceID, + playerId: sourceID + }, playerOptions)); + } + + return this.current; + } + }; + }; + + var PlayerPool = function PlayerPool(audio, options) { + this.players = {}; + + this.get = function (audioFileId) { + this.players[audioFileId] = this.players[audioFileId] || audio.createPlayer(audioFileId, options); + return this.players[audioFileId]; + }; + + this.dispose = function () { + this.players = {}; + }; + }; + /** + * Playing audio files. + * @alias pageflow.audio + * @member + */ + + + var Audio = function Audio(options) { + this.getSources = options.getSources || function (audioFileId) { + return options.audioFiles[audioFileId] || ''; + }; + /** + * Creates a player for the given audio file. + * + * @param {string|number} audioFileId + * Id of the audio file to play. The id can be of the form + * `"5.suffix"` to distinguish multiple occurences of the same + * audio file for example inside a pageflow.Audio.PlayerPool; + * + * @param {Object} [options] + * Options to pass on player creation + * + * @static + */ + + + this.createPlayer = function (audioFileId, options) { + var sources = this.getSources(removeSuffix(audioFileId)); + + if (sources) { + return new AudioPlayer(sources, _objectSpread2({ + volumeFading: true + }, options)); + } else { + return new AudioPlayer.Null(); + } + }; + /** + * Create a `MultiPlayer` to play and fade between multiple audio + * files. + * + * @param {Object} [options] + * All options supported by pageflow.AudioPlayer can be passed. + * + * @param {number} [options.fadeDuration] + * Time in milliseconds to fade audios in and out. + * + * @param {boolean} [options.playFromBeginning=false] + * Always restart audio files from the beginning. + * + * @param {boolean} [options.rewindOnChange=false] + * Play from beginning when changing audio files. + * + * @return {pageflow.Audio.MultiPlayer} + */ + + + this.createMultiPlayer = function (options) { + return new MultiPlayer(new PlayerPool(this, options), options); + }; + + function removeSuffix(id) { + if (!id) { + return id; + } + + return parseInt(id.toString().split('.')[0], 10); + } + }; + + Audio.setup = function (options) { + state.audio = new Audio(options); + }; + + Audio.PlayerPool = PlayerPool; + var isEventAdded = false; + var callbacks = []; + + var muteInBackground = function muteInBackground() { + callbacks.forEach(function (cb) { + cb(document.visibilityState); + }); + }; + + function documentHiddenState(callback) { + callbacks.push(callback); + + if (!isEventAdded) { + isEventAdded = true; + document.addEventListener('visibilitychange', muteInBackground, false); + } + + return { + removeCallback: function removeCallback() { + callbacks = callbacks.filter(function (c) { + return c !== callback; + }); + } + }; + } + + var consent = Consent.create(); + + /** + * Mute feature settings for background media (ATMO and background videos) + * + * @since 13.0 + */ + + var backgroundMedia = { + muted: false, + unmute: function unmute() { + if (this.muted) { + this.muted = false; + events.trigger('background_media:unmute'); + } + }, + mute: function mute() { + if (!this.muted) { + this.muted = true; + events.trigger('background_media:mute'); + } + } + }; + + var bandwidth = function bandwidth() { + var maxLoadTime = 5000; + bandwidth.promise = bandwidth.promise || new $.Deferred(function (deferred) { + var smallFileUrl = assetUrls.smallBandwidthProbe + "?" + new Date().getTime(), + largeFileUrl = assetUrls.largeBandwidthProbe + "?" + new Date().getTime(), + smallFileSize = 165, + largeFileSize = 1081010; + $.when(timeFile(smallFileUrl), timeFile(largeFileUrl)).done(function (timeToLoadSmallFile, timeToLoadLargeFile) { + var timeDelta = (timeToLoadLargeFile - timeToLoadSmallFile) / 1000; + var bitsDelta = (largeFileSize - smallFileSize) * 8; + timeDelta = Math.max(timeDelta, 0.01); + deferred.resolve({ + durationInSeconds: timeDelta, + speedInBps: (bitsDelta / timeDelta).toFixed(2) + }); + }).fail(function () { + deferred.resolve({ + durationInSeconds: Infinity, + speedInBps: 0 + }); + }); + }).promise(); + return bandwidth.promise; + + function timeFile(url) { + var startTime = new Date().getTime(); + return withTimeout(loadFile(url), maxLoadTime).pipe(function () { + return new Date().getTime() - startTime; + }); + } + + function loadFile(url, options) { + return new $.Deferred(function (deferred) { + var image = new Image(); + image.onload = deferred.resolve; + image.onerror = deferred.reject; + image.src = url; + }).promise(); + } + + function withTimeout(promise, milliseconds) { + return new $.Deferred(function (deferred) { + var timeout = setTimeout(function () { + deferred.reject(); + }, milliseconds); + promise.always(function () { + clearTimeout(timeout); + }).then(deferred.resolve, deferred.reject); + }).promise(); + } + }; + + browser.feature('high bandwidth', function () { + return bandwidth().pipe(function (result) { + var isHigh = result.speedInBps > 8000 * 1024; + + if (window.console) { + window.console.log('Detected bandwidth ' + result.speedInBps / 8 / 1024 + 'KB/s. High: ' + (isHigh ? 'Yes' : 'No')); + } + + return isHigh; + }); + }); + + var preload = { + image: function image(url) { + return $.Deferred(function (deferred) { + var image = new Image(); + image.onload = deferred.resolve; + image.onerror = deferred.resolve; + image.src = url; + }).promise(); + }, + backgroundImage: function backgroundImage(element) { + var that = this; + var promises = []; + $(element).addClass('load_image'); + $(element).each(function () { + var propertyValue = window.getComputedStyle(this).getPropertyValue('background-image'); + + if (propertyValue.match(/^url/)) { + promises.push(that.image(propertyValue.replace(/^url\(['"]?/, '').replace(/['"]?\)$/, ''))); + } + }); + return $.when.apply(null, promises); + } + }; + + var state$1 = window.pageflow || {}; + + var ChapterFilter = BaseObject.extend({ + initialize: function initialize(entryData) { + this.entry = entryData; + }, + strategies: { + non: function non() { + return false; + }, + current_storyline: function current_storyline(currentChapterId, otherChapterId) { + return this.entry.getStorylineIdByChapterId(currentChapterId) === this.entry.getStorylineIdByChapterId(otherChapterId); + }, + inherit_from_parent: function inherit_from_parent(currentChapterId, otherChapterId) { + return this.chapterVisibleFromChapter(this.entry.getParentChapterId(currentChapterId), otherChapterId); + } + }, + chapterVisibleFromPage: function chapterVisibleFromPage(currentPagePermaId, chapterId) { + var currentChapterId = this.entry.getChapterIdByPagePermaId(currentPagePermaId); + return this.chapterVisibleFromChapter(currentChapterId, chapterId); + }, + chapterVisibleFromChapter: function chapterVisibleFromChapter(currentChapterId, otherChapterId) { + return this.getStrategy(currentChapterId).call(this, currentChapterId, otherChapterId); + }, + getStrategy: function getStrategy(chapterId) { + return this.strategies[this.getNavigationBarMode(chapterId)] || this.strategies.current_storyline; + }, + getNavigationBarMode: function getNavigationBarMode(chapterId) { + var storylineId = this.entry.getStorylineIdByChapterId(chapterId); + return this.entry.getStorylineConfiguration(storylineId).navigation_bar_mode; + } + }); + ChapterFilter.strategies = _(ChapterFilter.prototype.strategies).keys(); + + ChapterFilter.create = function () { + return new ChapterFilter(state$1.entryData); + }; + + var readyDeferred = new $.Deferred(); + var ready = readyDeferred.promise(); + + var cookieNotice = { + request: function request() { + ready.then(function () { + events.trigger('cookie_notice:request'); + }); + } + }; + + var DelayedStart = function ($) { + return function () { + var waitDeferred = new $.Deferred(); + var promises = []; + var performed = false; + return { + promise: function promise() { + return waitDeferred.promise(); + }, + wait: function wait(callback) { + var cancelled = false; + waitDeferred.then(function () { + if (!cancelled) { + callback(); + } + }); + return { + cancel: function cancel() { + cancelled = true; + } + }; + }, + waitFor: function waitFor(callbackOrPromise) { + if (!performed) { + if (typeof callbackOrPromise === 'function') { + callbackOrPromise = new $.Deferred(function (deferred) { + callbackOrPromise(deferred.resolve); + }).promise(); + } + + promises.push(callbackOrPromise); + } + }, + perform: function perform() { + if (!performed) { + performed = true; + $.when.apply(null, promises).then(waitDeferred.resolve); + } + } + }; + }; + }($); + var delayedStart = new DelayedStart(); + + var manualStart = function ($) { + var requiredDeferred = $.Deferred(); + var waitDeferred = $.Deferred(); + $(function () { + if (manualStart.enabled) { + delayedStart.waitFor(waitDeferred); + requiredDeferred.resolve(waitDeferred.resolve); + } + }); + return { + required: function required() { + return requiredDeferred.promise(); + } + }; + }($); + + var KEY_TAB = 9; + var FocusOutline = BaseObject.extend({ + initialize: function initialize(element) { + this.element = element; + }, + showOnlyAfterKeyboardInteraction: function showOnlyAfterKeyboardInteraction() { + var focusOutline = this; + this.disable(); + this.element.on('keydown', function (event) { + if (event.which === KEY_TAB) { + focusOutline.enable(); + } + }); + this.element.on('mousedown', function () { + focusOutline.disable(); + }); + }, + disable: function disable() { + if (!this.disabled) { + this.disabled = true; + this.element.addClass('disable_focus_outline'); + this.element.removeClass('enable_focus_outline'); + } + }, + enable: function enable() { + if (this.disabled) { + this.disabled = false; + this.element.removeClass('disable_focus_outline'); + this.element.addClass('enable_focus_outline'); + } + } + }); + + FocusOutline.setup = function (element) { + state$1.focusOutline = new FocusOutline(element); + state$1.focusOutline.showOnlyAfterKeyboardInteraction(); + }; + + var Fullscreen = BaseObject.extend({ + toggle: function toggle() { + var fullscreen = this; + + if ($.support.fullscreen) { + $('#outer_wrapper').fullScreen({ + callback: function callback(active) { + fullscreen._active = active; + fullscreen.trigger('change'); + } + }); + } + }, + isSupported: function isSupported() { + return $.support.fullscreen; + }, + isActive: function isActive() { + return this._active; + } + }); + var fullscreen = new Fullscreen(); + + var HighlightedPage = BaseObject.extend({ + initialize: function initialize(entryData, options) { + this.customNavigationBarMode = options && options.customNavigationBarMode; + this.entry = entryData; + }, + getPagePermaId: function getPagePermaId(currentPagePermaId) { + var storylineId = this.entry.getStorylineIdByPagePermaId(currentPagePermaId); + + if (this.getNavigationBarMode(storylineId) === 'inherit_from_parent') { + var parentPagePermaId = this.entry.getParentPagePermaId(storylineId); + return parentPagePermaId && this.getPagePermaId(parentPagePermaId); + } else { + return this.getDisplayedPageInChapter(currentPagePermaId); + } + }, + getDisplayedPageInChapter: function getDisplayedPageInChapter(pagePermaId) { + return _(this.getChapterPagesUntil(pagePermaId).reverse()).find(function (permaId) { + return this.pageIsDisplayedInNavigation(permaId); + }, this); + }, + pageIsDisplayedInNavigation: function pageIsDisplayedInNavigation(permaId) { + return this.entry.getPageConfiguration(permaId).display_in_navigation !== false; + }, + getNavigationBarMode: function getNavigationBarMode(storylineId) { + if (this.customNavigationBarMode) { + return this.customNavigationBarMode(storylineId, this.entry); + } else { + return this.entry.getStorylineConfiguration(storylineId).navigation_bar_mode; + } + }, + getChapterPagesUntil: function getChapterPagesUntil(pagePermaId) { + var found = false; + var chapterId = this.entry.getChapterIdByPagePermaId(pagePermaId); + return _.filter(this.entry.getChapterPagePermaIds(chapterId), function (other) { + var result = !found; + found = found || pagePermaId === other; + return result; + }); + } + }); + + HighlightedPage.create = function (options) { + return new HighlightedPage(state$1.entryData, options); + }; + + var links = { + setup: function setup() { + this.ensureClickOnEnterKeyPress(); + this.setupContentSkipLinks(); + }, + ensureClickOnEnterKeyPress: function ensureClickOnEnterKeyPress() { + $('body').on('keypress', 'a, [tabindex]', function (e) { + if (e.which == 13) { + $(this).click(); + } + }); + $('body').on('keyup', 'a, [tabindex]', function (e) { + e.stopPropagation(); + }); + }, + setupContentSkipLinks: function setupContentSkipLinks() { + $('.content_link').attr('href', '#firstContent'); + $('.content_link').click(function (e) { + $('#firstContent').focus(); + e.preventDefault(); + return false; + }); + } + }; + + /** + * Manual interaction with the multimedia alert. + * + * @since 0.9 + */ + + var multimediaAlert = { + /** + * Display the multimedia alert. + */ + show: function show() { + events.trigger('request:multimedia_alert'); + } + }; + + var nativeScrolling = { + preventScrollBouncing: function preventScrollBouncing(slideshow) { + slideshow.on('touchmove', function (e) { + e.preventDefault(); + }); + }, + preventScrollingOnEmbed: function preventScrollingOnEmbed(slideshow) { + slideshow.on('wheel mousewheel DOMMouseScroll', function (e) { + e.stopPropagation(); + e.preventDefault(); + }); + } + }; + + var theme = { + mainColor: function mainColor() { + var probe = document.getElementById('theme_probe-main_color'); + return window.getComputedStyle(probe)['background-color']; + } + }; + + var Visited = function Visited(entryId, pages, events, cookies) { + var cookieName = '_pageflow_visited'; + var unvisitedPages = []; + + function _init() { + cookieNotice.request(); + + if (!cookies.hasItem(cookieName)) { + storeVisitedPageIds(getAllIds()); + } else { + var visitedIds = getVisitedPageIds(); + unvisitedPages = _.difference(getAllIds(), visitedIds); + } + + events.on('page:change', function (page) { + var id = page.getPermaId(); + var ids = getVisitedPageIds(); + + if (ids.indexOf(id) < 0) { + ids.push(id); + } + + storeVisitedPageIds(ids); + }); + } + + function migrateLegacyCookie() { + var legacyCookieName = '_pageflow_' + entryId + '_visited'; + + if (cookies.hasItem(legacyCookieName)) { + var ids = getCookieIds(legacyCookieName); + storeVisitedPageIds(_.uniq(ids)); + cookies.removeItem(legacyCookieName); + } + } + + function getAllIds() { + return pages.map(function (page) { + return page.perma_id; + }); + } + + function storeVisitedPageIds(ids) { + cookies.setItem(cookieName, ids, Infinity, location.pathname); + } + + function getVisitedPageIds() { + return getCookieIds(cookieName); + } + + function getCookieIds(name) { + if (cookies.hasItem(name) && !!cookies.getItem(name)) { + return cookies.getItem(name).split(',').map(function (id) { + return parseInt(id, 10); + }); + } + + return []; + } + + return { + init: function init() { + migrateLegacyCookie(); + + _init(); + }, + getUnvisitedPages: function getUnvisitedPages() { + return unvisitedPages; + } + }; + }; + + Visited.setup = function () { + state$1.visited = new Visited(state$1.entryId, state$1.pages, state$1.events, state$1.cookies); + + if (Visited.enabled) { + state$1.visited.init(); + } + }; + + var commonPageCssClasses = { + updateCommonPageCssClasses: function updateCommonPageCssClasses(pageElement, configuration) { + pageElement.toggleClass('invert', configuration.get('invert')); + pageElement.toggleClass('hide_title', configuration.get('hide_title')); + pageElement.toggleClass('hide_logo', !!configuration.get('hide_logo')); + toggleModeClass(state$1.Page.textPositions, 'text_position'); + toggleModeClass(state$1.Page.delayedTextFadeIn, 'delayed_text_fade_in'); + toggleModeClass(state$1.Page.scrollIndicatorModes, 'scroll_indicator_mode'); + toggleModeClass(state$1.Page.scrollIndicatorOrientations, 'scroll_indicator_orientation'); + + function toggleModeClass(modes, name) { + var value = configuration.get(name); + + _.each(modes, function (mode) { + pageElement.removeClass(name + '_' + mode); + }); + + if (value) { + pageElement.addClass(name + '_' + value); + } + } + + pageElement.toggleClass('no_text_content', !hasContent()); + + function hasContent() { + var hasTitle = _(['title', 'subtitle', 'tagline']).some(function (attribute) { + return !!$.trim(configuration.get(attribute)); + }); + + var text = $('
                  ').html(configuration.get('text')).text(); + var hasText = !!$.trim(text); + return hasTitle && !configuration.get('hide_title') || hasText; + } + } + }; + + var defaultPageContent = { + updateDefaultPageContent: function updateDefaultPageContent(pageElement, configuration) { + pageElement.find('.page_header-tagline').text(configuration.get('tagline') || ''); + pageElement.find('.page_header-title').text(configuration.get('title') || ''); + pageElement.find('.page_header-subtitle').text(configuration.get('subtitle') || ''); + pageElement.find('.page_text .paragraph').html(configuration.get('text') || ''); + } + }; + + var infoBox = { + updateInfoBox: function updateInfoBox(pageElement, configuration) { + var infoBox = pageElement.find('.add_info_box'); + + if (!infoBox.find('h3').length) { + infoBox.prepend($('

                  ')); + } + + infoBox.find('h3').html(configuration.get('additional_title') || ''); + infoBox.find('p').html(configuration.get('additional_description') || ''); + infoBox.toggleClass('empty', !configuration.get('additional_description') && !configuration.get('additional_title')); + infoBox.toggleClass('title_empty', !configuration.get('additional_title')); + infoBox.toggleClass('description_empty', !configuration.get('additional_description')); + } + }; + + var videoHelpers = { + updateVideoPoster: function updateVideoPoster(pageElement, imageUrl) { + pageElement.find('.vjs-poster').css('background-image', 'url(' + imageUrl + ')'); + }, + updateBackgroundVideoPosters: function updateBackgroundVideoPosters(pageElement, imageUrl, x, y) { + pageElement.find('.vjs-poster, .background-image').css({ + 'background-image': 'url(' + imageUrl + ');', + 'background-position': x + '% ' + y + '%;' + }); + } + }; + + var volumeFade = { + fadeSound: function fadeSound(media, endVolume, fadeTime) { + var fadeResolution = 10; + var startVolume = media.volume(); + var steps = fadeTime / fadeResolution; + var leap = (endVolume - startVolume) / steps; + clearInterval(this.fadeInterval); + + if (endVolume != startVolume) { + var fade = this.fadeInterval = setInterval(_.bind(function () { + media.volume(media.volume() + leap); + + if (media.volume() >= endVolume && endVolume >= startVolume || media.volume() <= endVolume && endVolume <= startVolume) { + clearInterval(fade); + } + }, this), fadeResolution); + } + } + }; + + var pageType = function () { + var base = { + enhance: function enhance(pageElement, configuarion) {}, + prepare: function prepare(pageElement, configuarion) {}, + unprepare: function unprepare(pageElement, configuarion) {}, + preload: function preload(pageElement, configuarion) {}, + resize: function resize(pageElement, configuarion) {}, + activating: function activating(pageElement, configuarion) {}, + activated: function activated(pageElement, configuarion) {}, + deactivating: function deactivating(pageElement, configuarion) {}, + deactivated: function deactivated(pageElement, configuarion) {}, + update: function update(pageElement, configuarion) {}, + cleanup: function cleanup(pageElement, configuarion) {}, + embeddedEditorViews: function embeddedEditorViews() {}, + linkedPages: function linkedPages() { + return []; + }, + isPageChangeAllowed: function isPageChangeAllowed(pageElement, configuarion, options) { + return true; + }, + prepareNextPageTimeout: 200 + }; + return { + repository: [], + initializers: {}, + registerInitializer: function registerInitializer(name, fn) { + this.initializers[name] = fn; + }, + invokeInitializers: function invokeInitializers(pages) { + var _this = this; + + _.each(pages, function (page) { + if (_this.initializers[page.template]) { + _this.initializers[page.template](page.configuration); + } + }); + }, + register: function register(name, pageType) { + var constructor = function constructor() {}; + + _.extend(constructor.prototype, base, Backbone.Events, pageType); + + this.repository[name] = constructor; + }, + get: function get(name) { + if (!this.repository[name]) { + throw 'Unknown page type "' + name + '"'; + } + + return new this.repository[name](); + } + }; + }(); + + var SimulatedAdapter = function SimulatedAdapter() { + var stack = [{ + hash: null, + state: null + }]; + + this.back = function () { + if (stack.length > 1) { + stack.pop(); + this.trigger('popstate'); + return true; + } + + return false; + }; + + this.pushState = function (state, title, hash) { + stack.push({ + state: state, + hash: hash + }); + }; + + this.replaceState = function (state, title, hash) { + peek().state = state; + peek().hash = hash; + }; + + this.state = function () { + return peek().state; + }; + + this.hash = function () { + return peek().hash; + }; + + function peek() { + return stack[stack.length - 1]; + } + }; + + _.extend(SimulatedAdapter.prototype, Backbone.Events); + + var PushStateAdapter = function PushStateAdapter() { + var counter = 0; + + this.back = function () { + if (counter > 0) { + window.history.back(); + return true; + } + + return false; + }; + + this.pushState = function (state, title, hash) { + counter += 1; + window.history.pushState(state, title, '#' + hash); + }; + + this.replaceState = function (state, title, hash) { + window.history.replaceState(state, title, '#' + hash); + }; + + this.state = function () { + return history.state; + }; + + this.hash = function () { + var match = window.location.href.match(/#(.*)$/); + return match ? match[1] : ''; + }; + + this.on = function (event, listener) { + return $(window).on(event, listener); + }; + + $(window).on('popstate', function () { + counter -= 1; + }); + }; + + var HashAdapter = function HashAdapter() { + var counter = 0; + + this.back = function () { + if (counter > 0) { + window.history.back(); + counter -= 1; + return true; + } + + return false; + }; + + this.pushState = function (state, title, hash) { + if (window.location.hash !== hash) { + counter += 1; + window.location.hash = hash; + } + }; + + this.replaceState = function (state, title, hash) { + window.location.hash = hash; + }; + + this.state = function () { + return {}; + }; + + this.hash = function () { + var match = window.location.href.match(/#(.*)$/); + return match ? match[1] : ''; + }; + + this.on = function (event, listener) { + return $(window).on(event, listener); + }; + }; + + var History = function History(slideshow, adapter) { + slideshow.on('slideshowchangepage', function (event, options) { + var hash = slideshow.currentPage().attr('id'); + + if (options.back) { + adapter.replaceState(null, '', adapter.hash()); + } else if (options.ignoreInHistory) { + adapter.replaceState(null, '', hash); + } else { + adapter.replaceState(options, '', adapter.hash()); + adapter.pushState(null, '', hash); + } + }); + adapter.on('popstate', function (event) { + if (!adapter.state()) { + return; + } + + slideshow.goToByPermaId(adapter.hash(), _.extend({ + back: true + }, _.pick(adapter.state(), 'direction', 'transition'))); + }); + adapter.on('hashchange', function () { + slideshow.goToByPermaId(adapter.hash()); + }); + + this.getLandingPagePermaId = function () { + return adapter.hash() || pageParameter(); + }; + + this.start = function () { + adapter.replaceState(null, '', slideshow.currentPage().attr('id')); + }; + + this.back = _.bind(adapter.back, adapter); + + function pageParameter() { + var match = window.location.href.match(/page=([^&]*)/); + return match ? match[1] : ''; + } + }; + + History.create = function (slideshow, options) { + options = options || {}; + var adapter; + + if (options.simulate) { + adapter = new SimulatedAdapter(); + } else if (browser.has('pushstate support')) { + adapter = new PushStateAdapter(); + } else { + adapter = new HashAdapter(); + } + + return new History(slideshow, adapter); + }; + + var attributeName = 'atmo_audio_file_id'; + var Atmo = BaseObject.extend({ + initialize: function initialize(options) { + this.slideshow = options.slideshow; + this.multiPlayer = options.multiPlayer; + this.backgroundMedia = options.backgroundMedia; + this.disabled = browser.has('mobile platform'); + this.listenTo(options.events, 'page:change page:update background_media:unmute', function () { + this.update(); + }); + this.listenTo(options.multiPlayer, 'playfailed', function () { + options.backgroundMedia.mute(); + }); + }, + disable: function disable() { + this.disabled = true; + this.multiPlayer.fadeOutAndPause(); + events.trigger('atmo:disabled'); + }, + enable: function enable() { + this.disabled = false; + this.update(); + events.trigger('atmo:enabled'); + }, + pause: function pause() { + if (browser.has('volume control support')) { + return this.multiPlayer.fadeOutAndPause(); + } else { + this.multiPlayer.pause(); + } + }, + turnDown: function turnDown() { + if (browser.has('volume control support')) { + return this.multiPlayer.changeVolumeFactor(0.2); + } else { + this.multiPlayer.pause(); + } + }, + resume: function resume() { + if (this.multiPlayer.paused()) { + if (this.disabled || this.backgroundMedia.muted) { + return new $.Deferred().resolve().promise(); + } else { + return this.multiPlayer.resumeAndFadeIn(); + } + } else { + return this.multiPlayer.changeVolumeFactor(1); + } + }, + update: function update() { + var configuration = this.slideshow.currentPageConfiguration(); + + if (!this.disabled) { + if (this.backgroundMedia.muted) { + this.multiPlayer.fadeOutAndPause(); + } else { + this.multiPlayer.fadeTo(configuration[attributeName]); + } + } + }, + createMediaPlayerHooks: function createMediaPlayerHooks(configuration) { + var atmo = this; + return { + before: function before() { + if (configuration.atmo_during_playback === 'mute') { + atmo.pause(); + } else if (configuration.atmo_during_playback === 'turn_down') { + atmo.turnDown(); + } + }, + after: function after() { + atmo.resume(); + } + }; + } + }); + + Atmo.create = function (slideshow, events, audio, backgroundMedia) { + return new Atmo({ + slideshow: slideshow, + events: events, + backgroundMedia: backgroundMedia, + multiPlayer: audio.createMultiPlayer({ + loop: true, + fadeDuration: 500, + crossFade: true, + playFromBeginning: false, + rewindOnChange: true, + pauseInBackground: true + }) + }); + }; + + Atmo.duringPlaybackModes = ['play', 'mute', 'turn_down']; + + (function ($) { + var creatingMethods = ['reinit', 'reactivate', 'activate', 'activateAsLandingPage', 'preload', 'prepare', 'linkedPages']; + var ignoredMethods = ['cleanup', 'refreshScroller', 'resize', 'deactivate', 'unprepare', 'isPageChangeAllowed']; + var prototype = { + _create: function _create() { + this.configuration = this.element.data('configuration') || this.options.configuration; + this.index = this.options.index; + }, + _destroy: function _destroy() { + this.isDestroyed = true; + }, + _ensureCreated: function _ensureCreated() { + this.created = true; + this.element.nonLazyPage(this.options); + }, + _delegateToInner: function _delegateToInner(method, args) { + return this.element.nonLazyPage.apply(this.element, [method].concat([].slice.call(args))); + }, + getPermaId: function getPermaId() { + return parseInt(this.element.attr('id'), 10); + }, + getConfiguration: function getConfiguration() { + return this.configuration; + }, + getAnalyticsData: function getAnalyticsData() { + return state$1.entryData.getPageAnalyticsData(this.getPermaId()); + }, + update: function update(configuration) { + if (this.created) { + this._delegateToInner('update', arguments); + } else { + _.extend(this.configuration, configuration.attributes); + } + } + }; + + _(creatingMethods).each(function (method) { + prototype[method] = function () { + this._ensureCreated(); + + return this._delegateToInner(method, arguments); + }; + }); + + _(ignoredMethods).each(function (method) { + prototype[method] = function () { + if (this.created) { + return this._delegateToInner(method, arguments); + } + }; + }); + + $.widget('pageflow.page', prototype); + })($); + + /** + * Utility functions for page types that dynamically switch to a two + * column layout where some kind of embed is displayed next to the + * text (i.e. `pageflow-chart` and `pageflow-embedded-video`). + * + * Works closely with the `page-with_split_layout` CSS class (see + * `pageflow/themes/default/page/line_lengths.scss`). + * + * @since 12.2 + */ + var pageSplitLayout = function () { + return { + /** + * Determine if the page is wide enough to display two columns. + * + * @memberof pageflow.pageSplitLayout + */ + pageIsWideEnough: function pageIsWideEnough(pageElement) { + var pageClientRect = pageElement[0].getBoundingClientRect(); + var contentClientRect = getContentClientRect(pageElement, pageClientRect); + var spaceRightFromTitle = pageClientRect.right - contentClientRect.right; + var spaceLeftFromTitle = contentClientRect.left - pageClientRect.left; + var leftPositionedEmbedWidth = pageClientRect.width * 0.51; + var rightPositionedEmbedWidth = pageClientRect.width * 0.55; + return spaceLeftFromTitle >= leftPositionedEmbedWidth || spaceRightFromTitle >= rightPositionedEmbedWidth; + } + }; + + function getContentClientRect(pageElement, pageClientRect) { + var pageTitle = pageElement.find('.page_header-title'); + var pageText = pageElement.find('.page_text .paragraph'); + var pageTitleClientRect = pageTitle[0].getBoundingClientRect(); + var pageTextClientRect = pageText[0].getBoundingClientRect(); + var contentRight; + var contentLeft; + + if (isTitleHidden(pageTitleClientRect)) { + contentRight = pageTextClientRect.right; + contentLeft = pageTextClientRect.left; + } else { + contentRight = Math.max(pageTitleClientRect.right, pageTextClientRect.right); + contentLeft = pageTitleClientRect.left; + } + + var contentTranslation = getContentTranslationCausedByHiddenText(pageElement, pageClientRect); + return { + right: contentRight - contentTranslation, + left: contentLeft - contentTranslation + }; + } + + function isTitleHidden(pageTitleClientRect) { + return pageTitleClientRect.width === 0; + } + + function getContentTranslationCausedByHiddenText(pageElement, pageClientRect) { + var contentWrapper = pageElement.find('.content_wrapper'); + var contentWrapperClientRect = contentWrapper[0].getBoundingClientRect(); + var contentWrapperMarginInsidePage = contentWrapper[0].offsetLeft; + var nonTranslatedContentWrapperLeft = pageClientRect.left + contentWrapperMarginInsidePage; + return contentWrapperClientRect.left - nonTranslatedContentWrapperLeft; + } + }(); + + var ScrollIndicator = BaseObject.extend({ + initialize: function initialize(pageElement) { + this.pageElement = pageElement; + }, + disable: function disable() { + if (this._isPageActive()) { + events.trigger('scroll_indicator:disable'); + } + }, + scheduleDisable: function scheduleDisable() { + if (this._isPageActive()) { + events.trigger('scroll_indicator:schedule_disable'); + } + }, + enable: function enable(text) { + if (this._isPageActive()) { + events.trigger('scroll_indicator:enable'); + } + }, + _isPageActive: function _isPageActive() { + return this.pageElement.hasClass('active'); + } + }); + + var widgets = function () { + return { + isPresent: function isPresent(name) { + return !!$('div.' + className(name)).length; + }, + areLoaded: function areLoaded() { + return !!$('div.widgets_present').length; + }, + use: function use(options, callback) { + var original = options.insteadOf; + var originalClassName = className(original); + var replacementClassNames = className(options.name) + ' ' + className(original, 'replaced'); + + if (this.isPresent(original)) { + replace(originalClassName, replacementClassNames); + callback(function () { + replace(replacementClassNames, originalClassName); + }); + } else { + callback(function () {}); + } + } + }; + + function replace(original, replacement) { + $('div.widgets_present').removeClass(original).addClass(replacement); + events.trigger('widgets:update'); + state$1.slides.triggerResizeHooks(); + } + + function className(name, state) { + return 'widget_' + name + '_' + (state || 'present'); + } + }(); + + var navigationDirection = function () { + var eventMapping = { + v: { + scrollerbumpnext: 'scrollerbumpdown', + scrollerbumpback: 'scrollerbumpup', + scrollerhintnext: 'scrollerhintdown', + scrollerhintback: 'scrollerhintup' + }, + h: { + scrollerbumpnext: 'scrollerbumpright', + scrollerbumpback: 'scrollerbumpleft', + scrollerhintnext: 'scrollerhintright', + scrollerhintback: 'scrollerhintleft' + } + }; + return { + isHorizontalOnPhone: function isHorizontalOnPhone() { + return widgets.isPresent('phone_horizontal_slideshow_mode'); + }, + isHorizontal: function isHorizontal() { + return this.isHorizontalOnPhone() && browser.has('phone platform'); + }, + getEventName: function getEventName(name) { + var result = eventMapping[this.isHorizontal() ? 'h' : 'v'][name]; + + if (!result) { + throw 'Unknown event name ' + name; + } + + return result; + } + }; + }(); + + var PageTransitions = BaseObject.extend({ + initialize: function initialize(navigationDirection) { + this.repository = {}; + this.navigationDirection = navigationDirection; + }, + register: function register(name, options) { + this.repository[name] = options; + }, + get: function get(name) { + var transition = this.repository[name]; + + if (!transition) { + throw 'Unknown page transition "' + name + '"'; + } + + return this.navigationDirection.isHorizontal() ? transition.h : transition.v; + }, + names: function names() { + return _.keys(this.repository); + } + }); + var pageTransitions = new PageTransitions(navigationDirection); + pageTransitions.register('fade', { + v: { + className: 'fade fade-v', + duration: 1100 + }, + h: { + className: 'fade fade-h', + duration: 600 + } + }); + pageTransitions.register('crossfade', { + v: { + className: 'crossfade', + duration: 1100 + }, + h: { + className: 'crossfade crossfade-fast', + duration: 600 + } + }); + pageTransitions.register('fade_to_black', { + v: { + className: 'fade_to_black', + duration: 2100 + }, + h: { + className: 'fade_to_black', + duration: 2100 + } + }); + pageTransitions.register('cut', { + v: { + className: 'cut', + duration: 1100 + }, + h: { + className: 'cut', + duration: 1100 + } + }); + pageTransitions.register('scroll', { + v: { + className: 'scroll scroll-in scroll-from_bottom', + duration: 1100 + }, + h: { + className: 'scroll scroll-in scroll-from_right scroll-fast', + duration: 600 + } + }); + pageTransitions.register('scroll_right', { + v: { + className: 'scroll scroll-in scroll-from_right', + duration: 1100 + }, + h: { + className: 'scroll scroll-in scroll-from_bottom scroll-fast', + duration: 600 + } + }); + pageTransitions.register('scroll_left', { + v: { + className: 'scroll scroll-in scroll-from_left', + duration: 1100 + }, + h: { + className: 'scroll scroll-in scroll-from_top scroll-fast', + duration: 600 + } + }); + pageTransitions.register('scroll_over_from_right', { + v: { + className: 'scroll scroll-over scroll-from_right', + duration: 1100 + }, + h: { + className: 'scroll scroll-over scroll-from_bottom scroll-fast', + duration: 600 + } + }); + pageTransitions.register('scroll_over_from_left', { + v: { + className: 'scroll scroll-over scroll-from_left', + duration: 1100 + }, + h: { + className: 'scroll scroll-over scroll-from_top scroll-fast', + duration: 600 + } + }); + + (function ($) { + $.widget('pageflow.nonLazyPage', { + widgetEventPrefix: 'page', + _create: function _create() { + this.configuration = this.element.data('configuration') || this.options.configuration; + this.index = this.options.index; + + this._setupNearBoundaryCssClasses(); + + this._setupContentLinkTargetHandling(); + + this.reinit(); + }, + getPermaId: function getPermaId() { + return parseInt(this.element.attr('id'), 10); + }, + getConfiguration: function getConfiguration() { + return this.configuration; + }, + getAnalyticsData: function getAnalyticsData() { + return state$1.entryData.getPageAnalyticsData(this.getPermaId()); + }, + update: function update(configuration) { + _.extend(this.configuration, configuration.attributes); + + this.pageType.update(this.element, configuration); + }, + reinit: function reinit() { + this.pageType = pageType.get(this.element.data('template')); + this.element.data('pageType', this.pageType); + this.preloaded = false; + + if (this.pageType.scroller === false) { + this.content = $(); + } else { + this.content = this.element.find('.scroller'); + } + + this.content.scroller(this.pageType.scrollerOptions || {}); + this.pageType.scroller = this.content.scroller('instance'); + this.pageType.scrollIndicator = new ScrollIndicator(this.element); + + this._setupHideTextOnSwipe(); + + this._triggerPageTypeHook('enhance'); + + this._trigger('enhanced'); + }, + reactivate: function reactivate() { + if (this.element.hasClass('active')) { + this.preload(); + this.content.scroller('enable'); + this.content.scroller('resetPosition'); + this.content.scroller('afterAnimationHook'); + + this._triggerPageTypeHook('activating'); + + this._triggerDelayedPageTypeHook('activated'); + } + }, + cleanup: function cleanup() { + this._triggerPageTypeHook('deactivating'); + + this._triggerDelayedPageTypeHook('deactivated'); + + this._triggerPageTypeHook('cleanup'); + }, + refreshScroller: function refreshScroller() { + this.content.scroller('refresh'); + }, + resize: function resize() { + this._triggerPageTypeHook('resize'); + }, + activateAsLandingPage: function activateAsLandingPage() { + this.element.addClass('active'); + this.content.scroller('enable'); + this.content.scroller('resetPosition'); + this.content.scroller('afterAnimationHook'); + + this._trigger('activate', null, { + page: this + }); + + this._triggerPageTypeHook('activating'); + + this._triggerDelayedPageTypeHook('activated'); + }, + prepare: function prepare() { + this._triggerPageTypeHook('prepare'); + }, + unprepare: function unprepare() { + this._triggerPageTypeHook('unprepare'); + }, + prepareNextPageTimeout: function prepareNextPageTimeout() { + return this.pageType.prepareNextPageTimeout; + }, + linkedPages: function linkedPages() { + return this._triggerPageTypeHook('linkedPages'); + }, + isPageChangeAllowed: function isPageChangeAllowed(options) { + return this._triggerPageTypeHook('isPageChangeAllowed', options); + }, + preload: function preload() { + var page = this; + + if (!this.preloaded) { + this.preloaded = true; + return $.when(this._triggerPageTypeHook('preload')).then(function () { + page._trigger('preloaded'); + }); + } + }, + activate: function activate(options) { + options = options || {}; + setTimeout(_.bind(function () { + this.element.addClass('active'); + }, this), 0); + var duration = this.animateTransition('in', options, function () { + this.content.scroller('enable'); + this.content.scroller('afterAnimationHook'); + + this._triggerDelayedPageTypeHook('activated'); + }); + this.content.scroller('resetPosition', { + position: options.position + }); + + this._trigger('activate', null, { + page: this + }); + + this._triggerPageTypeHook('activating', { + position: options.position + }); + + return duration; + }, + deactivate: function deactivate(options) { + options = options || {}; + this.element.removeClass('active'); + var duration = this.animateTransition('out', options, function () { + this._triggerPageTypeHook('deactivated'); + }); + this.content.scroller('disable'); + + this._trigger('deactivate'); + + this._triggerPageTypeHook('deactivating'); + + return duration; + }, + animateTransition: function animateTransition(destination, options, callback) { + var otherDestination = destination === 'in' ? 'out' : 'in'; + var transition = pageTransitions.get(options.transition || this.configuration.transition || 'fade'); + var animateClass = transition.className + ' animate-' + destination + '-' + options.direction; + this.element.removeClass('animate-' + otherDestination + '-forwards animate-' + otherDestination + '-backwards').addClass(animateClass); + setTimeout(_.bind(function () { + this.element.removeClass(animateClass); + callback.call(this); + }, this), transition.duration); + return transition.duration; + }, + _triggerDelayedPageTypeHook: function _triggerDelayedPageTypeHook(name) { + var that = this; + var handle = delayedStart.wait(function () { + that._triggerPageTypeHook(name); + }); + this.element.one('pagedeactivate', function () { + handle.cancel(); + }); + }, + _triggerPageTypeHook: function _triggerPageTypeHook(name, options) { + return this.pageType[name](this.element, this.configuration, options || {}); + }, + _setupHideTextOnSwipe: function _setupHideTextOnSwipe() { + if (state$1.entryData.getSiteOption('hide_text_on_swipe') && !navigationDirection.isHorizontal() && !this.pageType.noHideTextOnSwipe) { + this.element.hideTextOnSwipe({ + eventTargetSelector: // legacy ERB pages + '.content > .scroller,' + // React based pages + '.content > .scroller-wrapper > .scroller,' + // internal links/text page + '.content.scroller' + }); + } + }, + _setupNearBoundaryCssClasses: function _setupNearBoundaryCssClasses() { + var element = this.element; + + _(['top', 'bottom']).each(function (boundary) { + element.on('scrollernear' + boundary, function () { + element.addClass('is_near_' + boundary); + }); + element.on('scrollernotnear' + boundary, function () { + element.removeClass('is_near_' + boundary); + }); + }); + }, + _setupContentLinkTargetHandling: function _setupContentLinkTargetHandling() { + this._on({ + 'click .page_text .paragraph a': function clickPage_textParagraphA(event) { + var href = $(event.currentTarget).attr('href'); + var target = PAGEFLOW_EDITOR ? '_blank' : $(event.currentTarget).attr('target'); + + if (href[0] === '#') { + state$1.slides.goToByPermaId(href.substr(1)); + } else { + // There was a time when the rich text editor did not add + // target attributes to inline links even though it should + // have. Ensure all content links to external urls open in + // new tab, except explicitly specified otherwise by editor. + window.open(href, target || '_blank'); + } + + event.preventDefault(); + } + }); + } + }); + })($); + + (function ($) { + /** + * Wrapper widget around iScroll adding special bump events which + * are triggered when scrolling to the very top or very bottom + * (called boundary posititon below). + * @private + */ + $.widget('pageflow.scroller', { + dragThreshold: 50, + maxXDelta: 50, + maxYDelta: 50, + doubleBumpThreshold: 500, + _create: function _create() { + this.eventListenerTarget = this.options.eventListenerTarget ? $(this.options.eventListenerTarget) : this.element; + this.iscroll = new IScroll(this.element[0], _.extend({ + mouseWheel: true, + bounce: false, + keyBindings: true, + probeType: 2, + preventDefault: false, + eventListenerTarget: this.eventListenerTarget[0] + }, _.pick(this.options, 'freeScroll', 'scrollX', 'noMouseWheelScrollX'))); + this.iscroll.disable(); + + if (state$1.entryData.getSiteOption('page_change_by_scrolling')) { + this._initMousewheelBump('up'); + + this._initMousewheelBump('down'); + + this._initDragGestureBump(); + } + + this._initKeyboardBump('up'); + + this._initKeyboardBump('down'); + + this._initNearBottomEvents(); + + this._initNearTopEvents(); + + this._initMoveEvents(); + + this._onScrollEndCallbacks = new $.Callbacks(); + }, + enable: function enable() { + this.iscroll.enable(); + this.iscroll.refresh(); + }, + resetPosition: function resetPosition(options) { + options = options || {}; + this.iscroll.refresh(); + + if (options.position === 'bottom') { + this.iscroll.scrollTo(0, this.iscroll.maxScrollY, 0); + } else { + this.iscroll.scrollTo(0, 0, 0); + } + + this._triggerBoundaryEvents(); + }, + scrollBy: function scrollBy(deltaX, deltaY, time, easing) { + this.scrollTo(this.iscroll.x + deltaX, this.iscroll.y + deltaY, time, easing); + }, + scrollTo: function scrollTo(x, y, time, easing) { + this.iscroll.scrollTo(Math.max(Math.min(x, 0), this.iscroll.maxScrollX), Math.max(Math.min(y, 0), this.iscroll.maxScrollY), time, easing); + + this._onScrollEndCallbacks.fire(); + }, + refresh: function refresh() { + this.iscroll.refresh(); + }, + afterAnimationHook: function afterAnimationHook() { + this._triggerBoundaryEvents(); + }, + disable: function disable() { + this.iscroll.disable(); + }, + positionX: function positionX() { + return this.iscroll.x; + }, + positionY: function positionY() { + return this.iscroll.y; + }, + maxX: function maxX() { + return this.iscroll.maxScrollX; + }, + maxY: function maxY() { + return this.iscroll.maxScrollY; + }, + onScroll: function onScroll(callback) { + this.iscroll.on('scroll', callback); + }, + onScrollEnd: function onScrollEnd(callback) { + this.iscroll.on('scrollEnd', callback); + + this._onScrollEndCallbacks.add(callback); + }, + _initMoveEvents: function _initMoveEvents() { + this.iscroll.on('mousewheelup', _.bind(this._triggerMoveEvent, this)); + this.iscroll.on('mousewheeldown', _.bind(this._triggerMoveEvent, this)); + this.iscroll.on('afterkeyboard', _.bind(this._triggerMoveEvent, this)); + }, + _triggerMoveEvent: function _triggerMoveEvent() { + this._trigger('move'); + }, + _initNearBottomEvents: function _initNearBottomEvents() { + this.iscroll.on('scroll', _.bind(this._triggerNearBottomEvents, this)); + this.iscroll.on('scrollEnd', _.bind(this._triggerNearBottomEvents, this)); + this.iscroll.on('afterkeyboard', _.bind(this._triggerNearBottomEvents, this)); + }, + _initNearTopEvents: function _initNearTopEvents() { + this.iscroll.on('scroll', _.bind(this._triggerNearTopEvents, this)); + this.iscroll.on('scrollEnd', _.bind(this._triggerNearTopEvents, this)); + this.iscroll.on('afterkeyboard', _.bind(this._triggerNearTopEvents, this)); + }, + _triggerBoundaryEvents: function _triggerBoundaryEvents() { + this._triggerNearTopEvents(); + + this._triggerNearBottomEvents(); + }, + _triggerNearBottomEvents: function _triggerNearBottomEvents() { + if (this._atBoundary('down', { + delta: 50 + })) { + this._trigger('nearbottom'); + } else { + this._trigger('notnearbottom'); + } + }, + _triggerNearTopEvents: function _triggerNearTopEvents() { + if (this._atBoundary('up', { + delta: 50 + })) { + this._trigger('neartop'); + } else { + this._trigger('notneartop'); + } + }, + // Whenever the a mousewheel event is triggered, we test whether + // the scroller is at the very top or at the very bottom. If so, + // we trigger a hintdown or hintup event the first time the mouse + // wheel turns and a bumpup or bumpdown event when the mouse wheel + // is turned to times in a short period of time. + _initMousewheelBump: function _initMousewheelBump(direction) { + var firstBump = false; + this.iscroll.on('mousewheel' + direction, _.bind(function () { + if (!this._atBoundary(direction)) { + return; + } + + if (firstBump) { + this._trigger('bump' + direction); + + firstBump = false; + clearTimeout(this.waitForSecondBump); + } else { + this._trigger('hint' + direction); + + firstBump = true; + this.waitForSecondBump = setTimeout(function () { + firstBump = false; + }, this.doubleBumpThreshold); + } + }, this)); + }, + // Trigger bumpup or bumpdown event when the a up/down key is + // pressed while the scroller in boundary position. + _initKeyboardBump: function _initKeyboardBump(direction) { + this.iscroll.on('keyboard' + direction, _.bind(function (event) { + if (this._atBoundary(direction)) { + // Make sure other iScrolls which might be enabled by the + // bump event do not process the keyboard event again. + event.stopImmediatePropagation(); + + this._trigger('bump' + direction); + } + }, this)); + this.iscroll.on('keyboardhint' + direction, _.bind(function () { + if (this._atBoundary(direction)) { + this._trigger('hint' + direction); + } + }, this)); + }, + // Trigger bumpup or bumpdown when the user drags the page from a + // boundary position. Trigger bumpleft or bumpright if user drags + // horizontally. + _initDragGestureBump: function _initDragGestureBump() { + var allowUp = false, + allowDown = false, + allowLeft = false, + allowRight = false, + startX, + startY; + this.eventListenerTarget.on('touchstart MSPointerDown pointerdown', _.bind(function (event) { + var point = event.originalEvent.touches ? event.originalEvent.touches[0] : event.originalEvent; + startX = point.pageX; + startY = point.pageY; + + if (!this._isNonTouchPointer(event)) { + allowDown = this._atBoundary('down'); + allowUp = this._atBoundary('up'); + allowLeft = true; + allowRight = true; + } + }, this)); + this.eventListenerTarget.on('touchmove MSPointerMove pointermove', _.bind(function (event) { + var point = event.originalEvent.touches ? event.originalEvent.touches[0] : event.originalEvent; + var deltaX = point.pageX - startX; + var deltaY = point.pageY - startY; + + if (Math.abs(deltaX) > this.maxXDelta) { + allowDown = allowUp = false; + } + + if (Math.abs(deltaY) > this.maxYDelta) { + allowLeft = allowRight = false; + } + + if (allowUp && deltaY > this.dragThreshold) { + this._trigger('bumpup'); + + allowDown = allowUp = allowLeft = allowRight = false; + } else if (allowDown && deltaY < -this.dragThreshold) { + this._trigger('bumpdown'); + + allowDown = allowUp = allowLeft = allowRight = false; + } else if (allowLeft && deltaX > this.dragThreshold) { + this._trigger('bumpleft'); + + allowDown = allowUp = allowLeft = allowRight = false; + } else if (allowRight && deltaX < -this.dragThreshold) { + this._trigger('bumpright'); + + allowDown = allowUp = allowLeft = allowRight = false; + } + }, this)); + this.eventListenerTarget.on('touchend MSPointerUp pointerup', _.bind(function (event) { + var point = event.originalEvent.touches ? event.originalEvent.changedTouches[0] : event.originalEvent; + var deltaX = point.pageX - startX; + var deltaY = point.pageY - startY; + + if (allowUp && deltaY > 0) { + this._trigger('hintup'); + } else if (allowDown && deltaY < 0) { + this._trigger('hintdown'); + } + + if (allowLeft && deltaX > 0) { + this._trigger('hintleft'); + } else if (allowRight && deltaX < 0) { + this._trigger('hintright'); + } + }, this)); + }, + _isNonTouchPointer: function _isNonTouchPointer(event) { + return event.originalEvent.pointerType && event.originalEvent.pointerType !== event.originalEvent.MSPOINTER_TYPE_TOUCH && event.originalEvent.pointerType !== 'touch'; + }, + // Checks whether the scroller is at the very top or very bottom. + _atBoundary: function _atBoundary(direction, options) { + options = options || {}; + var delta = options.delta || 0; + + if (direction === 'up') { + return this.iscroll.y >= -delta; + } else { + return this.iscroll.y <= this.iscroll.maxScrollY + delta; + } + } + }); + })($); + + (function ($) { + var boundaries = { + back: 'top', + next: 'bottom' + }; + $.widget('pageflow.scrollIndicator', { + _create: function _create() { + var parent = this.options.parent, + direction = this.element.data('direction'), + boundary = boundaries[direction], + that = this, + fadeTimeout; + + function update(page) { + that.element.toggleClass('hidden_by_scoll_indicator_mode', hiddenByMode(page)); + that.element.toggleClass('hidden_for_page', hideScrollIndicatorForPage(page)); + that.element.toggleClass('invert', invertIndicator(page)); + that.element.toggleClass('horizontal', page.hasClass('scroll_indicator_orientation_horizontal')); + that.element.toggleClass('available', targetPageExists()); + } + + function hiddenByMode(page) { + return page.hasClass('scroll_indicator_mode_non') || page.hasClass('scroll_indicator_mode_only_next') && direction === 'back' || page.hasClass('scroll_indicator_mode_only_back') && direction === 'next'; + } + + function invertIndicator(page) { + var result = page.data('invertIndicator'); + + if (typeof result === 'undefined') { + result = page.hasClass('invert') && !hasSlimPlayerControls(page); + } + + return result; + } + + function hideScrollIndicatorForPage(page) { + return hasSlimPlayerControls(page) || !widgets.areLoaded(); + } + + function hasSlimPlayerControls(page) { + return hasPlayerControls(page) && widgets.isPresent('slim_player_controls'); + } + + function hasPlayerControls(page) { + return !!page.find('[data-role="player_controls"]').length; + } + + function targetPageExists() { + return direction === 'next' ? parent.nextPageExists() : parent.previousPageExists(); + } + + parent.on('pageactivate', function (event) { + update($(event.target)); + clearTimeout(fadeTimeout); + that.element.removeClass('faded'); + }); + events.on({ + 'page:update': function pageUpdate() { + update(parent.currentPage()); + }, + 'scroll_indicator:disable': function scroll_indicatorDisable() { + clearTimeout(fadeTimeout); + that.element.addClass('hidden_for_page'); + }, + 'scroll_indicator:schedule_disable': function scroll_indicatorSchedule_disable() { + clearTimeout(fadeTimeout); + fadeTimeout = setTimeout(function () { + that.element.addClass('faded'); + }, 2000); + }, + 'scroll_indicator:enable': function scroll_indicatorEnable() { + clearTimeout(fadeTimeout); + that.element.removeClass('faded hidden_for_page'); + } + }); + parent.on(navigationDirection.getEventName('scrollerhint' + direction), function () { + that.element.addClass('animate'); + setTimeout(function () { + that.element.removeClass('animate'); + }, 500); + }); + parent.on('scrollernear' + boundary, function (event) { + var page = $(event.target).parents('section'); + + if (page.hasClass('active')) { + that.element.toggleClass('visible', targetPageExists()); + } + }); + parent.on('scrollernotnear' + boundary + ' slideshowchangepage', function () { + that.element.removeClass('visible'); + }); + $.when(ready, delayedStart.promise()).done(function () { + setTimeout(function () { + that.element.addClass('attract'); + setTimeout(function () { + that.element.removeClass('attract'); + }, 1500); + }, 3000); + }); + this.element.on('click', function () { + if (direction === 'next') { + parent.next(); + } else { + parent.back(); + } + }); + } + }); + })($); + + (function ($) { + $.widget('pageflow.hiddenTextIndicator', { + _create: function _create() { + var parent = this.options.parent, + that = this; + parent.on('pageactivate', function (event) { + var pageOrPageWrapper = $(event.target).add('.content_and_background', event.target); + that.element.toggleClass('invert', $(event.target).hasClass('invert')); + that.element.toggleClass('hidden', pageOrPageWrapper.hasClass('hide_content_with_text') || pageOrPageWrapper.hasClass('no_hidden_text_indicator')); + }); + parent.on('hidetextactivate', function () { + that.element.addClass('visible'); + }); + parent.on('hidetextdeactivate', function () { + that.element.removeClass('visible'); + }); + } + }); + })($); + + var AdjacentPages = BaseObject.extend({ + initialize: function initialize(pages, scrollNavigator) { + this.pages = pages; + this.scrollNavigator = scrollNavigator; + }, + of: function of(page) { + var result = []; + var pages = this.pages(); + var nextPage = this.nextPage(page); + + if (nextPage) { + result.push(nextPage); + } + + _(page.linkedPages()).each(function (permaId) { + var linkedPage = pages.filter('#' + permaId); + + if (linkedPage.length) { + result.push(linkedPage.page('instance')); + } + }, this); + + return result; + }, + nextPage: function nextPage(page) { + var nextPage = this.scrollNavigator.getNextPage(page.element, this.pages()); + return nextPage.length && nextPage.page('instance'); + } + }); + + var AdjacentPreloader = BaseObject.extend({ + initialize: function initialize(adjacentPages) { + this.adjacentPages = adjacentPages; + }, + attach: function attach(events) { + this.listenTo(events, 'page:change', this.preloadAdjacent); + }, + preloadAdjacent: function preloadAdjacent(page) { + _(this.adjacentPages.of(page)).each(function (page) { + page.preload(); + }); + } + }); + + AdjacentPreloader.create = function (pages, scrollNavigator) { + return new AdjacentPreloader(new AdjacentPages(pages, scrollNavigator)); + }; + + var SuccessorPreparer = BaseObject.extend({ + initialize: function initialize(adjacentPages) { + this.adjacentPages = adjacentPages; + }, + attach: function attach(events) { + this.listenTo(events, 'page:change', this.schedule); + }, + schedule: function schedule(page) { + clearTimeout(this.scheduleTimeout); + + var prepare = _.bind(this.prepareSuccessor, this, page); + + this.scheduleTimeout = setTimeout(prepare, page.prepareNextPageTimeout()); + }, + prepareSuccessor: function prepareSuccessor(page) { + var preparedPages = _.compact([page, this.adjacentPages.nextPage(page)]); + + var noLongerPreparedPages = _.difference(this.lastPreparedPages, preparedPages); + + var newAdjacentPages = _.difference(preparedPages, this.lastPreparedPages); + + _(noLongerPreparedPages).each(function (page) { + if (!page.isDestroyed) { + page.unprepare(); + } + }); + + _(newAdjacentPages).each(function (adjacentPage) { + adjacentPage.prepare(); + }); + + this.lastPreparedPages = preparedPages; + } + }); + + SuccessorPreparer.create = function (pages, scrollNavigator) { + return new SuccessorPreparer(new AdjacentPages(pages, scrollNavigator)); + }; + + (function ($) { + $.widget('pageflow.swipeGesture', { + _create: function _create() { + var startX, startY, startTime, distX, distY; + this.options = _.extend({ + orientation: 'x', + minDist: 100, + maxOrthogonalDist: 50, + maxDuration: 500 + }, this.options); + var selector = this.options.eventTargetSelector; + this.element.on('touchstart MSPointerDown pointerdown', selector, _.bind(function (event) { + if (isNonTouchPointer(event)) { + return; + } + + var point = event.originalEvent.touches ? event.originalEvent.touches[0] : event.originalEvent; + startX = point.pageX; + startY = point.pageY; + distX = 0; + distY = 0; + startTime = new Date().getTime(); + }, this)); + this.element.on('touchmove MSPointerMove pointermove', selector, _.bind(function (event) { + if (isNonTouchPointer(event)) { + return; + } + + var point = event.originalEvent.touches ? event.originalEvent.touches[0] : event.originalEvent; + distX = point.pageX - startX; + distY = point.pageY - startY; + }, this)); + this.element.on('touchend MSPointerUp pointerup', selector, _.bind(function (event) { + if (isNonTouchPointer(event)) { + return; + } + + var elapsedTime = new Date().getTime() - startTime; + var dist = this.options.orientation === 'x' ? distX : distY; + var orthogonalDist = this.options.orientation === 'x' ? distY : distX; + + if (Math.abs(dist) > this.options.minDist && Math.abs(orthogonalDist) < this.options.maxOrthogonalDist && elapsedTime < this.options.maxDuration) { + if (this.options.orientation === 'x') { + this._trigger(dist > 0 ? 'right' : 'left'); + } else { + this._trigger(dist > 0 ? 'down' : 'up'); + } + } + }, this)); + + function isNonTouchPointer(event) { + return event.originalEvent.pointerType && event.originalEvent.pointerType !== event.originalEvent.MSPOINTER_TYPE_TOUCH && event.originalEvent.pointerType !== 'touch'; + } + } + }); + })($); + + var hideText = function () { + function element() { + return $('body'); + } + + function prefix(event) { + return _.map(event.split(' '), function (e) { + return 'hidetext' + e; + }).join(' '); + } + + $(function () { + element().on('keydown', function (event) { + if (event.keyCode == 27) { + hideText.deactivate(); + } + }); + }); + return { + isActive: function isActive() { + return element().hasClass('hideText'); + }, + toggle: function toggle() { + if (this.isActive()) { + this.deactivate(); + } else { + this.activate(); + } + }, + activate: function activate() { + if (!this.isActive()) { + element().addClass('hideText'); + element().trigger('hidetextactivate'); + } + }, + deactivate: function deactivate() { + if (this.isActive()) { + element().removeClass('hideText'); + element().trigger('hidetextdeactivate'); + } + }, + on: function on(event, callback) { + element().on(prefix(event), callback); + }, + off: function off(event, callback) { + element().off(prefix(event), callback); + } + }; + }(); + + (function ($) { + $.widget('pageflow.hideTextOnSwipe', { + _create: function _create() { + this.element.swipeGesture({ + orientation: 'x', + eventTargetSelector: this.options.eventTargetSelector + }); + this.element.on('swipegestureleft', function () { + hideText.activate(); + }); + this.element.on('touchstart MSPointerDown pointerdown mousedown', this.options.eventTargetSelector, function () { + if (hideText.isActive()) { + hideText.deactivate(); + } + }); + this.element.on('scrollermove', function () { + if (hideText.isActive()) { + hideText.deactivate(); + } + }); + } + }); + })($); + + var DomOrderScrollNavigator = function DomOrderScrollNavigator(slideshow, entryData) { + this.getLandingPage = function (pages) { + return pages.first(); + }; + + this.back = function (currentPage, pages) { + var position = 'bottom'; + var previousPage = this.getPreviousPage(currentPage, pages); + + if (previousPage.is(getParentPage(currentPage, pages))) { + position = null; + } + + slideshow.goTo(previousPage, { + position: position, + ignoreInHistory: true + }); + }; + + this.next = function (currentPage, pages) { + slideshow.goTo(this.getNextPage(currentPage, pages), { + ignoreInHistory: true + }); + }; + + this.nextPageExists = function (currentPage, pages) { + return !!this.getNextPage(currentPage, pages).length; + }; + + this.previousPageExists = function (currentPage, pages) { + return !!this.getPreviousPage(currentPage, pages).length; + }; + + this.getNextPage = function (currentPage, pages) { + var currentPageIndex = pages.index(currentPage); + var nextPage = currentPageIndex < pages.length - 1 ? $(pages.get(currentPageIndex + 1)) : $(); + + if (sameStoryline(currentPage, nextPage)) { + return nextPage; + } + + var scrollSuccessor = getScrollSuccessor(currentPage, pages); + + if (scrollSuccessor.length) { + return scrollSuccessor; + } + + return getParentPage(currentPage, pages); + }; + + this.getPreviousPage = function (currentPage, pages) { + var currentPageIndex = pages.index(currentPage); + var previousPage = currentPageIndex > 0 ? $(pages.get(currentPageIndex - 1)) : $(); + + if (sameStoryline(currentPage, previousPage)) { + return previousPage; + } + + return getParentPage(currentPage, pages); + }; + + this.getTransitionDirection = function (previousPage, currentPage, pages, options) { + return pages.index(currentPage) > pages.index(previousPage) ? 'forwards' : 'backwards'; + }; + + this.getDefaultTransition = function (previousPage, currentPage, pages) { + if (inParentStorylineOf(currentPage, previousPage, pages)) { + return getStorylinePageTransition(currentPage); + } else if (inParentStorylineOf(previousPage, currentPage, pages)) { + return getStorylinePageTransition(previousPage); + } + }; + + function inParentStorylineOf(page, otherPage, pages) { + var parentPage = getParentPage(page, pages); + return entryData.getStorylineIdByPagePermaId(parentPage.page('getPermaId')) == entryData.getStorylineIdByPagePermaId(otherPage.page('getPermaId')); + } + + function sameStoryline(page1, page2) { + return entryData.getStorylineIdByPagePermaId(page1.page('getPermaId')) == entryData.getStorylineIdByPagePermaId(page2.page('getPermaId')); + } + + function getParentPage(page, pages) { + var storylineConfiguration = getStorylineConfiguration(page); + + if ('parent_page_perma_id' in storylineConfiguration && entryData.getSiteOption('change_to_parent_page_at_storyline_boundary')) { + return pages.filter('#' + storylineConfiguration.parent_page_perma_id); + } + + return $(); + } + + function getStorylinePageTransition(page) { + var storylineConfiguration = getStorylineConfiguration(page); + return storylineConfiguration.page_transition || 'scroll_over_from_right'; + } + + function getScrollSuccessor(page, pages) { + var storylineConfiguration = getStorylineConfiguration(page); + + if ('scroll_successor_id' in storylineConfiguration) { + return pages.filter('#' + storylineConfiguration.scroll_successor_id); + } + + return $(); + } + + function getStorylineConfiguration(page) { + var permaId = page.page('getPermaId'); + var storylineId = entryData.getStorylineIdByPagePermaId(permaId); + return entryData.getStorylineConfiguration(storylineId); + } + }; + + var Slideshow = function Slideshow($el, configurations) { + var transitioning = false, + currentPage = $(), + pages = $(), + that = this, + currentPageIndex; + configurations = configurations || {}; + + function transitionMutex(fn, context) { + if (transitioning) { + return; + } + + transitioning = true; + var transitionDuration = fn.call(context); + setTimeout(function () { + transitioning = false; + }, transitionDuration); + } + + function nearestPage(index) { + var result = $(pages.get(index)); + + if (!result.length) { + return pages.last(); + } + + return result; + } + + this.nextPageExists = function () { + return this.scrollNavigator.nextPageExists(currentPage, pages); + }; + + this.previousPageExists = function () { + return this.scrollNavigator.previousPageExists(currentPage, pages); + }; + + this.isOnLandingPage = function () { + return currentPage.is(this.scrollNavigator.getLandingPage(pages)); + }; + + this.goToLandingPage = function () { + this.goTo(this.scrollNavigator.getLandingPage(pages)); + }; + + this.back = function () { + this.scrollNavigator.back(currentPage, pages); + }; + + this.next = function () { + this.scrollNavigator.next(currentPage, pages); + }; + + this.parentPageExists = function () { + return !!state$1.entryData.getParentPagePermaIdByPagePermaId(this.currentPagePermaId()); + }; + + this.goToParentPage = function () { + this.goToByPermaId(state$1.entryData.getParentPagePermaIdByPagePermaId(this.currentPagePermaId())); + }; + + this.goToById = function (id, options) { + return this.goTo($el.find('[data-id=' + id + ']'), options); + }; + + this.goToByPermaId = function (permaId, options) { + if (permaId) { + return this.goTo(getPageByPermaId(permaId), options); + } + }; + + this.goTo = function (page, options) { + options = options || {}; + + if (page.length && !page.is(currentPage)) { + var cancelled = false; + events.trigger('page:changing', { + cancel: function cancel() { + cancelled = true; + } + }); + + if (cancelled) { + return; + } + + transitionMutex(function () { + var previousPage = currentPage; + currentPage = page; + currentPageIndex = currentPage.index(); + var transition = options.transition || this.scrollNavigator.getDefaultTransition(previousPage, currentPage, pages); + var direction = this.scrollNavigator.getTransitionDirection(previousPage, currentPage, pages, options); + var outDuration = previousPage.page('deactivate', { + direction: direction, + transition: transition + }); + var inDuration = currentPage.page('activate', { + direction: direction, + position: options.position, + transition: transition + }); + currentPage.page('preload'); + $el.trigger('slideshowchangepage', [options]); + return Math.max(outDuration, inDuration); + }, this); + return true; + } + }; + + this.goToFirstPage = function () { + return this.goTo(pages.first()); + }; + + this.update = function (options) { + pages = $el.find('section.page'); + pages.each(function (index) { + var $page = $(this); + $page.page({ + index: index, + configuration: configurations[$page.data('id')] + }); + }); + ensureCurrentPage(options); + }; + + this.currentPage = function () { + return currentPage; + }; + + this.currentPagePermaId = function () { + return parseInt(currentPage.attr('id'), 10); + }; + + this.currentPageConfiguration = function () { + return currentPage.page('getConfiguration'); + }; + + function ensureCurrentPage(options) { + var newCurrentPage = findNewCurrentPage(options); + + if (newCurrentPage) { + currentPage = newCurrentPage; + currentPageIndex = currentPage.index(); + currentPage.page('activateAsLandingPage'); + currentPage.page('preload'); + } + } + + function findNewCurrentPage(options) { + if (!currentPage.length) { + var permaId = options && options.landingPagePermaId; + var landingPage = permaId ? getPageByPermaId(permaId) : $(); + return landingPage.length ? landingPage : that.scrollNavigator.getLandingPage(pages); + } else if (!currentPage.parent().length) { + return nearestPage(currentPageIndex); + } + } + + function getPageByPermaId(permaId) { + return $el.find('#' + parseInt(permaId, 10)); + } + + this.on = function () { + $el.on.apply($el, arguments); + }; + + this.triggerResizeHooks = function () { + currentPage.page('resize'); + events.trigger('resize'); + }; + + $el.on(navigationDirection.getEventName('scrollerbumpback'), _.bind(function (event) { + if (currentPage.page('isPageChangeAllowed', { + type: 'bumpback' + })) { + this.back(); + } + }, this)); + $el.on(navigationDirection.getEventName('scrollerbumpnext'), _.bind(function (event) { + if (currentPage.page('isPageChangeAllowed', { + type: 'bumpnext' + })) { + this.next(); + } + }, this)); + $el.on('click', 'a.to_top', _.bind(function () { + this.goToLandingPage(); + }, this)); + $(window).on('resize', this.triggerResizeHooks); + nativeScrolling.preventScrollBouncing($el); + $el.addClass('slideshow'); + $el.find('.hidden_text_indicator').hiddenTextIndicator({ + parent: $('body') + }); + this.on('slideshowchangepage', function () { + hideText.deactivate(); + }); + $el.find('.scroll_indicator').scrollIndicator({ + parent: this + }); + this.scrollNavigator = new DomOrderScrollNavigator(this, state$1.entryData); + AdjacentPreloader.create(function () { + return pages; + }, this.scrollNavigator).attach(events); + SuccessorPreparer.create(function () { + return pages; + }, this.scrollNavigator).attach(events); + }; + + Slideshow.setup = function (options) { + function configurationsById(pages) { + return _.reduce(pages, function (memo, page) { + memo[page.id] = page.configuration; + return memo; + }, {}); + } + + state$1.slides = new Slideshow(options.element, configurationsById(options.pages)); + features.enable('slideshow', options.enabledFeatureNames || []); + state$1.atmo = Atmo.create(state$1.slides, events, state$1.audio, backgroundMedia); + state$1.history = History.create(state$1.slides, { + simulate: options.simulateHistory + }); + + if (options.beforeFirstUpdate) { + options.beforeFirstUpdate(); + } + + state$1.slides.update({ + landingPagePermaId: state$1.history.getLandingPagePermaId() + }); + state$1.history.start(); + return state$1.slides; + }; + + $(function ($) { + $('body').on('click', 'a.navigation_main', function () { + events.trigger('button:header'); + }); + $('body').on('click', 'a.navigation_index', function () { + events.trigger('button:overview'); + }); + $('body').on('click', 'a.navigation_fullscreen', function () { + events.trigger('button:fullscreen'); + }); + $('body').on('click', '.mute a', function () { + events.trigger('button:mute'); + }); + $('body').on('click', 'a.share.facebook', function () { + events.trigger('share:facebook'); + }); + $('body').on('click', 'a.share.twitter', function () { + events.trigger('share:twitter'); + }); + $('body').on('click', 'a.share.google', function () { + events.trigger('share:google'); + }); + $('body').on('pageactivate', function (event, ui) { + events.trigger('page:change', ui.page); + }); + }); + + (function ($) { + $.widget('pageflow.fullscreenButton', { + _create: function _create() { + fullscreen.on('change', this.update, this); + this.update(); + this.element.click(function () { + fullscreen.toggle(); + }); + + if (!fullscreen.isSupported()) { + this.element.css('visibility', 'hidden'); + } + }, + _destroy: function _destroy() { + fullscreen.off('change', this.update); + }, + update: function update() { + this.element.toggleClass('active', !!fullscreen.isActive()).updateTitle(); + } + }); + })($); + + $(function ($) { + $.widget('pageflow.header', { + _create: function _create() { + var slideshow = this.options.slideshow, + that = this; + slideshow.on('pageactivate', function (event, options) { + updateClasses(slideshow.currentPage()); + }); + slideshow.on('scrollerneartop', function (event) { + var page = $(event.target).parents('section'); + + if (page.is(slideshow.currentPage())) { + that.element.addClass('near_top'); + } + }); + slideshow.on('scrollernotneartop', function (event) { + var page = $(event.target).parents('section'); + + if (page.is(slideshow.currentPage())) { + that.element.removeClass('near_top'); + } + }); + + if (slideshow.currentPage().length) { + updateClasses(slideshow.currentPage()); + } + + this.element.addClass('near_top'); + this.element.find('.header input').placeholder(); + + function updateClasses(page) { + that.element.toggleClass('invert', page.hasClass('invert')); + that.element.toggleClass('first_page', page.index() === 0); + that.element.toggleClass('hide_logo', page.hasClass('hide_logo')); + } + } + }); + }); + + (function ($) { + $.widget('pageflow.multimediaAlert', { + _create: function _create() { + var widget = this; + + function show() { + widget.element.show(); + toggleContent(false); + } + + function hide() { + widget.element.hide(); + toggleContent(true); + } + + function toggleContent(state) { + $('.page .content').toggleClass('initially_hidden', !state); + $('.slideshow .scroll_indicator').toggleClass('initially_hidden', !state); + } + + manualStart.required().then(function (start) { + show(); + widget.element.find('.close').one('click', function () { + hide(); + backgroundMedia.unmute(); + events.trigger('button:close_multimedia_alert'); + start(); + return false; + }); + }); + events.on('request:multimedia_alert', function () { + show(); + widget.element.find('.close').one('click', function () { + hide(); + }); + }, this); + nativeScrolling.preventScrollBouncing(this.element); + } + }); + })($); + + (function ($) { + $.widget('pageflow.muteButton', { + _create: function _create() { + var element = this.element; + var volumeBeforeMute = 1; + element.on('click', toggleMute); + settings.on('change:volume', this.update, this); + this.update(); + + function toggleMute() { + if (settings.get('volume') > 0) { + volumeBeforeMute = settings.get('volume'); + settings.set('volume', 0); + } else { + settings.set('volume', volumeBeforeMute); + } + } + }, + _destroy: function _destroy() { + settings.off('change:volume', this.update); + }, + update: function update() { + var volume = settings.get('volume'); + + if (volume === 0) { + this.element.attr('title', this.element.attr('data-muted-title')).addClass('muted'); + } else { + this.element.attr('title', this.element.attr('data-not-muted-title')).removeClass('muted'); + } + } + }); + })($); + + (function ($) { + $.widget('pageflow.navigation', { + _create: function _create() { + var element = this.element, + overlays = element.find('.navigation_site_detail'), + toggleIndicators = function toggleIndicators() {}; + + element.addClass('js').append(overlays); + $('a.navigation_top', element).topButton(); + $('.navigation_bar_bottom', element).append($('.navigation_bar_top > li', element).slice(-2)); + $('.navigation_volume_box', element).volumeSlider({ + orientation: 'h' + }); + $('.navigation_mute', element).muteButton(); + /* hide volume button on mobile devices */ + + if (browser.has('mobile platform')) { + $('li.mute', element).hide(); + $('.navigation_bar_bottom', element).css('height', '224px'); + $('.scroller', element).css('bottom', '224px'); + $('.navigation_scroll_indicator.bottom', element).css('bottom', '190px'); + } + /* header button */ + + + $('.navigation_main', element).click(function () { + $(this).toggleClass('active').updateTitle(); + $('.header').toggleClass('active'); + }); + /* open header through skiplinks */ + + $('a[href="#header"], a[href="#search"]', '#skipLinks').click(function () { + $('.navigation_main', element).addClass('active'); + $('.header').addClass('active'); + $(this.getAttribute('href')).select(); + }); + /* share-button */ + + $('.navigation_menu .navigation_menu_box a', element).focus(function () { + $(this).parents('.navigation_menu').addClass('focused'); + }).blur(function () { + $(this).parents('.navigation_menu').removeClass('focused'); + }); + var shareBox = $('.navigation_share_box', element), + links = $('.share_box_icons > a', shareBox); + shareBox.shareMenu({ + subMenu: $('.sub_share', element), + links: links, + insertAfter: $('.share_box_icons'), + closeOnMouseLeaving: shareBox + }); + /* pages */ + + var pageLinks = $('.navigation_thumbnails a', element), + target; + + function registerHandler() { + target = $(this); + target.one('mouseup touchend', goToPage); + } + + function removeHandler() { + pageLinks.off('mouseup touchend', goToPage); + } + + function hideOverlay() { + $(overlays).addClass('hidden').removeClass('visible'); + } + + function goToPage(e) { + if (target && target[0] != e.currentTarget) { + return; + } + + hideOverlay(); + state$1.slides.goToById(this.getAttribute("data-link")); + e.preventDefault(); + } + + pageLinks.each(function (index) { + var handlerIn = function handlerIn() { + if (!('ontouchstart' in document.documentElement)) { + $(overlays[index]).css("top", $(this).offset().top).addClass('visible').removeClass('hidden'); + overlays.loadLazyImages(); + } + }; + + $(this).on({ + 'mouseenter': handlerIn, + 'mouseleave': hideOverlay, + 'mousedown touchstart': registerHandler, + 'click': goToPage + }); + }); + $(window).on('resize', function () { + $(overlays).css("top", "0"); + initiateIndicators(); + }); + + var initiateIndicators = function initiateIndicators() { + setTimeout(function () { + $('.navigation_scroll_indicator', element).show(); + toggleIndicators(); + }, 500); + }; + + $('.scroller', element).each(function () { + var bottomIndicator = $('.navigation_scroll_indicator.bottom', element), + topIndicator = $('.navigation_scroll_indicator.top', element), + scrollUpIntervalID, + scrollDownIntervalID, + hideOverlay = function hideOverlay() { + overlays.addClass('hidden').removeClass('visible'); + }; + + var atBoundary = function atBoundary(direction) { + if (direction === 'up') { + return scroller.y >= 0; + } else { + return scroller.y <= scroller.maxScrollY; + } + }; + + toggleIndicators = function toggleIndicators() { + if (atBoundary('down')) { + clearInterval(scrollDownIntervalID); + bottomIndicator.removeClass('pressed'); + } + + if (atBoundary('up')) { + clearInterval(scrollUpIntervalID); + topIndicator.removeClass('pressed'); + } + + topIndicator.toggleClass('visible', !atBoundary('up')); + bottomIndicator.toggleClass('visible', !atBoundary('down')); + }; + + var keyPressHandler = function keyPressHandler(e) { + var that = this, + scrollByStep = function scrollByStep() { + if ($(that).hasClass('bottom')) { + scroller.scrollBy(0, -20, 80); + } else { + scroller.scrollBy(0, 20, 80); + } + + toggleIndicators(); + }; + + if (e.which == 13) { + scrollByStep(); + setTimeout(function () { + that.focus(); + }, 50); + } else if (e.which === 0) { + scrollByStep(); + } + }; + + var scrollerOptions = { + mouseWheel: true, + bounce: false, + probeType: 2 + }; + /* + This is just a quick fix to detect IE10. We should + refactor this condition if we decide to use Modernizr + or another more global detection. + */ + + if (window.navigator.msPointerEnabled) { + scrollerOptions.preventDefault = false; + } + + var scroller = new IScroll(this, scrollerOptions); + $('ul.navigation_thumbnails', element).pageNavigationList({ + scroller: scroller, + scrollToActive: true, + animationDuration: 500, + lazyLoadImages: true, + onAnimationStart: function onAnimationStart() { + element.addClass('is_animating'); + }, + onAnimationEnd: function onAnimationEnd() { + element.removeClass('is_animating'); + }, + onFilterChange: function onFilterChange() { + toggleIndicators(); + } + }); + ready.then(function () { + toggleIndicators(); + }); + topIndicator.on({ + 'mousedown': function mousedown() { + scrollUpIntervalID = setInterval(function () { + scroller.scrollBy(0, 1); + toggleIndicators(); + }, 5); + }, + 'keypress': keyPressHandler, + 'touchstart': keyPressHandler + }); + topIndicator.on('mouseup touchend', function () { + clearInterval(scrollUpIntervalID); + }); + bottomIndicator.on({ + 'mousedown': function mousedown() { + scrollDownIntervalID = setInterval(function () { + scroller.scrollBy(0, -1); + toggleIndicators(); + }, 5); + }, + 'keypress': keyPressHandler, + 'touchstart': keyPressHandler + }); + bottomIndicator.on('mouseup touchend', function () { + clearInterval(scrollDownIntervalID); + }); + toggleIndicators(); + scroller.on('scroll', function () { + toggleIndicators(); + hideOverlay(); + removeHandler(); + }); + }); + /* hide text button */ + + var hideTextElement = $('.navigation_hide_text', element); + hideTextElement.click(function () { + hideText.toggle(); + }); + hideText.on('activate deactivate', function () { + hideTextElement.toggleClass('active', hideText.isActive()).updateTitle(); + }); + /* fullscreen button */ + + $('.navigation_bar_bottom .fullscreen a', element).fullscreenButton(); + $('.button, .navigation_mute, .navigation_scroll_indicator', element).on({ + 'touchstart mousedown': function touchstartMousedown() { + $(this).addClass('pressed'); + }, + 'touchend mouseup': function touchendMouseup() { + $(this).removeClass('pressed'); + } + }); + $('.navigation_share, .navigation_credits', element).on({ + 'touchstart': function touchstart() { + var element = $(this).parent().parent(); + element.addClass('open'); + + function close(e) { + if (!element.find(e.target).length) { + element.removeClass('open'); + $('body').off('touchstart', close); + } + } + + $('body').on('touchstart', close); + } + }); + $('li', element).on('mouseleave', function () { + $(this).blur(); + }); + $('body').on({ + 'pageactivate': function pageactivate(e) { + toggleIndicators(); + } + }); + } + }); + })($); + + (function ($) { + $.widget('pageflow.navigationMobile', { + _create: function _create() { + var that = this, + element = this.element, + scroller; + nativeScrolling.preventScrollBouncing(element); + $('body').on('touchstart mousedown MSPointerDown pointerdown', function (event) { + if (element.hasClass('active') && !$(event.target).parents().filter(element).length) { + element.removeClass('active imprint sharing'); + } + }); + $('.menu.index', element).click(function () { + if (!$(element).hasClass('sharing') && !$(element).hasClass('imprint')) { + $(element).toggleClass('active'); + element.loadLazyImages(); + } + + $(element).removeClass('imprint sharing'); + }); + $('.menu.sharing', element).click(function () { + $(element).addClass('sharing'); + $(element).removeClass('imprint'); + }); + $('.menu.imprint', element).click(function () { + $(element).addClass('imprint'); + $(element).removeClass('sharing'); + }); + $('.imprint_mobile a', element).on('click touchstart', function (event) { + event.stopPropagation(); + }); + $('.parent_page', element).parentPageButton({ + visibleClass: 'is_visible' + }); + $('.wrapper', element).each(function () { + var sharingMobile = $(this).parents('.sharing_mobile'); + scroller = new IScroll(this, { + preventDefault: false, + mouseWheel: true, + bounce: false, + probeType: 3 + }); + $('ul.pages', element).pageNavigationList({ + scroller: scroller, + animationDuration: 500 + }); + scroller.on('scroll', function () { + $('.overview_mobile li', element).removeClass('touched').off('touchend mouseup MSPointerUp pointerup', that._goToPage); + $('.sub_share a', sharingMobile).off('touchend mouseup MSPointerUp pointerup', that._openLink); + }); + $('.menu', element).click(function () { + scroller.refresh(); + }); + + if (!$(element).data('touchBound')) { + $('li', element).on({ + 'touchstart mousedown MSPointerDown pointerdown': function touchstartMousedownMSPointerDownPointerdown() { + $(this).addClass('touched'); + }, + 'touchend mouseup MSPointerUp pointerup': function touchendMouseupMSPointerUpPointerup() { + $(this).removeClass('touched'); + } + }); + $('.overview_mobile li', element).on({ + 'touchstart mousedown MSPointerDown pointerdown': function touchstartMousedownMSPointerDownPointerdown() { + $(this).one('touchend mouseup MSPointerUp pointerup', that._goToPage); + } + }); + $(element).data('touchBound', true); + } + + $('.sub_share a', sharingMobile).on({ + 'touchstart mousedown MSPointerDown pointerdown': function touchstartMousedownMSPointerDownPointerdown() { + $(this).one('touchend mouseup MSPointerUp pointerup', that._openLink); + } + }); + sharingMobile.shareMenu({ + subMenu: $('.sub_share', element), + links: $('li > a', sharingMobile), + scroller: scroller + }); + }); + }, + _goToPage: function _goToPage() { + var a = $('a', this), + id = a.attr("data-link"); + + if (id !== undefined) { + state$1.slides.goToById(id); + $('.navigation_mobile').removeClass('active'); + } + }, + _openLink: function _openLink(event) { + event.preventDefault(); + window.open(this.href, '_blank'); + } + }); + })($); + + $(function ($) { + $.widget('pageflow.overview', { + _create: function _create() { + var that = this, + scroller, + chapterParts = $('.ov_chapter', this.element), + pages = $('.ov_page', this.element), + noOfChapterParts = chapterParts.size(), + scrollerWidth = noOfChapterParts * chapterParts.outerWidth(true), + closeButton = $('.close', this.element), + indexButton = $('.navigation_index'), + overview = $('.overview'), + wrapper = $('.wrapper', this.element); + + var toggleContent = function toggleContent(state) { + var scrollIndicator = $('.slideshow .scroll_indicator'); + overview.toggleClass('active', state); + overview.loadLazyImages(); + indexButton.toggleClass('active', state).updateTitle(); + $('section.page').toggleClass('hidden_by_overlay', state); + scrollIndicator.toggleClass('hidden', state); + + if (overview.hasClass('active')) { + events.once('page:change', function () { + toggleContent(false); + }, that); + } else { + events.off('page:change', null, that); + } + }; + + var goToPage = function goToPage() { + if (!$(this).hasClass('active')) { + state$1.slides.goToById(this.getAttribute("data-link")); + } + }; + + $('.scroller', this.element).width(scrollerWidth); + + if (wrapper.find('.ov_chapter').length) { + // scroller throws exception if initialized with empty set + // of pages + scroller = new IScroll(wrapper[0], { + snap: '.ov_chapter', + bounce: false, + scrollX: true, + scrollY: false, + probeType: 2, + mouseWheel: true, + preventDefault: false + }); + scroller.on('scroll', function () { + pages.removeClass('touched').off('touchend mouseup', goToPage); + }); + wrapper.pageNavigationList({ + scroller: scroller, + scrollToActive: '.ov_chapter' + }); + this.element.find('.overview_scroll_indicator.left').scrollButton({ + scroller: scroller, + page: true, + direction: 'left' + }); + this.element.find('.overview_scroll_indicator.right').scrollButton({ + scroller: scroller, + page: true, + direction: 'right' + }); + } + + pages.each(function () { + $(this).on({ + 'touchstart mousedown': function touchstartMousedown() { + $(this).addClass('touched'); + $(this).one('touchend mouseup', goToPage); + }, + 'touchend mouseup': function touchendMouseup() { + $(this).removeClass('touched'); + }, + 'click': function click(event) { + event.preventDefault(); + } + }); + }); + + if (scrollerWidth < wrapper.width()) { + var closeButtonPos = Math.max(400, scrollerWidth - closeButton.width() - 10); + + if (isDirLtr(closeButton)) { + closeButton.css({ + left: closeButtonPos + 'px', + right: 'auto' + }); + } else { + closeButton.css({ + right: closeButtonPos + 'px', + left: 'auto' + }); + } + } + + closeButton.click(toggleContent); + indexButton.click(toggleContent); + $('body').keyup(function (e) { + if (e.which == 27 && overview.hasClass('active')) { + toggleContent(); + } + }); + + function isDirLtr(el) { + var styles = window.getComputedStyle(el[0]); + return styles.direction == 'ltr'; + } + } + }); + }); + + var PageNavigationListAnimation = BaseObject.extend({ + initialize: function initialize(entryData) { + this.entry = entryData; + }, + update: function update(currentPagePermaId) { + var currentPagePosition = this.entry.getPagePosition(currentPagePermaId); + var currentStorylineId = this.entry.getStorylineIdByPagePermaId(currentPagePermaId); + var currentStorylineLevel = this.entry.getStorylineLevel(currentStorylineId); + this.enabled = this.lastStorylineId && this.lastStorylineId !== currentStorylineId; + this.movingUp = this.lastStorylineLevel > currentStorylineLevel; + this.movingDown = this.lastStorylineLevel < currentStorylineLevel; + this.movingForwards = this.lastStorylineLevel === currentStorylineLevel && this.lastPagePosition < currentPagePosition; + this.movingBackwards = this.lastStorylineLevel === currentStorylineLevel && this.lastPagePosition > currentPagePosition; + this.lastPagePosition = currentPagePosition; + this.lastStorylineId = currentStorylineId; + this.lastStorylineLevel = currentStorylineLevel; + }, + start: function start(element, visible) { + if (this.enabled) { + element.toggleClass('moving_up', this.movingUp); + element.toggleClass('moving_down', this.movingDown); + element.toggleClass('moving_forwards', this.movingForwards); + element.toggleClass('moving_backwards', this.movingBackwards); + element.toggleClass('animate_out', !visible); + } + }, + finish: function finish(element, visible) { + if (this.enabled) { + element.toggleClass('animate_in', !!visible); + } + } + }); + + PageNavigationListAnimation.create = function () { + return new PageNavigationListAnimation(state$1.entryData); + }; + + (function ($) { + $.widget('pageflow.pageNavigationList', { + _create: function _create() { + var element = this.element; + var options = this.options; + var scroller = options.scroller; + var links = element.find('a[href]'); + var chapterFilter = ChapterFilter.create(); + var highlightedPage = HighlightedPage.create(options.highlightedPage); + var animation = PageNavigationListAnimation.create(); + ready.then(function () { + highlightUnvisitedPages(state$1.visited.getUnvisitedPages()); + update(getPagePermaId(state$1.slides.currentPage())); + }); + state$1.slides.on('pageactivate', function (e) { + setPageVisited(e.target.getAttribute('id')); + update(getPagePermaId(e.target)); + }); + + function getPagePermaId(section) { + return parseInt($(section).attr('id') || $(section).attr('data-perma-id'), 10); + } + + function update(currentPagePermaId) { + var highlightedPagePermaId = highlightedPage.getPagePermaId(currentPagePermaId); + var highlightedChapterId = state$1.entryData.getChapterIdByPagePermaId(highlightedPagePermaId); + element.toggleClass('inside_sub_chapter', highlightedPagePermaId !== currentPagePermaId); + filterChapters(currentPagePermaId).then(function () { + highlightPage(highlightedPagePermaId, { + animate: !animation.enabled + }); + highlightChapter(highlightedChapterId); + + if (options.onFilterChange) { + options.onFilterChange(); + } + }); + } + + function highlightPage(permaId, highlightOptions) { + links.each(function () { + var link = $(this); + var active = '#' + permaId === link.attr('href'); + link.toggleClass('active', active); + link.attr('tabindex', active ? '-1' : '3'); + + if (active) { + if (options.scrollToActive) { + var target = options.scrollToActive === true ? link : link.parents(options.scrollToActive); + scroller.scrollToElement(target[0], highlightOptions.animate ? 800 : 0); + } + } + }); + } + + function highlightChapter(activeChapterId) { + links.each(function () { + var link = $(this); + var active = activeChapterId === link.data('chapterId'); + link.toggleClass('in_active_chapter', active); + }); + } + + function highlightUnvisitedPages(ids) { + links.each(function () { + var link = $(this); + var unvisited = ids.indexOf(parseInt(link.attr('href').substr(1), 10)) >= 0; + link.toggleClass('unvisited', unvisited); + }); + } + + function setPageVisited(id) { + element.find('[href="#' + id + '"]').removeClass('unvisited'); + } + + function filterChapters(currentPagePermaId) { + animation.update(currentPagePermaId); + links.each(function () { + var link = $(this); + animation.start(link.parent(), visible(currentPagePermaId, link)); + }); + return $.when(animation.enabled && animationDurationElapsed()).then(function () { + links.each(function () { + var link = $(this); + var pageIsVisible = visible(currentPagePermaId, link); + animation.finish(link.parent(), pageIsVisible); + link.parent().andSelf().toggleClass('filtered', !pageIsVisible); + + if (pageIsVisible && options.lazyLoadImages) { + link.loadLazyImages(); + } + }); + scroller.refresh(); + }); + } + + function visible(currentPagePermaId, link) { + return chapterFilter.chapterVisibleFromPage(currentPagePermaId, link.data('chapterId')); + } + + function animationDurationElapsed() { + if (options.animationDuration) { + if (options.onAnimationStart) { + options.onAnimationStart(); + } + + return $.Deferred(function (deferred) { + setTimeout(function () { + deferred.resolve(); + + if (options.onAnimationEnd) { + setTimeout(options.onAnimationEnd, 500); + } + }, 500); + }).promise(); + } + } + } + }); + })($); + + (function ($) { + $.widget('pageflow.parentPageButton', { + _create: function _create() { + var element = this.element; + var options = this.options; + element.click(function (event) { + state$1.slides.goToParentPage(); + event.preventDefault(); + }); + state$1.slides.on('pageactivate', function (e, ui) { + update(); + }); + update(); + + function update() { + var pagePermaId = parseInt(state$1.slides.currentPage().attr('id'), 10); + var chapterId = state$1.entryData.getChapterIdByPagePermaId(pagePermaId); + var chapterConfiguration = state$1.entryData.getChapterConfiguration(chapterId); + var visible = state$1.slides.parentPageExists() && chapterConfiguration.display_parent_page_button !== false; + + if (options.visibleClass) { + element.toggleClass(options.visibleClass, visible); + } else { + element.toggle(visible); + } + } + } + }); + })($); + + $.widget('pageflow.playerControls', { + _create: function _create() { + var player = this.options.player; + var playButton = this.element.find('.vjs-play-control'); + var progressHolder = this.element.find('.vjs-progress-holder'); + var playProgress = this.element.find('.vjs-play-progress'); + var currentTimeDisplay = this.element.find('.vjs-current-time-display'); + var durationDisplay = this.element.find('.vjs-duration-display'); + var progressHandler = this.element.find('.vjs-slider-handle'); + var smallTimestamp = durationDisplay.html().length === 5 && durationDisplay.html().charAt(0) === "0"; + + if (smallTimestamp) { + durationDisplay.html("0:00"); + currentTimeDisplay.html(currentTimeDisplay.html().substr(1)); + } else { + durationDisplay.html("00:00"); + } + + player.on('timeupdate', function (position, duration) { + var percent = duration > 0 ? player.position / player.duration * 100 : 0; + + if (!isNaN(position)) { + if (player.duration < 600) { + $(currentTimeDisplay).html(player.formatTime(position).substr(1)); + $(durationDisplay).html(player.formatTime(duration).substr(1)); + } else { + $(currentTimeDisplay).html(player.formatTime(position)); + $(durationDisplay).html(player.formatTime(duration)); + } + } + + var handlerLeft = (progressHolder.width() - progressHandler.width()) * percent / 100; + progressHandler.css({ + left: handlerLeft + 'px' + }); + playProgress.css({ + width: percent + "%" + }); + }); + player.on('play', function (position, duration) { + $(playButton).removeClass('vjs-play'); + $(playButton).addClass('vjs-pause vjs-playing'); + }); + player.on('pause', function (position, duration) { + $(playButton).removeClass('vjs-pause vjs-playing'); + $(playButton).addClass('vjs-play'); + }); + player.on('ended', function (position, duration) { + $(playButton).removeClass('vjs-pause vjs-playing'); + $(playButton).addClass('vjs-play'); + }); + + function togglePlay() { + if (player.playing) { + player.pause(); + } else { + player.play(); + } + } + + playButton.on({ + 'mousedown touchstart': function mousedownTouchstart() { + $(this).addClass('pressed'); + }, + 'mouseup touchend': function mouseupTouchend() { + $(this).removeClass('pressed'); + }, + 'click': function click() { + togglePlay(); + }, + 'keypress': function keypress(e) { + if (e.which == 13) { + var that = this; + togglePlay(); + setTimeout(function () { + $(that).focus(); + }, 20); + } + } + }); + $(progressHolder).on('mousedown touchstart', function (event) { + player.seek(getSeekPosition(event)); + $('body').on({ + 'mousemove touchmove': onMouseMove, + 'mouseup touchend': onMouseUp + }); + + function onMouseMove(event) { + player.seek(getSeekPosition(event)); + } + + function onMouseUp() { + $('body').off({ + 'mousemove touchmove': onMouseMove, + 'mouseup touchend': onMouseUp + }); + } + + function getSeekPosition(event) { + var position = getPointerPageX(event) - $(progressHolder).offset().left; + var fraction = position / $(progressHolder).width(); + return Math.min(Math.max(fraction, 0), 1) * player.duration; + } + + function getPointerPageX(event) { + if (event.originalEvent.changedTouches) { + return event.originalEvent.changedTouches[0].pageX; + } else { + return event.pageX; + } + } + }); + } + }); + + (function ($) { + var SPACE_KEY = 32; + $.widget('pageflow.scrollButton', { + _create: function _create() { + var element = this.element; + var scroller = this.options.scroller; + var direction = this.options.direction; + scroller.on('scrollEnd', function () { + updateVisibility(); + }); + this.element.on('click', function () {}); + + if (this.options.page) { + element.on({ + click: function click() { + changePage(); + element.blur(); + return false; + }, + keypress: function keypress(e) { + if (e.which == SPACE_KEY) { + changePage(); + } + }, + touchstart: function touchstart() { + changePage(); + } + }); + } + + updateVisibility(); + + function updateVisibility() { + element.toggle(!atBoundary()); + } + + function changePage() { + if (direction === 'top' || direction === 'left') { + scroller.prev(); + } else if (direction === 'down' || direction === 'right') { + scroller.next(); + } + } + + function atBoundary() { + if (direction === 'top') { + return scroller.y >= 0; + } else if (direction === 'left') { + return scroller.x >= 0; + } else if (direction === 'down') { + return scroller.y <= scroller.maxScrollY; + } else { + return scroller.x <= scroller.maxScrollX; + } + } + } + }); + })($); + + (function ($) { + $.widget('pageflow.shareMenu', { + _create: function _create() { + var $element = this.element, + options = this.options, + $links = options.links || $('a', $element), + $subMenu = options.subMenu || $($element.find('.sub_share')), + $subLinks = $('a', $subMenu), + $closeOnMouseLeaving = options.closeOnMouseLeaving, + scroller = options.scroller; + $links.on('click', function (event) { + var $this = $(this), + $a = $this.find('a').length ? $this.find('a') : $this, + active = $a.hasClass('active'); + + if ($a.data('share-page')) { + $links.removeClass('active'); + $a.addClass('active'); + event.preventDefault(); + var $currentPage = state$1.slides.currentPage(), + id = $currentPage.attr('id') || $currentPage.data('perma-id'), + siteShareUrl = $a.data('share-page').replace(/permaId$/, id), + $insertAfter = options.insertAfter || $a; + $($subLinks[0]).attr('href', $a.attr('href')); + $($subLinks[1]).attr('href', siteShareUrl); + + if (!$insertAfter.next().hasClass('sub_share')) { + $insertAfter.after($subMenu); + } + + if (active) { + $subMenu.toggle(); + $a.toggleClass('active'); + } else { + $subMenu.show(); + $links.find('.button').removeClass('pressed'); + $(this).find('.button').addClass('pressed'); + } + + if (scroller) { + scroller.refresh(); + } + } + }); + + if ($closeOnMouseLeaving) { + $closeOnMouseLeaving.on('mouseleave', function () { + $links.removeClass('active').blur(); + $(this).find('.button').removeClass('pressed'); + $subMenu.hide(); + }); + } + } + }); + })($); + + (function ($) { + $.widget('pageflow.skipPageButton', { + _create: function _create() { + this.element.on('click', function () { + state$1.slides.next(); + }); + events.on('page:change page:update', this.update, this); + this.update(); + }, + _destroy: function _destroy() { + events.off(null, this.update); + }, + update: function update() { + if (state$1.slides) { + this.element.toggleClass('enabled', !!state$1.slides.nextPageExists()); + } + } + }); + })($); + + $.widget('pageflow.thirdPartyEmbedConsent', { + _create: function _create() { + var element = this.element; + var vendorName = this.element.find('[data-consent-vendor]').data('consentVendor'); + + if (!vendorName) { + return; + } + + consent.requireAccepted(vendorName).then(function (result) { + if (result === 'fulfilled') { + element.addClass('consent_given'); + } + }); + + this._on(this.element, { + 'click .third_party_embed_opt_in-button': function clickThird_party_embed_opt_inButton() { + consent.accept(vendorName); + } + }); + } + }); + + (function ($) { + $.widget('pageflow.topButton', { + _create: function _create() { + var element = this.element; + element.click(function (event) { + state$1.slides.goToLandingPage(); + event.preventDefault(); + }); + state$1.slides.on('pageactivate', function (e, ui) { + toggle(); + }); + toggle(state$1.slides.currentPage()); + + function toggle() { + var onLandingPage = state$1.slides.isOnLandingPage(); + element.toggleClass('deactivated', onLandingPage); + element.attr('tabindex', onLandingPage ? '-1' : '2'); + } + } + }); + })($); + + (function ($) { + $.widget('pageflow.volumeSlider', { + _create: function _create() { + var element = this.element; + var orientation = this.options.orientation; + var slider = $('.volume-slider', element); + element.on('mousedown', function (event) { + var parent = $('body'); + parent.on('mousemove.volumeSlider', changeVolume); + element.addClass('lock_showing'); + parent.on('mouseup.volumeSlider', function () { + parent.off('mousemove.volumeSlider mouseup.volumeSlider'); + element.removeClass('lock_showing'); + }); + changeVolume(event); + + function changeVolume(event) { + var volume; + + if (orientation === 'v') { + volume = 1 - (event.pageY - slider.offset().top) / slider.height(); + } else { + volume = (event.pageX - slider.offset().left) / slider.width(); + } + + settings.set('volume', Math.min(1, Math.max(0, volume))); + } + }); + settings.on('change:volume', this.update, this); + this.update(); + }, + _destroy: function _destroy() { + settings.off('change:volume', this.update); + }, + update: function update() { + var volume = settings.get('volume'); + + if (this.options.orientation === 'v') { + $('.volume-level', this.element).css({ + height: volume * 100 + '%' + }); + $('.volume-handle', this.element).css({ + bottom: volume * 100 + '%', + top: 'initial' + }); + } else { + $('.volume-level', this.element).css({ + width: volume * 100 + '%' + }); + $('.volume-handle', this.element).css({ + left: volume * 100 + '%' + }); + } + + this.element.toggleClass('volume-high', volume > 2 / 3); + this.element.toggleClass('volume-medium', volume >= 1 / 3 && volume <= 2 / 3); + this.element.toggleClass('volume-low', volume < 1 / 3 && volume > 0); + this.element.toggleClass('volume-mute', volume === 0); + } + }); + })($); + + var phoneLandscapeFullscreen = function phoneLandscapeFullscreen() { + if (window.screen.orientation) { + if (browser.has('phone platform') && !browser.has('iphone platform')) { + window.screen.orientation.onchange = function () { + if (isLandscape()) { + requestFullscreen(document.body); + } + }; + } + } + + function isLandscape() { + return window.orientation == 90 || window.orientation == -90; + } + + function requestFullscreen(el) { + if (el.requestFullscreen) { + el.requestFullscreen(); + } else if (el.mozRequestFullScreen) { + el.mozRequestFullScreen(); + } else if (el.webkitRequestFullscreen) { + el.webkitRequestFullscreen(); + } else if (el.webkitEnterFullscreen) { + el.webkitEnterFullscreen(); + } + } + }; + + var widgetTypes = function () { + var registry = {}; + var base = { + enhance: function enhance(element) {} + }; + return { + register: function register(name, widgetType) { + registry[name] = _.extend({}, base, widgetType); + }, + enhance: function enhance(container) { + function enhance(element) { + var typeName = $(element).data('widget'); + + if (registry[typeName]) { + registry[typeName].enhance(element); + } + } + + container.find('[data-widget]').each(function () { + enhance($(this)); + }); + } + }; + }(); + + widgetTypes.register('default_navigation', { + enhance: function enhance(element) { + element.navigation(); + } + }); + widgetTypes.register('default_mobile_navigation', { + enhance: function enhance(element) { + element.navigationMobile(); + } + }); + + var pagePreloaded = new $.Deferred(function (pagePreloadedDeferred) { + $(document).one('pagepreloaded', pagePreloadedDeferred.resolve); + }).promise(); + + window.onload = function () { + browser.detectFeatures().then(function () { + var slideshow = $('[data-role=slideshow]'); + var body = $('body'); + Visited.setup(); + pagePreloaded.then(function () { + readyDeferred.resolve(); + events.trigger('ready'); + }); + slideshow.each(function () { + events.trigger('seed:loaded'); + state$1.entryData = new SeedEntryData(state$1.seed); + Audio.setup({ + audioFiles: state$1.audioFiles + }); + Slideshow.setup({ + element: $(this), + pages: state$1.pages, + enabledFeatureNames: state$1.enabledFeatureNames, + beforeFirstUpdate: function beforeFirstUpdate() { + $('.header').header({ + slideshow: state$1.slides + }); + $('.overview').overview(); + $('.multimedia_alert').multimediaAlert(); + pageType.invokeInitializers(state$1.pages); + widgetTypes.enhance(body); + delayedStart.perform(); + phoneLandscapeFullscreen(); + } + }); + consent.closeVendorRegistration(); + }); + links.setup(); + FocusOutline.setup(body); + nativeScrolling.preventScrollingOnEmbed(slideshow); + }); + }; + + /** + * A promise that is resolved once the document is printed. + */ + // old promise not working properly, resolved in print-mode + + /* return new $.Deferred(function(deferred) { + if (window.matchMedia) { + var mediaQueryList = window.matchMedia('print'); + mediaQueryList.addListener(function(mql) { + if (mql.matches) { + deferred.resolve(); + } + }); + } + + window.onbeforeprint = deferred.resolve; + }); + }; */ + + $(function ($) { + // now bound to pageflow.ready + ready.then(function () { + $('img.print_image').each(function () { + var img = $(this); + + if (img.data('src')) { + img.attr('src', img.data('printsrc')); + } + }); + }); + }); + + var Lazy = function Lazy(template, options) { + var placeholder = $(''), + that = this, + readyCallbacks = new $.Callbacks(), + disposeTimeout, + videoTag, + videoPlayer, + html; + saveHtml(template); + template.before(placeholder); + + this.ensureCreated = function () { + if (disposeTimeout) { + clearTimeout(disposeTimeout); + disposeTimeout = null; + } + + if (!videoTag) { + videoTag = createVideoTag(); + placeholder.replaceWith(videoTag); + videoPlayer = new VideoPlayer(videoTag[0], options); + videoPlayer.ready(readyCallbacks.fire); + } + }; + + this.isPresent = function () { + return videoTag && !disposeTimeout; + }; + + this.dispose = function () { + if (videoTag && !browser.has('mobile platform')) { + this.setEmptySrc(); + $(videoPlayer.el()).replaceWith(placeholder); + videoPlayer.dispose(); + videoPlayer = null; + videoTag = null; + } + }; + + this.setEmptySrc = function () { + videoPlayer.src([{ + type: 'video/webm', + src: assetUrls.emptyWebm + }, { + type: 'video/mp4', + src: assetUrls.emptyMp4 + }]); + }; + + this.scheduleDispose = function () { + if (!disposeTimeout) { + disposeTimeout = setTimeout(function () { + that.dispose(); + }, 5 * 1000); + } + }; // proxied methods + + + this.ready = function (callback) { + readyCallbacks.add(callback); + }; + + this.paused = function () { + return videoPlayer && videoPlayer.paused(); + }; + + this.volume = function () { + if (!videoPlayer) { + return 0; + } + + return videoPlayer.volume.apply(videoPlayer, arguments); + }; + + this.showPosterImage = function () { + return videoPlayer && videoPlayer.posterImage.show(); + }; + + this.hidePosterImage = function () { + return videoPlayer && videoPlayer.posterImage.unlockShowing(); + }; + + _.each(['play', 'playAndFadeIn', 'pause', 'fadeOutAndPause', 'prebuffer', 'src', 'on', 'load', 'currentTime', 'muted'], function (method) { + that[method] = function () { + var args = arguments; + + if (!videoPlayer) { + log('Video Player not yet initialized. (' + method + ')', { + force: true + }); + return; + } + + return new $.Deferred(function (deferred) { + videoPlayer.ready(function () { + $.when(videoPlayer[method].apply(videoPlayer, args)).then(deferred.resolve); + }); + }); + }; + }); + + function saveHtml(template) { + html = template.html(); + } + + function createVideoTag() { + var htmlWithPreload = html.replace(/preload="[a-z]*"/, 'preload="auto"'); + htmlWithPreload = htmlWithPreload.replace(/src="([^"]*)"/g, 'src="$1&t=' + new Date().getTime() + '"'); + var element = $(htmlWithPreload); + + if (browser.has('mobile platform') && element.attr('data-mobile-poster')) { + element.attr('poster', element.attr('data-mobile-poster')); + } else if (browser.has('high bandwidth') && !browser.has('mobile platform')) { + element.attr('poster', element.attr('data-large-poster')); + element.find('source').each(function () { + $(this).attr('src', $(this).attr('data-high-src')); + }); + } else { + element.attr('poster', element.attr('data-poster')); + } + + return element; + } + }; + + if (VideoJS.Html5DashJS) { + VideoJS.Html5DashJS.hook('beforeinitialize', function (player, mediaPlayer) { + mediaPlayer.getDebug().setLogToBrowserConsole(false); + }); + } + + VideoPlayer.Lazy = Lazy; + + exports.AdjacentPreloader = AdjacentPreloader; + exports.Atmo = Atmo; + exports.Audio = Audio; + exports.AudioPlayer = AudioPlayer; + exports.ChapterFilter = ChapterFilter; + exports.Consent = Consent; + exports.DelayedStart = DelayedStart; + exports.DomOrderScrollNavigator = DomOrderScrollNavigator; + exports.EntryData = EntryData; + exports.Features = Features; + exports.FocusOutline = FocusOutline; + exports.HighlightedPage = HighlightedPage; + exports.History = History; + exports.MediaPool = MediaPool; + exports.MediaType = MediaType; + exports.MultiPlayer = MultiPlayer; + exports.Object = BaseObject; + exports.PageNavigationListAnimation = PageNavigationListAnimation; + exports.PageTransitions = PageTransitions; + exports.PlayerSourceIDMap = PlayerSourceIDMap; + exports.ScrollIndicator = ScrollIndicator; + exports.SeedEntryData = SeedEntryData; + exports.Slideshow = Slideshow; + exports.SuccessorPreparer = SuccessorPreparer; + exports.VideoPlayer = VideoPlayer; + exports.Visited = Visited; + exports.assetUrls = assetUrls; + exports.audioContext = audioContext; + exports.backgroundMedia = backgroundMedia; + exports.bandwidth = bandwidth; + exports.blankSources = blankSources; + exports.browser = browser; + exports.commonPageCssClasses = commonPageCssClasses; + exports.consent = consent; + exports.cookieNotice = cookieNotice; + exports.cookies = cookies; + exports.debugMode = debugMode; + exports.defaultPageContent = defaultPageContent; + exports.delayedStart = delayedStart; + exports.documentHiddenState = documentHiddenState; + exports.events = events; + exports.features = features; + exports.fullscreen = fullscreen; + exports.hideText = hideText; + exports.infoBox = infoBox; + exports.links = links; + exports.log = log; + exports.manualStart = manualStart; + exports.media = media; + exports.mediaPlayer = mediaPlayer; + exports.multimediaAlert = multimediaAlert; + exports.nativeScrolling = nativeScrolling; + exports.navigationDirection = navigationDirection; + exports.pageSplitLayout = pageSplitLayout; + exports.pageTransitions = pageTransitions; + exports.pageType = pageType; + exports.phoneLandscapeFullscreen = phoneLandscapeFullscreen; + exports.preload = preload; + exports.ready = ready; + exports.readyDeferred = readyDeferred; + exports.setItemCookieString = setItemCookieString; + exports.settings = settings; + exports.theme = theme; + exports.videoHelpers = videoHelpers; + exports.volumeFade = volumeFade; + exports.widgetTypes = widgetTypes; + exports.widgets = widgets; + + return exports; + +}({}, jQuery, jQuery, Backbone, _, videojs, IScroll)); diff --git a/entry_types/paged/app/assets/javascripts/pageflow_paged/dist/react-client.js b/entry_types/paged/app/assets/javascripts/pageflow_paged/dist/react-client.js new file mode 100644 index 0000000000..06ec677524 --- /dev/null +++ b/entry_types/paged/app/assets/javascripts/pageflow_paged/dist/react-client.js @@ -0,0 +1,22 @@ +pageflow="object"==typeof pageflow?pageflow:{},pageflow.react=function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=407)}([function(e,t){e.exports=React},function(e,t,n){var r=n(8),o=n(22).f,i=n(21),a=n(20),s=n(99),c=n(125),u=n(65);e.exports=function(e,t){var n,l,f,p,d,h=e.target,v=e.global,g=e.stat;if(n=v?r:g?r[h]||s(h,{}):(r[h]||{}).prototype)for(l in t){if(p=t[l],f=e.noTargetGet?(d=o(n,l))&&d.value:n[l],!u(v?l:h+(g?".":"#")+l,e.forced)&&void 0!==f){if(typeof p==typeof f)continue;c(p,f)}(e.sham||f&&f.sham)&&i(p,"sham",!0),a(n,l,p,e)}}},function(e,t,n){e.exports=n(393)},function(e,t,n){e.exports=n(165)},function(e,t,n){"use strict";function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}n.d(t,"a",(function(){return r}))},function(e,t,n){var r; +/*! + Copyright (c) 2017 Jed Watson. + Licensed under the MIT License (MIT), see + http://jedwatson.github.io/classnames +*/!function(){"use strict";var n={}.hasOwnProperty;function o(){for(var e=[],t=0;t0?o(r(e),9007199254740991):0}},function(e,t,n){var r=n(12),o=n(122),i=n(10),a=n(34),s=Object.defineProperty;t.f=r?s:function(e,t,n){if(i(e),t=a(t,!0),i(n),o)try{return s(e,t,n)}catch(e){}if("get"in n||"set"in n)throw TypeError("Accessors not supported");return"value"in n&&(e[t]=n.value),e}},function(e,t,n){var r=n(23);e.exports=function(e){return Object(r(e))}},function(e,t,n){"use strict";var r=n(171),o="object"==typeof self&&self&&self.Object===Object&&self,i=(r.a||o||Function("return this")()).Symbol,a=Object.prototype,s=a.hasOwnProperty,c=a.toString,u=i?i.toStringTag:void 0;var l=function(e){var t=s.call(e,u),n=e[u];try{e[u]=void 0;var r=!0}catch(e){}var o=c.call(e);return r&&(t?e[u]=n:delete e[u]),o},f=Object.prototype.toString;var p=function(e){return f.call(e)},d="[object Null]",h="[object Undefined]",v=i?i.toStringTag:void 0;var g=function(e){return null==e?void 0===e?h:d:v&&v in Object(e)?l(e):p(e)};var y=function(e,t){return function(n){return e(t(n))}}(Object.getPrototypeOf,Object);var m=function(e){return null!=e&&"object"==typeof e},b="[object Object]",w=Function.prototype,E=Object.prototype,O=w.toString,x=E.hasOwnProperty,P=O.call(Object);var k=function(e){if(!m(e)||g(e)!=b)return!1;var t=y(e);if(null===t)return!0;var n=x.call(t,"constructor")&&t.constructor;return"function"==typeof n&&n instanceof n&&O.call(n)==P},_=n(121),S={INIT:"@@redux/INIT"};function T(e,t,n){var r;if("function"==typeof t&&void 0===n&&(n=t,t=void 0),void 0!==n){if("function"!=typeof n)throw new Error("Expected the enhancer to be a function.");return n(T)(e,t)}if("function"!=typeof e)throw new Error("Expected the reducer to be a function.");var o=e,i=t,a=[],s=a,c=!1;function u(){s===a&&(s=a.slice())}function l(){return i}function f(e){if("function"!=typeof e)throw new Error("Expected listener to be a function.");var t=!0;return u(),s.push(e),function(){if(t){t=!1,u();var n=s.indexOf(e);s.splice(n,1)}}}function p(e){if(!k(e))throw new Error("Actions must be plain objects. Use custom middleware for async actions.");if(void 0===e.type)throw new Error('Actions may not have an undefined "type" property. Have you misspelled a constant?');if(c)throw new Error("Reducers may not dispatch actions.");try{c=!0,i=o(i,e)}finally{c=!1}for(var t=a=s,n=0;n0&&void 0!==arguments[0]?arguments[0]:{},t=arguments[1];if(a)throw a;for(var r=!1,o={},s=0;sx;x++)if((p||x in w)&&(m=E(y=w[x],x,b),e))if(t)k[x]=m;else if(m)switch(e){case 3:return!0;case 5:return y;case 6:return x;case 2:c.call(k,y)}else if(l)return!1;return f?-1:u||l?l:k}};e.exports={forEach:u(0),map:u(1),filter:u(2),some:u(3),every:u(4),find:u(5),findIndex:u(6)}},function(e,t,n){var r=n(8),o=n(55),i=n(21),a=n(18),s=n(99),c=n(123),u=n(29),l=u.get,f=u.enforce,p=String(c).split("toString");o("inspectSource",(function(e){return c.call(e)})),(e.exports=function(e,t,n,o){var c=!!o&&!!o.unsafe,u=!!o&&!!o.enumerable,l=!!o&&!!o.noTargetGet;"function"==typeof n&&("string"!=typeof t||a(n,"name")||i(n,"name",t),f(n).source=p.join("string"==typeof t?t:"")),e!==r?(c?!l&&e[t]&&(u=!0):delete e[t],u?e[t]=n:i(e,t,n)):u?e[t]=n:s(t,n)})(Function.prototype,"toString",(function(){return"function"==typeof this&&l(this).source||c.call(this)}))},function(e,t,n){var r=n(12),o=n(15),i=n(43);e.exports=r?function(e,t,n){return o.f(e,t,i(1,n))}:function(e,t,n){return e[t]=n,e}},function(e,t,n){var r=n(12),o=n(75),i=n(43),a=n(24),s=n(34),c=n(18),u=n(122),l=Object.getOwnPropertyDescriptor;t.f=r?l:function(e,t){if(e=a(e),t=s(t,!0),u)try{return l(e,t)}catch(e){}if(c(e,t))return i(!o.f.call(e,t),e[t])}},function(e,t){e.exports=function(e){if(null==e)throw TypeError("Can't call method on "+e);return e}},function(e,t,n){var r=n(61),o=n(23);e.exports=function(e){return r(o(e))}},function(e,t,n){var r=n(126),o=n(18),i=n(131),a=n(15).f;e.exports=function(e){var t=r.Symbol||(r.Symbol={});o(t,e)||a(t,e,{value:i.f(e)})}},function(e,t,n){var r=n(23),o=/"/g;e.exports=function(e,t,n,i){var a=String(r(e)),s="<"+t;return""!==n&&(s+=" "+n+'="'+String(i).replace(o,""")+'"'),s+">"+a+""}},function(e,t,n){var r=n(6);e.exports=function(e){return r((function(){var t=""[e]('"');return t!==t.toLowerCase()||t.split('"').length>3}))}},function(e,t,n){e.exports=n(398)()},function(e,t,n){var r,o,i,a=n(124),s=n(8),c=n(9),u=n(21),l=n(18),f=n(76),p=n(63),d=s.WeakMap;if(a){var h=new d,v=h.get,g=h.has,y=h.set;r=function(e,t){return y.call(h,e,t),t},o=function(e){return v.call(h,e)||{}},i=function(e){return g.call(h,e)}}else{var m=f("state");p[m]=!0,r=function(e,t){return u(e,m,t),t},o=function(e){return l(e,m)?e[m]:{}},i=function(e){return l(e,m)}}e.exports={set:r,get:o,has:i,enforce:function(e){return i(e)?o(e):r(e,{})},getterFor:function(e){return function(t){var n;if(!c(t)||(n=o(t)).type!==e)throw TypeError("Incompatible receiver, "+e+" required");return n}}}},function(e,t){var n=Math.ceil,r=Math.floor;e.exports=function(e){return isNaN(e=+e)?0:(e>0?r:n)(e)}},function(e,t){e.exports=function(e){if("function"!=typeof e)throw TypeError(String(e)+" is not a function");return e}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var r,o=n(401),i=(r=o)&&r.__esModule?r:{default:r};t.default=i.default,e.exports=t.default},function(e,t){var n={}.toString;e.exports=function(e){return n.call(e).slice(8,-1)}},function(e,t,n){var r=n(9);e.exports=function(e,t){if(!r(e))return e;var n,o;if(t&&"function"==typeof(n=e.toString)&&!r(o=n.call(e)))return o;if("function"==typeof(n=e.valueOf)&&!r(o=n.call(e)))return o;if(!t&&"function"==typeof(n=e.toString)&&!r(o=n.call(e)))return o;throw TypeError("Can't convert object to primitive value")}},function(e,t,n){var r=n(126),o=n(8),i=function(e){return"function"==typeof e?e:void 0};e.exports=function(e,t){return arguments.length<2?i(r[e])||i(o[e]):r[e]&&r[e][t]||o[e]&&o[e][t]}},function(e,t,n){var r=n(15).f,o=n(18),i=n(13)("toStringTag");e.exports=function(e,t,n){e&&!o(e=n?e:e.prototype,i)&&r(e,i,{configurable:!0,value:t})}},function(e,t,n){var r=n(18),o=n(16),i=n(76),a=n(108),s=i("IE_PROTO"),c=Object.prototype;e.exports=a?Object.getPrototypeOf:function(e){return e=o(e),r(e,s)?e[s]:"function"==typeof e.constructor&&e instanceof e.constructor?e.constructor.prototype:e instanceof Object?c:null}},function(e,t,n){var r=n(10),o=n(103),i=n(101),a=n(63),s=n(129),c=n(98),u=n(76)("IE_PROTO"),l=function(){},f=function(){var e,t=c("iframe"),n=i.length;for(t.style.display="none",s.appendChild(t),t.src=String("javascript:"),(e=t.contentWindow.document).open(),e.write("