pageflow 14.5.1 → 14.5.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3114 @@
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));