pageflow 15.1.0.beta6 → 15.1.0.rc0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of pageflow might be problematic. Click here for more details.

Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +17 -0
  3. data/app/assets/javascripts/pageflow/dist/editor.js +613 -94
  4. data/app/assets/javascripts/pageflow/dist/ui.js +120 -3
  5. data/app/assets/stylesheets/pageflow/editor/base.scss +1 -0
  6. data/app/assets/stylesheets/pageflow/editor/composables.scss +9 -0
  7. data/app/assets/stylesheets/pageflow/editor/file_import.scss +7 -8
  8. data/app/helpers/pageflow/config_helper.rb +1 -1
  9. data/app/helpers/pageflow/entries_helper.rb +6 -1
  10. data/app/helpers/pageflow/social_share_links_helper.rb +5 -1
  11. data/config/locales/de.yml +34 -16
  12. data/config/locales/en.yml +34 -16
  13. data/entry_types/paged/app/assets/javascripts/pageflow_paged/dist/editor.js +613 -93
  14. data/entry_types/paged/app/views/layouts/pageflow_paged/application.html.erb +2 -1
  15. data/entry_types/paged/lib/pageflow_paged/engine.rb +1 -0
  16. data/entry_types/scrolled/app/controllers/pageflow_scrolled/editor/chapters_controller.rb +9 -1
  17. data/entry_types/scrolled/app/helpers/pageflow_scrolled/entry_json_seed_helper.rb +2 -0
  18. data/entry_types/scrolled/app/views/pageflow_scrolled/entry_json_seed/_entry.json.jbuilder +28 -0
  19. data/entry_types/scrolled/config/locales/new/de.yml +46 -0
  20. data/entry_types/scrolled/config/locales/new/en.yml +46 -0
  21. data/entry_types/scrolled/lib/pageflow_scrolled/engine.rb +1 -0
  22. data/entry_types/scrolled/package/editor.js +2844 -78
  23. data/entry_types/scrolled/package/frontend.js +955 -443
  24. data/entry_types/scrolled/package/package.json +1 -0
  25. data/lib/pageflow/version.rb +1 -1
  26. data/packages/pageflow/editor.js +485 -90
  27. data/packages/pageflow/ui.js +120 -3
  28. metadata +5 -4
  29. data/config/locales/new/entry_metadata_configuration.de.yml +0 -17
  30. data/config/locales/new/entry_metadata_configuration.en.yml +0 -17
@@ -33,6 +33,7 @@
33
33
  "eslint-plugin-react-hooks": "1.x",
34
34
  "jest": "^24.9.0",
35
35
  "jest-css-modules-transform": "^3.1.0",
36
+ "jest-svg-transformer": "^1.0.0",
36
37
  "react-test-renderer": "^16.9.0"
37
38
  },
38
39
  "scripts": {
@@ -1,3 +1,3 @@
1
1
  module Pageflow
2
- VERSION = '15.1.0.beta6'.freeze
2
+ VERSION = '15.1.0.rc0'.freeze
3
3
  end
@@ -725,6 +725,62 @@ _$1.each(['each', 'map', 'reduce', 'first', 'find', 'pluck'], function (method)
725
725
  };
726
726
  });
727
727
 
728
+ // different model types. Backbone.Collection tries to merge records
729
+ // if they have the same id.
730
+
731
+ var MultiCollection = function MultiCollection() {
732
+ this.records = {};
733
+ this.length = 0;
734
+ };
735
+
736
+ _$1.extend(MultiCollection.prototype, {
737
+ add: function add(record) {
738
+ if (!this.records[record.cid]) {
739
+ this.records[record.cid] = record;
740
+ this.length = _$1.keys(this.records).length;
741
+ this.trigger('add', record);
742
+ }
743
+ },
744
+ remove: function remove(record) {
745
+ if (this.records[record.cid]) {
746
+ delete this.records[record.cid];
747
+ this.length = _$1.keys(this.records).length;
748
+ this.trigger('remove', record);
749
+ }
750
+ },
751
+ isEmpty: function isEmpty() {
752
+ return this.length === 0;
753
+ }
754
+ });
755
+
756
+ _$1.extend(MultiCollection.prototype, Backbone.Events);
757
+
758
+ MultiCollection.extend = Backbone.Collection.extend;
759
+
760
+ /**
761
+ * Watch Backbone collections to track which models are currently
762
+ * being saved. Used to update the notifications view displaying
763
+ * saving status/failutes.
764
+ */
765
+
766
+ var SavingRecordsCollection = MultiCollection.extend({
767
+ /**
768
+ * Listen to events of models in collection to track when they are
769
+ * being saved.
770
+ *
771
+ * @param {Backbone.Collection} collection - Collection to watch.
772
+ */
773
+ watch: function watch(collection) {
774
+ var that = this;
775
+ this.listenTo(collection, 'request', function (model, xhr) {
776
+ that.add(model);
777
+ xhr.always(function () {
778
+ that.remove(model);
779
+ });
780
+ });
781
+ }
782
+ });
783
+
728
784
  var WidgetType = Object$1.extend({
729
785
  initialize: function initialize(serverSideConfig, clientSideConfig) {
730
786
  this.name = serverSideConfig.name;
@@ -814,6 +870,15 @@ var EditorApi = Object$1.extend(
814
870
  */
815
871
 
816
872
  this.failures = new FailuresAPI();
873
+ /**
874
+ * Tracking records that are currently being saved.
875
+ *
876
+ * @returns {SavingRecordsCollection}
877
+ * @memberof editor
878
+ * @since edge
879
+ */
880
+
881
+ this.savingRecords = new SavingRecordsCollection();
817
882
  /**
818
883
  * Set up editor integration for page types.
819
884
  * @memberof editor
@@ -860,7 +925,9 @@ var EditorApi = Object$1.extend(
860
925
  * Backbone view that will be rendered in the side bar.
861
926
  */
862
927
  registerEntryType: function registerEntryType(name, options) {
863
- this.entryType = options;
928
+ this.entryType = _objectSpread({
929
+ name: name
930
+ }, options);
864
931
  },
865
932
  createEntryModel: function createEntryModel(seed, options) {
866
933
  var entry = new this.entryType.entryModel(seed.entry, options);
@@ -1074,6 +1141,79 @@ var startEditor = function startEditor(options) {
1074
1141
  });
1075
1142
  };
1076
1143
 
1144
+ /**
1145
+ * Mixins for Backbone models and collections that use entry type
1146
+ * specific editor controllers registered via the `editor_app` entry
1147
+ * type option.
1148
+ */
1149
+
1150
+ var entryTypeEditorControllerUrls = {
1151
+ /**
1152
+ * Mixins for Backbone collections that defines `url` method.
1153
+ *
1154
+ * @param {Object} options
1155
+ * @param {String} options.resources - Path suffix of the controller route
1156
+ *
1157
+ * @example
1158
+ *
1159
+ * import {editor, entryTypeEditorControllerUrls} from 'pageflow/editor';
1160
+ *
1161
+ * editor.registerEntryType('test', {
1162
+ // ...
1163
+ });
1164
+ *
1165
+ * export const ItemsCollection = Backbone.Collection.extend({
1166
+ * mixins: [entryTypeEditorControllerUrls.forCollection({resources: 'items'})
1167
+ * });
1168
+ *
1169
+ * new ItemsCollection().url() // => '/editor/entries/10/test/items'
1170
+ */
1171
+ forCollection: function forCollection(_ref) {
1172
+ var resources = _ref.resources;
1173
+ return {
1174
+ url: function url() {
1175
+ return entryTypeEditorControllerUrl(resources);
1176
+ },
1177
+ urlSuffix: function urlSuffix() {
1178
+ return "/".concat(resources);
1179
+ }
1180
+ };
1181
+ },
1182
+
1183
+ /**
1184
+ * Mixins for Backbone models that defines `urlRoot` method.
1185
+ *
1186
+ * @param {Object} options
1187
+ * @param {String} options.resources - Path suffix of the controller route
1188
+ *
1189
+ * @example
1190
+ *
1191
+ * import {editor, entryTypeEditorControllerUrls} from 'pageflow/editor';
1192
+ *
1193
+ * editor.registerEntryType('test', {
1194
+ // ...
1195
+ });
1196
+ *
1197
+ * export const Item = Backbone.Model.extend({
1198
+ * mixins: [entryTypeEditorControllerUrls.forModel({resources: 'items'})
1199
+ * });
1200
+ *
1201
+ * new Item({id: 20}).url() // => '/editor/entries/10/test/items/20'
1202
+ */
1203
+ forModel: function forModel(_ref2) {
1204
+ var resources = _ref2.resources;
1205
+ return {
1206
+ urlRoot: function urlRoot() {
1207
+ return this.isNew() ? this.collection.url() : entryTypeEditorControllerUrl(resources);
1208
+ }
1209
+ };
1210
+ }
1211
+ };
1212
+
1213
+ function entryTypeEditorControllerUrl(resources) {
1214
+ return [state.entry.url(), editor$1.entryType.name, resources].join('/');
1215
+ }
1216
+
1077
1217
  var formDataUtils = {
1078
1218
  fromModel: function fromModel(model) {
1079
1219
  var object = {};
@@ -1181,7 +1321,7 @@ var SubsetCollection = Backbone.Collection.extend({
1181
1321
  this.reset();
1182
1322
  },
1183
1323
  url: function url() {
1184
- return this.parentModel.url() + _$1.result(this.parent, 'url');
1324
+ return this.parentModel.url() + (_$1.result(this.parent, 'urlSuffix') || _$1.result(this.parent, 'url'));
1185
1325
  },
1186
1326
  dispose: function dispose() {
1187
1327
  this.stopListening();
@@ -1490,10 +1630,23 @@ app.on('mixin:configuration', function (mixin) {
1490
1630
  Cocktail.mixin(Configuration, mixin);
1491
1631
  });
1492
1632
 
1633
+ /**
1634
+ * Remove model from collection only after the `DELETE` request has
1635
+ * succeeded. Still allow tracking that the model is being destroyed
1636
+ * by triggering a `destroying` event and adding a `isDestroying`
1637
+ * method.
1638
+ */
1639
+
1493
1640
  var delayedDestroying = {
1494
1641
  initialize: function initialize() {
1495
1642
  this._destroying = false;
1643
+ this._destroyed = false;
1496
1644
  },
1645
+
1646
+ /**
1647
+ * Trigger `destroying` event and send `DELETE` request. Only remove
1648
+ * model from collection once the request is done.
1649
+ */
1497
1650
  destroyWithDelay: function destroyWithDelay() {
1498
1651
  var model = this;
1499
1652
  this._destroying = true;
@@ -1502,17 +1655,33 @@ var delayedDestroying = {
1502
1655
  wait: true,
1503
1656
  success: function success() {
1504
1657
  model._destroying = false;
1658
+ model._destroyed = true;
1505
1659
  },
1506
1660
  error: function error() {
1507
1661
  model._destroying = false;
1508
1662
  }
1509
1663
  });
1510
1664
  },
1665
+
1666
+ /**
1667
+ * Get whether the model is currently being destroyed.
1668
+ */
1511
1669
  isDestroying: function isDestroying() {
1512
1670
  return this._destroying;
1671
+ },
1672
+
1673
+ /**
1674
+ * Get whether the model has been destroyed.
1675
+ */
1676
+ isDestroyed: function isDestroyed() {
1677
+ return this._destroyed;
1513
1678
  }
1514
1679
  };
1515
1680
 
1681
+ /**
1682
+ * Mixin for Backbone models that shall be watched by {@link
1683
+ * modelLifecycleTrackingView} mixin.
1684
+ */
1516
1685
  var failureTracking = {
1517
1686
  initialize: function initialize() {
1518
1687
  this._saveFailed = false;
@@ -2971,6 +3140,76 @@ var PageLinkFileSelectionHandler = function PageLinkFileSelectionHandler(options
2971
3140
  };
2972
3141
  editor$1.registerFileSelectionHandler('pageLink', PageLinkFileSelectionHandler);
2973
3142
 
3143
+ /**
3144
+ * Mixins for models with a nested configuration model.
3145
+ *
3146
+ * Triggers events on the parent model of the form
3147
+ * `change:configuration` and `change:configuration:<attribute>`, when
3148
+ * the configuration changes.
3149
+ *
3150
+ * @param {Object} [options]
3151
+ * @param {Function} [options.configurationModel] -
3152
+ * Backbone model to use for nested configuration model.
3153
+ * @param {Boolean} [options.autoSave] -
3154
+ * Save model when configuration changes.
3155
+ * @param {Boolean|Array<String>} [options.includeAttributesInJSON] -
3156
+ * Include all or specific attributes of the parent model in the
3157
+ * data returned by `toJSON` besides the `configuration` property.
3158
+ * @returns {Object} - Mixin to be included in model.
3159
+ *
3160
+ * @example
3161
+ *
3162
+ * import {configurationContainer} from 'pageflow/editor';
3163
+ *
3164
+ * const Section = Backbone.Model.extend({
3165
+ * mixins: [configurationContainer({autoSave: true})]
3166
+ * });
3167
+ *
3168
+ * const section = new Section({configuration: {some: 'value'}});
3169
+ * section.configuration.get('some') // => 'value';
3170
+ */
3171
+
3172
+ function configurationContainer() {
3173
+ var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
3174
+ configurationModel = _ref.configurationModel,
3175
+ autoSave = _ref.autoSave,
3176
+ includeAttributesInJSON = _ref.includeAttributesInJSON;
3177
+
3178
+ configurationModel = configurationModel || Configuration.extend({
3179
+ defaults: {}
3180
+ });
3181
+ return {
3182
+ initialize: function initialize() {
3183
+ this.configuration = new configurationModel(this.get('configuration'));
3184
+ this.configuration.parent = this;
3185
+ this.listenTo(this.configuration, 'change', function () {
3186
+ if (!this.isNew() && (!this.isDestroying || !this.isDestroying()) && (!this.isDestroyed || !this.isDestroyed()) && autoSave) {
3187
+ this.save();
3188
+ }
3189
+
3190
+ this.trigger('change:configuration', this);
3191
+
3192
+ _$1.chain(this.configuration.changed).keys().each(function (name) {
3193
+ this.trigger('change:configuration:' + name, this, this.configuration.get(name));
3194
+ }, this);
3195
+ });
3196
+ },
3197
+ toJSON: function toJSON() {
3198
+ var attributes = {};
3199
+
3200
+ if (includeAttributesInJSON === true) {
3201
+ attributes = _$1.clone(this.attributes);
3202
+ } else if (includeAttributesInJSON) {
3203
+ attributes = _$1.pick(this.attributes, includeAttributesInJSON);
3204
+ }
3205
+
3206
+ return _$1.extend(attributes, {
3207
+ configuration: this.configuration.toJSON()
3208
+ });
3209
+ }
3210
+ };
3211
+ }
3212
+
2974
3213
  var persistedPromise = {
2975
3214
  persisted: function persisted() {
2976
3215
  var model = this;
@@ -3334,6 +3573,61 @@ var ChaptersCollection = Backbone.Collection.extend({
3334
3573
  }
3335
3574
  });
3336
3575
 
3576
+ /**
3577
+ * A Backbone collection that is automatically updated to only
3578
+ * contain models with a foreign key matching the id of a parent
3579
+ * model.
3580
+ *
3581
+ * @param {Object} options
3582
+ * @param {Backbone.Model} options.parentModel -
3583
+ * Model whose id is compared to foreign keys.
3584
+ * @param {Backbone.Collection} options.parent -
3585
+ * Collection to filter items with matching foreign key from.
3586
+ * @param {String} options.foreignKeyAttribute -
3587
+ * Attribute to compare to id of parent model.
3588
+ * @param {String} options.parentReferenceAttribute -
3589
+ * Set reference to parent model on models in collection.
3590
+ *
3591
+ * @since edge
3592
+ */
3593
+
3594
+ var ForeignKeySubsetCollection = SubsetCollection.extend({
3595
+ mixins: [orderedCollection],
3596
+ constructor: function constructor(options) {
3597
+ var parent = options.parent;
3598
+ var parentModel = options.parentModel;
3599
+ SubsetCollection.prototype.constructor.call(this, {
3600
+ parent: parent,
3601
+ parentModel: parentModel,
3602
+ filter: function filter(item) {
3603
+ return !parentModel.isNew() && item.get(options.foreignKeyAttribute) === parentModel.id;
3604
+ },
3605
+ comparator: function comparator(item) {
3606
+ return item.get('position');
3607
+ }
3608
+ });
3609
+ this.listenTo(this, 'add', function (model) {
3610
+ if (options.parentReferenceAttribute) {
3611
+ model[options.parentReferenceAttribute] = parentModel;
3612
+ }
3613
+
3614
+ model.set(options.foreignKeyAttribute, parentModel.id);
3615
+ });
3616
+ this.listenTo(parentModel, 'destroy', function () {
3617
+ this.clear();
3618
+ });
3619
+
3620
+ if (options.parentReferenceAttribute) {
3621
+ this.each(function (model) {
3622
+ return model[options.parentReferenceAttribute] = parentModel;
3623
+ });
3624
+ this.listenTo(this, 'remove', function (model) {
3625
+ model[options.parentReferenceAttribute] = null;
3626
+ });
3627
+ }
3628
+ }
3629
+ });
3630
+
3337
3631
  var PageLinksCollection = Backbone.Collection.extend({
3338
3632
  model: PageLink,
3339
3633
  initialize: function initialize(models, options) {
@@ -3553,50 +3847,6 @@ var addAndReturnModel = {
3553
3847
  };
3554
3848
  Cocktail.mixin(Backbone.Collection, addAndReturnModel);
3555
3849
 
3556
- // different model types. Backbone.Collection tries to merge records
3557
- // if they have the same id.
3558
-
3559
- var MultiCollection = function MultiCollection() {
3560
- this.records = {};
3561
- this.length = 0;
3562
- };
3563
-
3564
- _$1.extend(MultiCollection.prototype, {
3565
- add: function add(record) {
3566
- if (!this.records[record.cid]) {
3567
- this.records[record.cid] = record;
3568
- this.length = _$1.keys(this.records).length;
3569
- this.trigger('add', record);
3570
- }
3571
- },
3572
- remove: function remove(record) {
3573
- if (this.records[record.cid]) {
3574
- delete this.records[record.cid];
3575
- this.length = _$1.keys(this.records).length;
3576
- this.trigger('remove', record);
3577
- }
3578
- },
3579
- isEmpty: function isEmpty() {
3580
- return this.length === 0;
3581
- }
3582
- });
3583
-
3584
- _$1.extend(MultiCollection.prototype, Backbone.Events);
3585
-
3586
- MultiCollection.extend = Backbone.Collection.extend;
3587
-
3588
- var SavingRecordsCollection = MultiCollection.extend({
3589
- watch: function watch(collection) {
3590
- var that = this;
3591
- this.listenTo(collection, 'request', function (model, xhr) {
3592
- that.add(model);
3593
- xhr.always(function () {
3594
- that.remove(model);
3595
- });
3596
- });
3597
- }
3598
- });
3599
-
3600
3850
  var SidebarRouter = Marionette.AppRouter.extend({
3601
3851
  appRoutes: {
3602
3852
  'page_links/:id': 'pageLink',
@@ -3777,24 +4027,87 @@ ConfirmEncodingView.create = function (options) {
3777
4027
  });
3778
4028
  };
3779
4029
 
3780
- var failureIndicatingView = {
3781
- modelEvents: {
3782
- 'change:failed': 'updateFailIndicator'
3783
- },
3784
- events: {
3785
- 'click .retry': function clickRetry() {
4030
+ /**
4031
+ * Mixin for Marionette Views that sets css class names according to
4032
+ * life cycle events of its model.
4033
+ *
4034
+ * @param {Object} options
4035
+ * @param {Object} options.classNames
4036
+ * @param {String} options.classNames.creating -
4037
+ * Class name to add to root element while model is still being created.
4038
+ * @param {String} options.classNames.destroying -
4039
+ * Class name to add to root element while model is being destroyed.
4040
+ * @param {String} options.classNames.failed -
4041
+ * Class name to add to root element while model is in failed state.
4042
+ * Model needs to include {@link failureTracking} mixin.
4043
+ * @param {String} options.classNames.failureMessage -
4044
+ * Class name of the element that shall be updated with the failure
4045
+ * message. Model needs to include {@link failureTracking} mixin.
4046
+ * @param {String} options.classNames.retryButton -
4047
+ * Class name of the element that shall act as a retry button.
4048
+ */
4049
+
4050
+ function modelLifecycleTrackingView(_ref) {
4051
+ var classNames = _ref.classNames;
4052
+ return {
4053
+ events: _defineProperty({}, "click .".concat(classNames.retryButton), function click() {
3786
4054
  editor$1.failures.retry();
3787
4055
  return false;
4056
+ }),
4057
+ initialize: function initialize() {
4058
+ var _this = this;
4059
+
4060
+ if (classNames.creating) {
4061
+ this.listenTo(this.model, 'change:id', function () {
4062
+ this.$el.removeClass(classNames.creating);
4063
+ });
4064
+ }
4065
+
4066
+ if (classNames.destroying) {
4067
+ this.listenTo(this.model, 'destroying', function () {
4068
+ this.$el.addClass(classNames.destroying);
4069
+ });
4070
+ this.listenTo(this.model, 'error', function () {
4071
+ this.$el.removeClass(classNames.destroying);
4072
+ });
4073
+ }
4074
+
4075
+ if (classNames.failed || classNames.failureMessage) {
4076
+ this.listenTo(this.model, 'change:failed', function () {
4077
+ return _this.updateFailIndicator();
4078
+ });
4079
+ }
4080
+ },
4081
+ render: function render() {
4082
+ if (this.model.isNew()) {
4083
+ this.$el.addClass(classNames.creating);
4084
+ }
4085
+
4086
+ if (this.model.isDestroying && this.model.isDestroying()) {
4087
+ this.$el.addClass(classNames.destroying);
4088
+ }
4089
+
4090
+ this.updateFailIndicator();
4091
+ },
4092
+ updateFailIndicator: function updateFailIndicator() {
4093
+ if (classNames.failed) {
4094
+ this.$el.toggleClass(classNames.failed, this.model.isFailed());
4095
+ }
4096
+
4097
+ if (classNames.failureMessage) {
4098
+ this.$el.find(".".concat(classNames.failureMessage)).text(this.model.getFailureMessage());
4099
+ }
3788
4100
  }
3789
- },
3790
- onRender: function onRender() {
3791
- this.updateFailIndicator();
3792
- },
3793
- updateFailIndicator: function updateFailIndicator() {
3794
- this.$el.toggleClass('failed', this.model.isFailed());
3795
- this.$el.find('.failure .message').text(this.model.getFailureMessage());
4101
+ };
4102
+ }
4103
+
4104
+ var failureIndicatingView = modelLifecycleTrackingView({
4105
+ classNames: {
4106
+ failed: 'failed',
4107
+ failureMessage: 'failure .message',
4108
+ retryButton: 'retry'
3796
4109
  }
3797
- };
4110
+ });
3798
4111
 
3799
4112
  function template$6(data) {
3800
4113
  var __t, __p = '';
@@ -4987,7 +5300,8 @@ var EditMetaDataView = Marionette.Layout.extend({
4987
5300
  var editor = this.options.editor || {};
4988
5301
  var configurationEditor = new ConfigurationEditorView({
4989
5302
  model: entry.metadata.configuration,
4990
- tab: this.options.tab
5303
+ tab: this.options.tab,
5304
+ attributeTranslationKeyPrefixes: ['pageflow.entry_types.' + editor.entryType.name + '.editor.entry_metadata_configuration_attributes']
4991
5305
  });
4992
5306
  configurationEditor.tab('general', function () {
4993
5307
  this.input('title', TextInputView, {
@@ -5020,7 +5334,10 @@ var EditMetaDataView = Marionette.Layout.extend({
5020
5334
  });
5021
5335
  });
5022
5336
  configurationEditor.tab('widgets', function () {
5023
- editor.entryType.appearanceInputs && editor.entryType.appearanceInputs(this, entry, state.theming);
5337
+ editor.entryType.appearanceInputs && editor.entryType.appearanceInputs(this, {
5338
+ entry: entry,
5339
+ theming: state.theming
5340
+ });
5024
5341
  entry.widgets && this.view(EditWidgetsView, {
5025
5342
  model: entry,
5026
5343
  widgetTypes: editor.widgetTypes
@@ -5329,28 +5646,12 @@ var EditWidgetView = Marionette.ItemView.extend({
5329
5646
  }
5330
5647
  });
5331
5648
 
5332
- var loadable = {
5333
- modelEvents: {
5334
- 'change:id': function changeId() {
5335
- this.$el.removeClass('creating');
5336
- },
5337
- destroying: function destroying() {
5338
- this.$el.addClass('destroying');
5339
- },
5340
- error: function error() {
5341
- this.$el.removeClass('destroying');
5342
- }
5343
- },
5344
- render: function render() {
5345
- if (this.model.isNew()) {
5346
- this.$el.addClass('creating');
5347
- }
5348
-
5349
- if (this.model.isDestroying && this.model.isDestroying()) {
5350
- this.$el.addClass('destroying');
5351
- }
5649
+ var loadable = modelLifecycleTrackingView({
5650
+ classNames: {
5651
+ creating: 'creating',
5652
+ destroying: 'destroying'
5352
5653
  }
5353
- };
5654
+ });
5354
5655
 
5355
5656
  function template$r(data) {
5356
5657
  var __p = '';
@@ -7467,8 +7768,8 @@ var NotificationsView = Marionette.ItemView.extend({
7467
7768
  onRender: function onRender() {
7468
7769
  this.listenTo(state.entry, 'change:uploading_files_count', this.notifyUploadCount);
7469
7770
  this.listenTo(state.entry, 'change:confirmable_files_count', this.notifyConfirmableFilesCount);
7470
- this.listenTo(state.savingRecords, 'add', this.update);
7471
- this.listenTo(state.savingRecords, 'remove', this.update);
7771
+ this.listenTo(editor$1.savingRecords, 'add', this.update);
7772
+ this.listenTo(editor$1.savingRecords, 'remove', this.update);
7472
7773
  this.listenTo(editor$1.failures, 'add', this.update);
7473
7774
  this.listenTo(editor$1.failures, 'remove', this.update);
7474
7775
  this.update();
@@ -7476,7 +7777,7 @@ var NotificationsView = Marionette.ItemView.extend({
7476
7777
  },
7477
7778
  update: function update() {
7478
7779
  this.$el.toggleClass('failed', !editor$1.failures.isEmpty());
7479
- this.$el.toggleClass('saving', !state.savingRecords.isEmpty());
7780
+ this.$el.toggleClass('saving', !editor$1.savingRecords.isEmpty());
7480
7781
  this.ui.failedCount.text(editor$1.failures.count());
7481
7782
  },
7482
7783
  notifyUploadCount: function notifyUploadCount(model, uploadCount) {
@@ -7855,6 +8156,101 @@ ConfirmUploadView.open = function (options) {
7855
8156
  app.dialogRegion.show(new ConfirmUploadView(options));
7856
8157
  };
7857
8158
 
8159
+ /**
8160
+ * Base view to edit configuration container models. Extend and
8161
+ * override the `configure` method which receives a {@link
8162
+ * ConfigurationEditorView} to define the tabs and inputs that shall
8163
+ * be displayed.
8164
+ *
8165
+ * Add a `translationKeyPrefix` property to the prototype and define
8166
+ * the following translations:
8167
+ *
8168
+ * * `<translationKeyPrefix>.tabs`: used as `tabTranslationKeyPrefix`
8169
+ * of the `ConfigurationEditorView`.
8170
+ *
8171
+ * * `<translationKeyPrefix>.attributes`: used as one of the
8172
+ * `attributeTranslationKeyPrefixes` of the
8173
+ * `ConfigurationEditorView`.
8174
+ *
8175
+ * * `<translationKeyPrefix>.back` (optional): Back button label.
8176
+ *
8177
+ * * `<translationKeyPrefix>.destroy` (optional): Destroy button
8178
+ * label.
8179
+ *
8180
+ * * `<translationKeyPrefix>.confirm_destroy` (optional): Confirm
8181
+ * message displayed before destroying.
8182
+ *
8183
+ * * `<translationKeyPrefix>.save_error` (optional): Header of the
8184
+ * failure message that is displayed if the model cannot be saved.
8185
+ *
8186
+ * * `<translationKeyPrefix>.retry` (optional): Label of the retry
8187
+ * button of the failure message.
8188
+ *
8189
+ * @param {Object} options
8190
+ * @param {Backbone.Model} options.model -
8191
+ * Model including the {@link configurationContainer},
8192
+ * {@link failureTracking} and {@link delayedDestroying} mixins.
8193
+ *
8194
+ * @since edge
8195
+ */
8196
+
8197
+ var EditConfigurationView = Marionette.Layout.extend({
8198
+ className: 'edit_configuration_view',
8199
+ template: function template(_ref) {
8200
+ var t = _ref.t;
8201
+ return "\n <a class=\"back\">".concat(t('back'), "</a>\n <a class=\"destroy\">").concat(t('destroy'), "</a>\n\n <div class=\"failure\">\n <p>").concat(t('save_error'), "</p>\n <p class=\"message\"></p>\n <a class=\"retry\" href=\"\">").concat(t('retry'), "</a>\n </div>\n\n <div class=\"configuration_container\"></div>\n ");
8202
+ },
8203
+ serializeData: function serializeData() {
8204
+ var _this = this;
8205
+
8206
+ return {
8207
+ t: function t(key) {
8208
+ return _this.t(key);
8209
+ }
8210
+ };
8211
+ },
8212
+ mixins: [failureIndicatingView],
8213
+ regions: {
8214
+ configurationContainer: '.configuration_container'
8215
+ },
8216
+ events: {
8217
+ 'click a.back': 'goBack',
8218
+ 'click a.destroy': 'destroy'
8219
+ },
8220
+ onRender: function onRender() {
8221
+ var translationKeyPrefix = _$1.result(this, 'translationKeyPrefix');
8222
+
8223
+ this.configurationEditor = new ConfigurationEditorView({
8224
+ tabTranslationKeyPrefix: "".concat(translationKeyPrefix, ".tabs"),
8225
+ attributeTranslationKeyPrefixes: ["".concat(translationKeyPrefix, ".attributes")],
8226
+ model: this.model.configuration
8227
+ });
8228
+ this.configure(this.configurationEditor);
8229
+ this.configurationContainer.show(this.configurationEditor);
8230
+ },
8231
+ onShow: function onShow() {
8232
+ this.configurationEditor.refreshScroller();
8233
+ },
8234
+ destroy: function destroy() {
8235
+ if (window.confirm(this.t('confirm_destroy'))) {
8236
+ this.model.destroyWithDelay();
8237
+ this.goBack();
8238
+ }
8239
+ },
8240
+ goBack: function goBack() {
8241
+ editor$1.navigate('/', {
8242
+ trigger: true
8243
+ });
8244
+ },
8245
+ t: function t(suffix) {
8246
+ var translationKeyPrefix = _$1.result(this, 'translationKeyPrefix');
8247
+
8248
+ return I18n$1.t("".concat(translationKeyPrefix, ".").concat(suffix), {
8249
+ defaultValue: I18n$1.t("pageflow.editor.views.edit_configuration.".concat(suffix))
8250
+ });
8251
+ }
8252
+ });
8253
+
7858
8254
  ConfigurationEditorView.register('audio', {
7859
8255
  configure: function configure() {
7860
8256
  this.tab('general', function () {
@@ -8360,9 +8756,8 @@ app.addInitializer(function (options) {
8360
8756
  editor$1.failures.watch(state.entry);
8361
8757
  editor$1.failures.watch(state.pages);
8362
8758
  editor$1.failures.watch(state.chapters);
8363
- state.savingRecords = new SavingRecordsCollection();
8364
- state.savingRecords.watch(state.pages);
8365
- state.savingRecords.watch(state.chapters);
8759
+ editor$1.savingRecords.watch(state.pages);
8760
+ editor$1.savingRecords.watch(state.chapters);
8366
8761
  pageflow.events.trigger('seed:loaded');
8367
8762
  });
8368
8763
 
@@ -8498,4 +8893,4 @@ app.addRegions({
8498
8893
  sidebarFooterRegion: 'sidebar .sidebar_footer_container'
8499
8894
  });
8500
8895
 
8501
- export { AudioFile, BackButtonDecoratorView, BackgroundImageEmbeddedView, BackgroundPositioningPreviewView, BackgroundPositioningSlidersView, BackgroundPositioningView, ChangeThemeDialogView, Chapter, ChapterConfiguration, ChapterPagesCollection, ChapterScaffold, ChaptersCollection, ChooseImporterView, Configuration, ConfirmEncodingView, ConfirmFileImportUploadView, ConfirmUploadView, ConfirmableFileItemView, DisabledAtmoIndicatorView, DropDownButtonItemListView, DropDownButtonItemView, DropDownButtonView, EditChapterView, EditEntryView, EditFileView, EditLock, EditLockContainer, EditMetaDataView, EditPageLinkView, EditPageView, EditStorylineView, EditWidgetView, EditWidgetsView, EditorApi, EditorView, EmulationModeButtonView, EncodedFile, EncodingConfirmation, Entry, EntryMetadata, EntryMetadataFileSelectionHandler, EntryPublication, EntryPublicationQuotaDecoratorView, ExplorerFileItemView, FileConfiguration, FileImport, FileInputView, FileItemView, FileMetaDataItemValueView, FileMetaDataItemView, FileProcessingStateDisplayView, FileReuse, FileSettingsDialogView, FileStage, FileStageItemView, FileThumbnailView, FileTypes, FileTypesCollection, FileUploader, FilesCollection, FilesExplorerView, FilesImporterView, FilesView, FilteredFilesView, HelpButtonView, HelpImageView, HelpView, ImageFile, InfoBoxView, InvalidNestedTypeError, LazyVideoEmbeddedView, ListItemView, ListView, LoadingView, LockedView, ModelThumbnailView, MultiCollection, NestedFilesCollection, NestedFilesView, NestedTypeError, NotificationsView, OrderedPageLinksCollection, OtherEntriesCollection, OtherEntriesCollectionView, OtherEntry, OtherEntryItemView, Page, PageConfigurationFileSelectionHandler, PageLink, PageLinkConfigurationEditorView, PageLinkFileSelectionHandler, PageLinkInputView, PageLinkItemView, PageLinksCollection, PageLinksView, PageThumbnailView, PagesCollection, PreviewEntryData, PublishEntryView, ReferenceInputView, ReusableFile, SavingRecordsCollection, Scaffold, ScrollingView, SelectButtonView, SidebarController, SidebarFooterView, SidebarRouter, StaticThumbnailView, Storyline, StorylineChaptersCollection, StorylineConfiguration, StorylineOrdering, StorylineScaffold, StorylineTransitiveChildPages, StorylinesCollection, SubsetCollection, TextFileMetaDataItemValueView, TextTrackFile, TextTracksFileMetaDataItemValueView, TextTracksView, Theme, ThemeInputView, ThemeItemView, ThemesCollection, Theming, UnmatchedUploadError, UploadError, UploadableFile, UploadableFilesView, UploaderView, VideoFile, Widget, WidgetConfiguration, WidgetConfigurationFileSelectionHandler, WidgetItemView, WidgetTypes, WidgetsCollection, addAndReturnModel, app, authenticationProvider, delayedDestroying, dialogView, editor$1 as editor, failureIndicatingView, failureTracking, fileWithType, filesCountWatcher, formDataUtils, loadable, orderedCollection, persistedPromise, polling, retryable, selectableView, stageProvider, startEditor, state, stylesheet, transientReferences, validFileTypeTranslationList };
8896
+ export { AudioFile, BackButtonDecoratorView, BackgroundImageEmbeddedView, BackgroundPositioningPreviewView, BackgroundPositioningSlidersView, BackgroundPositioningView, ChangeThemeDialogView, Chapter, ChapterConfiguration, ChapterPagesCollection, ChapterScaffold, ChaptersCollection, ChooseImporterView, Configuration, ConfirmEncodingView, ConfirmFileImportUploadView, ConfirmUploadView, ConfirmableFileItemView, DisabledAtmoIndicatorView, DropDownButtonItemListView, DropDownButtonItemView, DropDownButtonView, EditChapterView, EditConfigurationView, EditEntryView, EditFileView, EditLock, EditLockContainer, EditMetaDataView, EditPageLinkView, EditPageView, EditStorylineView, EditWidgetView, EditWidgetsView, EditorApi, EditorView, EmulationModeButtonView, EncodedFile, EncodingConfirmation, Entry, EntryMetadata, EntryMetadataFileSelectionHandler, EntryPublication, EntryPublicationQuotaDecoratorView, ExplorerFileItemView, Failure, FileConfiguration, FileImport, FileInputView, FileItemView, FileMetaDataItemValueView, FileMetaDataItemView, FileProcessingStateDisplayView, FileReuse, FileSettingsDialogView, FileStage, FileStageItemView, FileThumbnailView, FileTypes, FileTypesCollection, FileUploader, FilesCollection, FilesExplorerView, FilesImporterView, FilesView, FilteredFilesView, ForeignKeySubsetCollection, HelpButtonView, HelpImageView, HelpView, ImageFile, InfoBoxView, InvalidNestedTypeError, LazyVideoEmbeddedView, ListItemView, ListView, LoadingView, LockedView, ModelThumbnailView, NestedFilesCollection, NestedFilesView, NestedTypeError, NotificationsView, OrderedPageLinksCollection, OtherEntriesCollection, OtherEntriesCollectionView, OtherEntry, OtherEntryItemView, Page, PageConfigurationFileSelectionHandler, PageLink, PageLinkConfigurationEditorView, PageLinkFileSelectionHandler, PageLinkInputView, PageLinkItemView, PageLinksCollection, PageLinksView, PageThumbnailView, PagesCollection, PreviewEntryData, PublishEntryView, ReferenceInputView, ReusableFile, Scaffold, ScrollingView, SelectButtonView, SidebarController, SidebarFooterView, SidebarRouter, StaticThumbnailView, Storyline, StorylineChaptersCollection, StorylineConfiguration, StorylineOrdering, StorylineScaffold, StorylineTransitiveChildPages, StorylinesCollection, SubsetCollection, TextFileMetaDataItemValueView, TextTrackFile, TextTracksFileMetaDataItemValueView, TextTracksView, Theme, ThemeInputView, ThemeItemView, ThemesCollection, Theming, UnmatchedUploadError, UploadError, UploadableFile, UploadableFilesView, UploaderView, VideoFile, Widget, WidgetConfiguration, WidgetConfigurationFileSelectionHandler, WidgetItemView, WidgetTypes, WidgetsCollection, addAndReturnModel, app, authenticationProvider, configurationContainer, delayedDestroying, dialogView, editor$1 as editor, entryTypeEditorControllerUrls, failureIndicatingView, failureTracking, fileWithType, filesCountWatcher, formDataUtils, loadable, modelLifecycleTrackingView, orderedCollection, persistedPromise, polling, retryable, selectableView, stageProvider, startEditor, state, stylesheet, transientReferences, validFileTypeTranslationList };