pageflow 14.5.2 → 15.0.0.beta1

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 (77) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +44 -39
  3. data/admins/pageflow/membership.rb +1 -5
  4. data/app/assets/javascripts/pageflow/dist/react-client.js +62 -179
  5. data/app/assets/javascripts/pageflow/dist/react-server.js +62 -179
  6. data/app/assets/javascripts/pageflow/editor/api/widget_type.js +1 -0
  7. data/app/assets/javascripts/pageflow/editor/base.js +2 -2
  8. data/app/assets/javascripts/pageflow/editor/collections/files_collection.js +4 -0
  9. data/app/assets/javascripts/pageflow/editor/collections/nested_files_collection.js +4 -0
  10. data/app/assets/javascripts/pageflow/editor/initializers/setup_audio.js +1 -1
  11. data/app/assets/javascripts/pageflow/editor/models/encoded_file.js +1 -1
  12. data/app/assets/javascripts/pageflow/editor/models/image_file.js +1 -1
  13. data/app/assets/javascripts/pageflow/editor/models/mixins/transient_references.js +7 -7
  14. data/app/assets/javascripts/pageflow/editor/models/preview_entry_data.js +2 -2
  15. data/app/assets/javascripts/pageflow/editor/models/{uploaded_file.js → reusable_file.js} +1 -1
  16. data/app/assets/javascripts/pageflow/editor/models/text_track_file.js +2 -2
  17. data/app/assets/javascripts/pageflow/editor/models/{hosted_file.js → uploadable_file.js} +1 -1
  18. data/app/assets/javascripts/pageflow/editor/views/widget_types/title_loading_spinner.js +0 -3
  19. data/app/assets/stylesheets/pageflow/mixins/pageflow.scss +1 -1
  20. data/app/assets/stylesheets/pageflow/themes/default/base.scss +1 -1
  21. data/app/assets/stylesheets/pageflow/themes/default/loading_spinner/title.scss +50 -10
  22. data/app/assets/stylesheets/pageflow/themes/default/loading_spinner.scss +0 -2
  23. data/app/assets/stylesheets/pageflow/themes/default/player_controls/classic/info_box.scss +0 -5
  24. data/app/assets/stylesheets/pageflow/themes/default/player_controls/slim/info_box.scss +0 -4
  25. data/app/controllers/pageflow/files_controller.rb +3 -3
  26. data/app/helpers/pageflow/audio_files_helper.rb +18 -16
  27. data/app/helpers/pageflow/background_image_helper.rb +22 -17
  28. data/app/helpers/pageflow/entry_json_seed_helper.rb +3 -3
  29. data/app/helpers/pageflow/file_thumbnails_helper.rb +1 -1
  30. data/app/helpers/pageflow/meta_tags_helper.rb +3 -3
  31. data/app/helpers/pageflow/pages_helper.rb +10 -1
  32. data/app/helpers/pageflow/revision_file_helper.rb +27 -0
  33. data/app/helpers/pageflow/social_share_helper.rb +5 -3
  34. data/app/helpers/pageflow/social_share_links_helper.rb +1 -1
  35. data/app/helpers/pageflow/video_files_helper.rb +18 -13
  36. data/app/jobs/pageflow/{process_file_job.rb → process_image_or_text_track_job.rb} +1 -1
  37. data/app/models/concerns/pageflow/{uploaded_file.rb → reusable_file.rb} +59 -5
  38. data/app/models/concerns/pageflow/{hosted_file.rb → uploadable_file.rb} +26 -15
  39. data/app/models/pageflow/audio_file.rb +2 -2
  40. data/app/models/pageflow/draft_entry.rb +1 -1
  41. data/app/models/pageflow/entry.rb +0 -8
  42. data/app/models/pageflow/file_usage.rb +3 -1
  43. data/app/models/pageflow/image_file.rb +31 -29
  44. data/app/models/pageflow/membership.rb +4 -3
  45. data/app/models/pageflow/page.rb +0 -8
  46. data/app/models/pageflow/positioned_file.rb +3 -2
  47. data/app/models/pageflow/published_entry.rb +9 -3
  48. data/app/models/pageflow/revision.rb +6 -12
  49. data/app/models/pageflow/text_track_file.rb +13 -12
  50. data/app/models/pageflow/thumbnail_file_resolver.rb +3 -3
  51. data/app/models/pageflow/used_file.rb +4 -0
  52. data/app/models/pageflow/video_file.rb +2 -2
  53. data/app/models/pageflow/widget.rb +6 -12
  54. data/app/state_machines/pageflow/{processed_file_state_machine.rb → image_and_text_track_processing_state_machine.rb} +3 -2
  55. data/app/state_machines/pageflow/{encoded_file_state_machine.rb → media_encoding_state_machine.rb} +27 -19
  56. data/app/views/pageflow/editor/files/_file.json.jbuilder +1 -1
  57. data/app/views/pageflow/entries/mobile_navigation/_page.html.erb +1 -1
  58. data/app/views/pageflow/entries/navigation/_page.html.erb +2 -2
  59. data/app/views/pageflow/entries/overview/_page.html.erb +1 -1
  60. data/app/views/pageflow/files/_file.json.jbuilder +1 -0
  61. data/app/views/pageflow/social_share/_page_meta_tags.html.erb +1 -1
  62. data/config/locales/de.yml +0 -24
  63. data/config/locales/en.yml +0 -24
  64. data/db/migrate/20190306161431_copy_file_attributes_of_failed_uploads.rb +4 -4
  65. data/db/migrate/20190523151140_add_perma_id_to_file_usages.rb +13 -0
  66. data/lib/pageflow/built_in_widget_type.rb +0 -7
  67. data/lib/pageflow/built_in_widget_types_plugin.rb +0 -6
  68. data/lib/pageflow/user_mixin.rb +0 -6
  69. data/lib/pageflow/version.rb +1 -1
  70. data/lib/pageflow/widget_types.rb +1 -9
  71. data/spec/factories/{hosted_files.rb → uploadable_files.rb} +3 -3
  72. metadata +16 -32
  73. data/app/assets/javascripts/pageflow/dist/editor.js +0 -11890
  74. data/app/assets/javascripts/pageflow/dist/frontend.js +0 -5800
  75. data/app/assets/javascripts/pageflow/dist/ui.js +0 -3114
  76. data/app/assets/javascripts/pageflow/editor/views/widget_types/media_loading_spinner.js +0 -18
  77. data/app/assets/stylesheets/pageflow/themes/default/loading_spinner/media.scss +0 -56
@@ -1,3114 +0,0 @@
1
- this.pageflow = this.pageflow || {};
2
- this.pageflow._uiGlobalInterop = (function (exports, Marionette, _, $, I18n$1, Backbone, ChildViewContainer, IScroll, jquery_minicolors, wysihtml5, Cocktail) {
3
- 'use strict';
4
-
5
- Marionette = Marionette && Marionette.hasOwnProperty('default') ? Marionette['default'] : Marionette;
6
- _ = _ && _.hasOwnProperty('default') ? _['default'] : _;
7
- $ = $ && $.hasOwnProperty('default') ? $['default'] : $;
8
- I18n$1 = I18n$1 && I18n$1.hasOwnProperty('default') ? I18n$1['default'] : I18n$1;
9
- Backbone = Backbone && Backbone.hasOwnProperty('default') ? Backbone['default'] : Backbone;
10
- ChildViewContainer = ChildViewContainer && ChildViewContainer.hasOwnProperty('default') ? ChildViewContainer['default'] : ChildViewContainer;
11
- IScroll = IScroll && IScroll.hasOwnProperty('default') ? IScroll['default'] : IScroll;
12
- wysihtml5 = wysihtml5 && wysihtml5.hasOwnProperty('default') ? wysihtml5['default'] : wysihtml5;
13
- Cocktail = Cocktail && Cocktail.hasOwnProperty('default') ? Cocktail['default'] : Cocktail;
14
-
15
- /*global JST*/
16
-
17
- Marionette.Renderer.render = function (template, data) {
18
- if (_.isFunction(template)) {
19
- return template(data);
20
- }
21
-
22
- if (template.indexOf('templates/') === 0) {
23
- template = 'pageflow/editor/' + template;
24
- }
25
-
26
- if (!JST[template]) {
27
- throw "Template '" + template + "' not found!";
28
- }
29
-
30
- return JST[template](data);
31
- };
32
-
33
- /**
34
- * Returns an array of translation keys based on the `prefixes`
35
- * option and the given `keyName`.
36
- *
37
- * @param {string} keyName
38
- * Suffix to append to prefixes.
39
- *
40
- * @param {string[]} [options.prefixes]
41
- * Array of translation key prefixes.
42
- *
43
- * @param {string} [options.fallbackPrefix]
44
- * Optional additional prefix to form a model based translation
45
- * key of the form
46
- * `prefix.fallbackModelI18nKey.propertyName.keyName`.
47
- *
48
- * @param {string} [options.fallbackModelI18nKey]
49
- * Required if `fallbackPrefix` option is present.
50
- *
51
- * @return {string[]}
52
- * @memberof i18nUtils
53
- * @since 12.0
54
- */
55
-
56
- function attributeTranslationKeys(attributeName, keyName, options) {
57
- var result = [];
58
-
59
- if (options.prefixes) {
60
- result = result.concat(_(options.prefixes).map(function (prefix) {
61
- return prefix + '.' + attributeName + '.' + keyName;
62
- }, this));
63
- }
64
-
65
- if (options && options.fallbackPrefix) {
66
- result.push(options.fallbackPrefix + '.' + options.fallbackModelI18nKey + '.' + attributeName);
67
- }
68
-
69
- return result;
70
- }
71
- /**
72
- * Takes the same parameters as {@link
73
- * #i18nutilsattributetranslationkeys attributeTranslationKeys}, but returns the first existing
74
- * translation.
75
- *
76
- * @return {string}
77
- * @memberof i18nUtils
78
- * @since 12.0
79
- */
80
-
81
- function attributeTranslation(attributeName, keyName, options) {
82
- return findTranslation(attributeTranslationKeys(attributeName, keyName, options));
83
- }
84
- /**
85
- * Find the first key for which a translation exists and return the
86
- * translation.
87
- *
88
- * @param {string[]} keys
89
- * Translation key candidates.
90
- *
91
- * @param {string} [options.defaultValue]
92
- * Value to return if none of the keys has a translation. Is
93
- * treated like an HTML translation if html flag is set.
94
- *
95
- * @param {boolean} [options.html]
96
- * If true, also search for keys ending in '_html' and HTML-escape
97
- * keys that do not end in 'html'
98
- *
99
- * @memberof i18nUtils
100
- * @return {string}
101
- */
102
-
103
- function findTranslation(keys, options) {
104
- options = options || {};
105
-
106
- if (options.html) {
107
- keys = translationKeysWithSuffix(keys, 'html');
108
- }
109
-
110
- return _.chain(keys).reverse().reduce(function (result, key) {
111
- var unescapedTranslation = I18n$1.t(key, _.extend({}, options, {
112
- defaultValue: result
113
- }));
114
-
115
- if (!options.html || key.match(/_html$/) || result == unescapedTranslation) {
116
- return unescapedTranslation;
117
- } else {
118
- return $('<div />').text(unescapedTranslation).html();
119
- }
120
- }, options.defaultValue).value();
121
- }
122
- /**
123
- * Return the first key for which a translation exists. Returns the
124
- * first if non of the keys has a translation.
125
- *
126
- * @param {string[]} keys
127
- * Translation key candidates.
128
- *
129
- * @memberof i18nUtils
130
- * @return {string}
131
- */
132
-
133
- function findKeyWithTranslation(keys) {
134
- var missing = '_not_translated';
135
- return _(keys).detect(function (key) {
136
- return I18n$1.t(key, {
137
- defaultValue: missing
138
- }) !== missing;
139
- }) || _.first(keys);
140
- }
141
- function translationKeysWithSuffix(keys, suffix) {
142
- return _.chain(keys).map(function (key) {
143
- return [key + '_' + suffix, key];
144
- }).flatten().value();
145
- }
146
-
147
- var i18nUtils = /*#__PURE__*/Object.freeze({
148
- __proto__: null,
149
- attributeTranslationKeys: attributeTranslationKeys,
150
- attributeTranslation: attributeTranslation,
151
- findTranslation: findTranslation,
152
- findKeyWithTranslation: findKeyWithTranslation,
153
- translationKeysWithSuffix: translationKeysWithSuffix
154
- });
155
-
156
- function _arrayWithHoles(arr) {
157
- if (Array.isArray(arr)) return arr;
158
- }
159
-
160
- function _iterableToArrayLimit(arr, i) {
161
- if (typeof Symbol === "undefined" || !(Symbol.iterator in Object(arr))) return;
162
- var _arr = [];
163
- var _n = true;
164
- var _d = false;
165
- var _e = undefined;
166
-
167
- try {
168
- for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
169
- _arr.push(_s.value);
170
-
171
- if (i && _arr.length === i) break;
172
- }
173
- } catch (err) {
174
- _d = true;
175
- _e = err;
176
- } finally {
177
- try {
178
- if (!_n && _i["return"] != null) _i["return"]();
179
- } finally {
180
- if (_d) throw _e;
181
- }
182
- }
183
-
184
- return _arr;
185
- }
186
-
187
- function _arrayLikeToArray(arr, len) {
188
- if (len == null || len > arr.length) len = arr.length;
189
-
190
- for (var i = 0, arr2 = new Array(len); i < len; i++) {
191
- arr2[i] = arr[i];
192
- }
193
-
194
- return arr2;
195
- }
196
-
197
- function _unsupportedIterableToArray(o, minLen) {
198
- if (!o) return;
199
- if (typeof o === "string") return _arrayLikeToArray(o, minLen);
200
- var n = Object.prototype.toString.call(o).slice(8, -1);
201
- if (n === "Object" && o.constructor) n = o.constructor.name;
202
- if (n === "Map" || n === "Set") return Array.from(n);
203
- if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
204
- }
205
-
206
- function _nonIterableRest() {
207
- throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
208
- }
209
-
210
- function _slicedToArray(arr, i) {
211
- return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();
212
- }
213
-
214
- /**
215
- * Create object that can be passed to Marionette ui property from CSS
216
- * module object.
217
- *
218
- * @param {Object} styles
219
- * Class name mapping imported from `.module.css` file.
220
- *
221
- * @param {...string} classNames
222
- * Keys from the styles object that shall be used in the ui object.
223
- *
224
- * @return {Object}
225
- *
226
- * @example
227
- *
228
- * // MyView.module.css
229
- *
230
- * .container {}
231
- *
232
- * // MyView.js
233
- *
234
- * import Marionette from 'marionette';
235
- * import {cssModulesUtils} from 'pageflow/ui';
236
- *
237
- * import styles from './MyView.module.css';
238
- *
239
- * export const MyView = Marionette.ItemView({
240
- * template: () => `
241
- * <div class=${styles.container}></div>
242
- * `,
243
- *
244
- * ui: cssModulesUtils.ui(styles, 'container'),
245
- *
246
- * onRender() {
247
- * this.ui.container // => JQuery wrapper for container element
248
- * }
249
- * });
250
- *
251
- * @memberof cssModulesUtils
252
- */
253
- function ui(styles) {
254
- for (var _len = arguments.length, classNames = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
255
- classNames[_key - 1] = arguments[_key];
256
- }
257
-
258
- return classNames.reduce(function (result, className) {
259
- result[className] = selector(styles, className);
260
- return result;
261
- }, {});
262
- }
263
- /**
264
- * Create object that can be passed to Marionette events property from CSS
265
- * module object.
266
- *
267
- * @param {Object} styles
268
- * Class name mapping imported from `.module.css` file.
269
- *
270
- * @param {Object} mapping
271
- * Events mapping using keys from the `styles` instead of CSS class names.
272
- *
273
- * @return {Object}
274
- *
275
- * @example
276
- *
277
- * // MyView.module.css
278
- *
279
- * .addButton {}
280
- *
281
- * // MyView.js
282
- *
283
- * import Marionette from 'marionette';
284
- * import {cssModulesUtils} from 'pageflow/ui';
285
- *
286
- * import styles from './MyView.module.css';
287
- *
288
- * export const MyView = Marionette.ItemView({
289
- * template: () => `
290
- * <button class=${styles.addButton}></button>
291
- * `,
292
- *
293
- * events: cssModulesUtils.events(styles, {
294
- * 'click addButton': () => console.log('clicked add button');
295
- * })
296
- * });
297
- *
298
- * @memberof cssModulesUtils
299
- */
300
-
301
- function events(styles, mapping) {
302
- return Object.keys(mapping).reduce(function (result, key) {
303
- var _key$split = key.split(' '),
304
- _key$split2 = _slicedToArray(_key$split, 2),
305
- event = _key$split2[0],
306
- className = _key$split2[1];
307
-
308
- result["".concat(event, " ").concat(selector(styles, className))] = mapping[key];
309
- return result;
310
- }, {});
311
- }
312
- /**
313
- * Generates a CSS selector from a CSS module rule.
314
- *
315
- * @param {Object} styles
316
- * Class name mapping imported from `.module.css` file.
317
- *
318
- * @param {String} className
319
- * Key from the `styles` object.
320
- *
321
- * @return {String} CSS Selector
322
- * @memberof cssModulesUtils
323
- */
324
-
325
- function selector(styles, className) {
326
- var classNames = styles[className];
327
-
328
- if (!classNames) {
329
- throw new Error("Unknown class name ".concat(className, " in mapping. Knwon names: ").concat(Object.keys(styles).join(', '), "."));
330
- }
331
-
332
- return ".".concat(classNames.replace(/ /g, '.'));
333
- }
334
-
335
- var cssModulesUtils = /*#__PURE__*/Object.freeze({
336
- __proto__: null,
337
- ui: ui,
338
- events: events,
339
- selector: selector
340
- });
341
-
342
- // https://github.com/jashkenas/backbone/issues/2601
343
-
344
- function BaseObject(options) {
345
- this.initialize.apply(this, arguments);
346
- }
347
-
348
- _.extend(BaseObject.prototype, Backbone.Events, {
349
- initialize: function initialize(options) {}
350
- }); // The self-propagating extend function that Backbone classes use.
351
-
352
-
353
- BaseObject.extend = Backbone.Model.extend;
354
-
355
- var serverSideValidation = {
356
- initialize: function initialize() {
357
- var _this = this;
358
-
359
- this.validationErrors = {};
360
- this.listenTo(this, 'error', function (model, request) {
361
- if (request.status === 422) {
362
- _this.validationErrors = JSON.parse(request.responseText).errors;
363
-
364
- _this.trigger('invalid');
365
- }
366
- });
367
- this.listenTo(this, 'sync', function () {
368
- _this.validationErrors = {};
369
- });
370
- }
371
- };
372
-
373
- var CollectionView = Marionette.View.extend({
374
- initialize: function initialize() {
375
- this.rendered = false;
376
- this.itemViews = new ChildViewContainer();
377
- this.collection.map(this.addItem, this);
378
- this.listenTo(this.collection, 'add', this.addItem);
379
- this.listenTo(this.collection, 'remove', this.removeItem);
380
- this.listenTo(this.collection, 'sort', this.sort);
381
-
382
- if (this.options.loadingViewConstructor) {
383
- this.listenTo(this.collection, 'request', function () {
384
- this.loading = true;
385
- this.togglePlaceHolder();
386
- });
387
- this.listenTo(this.collection, 'sync', function () {
388
- this.loading = false;
389
- this.togglePlaceHolder();
390
- });
391
- }
392
- },
393
- render: function render() {
394
- if (!this.rendered) {
395
- this.$el.append(this.itemViews.map(function (itemView) {
396
- itemView.$el.data('view', itemView);
397
- return itemView.render().el;
398
- }));
399
- this.togglePlaceHolder();
400
- this.rendered = true;
401
- }
402
-
403
- return this;
404
- },
405
- onClose: function onClose() {
406
- this.itemViews.call('close');
407
- this.closePlaceHolderView();
408
- },
409
- addItem: function addItem(item) {
410
- var view = new this.options.itemViewConstructor(_.extend({
411
- model: item
412
- }, this.getItemViewOptions(item)));
413
- this.itemViews.add(view);
414
-
415
- if (this.rendered) {
416
- var index = this.collection.indexOf(item);
417
- view.render();
418
- view.$el.data('view', view);
419
-
420
- if (index > 0) {
421
- this.$el.children().eq(index - 1).after(view.el);
422
- } else {
423
- this.$el.prepend(view.el);
424
- }
425
-
426
- this.togglePlaceHolder();
427
- }
428
- },
429
- removeItem: function removeItem(item) {
430
- var view = this.itemViews.findByModel(item);
431
-
432
- if (view) {
433
- this.itemViews.remove(view);
434
- view.close();
435
- this.togglePlaceHolder();
436
- }
437
- },
438
- sort: function sort() {
439
- var last = null;
440
- this.collection.each(function (item) {
441
- var itemView = this.itemViews.findByModel(item);
442
- var element;
443
-
444
- if (!itemView) {
445
- return;
446
- }
447
-
448
- element = itemView.$el;
449
-
450
- if (last) {
451
- last.after(element);
452
- } else {
453
- this.$el.prepend(element);
454
- }
455
-
456
- last = element;
457
- }, this);
458
- },
459
- getItemViewOptions: function getItemViewOptions(item) {
460
- if (typeof this.options.itemViewOptions === 'function') {
461
- return this.options.itemViewOptions(item);
462
- } else {
463
- return this.options.itemViewOptions || {};
464
- }
465
- },
466
- closePlaceHolderView: function closePlaceHolderView() {
467
- if (this.placeHolderView) {
468
- this.placeHolderView.close();
469
- this.placeHolderView = null;
470
- }
471
- },
472
- togglePlaceHolder: function togglePlaceHolder() {
473
- var lastPlaceholderConstructor = this.placeHolderConstructor;
474
- this.placeHolderConstructor = this.getPlaceHolderConstructor();
475
-
476
- if (this.itemViews.length || !this.placeHolderConstructor) {
477
- this.closePlaceHolderView();
478
- } else if (!this.placeHolderView || lastPlaceholderConstructor !== this.placeHolderConstructor) {
479
- this.closePlaceHolderView();
480
- this.placeHolderView = new this.placeHolderConstructor();
481
- this.$el.append(this.placeHolderView.render().el);
482
- }
483
- },
484
- getPlaceHolderConstructor: function getPlaceHolderConstructor() {
485
- if (this.loading && this.options.loadingViewConstructor) {
486
- return this.options.loadingViewConstructor;
487
- } else if (this.options.blankSlateViewConstructor) {
488
- return this.options.blankSlateViewConstructor;
489
- }
490
- }
491
- });
492
-
493
- var SortableCollectionView = CollectionView.extend({
494
- render: function render() {
495
- CollectionView.prototype.render.call(this);
496
- this.$el.sortable({
497
- connectWith: this.options.connectWith,
498
- placeholder: 'sortable-placeholder',
499
- forcePlaceholderSize: true,
500
- delay: 200,
501
- update: _.bind(function (event, ui) {
502
- if (ui.item.parent().is(this.el)) {
503
- this.updateOrder();
504
- }
505
- }, this),
506
- receive: _.bind(function (event, ui) {
507
- var view = ui.item.data('view');
508
- this.reindexPositions();
509
- this.itemViews.add(view);
510
- this.collection.add(view.model);
511
- }, this),
512
- remove: _.bind(function (event, ui) {
513
- var view = ui.item.data('view');
514
- this.itemViews.remove(view);
515
- this.collection.remove(view.model);
516
- }, this)
517
- });
518
- return this;
519
- },
520
- addItem: function addItem(item) {
521
- if (!this.itemViews.findByModel(item)) {
522
- CollectionView.prototype.addItem.call(this, item);
523
- }
524
- },
525
- removeItem: function removeItem(item) {
526
- if (this.itemViews.findByModel(item)) {
527
- CollectionView.prototype.removeItem.call(this, item);
528
- }
529
- },
530
- updateOrder: function updateOrder() {
531
- this.reindexPositions();
532
- this.collection.sort();
533
- this.collection.saveOrder();
534
- },
535
- reindexPositions: function reindexPositions() {
536
- this.$el.children().each(function (index) {
537
- $(this).data('view').model.set('position', index);
538
- });
539
- }
540
- });
541
-
542
- var ConfigurationEditorTabView = Marionette.View.extend({
543
- className: 'configuration_editor_tab',
544
- initialize: function initialize() {
545
- this.inputs = new ChildViewContainer();
546
- this.groups = this.options.groups || ConfigurationEditorTabView.groups;
547
- },
548
- input: function input(propertyName, view, options) {
549
- this.view(view, _.extend({
550
- placeholderModel: this.options.placeholderModel,
551
- propertyName: propertyName,
552
- attributeTranslationKeyPrefixes: this.options.attributeTranslationKeyPrefixes
553
- }, options || {}));
554
- },
555
- view: function view(_view, options) {
556
- this.inputs.add(new _view(_.extend({
557
- model: this.model,
558
- parentTab: this.options.tab
559
- }, options || {})));
560
- },
561
- group: function group(name, options) {
562
- this.groups.apply(name, this, options);
563
- },
564
- render: function render() {
565
- this.inputs.each(function (input) {
566
- this.$el.append(input.render().el);
567
- }, this);
568
- return this;
569
- },
570
- onClose: function onClose() {
571
- if (this.inputs) {
572
- this.inputs.call('close');
573
- }
574
- }
575
- });
576
-
577
- ConfigurationEditorTabView.Groups = function () {
578
- var groups = {};
579
-
580
- this.define = function (name, fn) {
581
- if (typeof fn !== 'function') {
582
- throw 'Group has to be function.';
583
- }
584
-
585
- groups[name] = fn;
586
- };
587
-
588
- this.apply = function (name, context, options) {
589
- if (!(name in groups)) {
590
- throw 'Undefined group named "' + name + '".';
591
- }
592
-
593
- groups[name].call(context, options || {});
594
- };
595
- };
596
-
597
- ConfigurationEditorTabView.groups = new ConfigurationEditorTabView.Groups();
598
-
599
- function template(data) {
600
- var __p = '';
601
- __p += '<div class="tabs_view-scroller">\n <ul class="tabs_view-headers"></ul>\n</div>\n<div class="tabs_view-container"></div>\n';
602
- return __p
603
- }
604
-
605
- /*global pageflow*/
606
- /**
607
- * Switch between different views using tabs.
608
- *
609
- * @param {Object} [options]
610
- *
611
- * @param {string} [options.defaultTab]
612
- * Name of the tab to enable by default.
613
- *
614
- * @param {string[]} [options.translationKeyPrefixes]
615
- * List of prefixes to append tab name to. First exisiting translation is used as label.
616
- *
617
- * @param {string} [options.fallbackTranslationKeyPrefix]
618
- * Translation key prefix to use if non of the `translationKeyPrefixes` result in an
619
- * existing translation for a tab name.
620
- *
621
- * @param {string} [options.i18n]
622
- * Legacy alias for `fallbackTranslationKeyPrefix`.
623
- *
624
- * @class
625
- */
626
-
627
- var TabsView = Marionette.Layout.extend(
628
- /* @lends TabView.prototype */
629
- {
630
- template: template,
631
- className: 'tabs_view',
632
- ui: {
633
- headers: '.tabs_view-headers',
634
- scroller: '.tabs_view-scroller'
635
- },
636
- regions: {
637
- container: '.tabs_view-container'
638
- },
639
- events: {
640
- 'click .tabs_view-headers > li': function clickTabs_viewHeadersLi(event) {
641
- this.changeTab($(event.target).data('tab-name'));
642
- }
643
- },
644
- initialize: function initialize() {
645
- this.tabFactoryFns = {};
646
- this.tabNames = [];
647
- this.currentTabName = null;
648
-
649
- this._refreshScrollerOnSideBarResize();
650
- },
651
- tab: function tab(name, factoryFn) {
652
- this.tabFactoryFns[name] = factoryFn;
653
- this.tabNames.push(name);
654
- },
655
- onRender: function onRender() {
656
- _.each(this.tabNames, function (name) {
657
- var label = findTranslation(this._labelTranslationKeys(name));
658
- this.ui.headers.append($('<li />').attr('data-tab-name', name).text(label));
659
- }, this);
660
-
661
- this.scroller = new IScroll(this.ui.scroller[0], {
662
- scrollX: true,
663
- scrollY: false,
664
- bounce: false,
665
- mouseWheel: true,
666
- preventDefault: false
667
- });
668
- this.changeTab(this.defaultTab());
669
- },
670
- changeTab: function changeTab(name) {
671
- this.container.show(this.tabFactoryFns[name]());
672
-
673
- this._updateActiveHeader(name);
674
-
675
- this.currentTabName = name;
676
- },
677
- defaultTab: function defaultTab() {
678
- if (_.include(this.tabNames, this.options.defaultTab)) {
679
- return this.options.defaultTab;
680
- } else {
681
- return _.first(this.tabNames);
682
- }
683
- },
684
-
685
- /**
686
- * Rerender current tab.
687
- */
688
- refresh: function refresh() {
689
- this.changeTab(this.currentTabName);
690
- },
691
-
692
- /**
693
- * Adjust tabs scroller to changed width of view.
694
- */
695
- refreshScroller: function refreshScroller() {
696
- this.scroller.refresh();
697
- },
698
- toggleSpinnerOnTab: function toggleSpinnerOnTab(name, visible) {
699
- this.$('[data-tab-name=' + name + ']').toggleClass('spinner', visible);
700
- },
701
- _labelTranslationKeys: function _labelTranslationKeys(name) {
702
- var result = _.map(this.options.translationKeyPrefixes, function (prefix) {
703
- return prefix + '.' + name;
704
- });
705
-
706
- if (this.options.i18n) {
707
- result.push(this.options.i18n + '.' + name);
708
- }
709
-
710
- if (this.options.fallbackTranslationKeyPrefix) {
711
- result.push(this.options.fallbackTranslationKeyPrefix + '.' + name);
712
- }
713
-
714
- return result;
715
- },
716
- _updateActiveHeader: function _updateActiveHeader(activeTabName) {
717
- var scroller = this.scroller;
718
- this.ui.headers.children().each(function () {
719
- if ($(this).data('tab-name') === activeTabName) {
720
- scroller.scrollToElement(this, 200, true);
721
- $(this).addClass('active');
722
- } else {
723
- $(this).removeClass('active');
724
- }
725
- });
726
- },
727
- _refreshScrollerOnSideBarResize: function _refreshScrollerOnSideBarResize() {
728
- if (pageflow.app) {
729
- this.listenTo(pageflow.app, 'resize', function () {
730
- this.scroller.refresh();
731
- });
732
- }
733
- }
734
- });
735
-
736
- /**
737
- * Render a inputs on multiple tabs.
738
- *
739
- * @param {Object} [options]
740
- *
741
- * @param {string} [options.model]
742
- * Backbone model to use for input views.
743
- *
744
- * @param {string} [options.placeholderModel]
745
- * Backbone model to read placeholder values from.
746
-
747
- * @param {string} [options.tab]
748
- * Name of the tab to enable by default.
749
- *
750
- * @param {string[]} [options.attributeTranslationKeyPrefixes]
751
- * List of prefixes to use in input views for attribute based transltions.
752
- *
753
- * @param {string[]} [options.tabTranslationKeyPrefixes]
754
- * List of prefixes to append tab name to. First exisiting translation is used as label.
755
- *
756
- * @param {string} [options.tabTranslationKeyPrefix]
757
- * Prefixes to append tab name to.
758
- *
759
- * @class
760
- */
761
-
762
- var ConfigurationEditorView = Marionette.View.extend({
763
- className: 'configuration_editor',
764
- initialize: function initialize() {
765
- this.tabsView = new TabsView({
766
- translationKeyPrefixes: this.options.tabTranslationKeyPrefixes || [this.options.tabTranslationKeyPrefix],
767
- fallbackTranslationKeyPrefix: 'pageflow.ui.configuration_editor.tabs',
768
- defaultTab: this.options.tab
769
- });
770
- this.configure();
771
- },
772
- configure: function configure() {},
773
- tab: function tab(name, callbackOrOptions, callback) {
774
- callback = callback || callbackOrOptions;
775
- var options = callback ? callbackOrOptions : {};
776
- this.tabsView.tab(name, _.bind(function () {
777
- var tabView = new ConfigurationEditorTabView({
778
- model: options.model || this.model,
779
- placeholderModel: this.options.placeholderModel,
780
- tab: name,
781
- attributeTranslationKeyPrefixes: this.options.attributeTranslationKeyPrefixes
782
- });
783
- callback.call(tabView);
784
- return tabView;
785
- }, this));
786
- },
787
-
788
- /**
789
- * Rerender current tab.
790
- */
791
- refresh: function refresh() {
792
- this.tabsView.refresh();
793
- },
794
-
795
- /**
796
- * Adjust tabs scroller to changed width of view.
797
- */
798
- refreshScroller: function refreshScroller() {
799
- this.tabsView.refreshScroller();
800
- },
801
- render: function render() {
802
- this.$el.append(this.subview(this.tabsView).el);
803
- return this;
804
- }
805
- });
806
-
807
- _.extend(ConfigurationEditorView, {
808
- repository: {},
809
- register: function register(pageTypeName, prototype) {
810
- this.repository[pageTypeName] = ConfigurationEditorView.extend(prototype);
811
- }
812
- });
813
-
814
- function template$1(data) {
815
- var __p = '';
816
- __p += '';
817
- return __p
818
- }
819
-
820
- /**
821
- * Base class for table cell views.
822
- *
823
- * Inside sub classes the name of the column options are available as
824
- * `this.options.column`. Override the `update` method to populate the
825
- * element.
826
- *
827
- * @param {Object} [options]
828
- *
829
- * @param {string} [options.className]
830
- * Class attribute to apply to the cell element.
831
- *
832
- * @since 12.0
833
- */
834
-
835
- var TableCellView = Marionette.ItemView.extend({
836
- tagName: 'td',
837
- template: template$1,
838
- className: function className() {
839
- return this.options.className;
840
- },
841
- onRender: function onRender() {
842
- this.listenTo(this.getModel(), 'change:' + this.options.column.name, this.update);
843
- this.setupContentBinding();
844
- this.update();
845
- },
846
-
847
- /**
848
- * Override in concrete cell view.
849
- */
850
- update: function update() {
851
- throw 'Not implemented';
852
- },
853
-
854
- /**
855
- * Returns the column attribute's value in the row model.
856
- */
857
- attributeValue: function attributeValue() {
858
- if (typeof this.options.column.value == 'function') {
859
- return this.options.column.value(this.model);
860
- } else {
861
- return this.getModel().get(this.options.column.name);
862
- }
863
- },
864
- getModel: function getModel() {
865
- if (this.options.column.configurationAttribute) {
866
- return this.model.configuration;
867
- } else {
868
- return this.model;
869
- }
870
- },
871
-
872
- /**
873
- * Look up attribute specific translations based on
874
- * `attributeTranslationKeyPrefixes` of the the parent `TableView`.
875
- *
876
- * @param {Object} [options]
877
- * Interpolations to apply to the translation.
878
- *
879
- * @param {string} [options.defaultValue]
880
- * Fallback value if no translation is found.
881
- *
882
- * @protected
883
- *
884
- * @example
885
- *
886
- * this.attribute.attributeTranslation("cell_title");
887
- * // Looks for keys of the form:
888
- * // <table_view_translation_key_prefix>.<column_attribute>.cell_title
889
- */
890
- attributeTranslation: function attributeTranslation(keyName, options) {
891
- return findTranslation(this.attributeTranslationKeys(keyName), options);
892
- },
893
- attributeTranslationKeys: function attributeTranslationKeys(keyName) {
894
- return _(this.options.attributeTranslationKeyPrefixes || []).map(function (prefix) {
895
- return prefix + '.' + this.options.column.name + '.' + keyName;
896
- }, this);
897
- },
898
-
899
- /**
900
- * Set up content binding to update this view upon change of
901
- * specified attribute on this.getModel().
902
- *
903
- * @param {string} [options.column.contentBinding]
904
- * Name of the attribute to which this cell's update is bound
905
- *
906
- * @protected
907
- */
908
- setupContentBinding: function setupContentBinding() {
909
- if (this.options.column.contentBinding) {
910
- this.listenTo(this.getModel(), 'change:' + this.options.column.contentBinding, this.update);
911
- this.update();
912
- }
913
- }
914
- });
915
-
916
- var TableHeaderCellView = TableCellView.extend({
917
- tagName: 'th',
918
- render: function render() {
919
- this.$el.text(this.options.column.headerText || this.attributeTranslation('column_header'));
920
- this.$el.data('columnName', this.options.column.name);
921
- return this;
922
- }
923
- });
924
-
925
- var TableRowView = Marionette.View.extend({
926
- tagName: 'tr',
927
- events: {
928
- 'click': function click() {
929
- if (this.options.selection) {
930
- this.options.selection.set(this.selectionAttribute(), this.model);
931
- }
932
- }
933
- },
934
- initialize: function initialize() {
935
- if (this.options.selection) {
936
- this.listenTo(this.options.selection, 'change', this.updateClassName);
937
- }
938
- },
939
- render: function render() {
940
- _(this.options.columns).each(function (column) {
941
- this.appendSubview(new column.cellView(_.extend({
942
- model: this.model,
943
- column: column,
944
- attributeTranslationKeyPrefixes: this.options.attributeTranslationKeyPrefixes
945
- }, column.cellViewOptions || {})));
946
- }, this);
947
-
948
- this.updateClassName();
949
- return this;
950
- },
951
- updateClassName: function updateClassName() {
952
- this.$el.toggleClass('is_selected', this.isSelected());
953
- },
954
- isSelected: function isSelected() {
955
- return this.options.selection && this.options.selection.get(this.selectionAttribute()) === this.model;
956
- },
957
- selectionAttribute: function selectionAttribute() {
958
- return this.options.selectionAttribute || 'current';
959
- }
960
- });
961
-
962
- function template$2(data) {
963
- var __p = '';
964
- __p += '<table>\n <thead>\n <tr></tr>\n </thead>\n <tbody>\n </tbody>\n</table>\n';
965
- return __p
966
- }
967
-
968
- function blankSlateTemplate(data) {
969
- var __t, __p = '';
970
- __p += '<td colspan="' +
971
- ((__t = ( data.colSpan )) == null ? '' : __t) +
972
- '">\n ' +
973
- ((__t = ( data.blankSlateText )) == null ? '' : __t) +
974
- '\n</td>\n';
975
- return __p
976
- }
977
-
978
- var TableView = Marionette.ItemView.extend({
979
- tagName: 'table',
980
- className: 'table_view',
981
- template: template$2,
982
- ui: {
983
- headRow: 'thead tr',
984
- body: 'tbody'
985
- },
986
- onRender: function onRender() {
987
- var view = this;
988
-
989
- _(this.options.columns).each(function (column) {
990
- this.ui.headRow.append(this.subview(new TableHeaderCellView({
991
- column: column,
992
- attributeTranslationKeyPrefixes: this.options.attributeTranslationKeyPrefixes
993
- })).el);
994
- }, this);
995
-
996
- this.subview(new CollectionView({
997
- el: this.ui.body,
998
- collection: this.collection,
999
- itemViewConstructor: TableRowView,
1000
- itemViewOptions: {
1001
- columns: this.options.columns,
1002
- selection: this.options.selection,
1003
- selectionAttribute: this.options.selectionAttribute,
1004
- attributeTranslationKeyPrefixes: this.options.attributeTranslationKeyPrefixes
1005
- },
1006
- blankSlateViewConstructor: Marionette.ItemView.extend({
1007
- tagName: 'tr',
1008
- className: 'blank_slate',
1009
- template: blankSlateTemplate,
1010
- serializeData: function serializeData() {
1011
- return {
1012
- blankSlateText: view.options.blankSlateText,
1013
- colSpan: view.options.columns.length
1014
- };
1015
- }
1016
- })
1017
- }));
1018
- }
1019
- });
1020
-
1021
- function template$3(data) {
1022
- var __p = '';
1023
- __p += '<span class="label">\n</span>\n';
1024
- return __p
1025
- }
1026
-
1027
- var TooltipView = Marionette.ItemView.extend({
1028
- template: template$3,
1029
- className: 'tooltip',
1030
- ui: {
1031
- label: '.label'
1032
- },
1033
- hide: function hide() {
1034
- this.visible = false;
1035
- clearTimeout(this.timeout);
1036
- this.$el.removeClass('visible');
1037
- },
1038
- show: function show(text, position, options) {
1039
- options = options || {};
1040
- this.visible = true;
1041
- clearTimeout(this.timeout);
1042
- this.timeout = setTimeout(_.bind(function () {
1043
- var offsetTop;
1044
- var offsetLeft;
1045
- this.ui.label.text(text);
1046
- this.$el.toggleClass('align_bottom_right', options.align === 'bottom right');
1047
- this.$el.toggleClass('align_bottom_left', options.align === 'bottom left');
1048
- this.$el.toggleClass('align_top_center', options.align === 'top center');
1049
-
1050
- if (options.align === 'bottom right' || options.align === 'bottom left') {
1051
- offsetTop = 10;
1052
- offsetLeft = 0;
1053
- } else if (options.align === 'top center') {
1054
- offsetTop = -10;
1055
- offsetLeft = 0;
1056
- } else {
1057
- offsetTop = -17;
1058
- offsetLeft = 10;
1059
- }
1060
-
1061
- this.$el.css({
1062
- top: position.top + offsetTop + 'px',
1063
- left: position.left + offsetLeft + 'px'
1064
- });
1065
- this.$el.addClass('visible');
1066
- }, this), 200);
1067
- }
1068
- });
1069
-
1070
- /**
1071
- * Mixin for input views handling common concerns like labels,
1072
- * inline help, visiblity and disabling.
1073
- *
1074
- * ## Label and Inline Help Translations
1075
- *
1076
- * By default `#labelText` and `#inlineHelpText` are defined through
1077
- * translations. If no `attributeTranslationKeyPrefixes` are given,
1078
- * translation keys for labels and inline help are constructed from
1079
- * the `i18nKey` of the model and the given `propertyName`
1080
- * option. Suppose the model's `i18nKey` is "page" and the
1081
- * `propertyName` option is "title". Then the key
1082
- *
1083
- * activerecord.attributes.page.title
1084
- *
1085
- * will be used for the label. And the key
1086
- *
1087
- * pageflow.ui.inline_help.page.title_html
1088
- * pageflow.ui.inline_help.page.title
1089
- *
1090
- * will be used for the inline help.
1091
- *
1092
- * ### Attribute Translation Key Prefixes
1093
- *
1094
- * The `attributeTranslationKeyPrefixes` option can be used to supply
1095
- * an array of scopes in which label and inline help translations
1096
- * shall be looked up based on the `propertyName` option.
1097
- *
1098
- * Suppose the array `['some.attributes', 'fallback.attributes']` is
1099
- * given as `attributeTranslationKeyPrefixes` option. Then, in the
1100
- * example above, the first existing translation key is used as label:
1101
- *
1102
- * some.attributes.title.label
1103
- * fallback.attributes.title.label
1104
- * activerecord.attributes.post.title
1105
- *
1106
- * Accordingly, for the inline help:
1107
- *
1108
- * some.attributes.title.inline_help_html
1109
- * some.attributes.title.inline_help
1110
- * fallback.attributes.title.inline_help_html
1111
- * fallback.attributes.title.inline_help
1112
- * pageflow.ui.inline_help.post.title_html
1113
- * pageflow.ui.inline_help.post.title
1114
- *
1115
- * This setup allows to keep all translation keys for an attribute
1116
- * to share a common prefix:
1117
- *
1118
- * some:
1119
- * attributes:
1120
- * title:
1121
- * label: "Label"
1122
- * inline_help: "..."
1123
- * inline_help_disabled: "..."
1124
- *
1125
- * ### Inline Help for Disabled Inputs
1126
- *
1127
- * For each inline help translation key, a separate key with an
1128
- * `"_disabled"` suffix can be supplied, which provides a help string
1129
- * that shall be displayed when the input is disabled. More specific
1130
- * attribute translation key prefixes take precedence over suffixed
1131
- * keys:
1132
- *
1133
- * some.attributes.title.inline_help_html
1134
- * some.attributes.title.inline_help
1135
- * some.attributes.title.inline_help_disabled_html
1136
- * some.attributes.title.inline_help_disabled
1137
- * fallback.attributes.title.inline_help_html
1138
- * fallback.attributes.title.inline_help
1139
- * fallback.attributes.title.inline_help_disabled_html
1140
- * fallback.attributes.title.inline_help_disabled
1141
- * pageflow.ui.inline_help.post.title_html
1142
- * pageflow.ui.inline_help.post.title
1143
- * pageflow.ui.inline_help.post.title_disabled_html
1144
- * pageflow.ui.inline_help.post.title_disabled
1145
- *
1146
- * @param {string} options
1147
- * Common constructor options for all views that include this mixin.
1148
- *
1149
- * @param {string} options.propertyName
1150
- * Name of the attribute on the model to display and edit.
1151
- *
1152
- * @param {string} [options.label]
1153
- * Label text for the input.
1154
- *
1155
- * @param {string[]} [options.attributeTranslationKeyPrefixes]
1156
- * An array of prefixes to lookup translations for labels and
1157
- * inline help texts based on attribute names.
1158
- *
1159
- * @param {string} [options.additionalInlineHelpText]
1160
- * A text that will be appended to the translation based inline
1161
- * text.
1162
- *
1163
- * @param {string|string[]} [options.disabledBinding]
1164
- * Name of an attribute to control whether the input is disabled. If
1165
- * the `disabled` and `disabledBinding` options are not set,
1166
- * input will be disabled whenever this attribute has a truthy value.
1167
- * When multiple attribute names are passed, the function passed to
1168
- * the `disabled` option will receive an array of values in the same
1169
- * order.
1170
- *
1171
- * @param {function|boolean} [options.disabled]
1172
- * Render input as disabled. A Function taking the value of the
1173
- * `disabledBinding` attribute as parameter. Input will be disabled
1174
- * only if function returns `true`.
1175
- *
1176
- * @param {any} [options.disabledBindingValue]
1177
- * Input will be disabled whenever the value of the `disabledBinding`
1178
- * attribute equals the value of this option.
1179
- *
1180
- * @param {string|string[]} [options.visibleBinding]
1181
- * Name of an attribute to control whether the input is visible. If
1182
- * the `visible` and `visibleBindingValue` options are not set,
1183
- * input will be visible whenever this attribute has a truthy value.
1184
- * When multiple attribute names are passed, the function passed to
1185
- * the `visible` option will receive an array of values in the same
1186
- * order.
1187
- *
1188
- * @param {function|boolean} [options.visible]
1189
- * A Function taking the value of the `visibleBinding` attribute as
1190
- * parameter. Input will be visible only if function returns `true`.
1191
- *
1192
- * @param {any} [options.visibleBindingValue]
1193
- * Input will be visible whenever the value of the `visibleBinding`
1194
- * attribute equals the value of this option.
1195
- *
1196
- * @mixin
1197
- */
1198
-
1199
- var inputView = {
1200
- ui: {
1201
- label: 'label',
1202
- labelText: 'label .name',
1203
- inlineHelp: 'label .inline_help'
1204
- },
1205
-
1206
- /**
1207
- * Returns an array of translation keys based on the
1208
- * `attributeTranslationKeyPrefixes` option and the given keyName.
1209
- *
1210
- * Combined with {@link #i18nutils
1211
- * i18nUtils.findTranslation}, this can be used inside input views
1212
- * to obtain additional translations with the same logic as for
1213
- * labels and inline help texts.
1214
- *
1215
- * findTranslation(this.attributeTranslationKeys('default_value'));
1216
- *
1217
- * @param {string} keyName
1218
- * Suffix to append to prefixes.
1219
- *
1220
- * @param {string} [options.fallbackPrefix]
1221
- * Optional additional prefix to form a model based translation
1222
- * key of the form `prefix.modelI18nKey.propertyName.keyName
1223
- *
1224
- * @return {string[]}
1225
- * @since 0.9
1226
- * @member
1227
- */
1228
- attributeTranslationKeys: function attributeTranslationKeys$1(keyName, options) {
1229
- return attributeTranslationKeys(this.options.propertyName, keyName, _.extend({
1230
- prefixes: this.options.attributeTranslationKeyPrefixes,
1231
- fallbackModelI18nKey: this.model.i18nKey
1232
- }, options || {}));
1233
- },
1234
- onRender: function onRender() {
1235
- this.$el.addClass('input');
1236
- this.$el.addClass(this.model.modelName + '_' + this.options.propertyName);
1237
- this.$el.data('inputPropertyName', this.options.propertyName);
1238
- this.$el.data('labelText', this.labelText());
1239
- this.$el.data('inlineHelpText', this.inlineHelpText());
1240
- this.ui.labelText.text(this.labelText());
1241
- this.updateInlineHelp();
1242
- this.setLabelFor();
1243
- this.setupAttributeBinding('disabled', this.updateDisabled);
1244
- this.setupAttributeBinding('visible', this.updateVisible);
1245
- },
1246
-
1247
- /**
1248
- * The label to display in the form.
1249
- * @return {string}
1250
- */
1251
- labelText: function labelText() {
1252
- return this.options.label || this.localizedAttributeName();
1253
- },
1254
- localizedAttributeName: function localizedAttributeName() {
1255
- return findTranslation(this.attributeTranslationKeys('label', {
1256
- fallbackPrefix: 'activerecord.attributes'
1257
- }));
1258
- },
1259
- updateInlineHelp: function updateInlineHelp() {
1260
- this.ui.inlineHelp.html(this.inlineHelpText());
1261
-
1262
- if (!this.inlineHelpText()) {
1263
- this.ui.inlineHelp.hide();
1264
- }
1265
- },
1266
-
1267
- /**
1268
- * The inline help text for the form field.
1269
- * @return {string}
1270
- */
1271
- inlineHelpText: function inlineHelpText() {
1272
- var keys = this.attributeTranslationKeys('inline_help', {
1273
- fallbackPrefix: 'pageflow.ui.inline_help'
1274
- });
1275
-
1276
- if (this.isDisabled()) {
1277
- keys = translationKeysWithSuffix(keys, 'disabled');
1278
- }
1279
-
1280
- return _.compact([findTranslation(keys, {
1281
- defaultValue: '',
1282
- html: true
1283
- }), this.options.additionalInlineHelpText]).join(' ');
1284
- },
1285
- setLabelFor: function setLabelFor() {
1286
- if (this.ui.input && this.ui.label.length === 1 && !this.ui.input.attr('id')) {
1287
- var id = 'input_' + this.model.modelName + '_' + this.options.propertyName;
1288
- this.ui.input.attr('id', id);
1289
- this.ui.label.attr('for', id);
1290
- }
1291
- },
1292
- isDisabled: function isDisabled() {
1293
- return this.getAttributeBoundOption('disabled');
1294
- },
1295
- updateDisabled: function updateDisabled() {
1296
- this.$el.toggleClass('input-disabled', !!this.isDisabled());
1297
- this.updateInlineHelp();
1298
-
1299
- if (this.ui.input) {
1300
- this.updateDisabledAttribute(this.ui.input);
1301
- }
1302
- },
1303
- updateDisabledAttribute: function updateDisabledAttribute(element) {
1304
- if (this.isDisabled()) {
1305
- element.attr('disabled', true);
1306
- } else {
1307
- element.removeAttr('disabled');
1308
- }
1309
- },
1310
- updateVisible: function updateVisible() {
1311
- this.$el.toggleClass('input-hidden_via_binding', this.getAttributeBoundOption('visible') === false);
1312
- },
1313
- setupAttributeBinding: function setupAttributeBinding(optionName, updateMethod) {
1314
- var _this = this;
1315
-
1316
- var binding = this.options["".concat(optionName, "Binding")];
1317
- var view = this;
1318
-
1319
- if (binding) {
1320
- _.flatten([binding]).forEach(function (attribute) {
1321
- _this.listenTo(_this.model, 'change:' + attribute, update);
1322
- });
1323
- }
1324
-
1325
- update();
1326
-
1327
- function update() {
1328
- updateMethod.call(view, view.getAttributeBoundOption(optionName));
1329
- }
1330
- },
1331
- getAttributeBoundOption: function getAttributeBoundOption(optionName) {
1332
- var _this2 = this;
1333
-
1334
- var binding = this.options["".concat(optionName, "Binding")];
1335
- var bindingValueOptionName = "".concat(optionName, "BindingValue");
1336
- var value = Array.isArray(binding) ? binding.map(function (attribute) {
1337
- return _this2.model.get(attribute);
1338
- }) : this.model.get(binding);
1339
-
1340
- if (bindingValueOptionName in this.options) {
1341
- return value === this.options[bindingValueOptionName];
1342
- } else if (typeof this.options[optionName] === 'function') {
1343
- return !!this.options[optionName](value);
1344
- } else if (optionName in this.options) {
1345
- return !!this.options[optionName];
1346
- } else if (binding) {
1347
- return !!value;
1348
- }
1349
- }
1350
- };
1351
-
1352
- function template$4(data) {
1353
- var __p = '';
1354
- __p += '<label>\n <span class="name"></span>\n <span class="inline_help"></span>\n</label>\n<div class="check_boxes_container" />\n';
1355
- return __p
1356
- }
1357
-
1358
- /**
1359
- * Input view for attributes storing configuration hashes with boolean values.
1360
- * See {@link inputView} for further options.
1361
- *
1362
- * @param {Object} [options]
1363
- *
1364
- * @class
1365
- */
1366
-
1367
- var CheckBoxGroupInputView = Marionette.ItemView.extend({
1368
- mixins: [inputView],
1369
- template: template$4,
1370
- className: 'check_box_group_input',
1371
- events: {
1372
- 'change': 'save'
1373
- },
1374
- ui: {
1375
- label: 'label',
1376
- container: '.check_boxes_container'
1377
- },
1378
- initialize: function initialize() {
1379
- if (!this.options.texts) {
1380
- if (!this.options.translationKeys) {
1381
- var translationKeyPrefix = this.options.translationKeyPrefix || findKeyWithTranslation(this.attributeTranslationKeys('values', {
1382
- fallbackPrefix: 'activerecord.values'
1383
- }));
1384
- this.options.translationKeys = _.map(this.options.values, function (value) {
1385
- return translationKeyPrefix + '.' + value;
1386
- }, this);
1387
- }
1388
-
1389
- this.options.texts = _.map(this.options.translationKeys, function (key) {
1390
- return I18n$1.t(key);
1391
- });
1392
- }
1393
- },
1394
- onRender: function onRender() {
1395
- this.ui.label.attr('for', this.cid);
1396
- this.appendOptions();
1397
- this.load();
1398
- this.listenTo(this.model, 'change:' + this.options.propertyName, this.load);
1399
- },
1400
- appendOptions: function appendOptions() {
1401
- _.each(this.options.values, function (value, index) {
1402
- var option = '<div class="check_box">' + '<label><input type="checkbox" name="' + value + '" />' + this.options.texts[index] + '</label></div>';
1403
- this.ui.container.append($(option));
1404
- }, this);
1405
- },
1406
- save: function save() {
1407
- var configured = {};
1408
-
1409
- _.each(this.ui.container.find('input'), function (input) {
1410
- configured[$(input).attr('name')] = $(input).prop('checked');
1411
- });
1412
-
1413
- this.model.set(this.options.propertyName, configured);
1414
- },
1415
- load: function load() {
1416
- if (!this.isClosed) {
1417
- _.each(this.options.values, function (value) {
1418
- this.ui.container.find('input[name="' + value + '"]').prop('checked', this.model.get(this.options.propertyName)[value]);
1419
- }, this);
1420
- }
1421
- }
1422
- });
1423
-
1424
- function template$5(data) {
1425
- var __t, __p = '';
1426
- __p += '<label>\n <span class="name"></span>\n <span class="inline_help"></span>\n</label>\n<a class="original" href="#" download target="_blank">\n ' +
1427
- ((__t = ( I18n.t('pageflow.ui.templates.inputs.url_display.link_text') )) == null ? '' : __t) +
1428
- '\n</a>\n';
1429
- return __p
1430
- }
1431
-
1432
- /**
1433
- * Display view for a link to a URL, to be used like an input view.
1434
- * See {@link inputView} for further options
1435
- *
1436
- * @param {Object} [options]
1437
- *
1438
- * @param {string} [options.propertyName]
1439
- * Target URL for link
1440
- *
1441
- * @class
1442
- */
1443
-
1444
- var UrlDisplayView = Marionette.ItemView.extend({
1445
- mixins: [inputView],
1446
- template: template$5,
1447
- ui: {
1448
- link: 'a'
1449
- },
1450
- modelEvents: {
1451
- 'change': 'update'
1452
- },
1453
- events: {
1454
- 'click a': function clickA(event) {
1455
- // Ensure default is not prevented by parent event listener.
1456
- event.stopPropagation();
1457
- }
1458
- },
1459
- onRender: function onRender() {
1460
- this.update();
1461
- },
1462
- update: function update() {
1463
- var url = this.model.get('original_url');
1464
- this.$el.toggle(this.model.isUploaded() && !_.isEmpty(url));
1465
- this.ui.link.attr('href', url);
1466
- }
1467
- });
1468
-
1469
- /**
1470
- * Text based input view that can display a placeholder.
1471
- *
1472
- * @param {Object} [options]
1473
- *
1474
- * @param {string|function} [options.placeholder]
1475
- * Display a placeholder string if the input is blank. Either a
1476
- * string or a function taking the model as a first parameter and
1477
- * returning a string.
1478
- *
1479
- * @param {string} [options.placeholderBinding]
1480
- * Name of an attribute. Recompute the placeholder function whenever
1481
- * this attribute changes.
1482
- *
1483
- * @param {boolean} [options.hidePlaceholderIfDisabled]
1484
- * Do not display the placeholder if the input is disabled.
1485
- *
1486
- * @param {Backbone.Model} [options.placeholderModel]
1487
- * Obtain placeholder by looking up the configured `propertyName`
1488
- * inside a given model.
1489
- */
1490
- var inputWithPlaceholderText = {
1491
- onRender: function onRender() {
1492
- this.updatePlaceholder();
1493
-
1494
- if (this.options.placeholderBinding) {
1495
- this.listenTo(this.model, 'change:' + this.options.placeholderBinding, this.updatePlaceholder);
1496
- }
1497
- },
1498
- updateDisabled: function updateDisabled() {
1499
- this.updatePlaceholder();
1500
- },
1501
- updatePlaceholder: function updatePlaceholder() {
1502
- this.ui.input.attr('placeholder', this.placeholderText());
1503
- },
1504
- placeholderText: function placeholderText() {
1505
- if (!this.isDisabled() || !this.options.hidePlaceholderIfDisabled) {
1506
- if (this.options.placeholder) {
1507
- if (typeof this.options.placeholder == 'function') {
1508
- return this.options.placeholder(this.model);
1509
- } else {
1510
- return this.options.placeholder;
1511
- }
1512
- } else {
1513
- return this.placeholderModelValue();
1514
- }
1515
- }
1516
-
1517
- return '';
1518
- },
1519
- placeholderModelValue: function placeholderModelValue() {
1520
- return this.options.placeholderModel && this.options.placeholderModel.get(this.options.propertyName);
1521
- }
1522
- };
1523
-
1524
- var viewWithValidationErrorMessages = {
1525
- onRender: function onRender() {
1526
- this.listenTo(this.model, 'invalid sync', this.updateValidationErrorMessages);
1527
- this.updateValidationErrorMessages();
1528
- },
1529
- updateValidationErrorMessages: function updateValidationErrorMessages() {
1530
- var _this = this;
1531
-
1532
- var errors = this.model.validationErrors && this.model.validationErrors[this.options.propertyName] || [];
1533
-
1534
- if (errors.length) {
1535
- this.validationErrorList = this.validationErrorList || $('<ul class="validation_error_messages" />').appendTo(this.el);
1536
- this.validationErrorList.html('');
1537
- errors.forEach(function (error) {
1538
- return _this.validationErrorList.append("<li>".concat(error, "</li>"));
1539
- });
1540
- this.$el.addClass('invalid');
1541
- } else if (this.validationErrorList) {
1542
- this.validationErrorList.remove();
1543
- this.validationErrorList = null;
1544
- this.$el.removeClass('invalid');
1545
- }
1546
- }
1547
- };
1548
-
1549
- function template$6(data) {
1550
- var __p = '';
1551
- __p += '<label>\n <span class="name"></span>\n <span class="inline_help"></span>\n</label>\n<input type="text" dir="auto" />\n';
1552
- return __p
1553
- }
1554
-
1555
- /**
1556
- * Input view for a single line of text.
1557
- *
1558
- * See {@link inputWithPlaceholderText} for placeholder related
1559
- * further options. See {@link inputView} for further options.
1560
- *
1561
- * @param {Object} [options]
1562
- *
1563
- * @param {boolean} [options.required=false]
1564
- * Display an error if the input is blank.
1565
- *
1566
- * @param {number} [options.maxLength=255]
1567
- * Maximum length of characters for this input. To support legacy
1568
- * data which consists of more characters than the specified
1569
- * maxLength, the option will only take effect for data which is
1570
- * shorter than the specified maxLength.
1571
- *
1572
- * @class
1573
- */
1574
-
1575
- var TextInputView = Marionette.ItemView.extend({
1576
- mixins: [inputView, inputWithPlaceholderText, viewWithValidationErrorMessages],
1577
- template: template$6,
1578
- ui: {
1579
- input: 'input'
1580
- },
1581
- events: {
1582
- 'change': 'onChange'
1583
- },
1584
- onRender: function onRender() {
1585
- this.load();
1586
- this.validate();
1587
- this.listenTo(this.model, 'change:' + this.options.propertyName, this.load);
1588
- },
1589
- onChange: function onChange() {
1590
- if (this.validate()) {
1591
- this.save();
1592
- }
1593
- },
1594
- onClose: function onClose() {
1595
- if (this.validate()) {
1596
- this.save();
1597
- }
1598
- },
1599
- save: function save() {
1600
- this.model.set(this.options.propertyName, this.ui.input.val());
1601
- },
1602
- load: function load() {
1603
- var input = this.ui.input;
1604
- input.val(this.model.get(this.options.propertyName)); // set mysql varchar length as default for non-legacy data
1605
-
1606
- this.options.maxLength = this.options.maxLength || 255; // do not validate legacy data which length exceeds the specified maximum
1607
- // for new and maxLength-conforming data: add validation
1608
-
1609
- this.validateMaxLength = input.val().length <= this.options.maxLength;
1610
- },
1611
- validate: function validate() {
1612
- var input = this.ui.input;
1613
-
1614
- if (this.options.required && !input.val()) {
1615
- this.displayValidationError(I18n$1.t('pageflow.ui.views.inputs.text_input_view.required_field'));
1616
- return false;
1617
- }
1618
-
1619
- if (this.validateMaxLength && input.val().length > this.options.maxLength) {
1620
- this.displayValidationError(I18n$1.t('pageflow.ui.views.inputs.text_input_view.max_characters_exceeded', {
1621
- max_length: this.options.maxLength
1622
- }));
1623
- return false;
1624
- } else {
1625
- this.resetValidationError();
1626
- return true;
1627
- }
1628
- },
1629
- displayValidationError: function displayValidationError(message) {
1630
- this.$el.addClass('invalid');
1631
- this.ui.input.attr('title', message);
1632
- },
1633
- resetValidationError: function resetValidationError(message) {
1634
- this.$el.removeClass('invalid');
1635
- this.ui.input.attr('title', '');
1636
- }
1637
- });
1638
-
1639
- /**
1640
- * Input view for a color value in hex representation.
1641
- * See {@link inputView} for further options
1642
- *
1643
- * @param {Object} [options]
1644
- *
1645
- * @param {string|function} [options.defaultValue]
1646
- * Color value to display by default. The corresponding value is not
1647
- * stored in the model. Selecting the default value when a different
1648
- * value was set before, unsets the attribute in the model.
1649
- *
1650
- * @param {string} [options.defaultValueBinding]
1651
- * Name of an attribute the default value depends on. If a function
1652
- * is used as defaultValue option, it will be passed the value of the
1653
- * defaultValueBinding attribute each time it changes. If no
1654
- * defaultValue option is set, the value of the defaultValueBinding
1655
- * attribute will be used as default value.
1656
- *
1657
- * @param {string[]} [options.swatches]
1658
- * Preset color values to be displayed inside the picker drop
1659
- * down. The default value, if present, is always used as the
1660
- * first swatch automatically.
1661
- *
1662
- * @class
1663
- */
1664
-
1665
- var ColorInputView = Marionette.ItemView.extend({
1666
- mixins: [inputView],
1667
- template: template$6,
1668
- className: 'color_input',
1669
- ui: {
1670
- input: 'input'
1671
- },
1672
- events: {
1673
- 'mousedown': 'refreshPicker'
1674
- },
1675
- onRender: function onRender() {
1676
- this.ui.input.minicolors({
1677
- changeDelay: 200,
1678
- change: _.bind(function (color) {
1679
- if (color === this.defaultValue()) {
1680
- this.model.unset(this.options.propertyName);
1681
- } else {
1682
- this.model.set(this.options.propertyName, color);
1683
- }
1684
- }, this)
1685
- });
1686
- this.listenTo(this.model, 'change:' + this.options.propertyName, this.load);
1687
-
1688
- if (this.options.defaultValueBinding) {
1689
- this.listenTo(this.model, 'change:' + this.options.defaultValueBinding, this.updateSettings);
1690
- }
1691
-
1692
- this.updateSettings();
1693
- },
1694
- updateSettings: function updateSettings() {
1695
- this.resetSwatchesInStoredSettings();
1696
- this.ui.input.minicolors('settings', {
1697
- defaultValue: this.defaultValue(),
1698
- swatches: this.getSwatches()
1699
- });
1700
- this.load();
1701
- },
1702
- // see https://github.com/claviska/jquery-minicolors/issues/287
1703
- resetSwatchesInStoredSettings: function resetSwatchesInStoredSettings() {
1704
- var settings = this.ui.input.data('minicolors-settings');
1705
-
1706
- if (settings) {
1707
- delete settings.swatches;
1708
- this.ui.input.data('minicolors-settings', settings);
1709
- }
1710
- },
1711
- load: function load() {
1712
- this.ui.input.minicolors('value', this.model.get(this.options.propertyName) || this.defaultValue());
1713
- this.$el.toggleClass('is_default', !this.model.has(this.options.propertyName));
1714
- },
1715
- refreshPicker: function refreshPicker() {
1716
- this.ui.input.minicolors('value', {});
1717
- },
1718
- getSwatches: function getSwatches() {
1719
- return _.chain([this.defaultValue(), this.options.swatches]).flatten().uniq().compact().value();
1720
- },
1721
- defaultValue: function defaultValue() {
1722
- var bindingValue;
1723
-
1724
- if (this.options.defaultValueBinding) {
1725
- bindingValue = this.model.get(this.options.defaultValueBinding);
1726
- }
1727
-
1728
- if (typeof this.options.defaultValue === 'function') {
1729
- return this.options.defaultValue(bindingValue);
1730
- } else if ('defaultValue' in this.options) {
1731
- return this.options.defaultValue;
1732
- } else {
1733
- return bindingValue;
1734
- }
1735
- }
1736
- });
1737
-
1738
- function template$7(data) {
1739
- var __p = '';
1740
- __p += '<label>\n <span class="name"></span>\n <span class="inline_help"></span>\n</label>\n<select></select>';
1741
- return __p
1742
- }
1743
-
1744
- /**
1745
- * A drop down with support for grouped items.
1746
- * See {@link inputView} for further options
1747
- *
1748
- * @param {Object} [options]
1749
- *
1750
- * @param {string[]} [options.values]
1751
- * List of possible values to persist in the attribute.
1752
- *
1753
- * @param {string[]} [options.texts]
1754
- * List of display texts for drop down items.
1755
- *
1756
- * @param {string[]} [options.translationKeys]
1757
- * Translation keys to obtain item texts from.
1758
- *
1759
- * @param {string[]} [options.translationKeyPrefix]
1760
- * Obtain texts for items from translations by appending the item
1761
- * value to this prefix separated by a dot. By default the
1762
- * [`attributeTranslationKeyPrefixes` option]{@link inputView}
1763
- * is used by appending the suffix `.values` to each candidate.
1764
- *
1765
- * @param {string[]} [options.groups]
1766
- * Array of same length as `values` array, containing the display
1767
- * name of a group header each item shall be grouped under.
1768
- *
1769
- * @param {Backbone.Model[]} [options.collection]
1770
- * Create items for each model in the collection. Use the
1771
- * `*Property` options to extract values and texts for each items
1772
- * from the models.
1773
- *
1774
- * @param {string} [options.valueProperty]
1775
- * Attribute to use as item value.
1776
- *
1777
- * @param {string} [options.textProperty]
1778
- * Attribute to use as item display text.
1779
- *
1780
- * @param {string} [options.groupProperty]
1781
- * Attribute to use as item group name.
1782
- *
1783
- * @param {string} [options.translationKeyProperty]
1784
- * Attribute to use as translation key to obtain display text.
1785
- *
1786
- * @param {string} [options.groupTranslationKeyProperty]
1787
- * Attribute to use as translation key to obtain group name.
1788
- *
1789
- * @param {boolean} [options.ensureValueDefined]
1790
- * Set the attribute to the first value on view creation.
1791
- *
1792
- * @param {boolean} [options.includeBlank]
1793
- * Include an item that sets the value of the attribute to a blank
1794
- * string.
1795
- *
1796
- * @param {string} [options.blankText]
1797
- * Display text for the blank item.
1798
- *
1799
- * @param {string} [options.blankTranslationKey]
1800
- * Translation key to obtain display text for blank item.
1801
- *
1802
- * @param {string} [options.placeholderValue]
1803
- * Include an item that sets the value of the attribute to a blank
1804
- * string and indicate that the attribute is set to a default
1805
- * value. Include the display name of the given value, in the
1806
- * text. This option can be used if a fallback to the
1807
- * `placeholderValue` occurs whenever the attribute is blank.
1808
- *
1809
- * @param {Backbone.Model} [options.placeholderModel]
1810
- * Behaves like `placeholderValue`, but obtains the value by looking
1811
- * up the `propertyName` attribute inside the given model. This
1812
- * option can be used if a fallback to the corresponding attribute
1813
- * value of the `placeholderModel` occurs whenever the attribute is
1814
- * blank.
1815
- *
1816
- * @param {function} [options.optionDisabled]
1817
- * Receives value and has to return boolean indicating whether
1818
- * option is disabled.
1819
- *
1820
- * @class
1821
- */
1822
-
1823
- var SelectInputView = Marionette.ItemView.extend({
1824
- mixins: [inputView],
1825
- template: template$7,
1826
- events: {
1827
- 'change': 'save'
1828
- },
1829
- ui: {
1830
- select: 'select',
1831
- input: 'select'
1832
- },
1833
- initialize: function initialize() {
1834
- if (this.options.collection) {
1835
- this.options.values = _.pluck(this.options.collection, this.options.valueProperty);
1836
-
1837
- if (this.options.textProperty) {
1838
- this.options.texts = _.pluck(this.options.collection, this.options.textProperty);
1839
- } else if (this.options.translationKeyProperty) {
1840
- this.options.translationKeys = _.pluck(this.options.collection, this.options.translationKeyProperty);
1841
- }
1842
-
1843
- if (this.options.groupProperty) {
1844
- this.options.groups = _.pluck(this.options.collection, this.options.groupProperty);
1845
- } else if (this.options.groupTranslationKeyProperty) {
1846
- this.options.groupTanslationKeys = _.pluck(this.options.collection, this.options.groupTranslationKeyProperty);
1847
- }
1848
- }
1849
-
1850
- if (!this.options.texts) {
1851
- if (!this.options.translationKeys) {
1852
- var translationKeyPrefix = this.options.translationKeyPrefix || findKeyWithTranslation(this.attributeTranslationKeys('values', {
1853
- fallbackPrefix: 'activerecord.values'
1854
- }));
1855
- this.options.translationKeys = _.map(this.options.values, function (value) {
1856
- return translationKeyPrefix + '.' + value;
1857
- }, this);
1858
- }
1859
-
1860
- this.options.texts = _.map(this.options.translationKeys, function (key) {
1861
- return I18n$1.t(key);
1862
- });
1863
- }
1864
-
1865
- if (!this.options.groups) {
1866
- this.options.groups = _.map(this.options.groupTanslationKeys, function (key) {
1867
- return I18n$1.t(key);
1868
- });
1869
- }
1870
-
1871
- this.optGroups = {};
1872
- },
1873
- onRender: function onRender() {
1874
- this.appendBlank();
1875
- this.appendPlaceholder();
1876
- this.appendOptions();
1877
- this.load();
1878
- this.listenTo(this.model, 'change:' + this.options.propertyName, this.load);
1879
-
1880
- if (this.options.ensureValueDefined && !this.model.has(this.options.propertyName)) {
1881
- this.save();
1882
- }
1883
- },
1884
- appendBlank: function appendBlank() {
1885
- if (!this.options.includeBlank) {
1886
- return;
1887
- }
1888
-
1889
- if (this.options.blankTranslationKey) {
1890
- this.options.blankText = I18n$1.t(this.options.blankTranslationKey);
1891
- }
1892
-
1893
- var option = document.createElement('option');
1894
- option.value = '';
1895
- option.text = this.options.blankText || I18n$1.t('pageflow.ui.views.inputs.select_input_view.none');
1896
- this.ui.select.append(option);
1897
- },
1898
- appendPlaceholder: function appendPlaceholder() {
1899
- if (!this.options.placeholderModel && !this.options.placeholderValue) {
1900
- return;
1901
- }
1902
-
1903
- var placeholderValue = this.options.placeholderValue || this.options.placeholderModel.get(this.options.propertyName);
1904
- var placeholderIndex = this.options.values.indexOf(placeholderValue);
1905
-
1906
- if (placeholderIndex >= 0) {
1907
- var option = document.createElement('option');
1908
- option.value = '';
1909
- option.text = I18n$1.t('pageflow.ui.views.inputs.select_input_view.placeholder', {
1910
- text: this.options.texts[placeholderIndex]
1911
- });
1912
- this.ui.select.append(option);
1913
- }
1914
- },
1915
- appendOptions: function appendOptions() {
1916
- _.each(this.options.values, function (value, index) {
1917
- var option = document.createElement('option');
1918
- var group = this.options.groups[index];
1919
- option.value = value;
1920
- option.text = this.options.texts[index];
1921
-
1922
- if (this.options.optionDisabled && this.options.optionDisabled(value)) {
1923
- option.setAttribute('disabled', true);
1924
- }
1925
-
1926
- if (group) {
1927
- option.setAttribute('data-group', group);
1928
- this.findOrCreateOptGroup(group).append(option);
1929
- } else {
1930
- this.ui.select.append(option);
1931
- }
1932
- }, this);
1933
- },
1934
- findOrCreateOptGroup: function findOrCreateOptGroup(label) {
1935
- if (!this.optGroups[label]) {
1936
- this.optGroups[label] = $('<optgroup />', {
1937
- label: label
1938
- }).appendTo(this.ui.select);
1939
- }
1940
-
1941
- return this.optGroups[label];
1942
- },
1943
- save: function save() {
1944
- this.model.set(this.options.propertyName, this.ui.select.val());
1945
- },
1946
- load: function load() {
1947
- if (!this.isClosed) {
1948
- var value = this.model.get(this.options.propertyName);
1949
-
1950
- if (this.model.has(this.options.propertyName) && this.ui.select.find('option[value="' + value + '"]:not([disabled])').length) {
1951
- this.ui.select.val(value);
1952
- } else {
1953
- this.ui.select.val(this.ui.select.find('option:not([disabled]):first').val());
1954
- }
1955
- }
1956
- }
1957
- });
1958
-
1959
- var ExtendedSelectInputView = SelectInputView.extend({
1960
- className: 'extended_select_input',
1961
- initialize: function initialize() {
1962
- SelectInputView.prototype.initialize.apply(this, arguments);
1963
-
1964
- if (this.options.collection) {
1965
- if (this.options.descriptionProperty) {
1966
- this.options.descriptions = _.pluck(this.options.collection, this.options.descriptionProperty);
1967
- } else if (this.options.descriptionTranslationKeyProperty) {
1968
- this.options.descriptionTanslationKeys = _.pluck(this.options.collection, this.options.descriptionTranslationKeyProperty);
1969
- }
1970
- }
1971
-
1972
- if (!this.options.descriptions) {
1973
- this.options.descriptions = _.map(this.options.descriptionTanslationKeys, function (key) {
1974
- return I18n$1.t(key);
1975
- });
1976
- }
1977
- },
1978
- onRender: function onRender() {
1979
- var view = this,
1980
- options = this.options;
1981
- SelectInputView.prototype.onRender.apply(this, arguments);
1982
- $.widget("custom.extendedselectmenu", $.ui.selectmenu, {
1983
- _renderItem: function _renderItem(ul, item) {
1984
- var widget = this;
1985
- var li = $('<li>', {
1986
- "class": item.value
1987
- });
1988
- var container = $('<div>', {
1989
- "class": 'text-container'
1990
- }).appendTo(li);
1991
- var index = options.values.indexOf(item.value);
1992
-
1993
- if (item.disabled) {
1994
- li.addClass('ui-state-disabled');
1995
- }
1996
-
1997
- if (options.pictogramClass) {
1998
- $('<span>', {
1999
- "class": options.pictogramClass
2000
- }).prependTo(li);
2001
- }
2002
-
2003
- $('<p>', {
2004
- text: item.label,
2005
- "class": 'item-text'
2006
- }).appendTo(container);
2007
- $('<p>', {
2008
- text: options.descriptions[index],
2009
- "class": 'item-description'
2010
- }).appendTo(container);
2011
-
2012
- if (options.helpLinkClicked) {
2013
- $('<a>', {
2014
- href: '#',
2015
- title: I18n$1.t('pageflow.ui.views.extended_select_input_view.display_help')
2016
- }).on('click', function () {
2017
- widget.close();
2018
- options.helpLinkClicked(item.value);
2019
- return false;
2020
- }).appendTo(li);
2021
- }
2022
-
2023
- return li.appendTo(ul);
2024
- },
2025
- _resizeMenu: function _resizeMenu() {
2026
- this.menuWrap.addClass('extended_select_input_menu');
2027
- var menuHeight = this.menu.height(),
2028
- menuOffset = this.button.offset().top + this.button.outerHeight(),
2029
- bodyHeight = $('body').height();
2030
-
2031
- if (menuHeight + menuOffset > bodyHeight) {
2032
- this.menuWrap.outerHeight(bodyHeight - menuOffset - 5).css({
2033
- 'overflow-y': 'scroll'
2034
- });
2035
- } else {
2036
- this.menuWrap.css({
2037
- height: 'initial',
2038
- 'overflow-y': 'initial'
2039
- });
2040
- }
2041
- }
2042
- });
2043
- this.ui.select.extendedselectmenu({
2044
- select: view.select.bind(view),
2045
- width: '100%',
2046
- position: {
2047
- my: 'right top',
2048
- at: 'right bottom'
2049
- }
2050
- });
2051
- },
2052
- select: function select(event, ui) {
2053
- this.ui.select.val(ui.item.value);
2054
- this.save();
2055
- }
2056
- });
2057
-
2058
- function template$8(data) {
2059
- var __t, __p = '';
2060
- __p += '<label>\n <span class="name"></span>\n <span class="inline_help"></span>\n</label>\n\n<!-- inline style for wysihtml5 to pick up -->\n<textarea style="width: 100%;" dir="auto"></textarea>\n\n<div class="toolbar">\n <a data-wysihtml5-command="bold" title="' +
2061
- ((__t = ( I18n.t('pageflow.ui.templates.inputs.text_area_input.bold') )) == null ? '' : __t) +
2062
- '"></a>\n <a data-wysihtml5-command="italic" title="' +
2063
- ((__t = ( I18n.t('pageflow.ui.templates.inputs.text_area_input.italic') )) == null ? '' : __t) +
2064
- '"></a>\n <a data-wysihtml5-command="underline" title="' +
2065
- ((__t = ( I18n.t('pageflow.ui.templates.inputs.text_area_input.underline') )) == null ? '' : __t) +
2066
- '"></a>\n <a data-wysihtml5-command="createLink" class="link_button" title="' +
2067
- ((__t = ( I18n.t('pageflow.ui.templates.inputs.text_area_input.create_link') )) == null ? '' : __t) +
2068
- '"></a>\n <a data-wysihtml5-command="insertOrderedList" title="' +
2069
- ((__t = ( I18n.t('pageflow.ui.templates.inputs.text_area_input.insert_ordered_list') )) == null ? '' : __t) +
2070
- '"></a>\n <a data-wysihtml5-command="insertUnorderedList" title="' +
2071
- ((__t = ( I18n.t('pageflow.ui.templates.inputs.text_area_input.insert_unordered_list') )) == null ? '' : __t) +
2072
- '"></a>\n\n <div data-wysihtml5-dialog="createLink" class="dialog link_dialog" style="display: none;">\n <div class="link_type_select">\n <label>\n <input type="radio" name="link_type" class="url_link_radio_button">\n ' +
2073
- ((__t = ( I18n.t('pageflow.ui.templates.inputs.text_area_input.link_type.url') )) == null ? '' : __t) +
2074
- '\n </label>\n <label>\n <input type="radio" name="link_type" class="fragment_link_radio_button">\n ' +
2075
- ((__t = ( I18n.t('pageflow.ui.templates.inputs.text_area_input.link_type.page_link') )) == null ? '' : __t) +
2076
- '\n </label>\n </div>\n <div class="url_link_panel">\n <label>\n <span>\n ' +
2077
- ((__t = ( I18n.t('pageflow.ui.templates.inputs.text_area_input.target') )) == null ? '' : __t) +
2078
- '\n </span>\n </label>\n <input type="text" class="display_url">\n <div class="open_in_new_tab_section">\n <label>\n <input type="checkbox" class="open_in_new_tab">\n ' +
2079
- ((__t = ( I18n.t('pageflow.ui.templates.inputs.text_area_input.open_in_new_tab') )) == null ? '' : __t) +
2080
- '\n </label>\n <span class="inline_help">\n ' +
2081
- ((__t = ( I18n.t('pageflow.ui.templates.inputs.text_area_input.open_in_new_tab_help') )) == null ? '' : __t) +
2082
- '\n </span>\n </div>\n </div>\n <div class="fragment_link_panel">\n <!-- LinkInputView is inserted here -->\n </div>\n\n <!-- wysihtml5 does not handle hidden fields correctly -->\n <div class="internal">\n <input type="text" data-wysihtml5-dialog-field="href" class="current_url" value="">\n <input type="text" data-wysihtml5-dialog-field="target" class="current_target" value="_blank">\n </div>\n\n <a class="button" data-wysihtml5-dialog-action="save">\n ' +
2083
- ((__t = ( I18n.t('pageflow.ui.templates.inputs.text_area_input.save') )) == null ? '' : __t) +
2084
- '\n </a>\n <a class="button" data-wysihtml5-dialog-action="cancel">\n ' +
2085
- ((__t = ( I18n.t('pageflow.ui.templates.inputs.text_area_input.cancel') )) == null ? '' : __t) +
2086
- '\n </a>\n\n <a data-wysihtml5-command="removeLink">' +
2087
- ((__t = ( I18n.t('pageflow.ui.templates.inputs.text_area_input.remove_link') )) == null ? '' : __t) +
2088
- '</a>\n </div>\n</div>\n';
2089
- return __p
2090
- }
2091
-
2092
- /**
2093
- * Input view for multi line text with simple formatting options.
2094
- * See {@link inputWithPlaceholderText} for placeholder related options.
2095
- * See {@link inputView} for further options.
2096
- *
2097
- * @param {Object} [options]
2098
- *
2099
- * @param {string} [options.size="normal"]
2100
- * Pass `"short"` to reduce the text area height.
2101
- *
2102
- * @param {boolean} [options.disableLinks=false]
2103
- * Do not allow links inside the text.
2104
- *
2105
- * @param {boolean} [options.disableRichtext=false]
2106
- * Do not provide text formatting options.
2107
- *
2108
- * @param {Backbone.View} [options.fragmentLinkInputView]
2109
- * A view to select an id to use in links which only consist
2110
- * of a url fragment. Will receive a model with a `linkId`
2111
- * attribute.
2112
- *
2113
- * @class
2114
- */
2115
-
2116
- var TextAreaInputView = Marionette.ItemView.extend({
2117
- mixins: [inputView, inputWithPlaceholderText],
2118
- template: template$8,
2119
- ui: {
2120
- input: 'textarea',
2121
- toolbar: '.toolbar',
2122
- linkButton: '.link_button',
2123
- linkDialog: '.link_dialog',
2124
- urlInput: '.current_url',
2125
- targetInput: '.current_target',
2126
- linkTypeSelection: '.link_type_select',
2127
- urlLinkRadioButton: '.url_link_radio_button',
2128
- fragmentLinkRadioButton: '.fragment_link_radio_button',
2129
- urlLinkPanel: '.url_link_panel',
2130
- displayUrlInput: '.display_url',
2131
- openInNewTabCheckBox: '.open_in_new_tab',
2132
- fragmentLinkPanel: '.fragment_link_panel'
2133
- },
2134
- events: {
2135
- 'change textarea': 'save',
2136
- 'click .url_link_radio_button': 'showUrlLinkPanel',
2137
- 'click .fragment_link_radio_button': 'showFragmentLinkPanel',
2138
- 'change .open_in_new_tab': 'setTargetFromOpenInNewTabCheckBox',
2139
- 'change .display_url': 'setUrlFromDisplayUrl'
2140
- },
2141
- onRender: function onRender() {
2142
- this.ui.input.addClass(this.options.size);
2143
- this.load();
2144
- this.updatePlaceholder();
2145
- this.editor = new wysihtml5.Editor(this.ui.input[0], {
2146
- toolbar: this.ui.toolbar[0],
2147
- autoLink: this.options.disableLinks ? 0 : 1,
2148
- parserRules: {
2149
- tags: {
2150
- em: {
2151
- unwrap: this.options.disableRichtext ? 1 : 0,
2152
- rename_tag: "i"
2153
- },
2154
- strong: {
2155
- unwrap: this.options.disableRichtext ? 1 : 0,
2156
- rename_tag: "b"
2157
- },
2158
- u: {
2159
- unwrap: this.options.disableRichtext ? 1 : 0
2160
- },
2161
- b: {
2162
- unwrap: this.options.disableRichtext ? 1 : 0
2163
- },
2164
- i: {
2165
- unwrap: this.options.disableRichtext ? 1 : 0
2166
- },
2167
- ol: {
2168
- unwrap: this.options.enableLists ? 0 : 1
2169
- },
2170
- ul: {
2171
- unwrap: this.options.enableLists ? 0 : 1
2172
- },
2173
- li: {
2174
- unwrap: this.options.enableLists ? 0 : 1
2175
- },
2176
- br: {},
2177
- a: {
2178
- unwrap: this.options.disableLinks ? 1 : 0,
2179
- check_attributes: {
2180
- href: 'href',
2181
- target: 'any'
2182
- },
2183
- set_attributes: {
2184
- rel: 'nofollow'
2185
- }
2186
- }
2187
- }
2188
- }
2189
- });
2190
-
2191
- if (this.options.disableRichtext) {
2192
- this.ui.toolbar.find('a[data-wysihtml5-command="bold"]').hide();
2193
- this.ui.toolbar.find('a[data-wysihtml5-command="italic"]').hide();
2194
- this.ui.toolbar.find('a[data-wysihtml5-command="underline"]').hide();
2195
- this.ui.toolbar.find('a[data-wysihtml5-command="insertOrderedList"]').hide();
2196
- this.ui.toolbar.find('a[data-wysihtml5-command="insertUnorderedList"]').hide();
2197
- }
2198
-
2199
- if (!this.options.enableLists) {
2200
- this.ui.toolbar.find('a[data-wysihtml5-command="insertOrderedList"]').hide();
2201
- this.ui.toolbar.find('a[data-wysihtml5-command="insertUnorderedList"]').hide();
2202
- }
2203
-
2204
- if (this.options.disableLinks) {
2205
- this.ui.toolbar.find('a[data-wysihtml5-command="createLink"]').hide();
2206
- } else {
2207
- this.setupUrlLinkPanel();
2208
- this.setupFragmentLinkPanel();
2209
- }
2210
-
2211
- this.editor.on('change', _.bind(this.save, this));
2212
- this.editor.on('aftercommand:composer', _.bind(this.save, this));
2213
- },
2214
- onClose: function onClose() {
2215
- this.editor.fire('destroy:composer');
2216
- },
2217
- save: function save() {
2218
- this.model.set(this.options.propertyName, this.editor.getValue());
2219
- },
2220
- load: function load() {
2221
- this.ui.input.val(this.model.get(this.options.propertyName));
2222
- },
2223
- setupUrlLinkPanel: function setupUrlLinkPanel() {
2224
- this.editor.on('show:dialog', _.bind(function () {
2225
- this.ui.linkDialog.toggleClass('for_existing_link', this.ui.linkButton.hasClass('wysihtml5-command-active'));
2226
- var currentUrl = this.ui.urlInput.val();
2227
-
2228
- if (currentUrl.startsWith('#')) {
2229
- this.ui.displayUrlInput.val('');
2230
- this.ui.openInNewTabCheckBox.prop('checked', true);
2231
- } else {
2232
- this.ui.displayUrlInput.val(currentUrl);
2233
- this.ui.openInNewTabCheckBox.prop('checked', this.ui.targetInput.val() !== '_self');
2234
- }
2235
- }, this));
2236
- },
2237
- setupFragmentLinkPanel: function setupFragmentLinkPanel() {
2238
- if (this.options.fragmentLinkInputView) {
2239
- this.fragmentLinkModel = new Backbone.Model();
2240
- this.listenTo(this.fragmentLinkModel, 'change', function (model, options) {
2241
- if (!options.skipCurrentUrlUpdate) {
2242
- this.setInputsFromFragmentLinkModel();
2243
- }
2244
- });
2245
- this.editor.on('show:dialog', _.bind(function () {
2246
- var currentUrl = this.ui.urlInput.val();
2247
- var id = currentUrl.startsWith('#') ? currentUrl.substr(1) : null;
2248
- this.fragmentLinkModel.set('linkId', id, {
2249
- skipCurrentUrlUpdate: true
2250
- });
2251
- this.initLinkTypePanels(!id);
2252
- }, this));
2253
- var fragmentLinkInput = new this.options.fragmentLinkInputView({
2254
- model: this.fragmentLinkModel,
2255
- propertyName: 'linkId',
2256
- label: I18n$1.t('pageflow.ui.templates.inputs.text_area_input.target'),
2257
- hideUnsetButton: true
2258
- });
2259
- this.ui.fragmentLinkPanel.append(fragmentLinkInput.render().el);
2260
- } else {
2261
- this.ui.linkTypeSelection.hide();
2262
- this.ui.fragmentLinkPanel.hide();
2263
- }
2264
- },
2265
- initLinkTypePanels: function initLinkTypePanels(isUrlLink) {
2266
- if (isUrlLink) {
2267
- this.ui.urlLinkRadioButton.prop('checked', true);
2268
- } else {
2269
- this.ui.fragmentLinkRadioButton.prop('checked', true);
2270
- }
2271
-
2272
- this.ui.toolbar.toggleClass('fragment_link_panel_active', !isUrlLink);
2273
- },
2274
- showUrlLinkPanel: function showUrlLinkPanel() {
2275
- this.ui.toolbar.removeClass('fragment_link_panel_active');
2276
- this.setUrlFromDisplayUrl();
2277
- this.setTargetFromOpenInNewTabCheckBox();
2278
- },
2279
- showFragmentLinkPanel: function showFragmentLinkPanel() {
2280
- this.ui.toolbar.addClass('fragment_link_panel_active');
2281
- this.setInputsFromFragmentLinkModel();
2282
- },
2283
- setInputsFromFragmentLinkModel: function setInputsFromFragmentLinkModel() {
2284
- this.ui.urlInput.val('#' + (this.fragmentLinkModel.get('linkId') || ''));
2285
- this.ui.targetInput.val('_self');
2286
- },
2287
- setUrlFromDisplayUrl: function setUrlFromDisplayUrl() {
2288
- this.ui.urlInput.val(this.ui.displayUrlInput.val());
2289
- },
2290
- setTargetFromOpenInNewTabCheckBox: function setTargetFromOpenInNewTabCheckBox() {
2291
- this.ui.targetInput.val(this.ui.openInNewTabCheckBox.is(':checked') ? '_blank' : '_self');
2292
- }
2293
- });
2294
-
2295
- function _defineProperty(obj, key, value) {
2296
- if (key in obj) {
2297
- Object.defineProperty(obj, key, {
2298
- value: value,
2299
- enumerable: true,
2300
- configurable: true,
2301
- writable: true
2302
- });
2303
- } else {
2304
- obj[key] = value;
2305
- }
2306
-
2307
- return obj;
2308
- }
2309
-
2310
- function template$9(data) {
2311
- var __p = '';
2312
- __p += '<label>\n <span class="name"></span>\n <span class="inline_help"></span>\n</label>\n<input type="text" />\n<div class="validation"></div>\n';
2313
- return __p
2314
- }
2315
-
2316
- /**
2317
- * Input view for URLs.
2318
- * See {@link inputView} for further options
2319
- *
2320
- * @param {Object} [options]
2321
- *
2322
- * @param {string[]} options.supportedHosts
2323
- * List of allowed url prefixes.
2324
- *
2325
- * @param {boolean} [options.required=false]
2326
- * Display an error if the url is blank.
2327
- *
2328
- * @param {boolean} [options.permitHttps=false]
2329
- * Allow urls with https protocol.
2330
- *
2331
- * @class
2332
- */
2333
-
2334
- var UrlInputView = Marionette.Layout.extend(
2335
- /** @lends UrlInputView.prototype */
2336
- {
2337
- mixins: [inputView],
2338
- template: template$9,
2339
- ui: {
2340
- input: 'input',
2341
- validation: '.validation'
2342
- },
2343
- events: {
2344
- 'change': 'onChange'
2345
- },
2346
- onRender: function onRender() {
2347
- this.ui.validation.hide();
2348
- this.load();
2349
- this.validate();
2350
- },
2351
- onChange: function onChange() {
2352
- var _this = this;
2353
-
2354
- this.validate().then(function () {
2355
- return _this.save();
2356
- }, function () {
2357
- return _this.saveDisplayProperty();
2358
- });
2359
- },
2360
- saveDisplayProperty: function saveDisplayProperty() {
2361
- this.model.unset(this.options.propertyName, {
2362
- silent: true
2363
- });
2364
- this.model.set(this.options.displayPropertyName, this.ui.input.val());
2365
- },
2366
- save: function save() {
2367
- var _this2 = this;
2368
-
2369
- var value = this.ui.input.val();
2370
- $.when(this.transformPropertyValue(value)).then(function (transformedValue) {
2371
- var _this2$model$set;
2372
-
2373
- _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));
2374
- });
2375
- },
2376
- load: function load() {
2377
- this.ui.input.val(this.model.has(this.options.displayPropertyName) ? this.model.get(this.options.displayPropertyName) : this.model.get(this.options.propertyName));
2378
- this.onLoad();
2379
- },
2380
-
2381
- /**
2382
- * Override to be notified when the input has been loaded.
2383
- */
2384
- onLoad: function onLoad() {},
2385
-
2386
- /**
2387
- * Override to validate the untransformed url. Validation error
2388
- * message can be passed as rejected promise. Progress notifications
2389
- * are displayed. Only valid urls are stored in the configuration.
2390
- *
2391
- * @return Promise
2392
- */
2393
- validateUrl: function validateUrl(url) {
2394
- return $.Deferred().resolve().promise();
2395
- },
2396
-
2397
- /**
2398
- * Override to transform the property value before it is stored.
2399
- *
2400
- * @return Promise | String
2401
- */
2402
- transformPropertyValue: function transformPropertyValue(value) {
2403
- return value;
2404
- },
2405
-
2406
- /**
2407
- * Override to change the list of supported host names.
2408
- */
2409
- supportedHosts: function supportedHosts() {
2410
- return this.options.supportedHosts;
2411
- },
2412
- // Host names used to be expected to include protocols. Remove
2413
- // protocols for backwards compatilbity. Since supportedHosts
2414
- // is supposed to be overridden in subclasses, we do it in a
2415
- // separate method.
2416
- supportedHostsWithoutLegacyProtocols: function supportedHostsWithoutLegacyProtocols() {
2417
- return _.map(this.supportedHosts(), function (host) {
2418
- return host.replace(/^https?:\/\//, '');
2419
- });
2420
- },
2421
- validate: function validate(success) {
2422
- var view = this;
2423
- var options = this.options;
2424
- var value = this.ui.input.val();
2425
-
2426
- if (options.required && !value) {
2427
- displayValidationError(I18n$1.t('pageflow.ui.views.inputs.url_input_view.required_field'));
2428
- } else if (value && !isValidUrl(value)) {
2429
- var errorMessage = I18n$1.t('pageflow.ui.views.inputs.url_input_view.url_hint');
2430
-
2431
- if (options.permitHttps) {
2432
- errorMessage = I18n$1.t('pageflow.ui.views.inputs.url_input_view.url_hint_https');
2433
- }
2434
-
2435
- displayValidationError(errorMessage);
2436
- } else if (value && !hasSupportedHost(value)) {
2437
- displayValidationError(I18n$1.t('pageflow.ui.views.inputs.url_input_view.supported_vendors') + _.map(view.supportedHosts(), function (url) {
2438
- return '<li>' + url + '</li>';
2439
- }).join(''));
2440
- } else {
2441
- return view.validateUrl(value).progress(function (message) {
2442
- displayValidationPending(message);
2443
- }).done(function () {
2444
- resetValidationError();
2445
- }).fail(function (error) {
2446
- displayValidationError(error);
2447
- });
2448
- }
2449
-
2450
- return $.Deferred().reject().promise();
2451
-
2452
- function isValidUrl(url) {
2453
- return options.permitHttps ? url.match(/^https?:\/\//i) : url.match(/^http:\/\//i);
2454
- }
2455
-
2456
- function hasSupportedHost(url) {
2457
- return _.any(view.supportedHostsWithoutLegacyProtocols(), function (host) {
2458
- return url.match(new RegExp('^https?://' + host));
2459
- });
2460
- }
2461
-
2462
- function displayValidationError(message) {
2463
- view.$el.addClass('invalid');
2464
- view.ui.input.attr('aria-invalid', 'true');
2465
- view.ui.validation.removeClass('pending').addClass('failed').html(message).show();
2466
- }
2467
-
2468
- function displayValidationPending(message) {
2469
- view.$el.removeClass('invalid');
2470
- view.ui.input.removeAttr('aria-invalid');
2471
- view.ui.validation.removeClass('failed').addClass('pending').html(message).show();
2472
- }
2473
-
2474
- function resetValidationError(message) {
2475
- view.$el.removeClass('invalid');
2476
- view.ui.input.attr('aria-invalid', 'false');
2477
- view.ui.validation.hide();
2478
- }
2479
- }
2480
- });
2481
-
2482
- /**
2483
- * Input view that verifies that a certain URL is reachable via a
2484
- * proxy. To conform with same origin restrictions, this input view
2485
- * lets the user enter some url and saves a rewritten url where the
2486
- * domain is replaced with some path segment.
2487
- *
2488
- * That way, when `/example` is setup to proxy requests to
2489
- * `http://example.com`, the user can enter an url of the form
2490
- * `http://example.com/some/path` but the string `/example/some/path`
2491
- * is persisited to the database.
2492
- *
2493
- * See {@link inputView} for further options
2494
- *
2495
- * @param {Object} options
2496
- *
2497
- * @param {string} options.displayPropertyName
2498
- * Attribute name to store the url entered by the user.
2499
- *
2500
- * @param {Object[]} options.proxies
2501
- * List of supported proxies.
2502
- *
2503
- * @param {string} options.proxies[].url
2504
- * Supported prefix of an url that can be entered by the user.
2505
- *
2506
- * @param {string} options.proxies[].base_path
2507
- * Path to replace the url prefix with.
2508
- *
2509
- * @param {boolean} [options.required=false]
2510
- * Display an error if the url is blank.
2511
- *
2512
- * @param {boolean} [options.permitHttps=false]
2513
- * Allow urls with https protocol.
2514
- *
2515
- * @example
2516
- *
2517
- * this.input('url, ProxyUrlInputView, {
2518
- * proxies: [
2519
- * {
2520
- * url: 'http://example.com',
2521
- * base_path: '/example'
2522
- * }
2523
- * ]
2524
- * });
2525
- *
2526
- * @class
2527
- */
2528
-
2529
- var ProxyUrlInputView = UrlInputView.extend(
2530
- /** @lends ProxyUrlInputView.prototype */
2531
- {
2532
- // @override
2533
- validateUrl: function validateUrl(url) {
2534
- var view = this;
2535
- return $.Deferred(function (deferred) {
2536
- deferred.notify(I18n$1.t('pageflow.ui.views.inputs.proxy_url_input_view.url_validation'));
2537
- $.ajax({
2538
- url: view.rewriteUrl(url),
2539
- dataType: 'html'
2540
- }).done(deferred.resolve).fail(function (xhr) {
2541
- deferred.reject(I18n$1.t('pageflow.ui.views.inputs.proxy_url_input_view.http_error', {
2542
- status: xhr.status
2543
- }));
2544
- });
2545
- }).promise();
2546
- },
2547
- // override
2548
- transformPropertyValue: function transformPropertyValue(url) {
2549
- return this.rewriteUrl(url);
2550
- },
2551
- // override
2552
- supportedHosts: function supportedHosts() {
2553
- return _.pluck(this.options.proxies, 'url');
2554
- },
2555
- rewriteUrl: function rewriteUrl(url) {
2556
- _.each(this.options.proxies, function (proxy) {
2557
- url = url.replace(new RegExp('^' + proxy.url + '/?'), proxy.base_path + '/');
2558
- });
2559
-
2560
- return url;
2561
- }
2562
- });
2563
-
2564
- function template$a(data) {
2565
- var __p = '';
2566
- __p += '<label>\n <span class="name"></span>\n <span class="inline_help"></span>\n</label>\n<div class="value"></div>\n<div class="slider"></div>\n';
2567
- return __p
2568
- }
2569
-
2570
- /**
2571
- * A slider for numeric inputs.
2572
- * See {@link inputView} for options
2573
- *
2574
- * @param {Object} [options]
2575
- *
2576
- * @class
2577
- */
2578
-
2579
- var SliderInputView = Marionette.ItemView.extend({
2580
- mixins: [inputView],
2581
- className: 'slider_input',
2582
- template: template$a,
2583
- ui: {
2584
- widget: '.slider',
2585
- value: '.value'
2586
- },
2587
- events: {
2588
- 'slidechange': 'save'
2589
- },
2590
- onRender: function onRender() {
2591
- this.ui.widget.slider({
2592
- animate: 'fast',
2593
- min: 'minValue' in this.options ? this.options.minValue : 0,
2594
- max: 'maxValue' in this.options ? this.options.maxValue : 100
2595
- });
2596
- this.load();
2597
- },
2598
- updateDisabled: function updateDisabled(disabled) {
2599
- this.$el.toggleClass('disabled', !!disabled);
2600
-
2601
- if (disabled) {
2602
- this.ui.widget.slider('disable');
2603
- } else {
2604
- this.ui.widget.slider('enable');
2605
- }
2606
- },
2607
- save: function save() {
2608
- var value = this.ui.widget.slider('option', 'value');
2609
- var unit = 'unit' in this.options ? this.options.unit : '%';
2610
- this.ui.value.text(value + unit);
2611
- this.model.set(this.options.propertyName, value);
2612
- },
2613
- load: function load() {
2614
- var value;
2615
-
2616
- if (this.model.has(this.options.propertyName)) {
2617
- value = this.model.get(this.options.propertyName);
2618
- } else {
2619
- value = 'defaultValue' in this.options ? this.options.defaultValue : 0;
2620
- }
2621
-
2622
- this.ui.widget.slider('option', 'value', value);
2623
- }
2624
- });
2625
-
2626
- function template$b(data) {
2627
- var __p = '';
2628
- __p += '<label>\n <span class="name"></span>\n <span class="inline_help"></span>\n</label>\n\n<textarea></textarea>\n';
2629
- return __p
2630
- }
2631
-
2632
- var JsonInputView = Marionette.ItemView.extend({
2633
- mixins: [inputView],
2634
- template: template$b,
2635
- className: 'json_input',
2636
- ui: {
2637
- input: 'textarea'
2638
- },
2639
- events: {
2640
- 'change': 'onChange',
2641
- 'keyup': 'validate'
2642
- },
2643
- onRender: function onRender() {
2644
- this.load();
2645
- this.validate();
2646
- this.listenTo(this.model, 'change:' + this.options.propertyName, this.load);
2647
- },
2648
- onChange: function onChange() {
2649
- if (this.validate()) {
2650
- this.save();
2651
- }
2652
- },
2653
- onClose: function onClose() {
2654
- if (this.validate()) {
2655
- this.save();
2656
- }
2657
- },
2658
- save: function save() {
2659
- this.model.set(this.options.propertyName, this.ui.input.val() ? JSON.parse(this.ui.input.val()) : null);
2660
- },
2661
- load: function load() {
2662
- var input = this.ui.input;
2663
- var value = this.model.get(this.options.propertyName);
2664
- input.val(value ? JSON.stringify(value, null, 2) : '');
2665
- },
2666
- validate: function validate() {
2667
- var input = this.ui.input;
2668
-
2669
- if (input.val() && !this.isValidJson(input.val())) {
2670
- this.displayValidationError(I18n$1.t('pageflow.ui.views.inputs.json_input_view.invalid'));
2671
- return false;
2672
- } else {
2673
- this.resetValidationError();
2674
- return true;
2675
- }
2676
- },
2677
- displayValidationError: function displayValidationError(message) {
2678
- this.$el.addClass('invalid');
2679
- this.ui.input.attr('title', message);
2680
- },
2681
- resetValidationError: function resetValidationError(message) {
2682
- this.$el.removeClass('invalid');
2683
- this.ui.input.attr('title', '');
2684
- },
2685
- isValidJson: function isValidJson(text) {
2686
- try {
2687
- JSON.parse(text);
2688
- return true;
2689
- } catch (e) {
2690
- return false;
2691
- }
2692
- }
2693
- });
2694
-
2695
- function template$c(data) {
2696
- var __p = '';
2697
- __p += '<input type="checkbox" />\n<label>\n <span class="name"></span>\n <span class="inline_help"></span>\n</label>';
2698
- return __p
2699
- }
2700
-
2701
- /**
2702
- * Input view for boolean values.
2703
- * See {@link inputView} for further options
2704
- *
2705
- * @param {Object} [options]
2706
- *
2707
- * @param {boolean} [options.displayUncheckedIfDisabled=false]
2708
- * Ignore the attribute value if the input is disabled and display
2709
- * an unchecked check box.
2710
- *
2711
- * @param {boolean} [options.displayCheckedIfDisabled=false]
2712
- * Ignore the attribute value if the input is disabled and display
2713
- * an checked check box.
2714
- *
2715
- * @param {string} [options.storeInverted]
2716
- * Display checked by default and store true in given attribute when
2717
- * unchecked. The property name passed to `input` is only used for
2718
- * translations.
2719
- *
2720
- * @class
2721
- */
2722
-
2723
- var CheckBoxInputView = Marionette.ItemView.extend({
2724
- mixins: [inputView],
2725
- template: template$c,
2726
- className: 'check_box_input',
2727
- events: {
2728
- 'change': 'save'
2729
- },
2730
- ui: {
2731
- input: 'input',
2732
- label: 'label'
2733
- },
2734
- onRender: function onRender() {
2735
- this.ui.label.attr('for', this.cid);
2736
- this.ui.input.attr('id', this.cid);
2737
- this.load();
2738
- this.listenTo(this.model, 'change:' + this.options.propertyName, this.load);
2739
- },
2740
- updateDisabled: function updateDisabled() {
2741
- this.load();
2742
- },
2743
- save: function save() {
2744
- if (!this.isDisabled()) {
2745
- var value = this.ui.input.is(':checked');
2746
-
2747
- if (this.options.storeInverted) {
2748
- this.model.set(this.options.storeInverted, !value);
2749
- } else {
2750
- this.model.set(this.options.propertyName, value);
2751
- }
2752
- }
2753
- },
2754
- load: function load() {
2755
- if (!this.isClosed) {
2756
- this.ui.input.prop('checked', this.displayValue());
2757
- }
2758
- },
2759
- displayValue: function displayValue() {
2760
- if (this.isDisabled() && this.options.displayUncheckedIfDisabled) {
2761
- return false;
2762
- } else if (this.isDisabled() && this.options.displayCheckedIfDisabled) {
2763
- return true;
2764
- } else if (this.options.storeInverted) {
2765
- return !this.model.get(this.options.storeInverted);
2766
- } else {
2767
- return this.model.get(this.options.propertyName);
2768
- }
2769
- }
2770
- });
2771
-
2772
- /**
2773
- * Render a separator in a {@link ConfigurationEditorView} tab.
2774
- *
2775
- * @example
2776
- *
2777
- * this.view(SeparatorView);
2778
- *
2779
- * @class
2780
- */
2781
-
2782
- var SeparatorView = Marionette.View.extend({
2783
- className: 'separator'
2784
- });
2785
-
2786
- /**
2787
- * Render an input that is only a label. Can be used to render
2788
- * additional inline help.
2789
- *
2790
- * See {@link inputView} for further options
2791
- *
2792
- * @class
2793
- */
2794
-
2795
- var LabelOnlyView = Marionette.ItemView.extend({
2796
- mixins: [inputView],
2797
- template: function template() {
2798
- return "\n <label>\n <span class=\"name\"></span>\n <span class=\"inline_help\"></span>\n </label>\n ";
2799
- },
2800
- ui: {
2801
- label: 'label'
2802
- }
2803
- });
2804
-
2805
- /**
2806
- * A table cell mapping column attribute values to a list of
2807
- * translations.
2808
- *
2809
- * ## Attribute Translations
2810
- *
2811
- * The following attribute translations are used:
2812
- *
2813
- * - `.cell_text.<attribute_value>` - Used as cell content.
2814
- * - `.cell_text.blank` - Used as cell content if attribute is blank.
2815
- * - `.cell_title.<attribute_value>` - Used as title attribute.
2816
- * - `.cell_title.blank` - Used as title attribute if attribute is blank.
2817
- *
2818
- * @since 12.0
2819
- */
2820
-
2821
- var EnumTableCellView = TableCellView.extend({
2822
- className: 'enum_table_cell',
2823
- update: function update() {
2824
- this.$el.text(this.attributeTranslation('cell_text.' + (this.attributeValue() || 'blank')));
2825
- this.$el.attr('title', this.attributeTranslation('cell_title.' + (this.attributeValue() || 'blank'), {
2826
- defaultValue: ''
2827
- }));
2828
- }
2829
- });
2830
-
2831
- function template$d(data) {
2832
- var __t, __p = '';
2833
- __p += '<a class="remove" title="' +
2834
- ((__t = ( I18n.t('pageflow.editor.templates.row.destroy') )) == null ? '' : __t) +
2835
- '"></a>\n';
2836
- return __p
2837
- }
2838
-
2839
- /**
2840
- * A table cell providing a button which destroys the model that the
2841
- * current row refers to.
2842
- *
2843
- * ## Attribute Translations
2844
- *
2845
- * The following attribute translation is used:
2846
- *
2847
- * - `.cell_title` - Used as title attribute.
2848
- *
2849
- * @param {Object} [options]
2850
- *
2851
- * @param {function} [options.toggleDeleteButton]
2852
- * A function with boolean return value to be called on
2853
- * this.getModel(). Delete button will be visible only if the
2854
- * function returns a truthy value.
2855
- *
2856
- * @param {boolean} [options.invertToggleDeleteButton]
2857
- * Invert the return value of `toggleDeleteButton`?
2858
- *
2859
- * @since 12.0
2860
- */
2861
-
2862
- var DeleteRowTableCellView = TableCellView.extend({
2863
- className: 'delete_row_table_cell',
2864
- template: template$d,
2865
- ui: {
2866
- removeButton: '.remove'
2867
- },
2868
- events: {
2869
- 'click .remove': 'destroy',
2870
- 'click': function click() {
2871
- return false;
2872
- }
2873
- },
2874
- showButton: function showButton() {
2875
- if (this.options.toggleDeleteButton) {
2876
- var context = this.getModel();
2877
- var toggle = context[this.options.toggleDeleteButton].apply(context);
2878
-
2879
- if (this.options.invertToggleDeleteButton) {
2880
- return !toggle;
2881
- } else {
2882
- return !!toggle;
2883
- }
2884
- } else {
2885
- return true;
2886
- }
2887
- },
2888
- update: function update() {
2889
- this.ui.removeButton.toggleClass('remove', this.showButton());
2890
- this.ui.removeButton.attr('title', this.attributeTranslation('cell_title'));
2891
- },
2892
- destroy: function destroy() {
2893
- this.getModel().destroy();
2894
- }
2895
- });
2896
-
2897
- /**
2898
- * A table cell representing whether the column attribute is present
2899
- * on the row model.
2900
- *
2901
- * ## Attribute Translations
2902
- *
2903
- * The following attribute translations are used:
2904
- *
2905
- * - `.cell_title.present` - Used as title attribute if the attribute
2906
- * is present. The current attribute value is provided as
2907
- * interpolation `%{value}`.
2908
- * - `.cell_title.blank` - Used as title attribute if the
2909
- * attribute is blank.
2910
- *
2911
- * @since 12.0
2912
- */
2913
-
2914
- var PresenceTableCellView = TableCellView.extend({
2915
- className: 'presence_table_cell',
2916
- update: function update() {
2917
- var isPresent = !!this.attributeValue();
2918
- this.$el.attr('title', isPresent ? this.attributeTranslation('cell_title.present', {
2919
- value: this.attributeValue()
2920
- }) : this.attributeTranslation('cell_title.blank'));
2921
- this.$el.toggleClass('is_present', isPresent);
2922
- }
2923
- });
2924
-
2925
- /**
2926
- * A table cell mapping column attribute values to icons.
2927
- *
2928
- * ## Attribute Translations
2929
- *
2930
- * The following attribute translations are used:
2931
- *
2932
- * - `.cell_title.<attribute_value>` - Used as title attribute.
2933
- * - `.cell_title.blank` - Used as title attribute if attribute is blank.
2934
- *
2935
- * @param {Object} [options]
2936
- *
2937
- * @param {string[]} [options.icons]
2938
- * An array of all possible attribute values to be mapped to HTML
2939
- * classes of the same name. A global mapping from those classes to
2940
- * icon mixins is provided in
2941
- * pageflow/ui/table_cells/icon_table_cell.scss.
2942
- *
2943
- * @since 12.0
2944
- */
2945
-
2946
- var IconTableCellView = TableCellView.extend({
2947
- className: 'icon_table_cell',
2948
- update: function update() {
2949
- var icon = this.attributeValue();
2950
- var isPresent = !!this.attributeValue();
2951
- this.removeExistingIcons();
2952
- this.$el.attr('title', isPresent ? this.attributeTranslation('cell_title.' + icon, {
2953
- value: this.attributeValue()
2954
- }) : this.attributeTranslation('cell_title.blank'));
2955
- this.$el.addClass(icon);
2956
- },
2957
- removeExistingIcons: function removeExistingIcons() {
2958
- this.$el.removeClass(this.options.icons.join(' '));
2959
- }
2960
- });
2961
-
2962
- /**
2963
- * A table cell using the row model's value of the column attribute as
2964
- * text. If attribute value is empty, use most specific default
2965
- * available.
2966
- *
2967
- * @param {Object} [options]
2968
- *
2969
- * @param {function|string} [options.column.default]
2970
- * A function returning a default value for display if attribute
2971
- * value is empty.
2972
- *
2973
- * @param {string} [options.column.contentBinding]
2974
- * If this is provided, the function `options.column.default`
2975
- * receives the values of `options.column.contentBinding` and of
2976
- * this.getModel() via its options hash. No-op if
2977
- * `options.column.default` is not a function.
2978
- *
2979
- * @since 12.0
2980
- */
2981
-
2982
- var TextTableCellView = TableCellView.extend({
2983
- className: 'text_table_cell',
2984
- update: function update() {
2985
- this.$el.text(this._updateText());
2986
- },
2987
- _updateText: function _updateText() {
2988
- if (this.attributeValue()) {
2989
- return this.attributeValue();
2990
- } else if (typeof this.options.column["default"] === 'function') {
2991
- var options = {};
2992
-
2993
- if (this.options.column.contentBinding) {
2994
- options = {
2995
- contentBinding: this.options.column.contentBinding,
2996
- model: this.getModel()
2997
- };
2998
- }
2999
-
3000
- return this.options.column["default"](options);
3001
- } else if ('default' in this.options.column) {
3002
- return this.options.column["default"];
3003
- } else {
3004
- return I18n$1.t('pageflow.ui.text_table_cell_view.empty');
3005
- }
3006
- }
3007
- });
3008
-
3009
- var subviewContainer = {
3010
- subview: function subview(view) {
3011
- this.subviews = this.subviews || new ChildViewContainer();
3012
- this.subviews.add(view.render());
3013
- return view;
3014
- },
3015
- appendSubview: function appendSubview(view) {
3016
- return this.$el.append(this.subview(view).el);
3017
- },
3018
- onClose: function onClose() {
3019
- if (this.subviews) {
3020
- this.subviews.call('close');
3021
- }
3022
- }
3023
- };
3024
-
3025
- if (!Marionette.View.prototype.appendSubview) {
3026
- Cocktail.mixin(Marionette.View, subviewContainer);
3027
- }
3028
-
3029
- var tooltipContainer = {
3030
- events: {
3031
- 'mouseover [data-tooltip]': function mouseoverDataTooltip(event) {
3032
- if (!this.tooltip.visible) {
3033
- var target = $(event.currentTarget);
3034
- var key = target.attr('data-tooltip');
3035
- var position;
3036
-
3037
- if (target.data('tooltipAlign') === 'bottom left') {
3038
- position = {
3039
- left: target.position().left,
3040
- top: target.position().top + target.outerHeight()
3041
- };
3042
- } else if (target.data('tooltipAlign') === 'bottom right') {
3043
- position = {
3044
- left: target.position().left + target.outerWidth(),
3045
- top: target.position().top + target.outerHeight()
3046
- };
3047
- } else if (target.data('tooltipAlign') === 'top center') {
3048
- position = {
3049
- left: target.position().left + target.outerWidth() / 2,
3050
- top: target.position().top + 2
3051
- };
3052
- } else {
3053
- position = {
3054
- left: target.position().left + target.outerWidth(),
3055
- top: target.position().top + target.outerHeight() / 2
3056
- };
3057
- }
3058
-
3059
- this.tooltip.show(I18n$1.t(key), position, {
3060
- align: target.data('tooltipAlign')
3061
- });
3062
- }
3063
- },
3064
- 'mouseleave [data-tooltip]': function mouseleaveDataTooltip() {
3065
- this.tooltip.hide();
3066
- }
3067
- },
3068
- onRender: function onRender() {
3069
- this.appendSubview(this.tooltip = new TooltipView());
3070
- }
3071
- };
3072
-
3073
- exports.CheckBoxGroupInputView = CheckBoxGroupInputView;
3074
- exports.CheckBoxInputView = CheckBoxInputView;
3075
- exports.CollectionView = CollectionView;
3076
- exports.ColorInputView = ColorInputView;
3077
- exports.ConfigurationEditorTabView = ConfigurationEditorTabView;
3078
- exports.ConfigurationEditorView = ConfigurationEditorView;
3079
- exports.DeleteRowTableCellView = DeleteRowTableCellView;
3080
- exports.EnumTableCellView = EnumTableCellView;
3081
- exports.ExtendedSelectInputView = ExtendedSelectInputView;
3082
- exports.IconTableCellView = IconTableCellView;
3083
- exports.JsonInputView = JsonInputView;
3084
- exports.LabelOnlyView = LabelOnlyView;
3085
- exports.Object = BaseObject;
3086
- exports.PresenceTableCellView = PresenceTableCellView;
3087
- exports.ProxyUrlInputView = ProxyUrlInputView;
3088
- exports.SelectInputView = SelectInputView;
3089
- exports.SeparatorView = SeparatorView;
3090
- exports.SliderInputView = SliderInputView;
3091
- exports.SortableCollectionView = SortableCollectionView;
3092
- exports.TableCellView = TableCellView;
3093
- exports.TableHeaderCellView = TableHeaderCellView;
3094
- exports.TableRowView = TableRowView;
3095
- exports.TableView = TableView;
3096
- exports.TabsView = TabsView;
3097
- exports.TextAreaInputView = TextAreaInputView;
3098
- exports.TextInputView = TextInputView;
3099
- exports.TextTableCellView = TextTableCellView;
3100
- exports.TooltipView = TooltipView;
3101
- exports.UrlDisplayView = UrlDisplayView;
3102
- exports.UrlInputView = UrlInputView;
3103
- exports.cssModulesUtils = cssModulesUtils;
3104
- exports.i18nUtils = i18nUtils;
3105
- exports.inputView = inputView;
3106
- exports.inputWithPlaceholderText = inputWithPlaceholderText;
3107
- exports.serverSideValidation = serverSideValidation;
3108
- exports.subviewContainer = subviewContainer;
3109
- exports.tooltipContainer = tooltipContainer;
3110
- exports.viewWithValidationErrorMessages = viewWithValidationErrorMessages;
3111
-
3112
- return exports;
3113
-
3114
- }({}, Backbone.Marionette, _, jQuery, I18n, Backbone, Backbone.ChildViewContainer, IScroll, jQuery, wysihtml5, Cocktail));