• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

divio / django-cms / #29546

24 Nov 2022 11:39AM UTC coverage: 77.399%. Remained the same
#29546

push

travis-ci

web-flow
build: bump loader-utils from 1.4.0 to 1.4.2 (#7435)

Bumps [loader-utils](https://github.com/webpack/loader-utils) from 1.4.0 to 1.4.2.
- [Release notes](https://github.com/webpack/loader-utils/releases)
- [Changelog](https://github.com/webpack/loader-utils/blob/v1.4.2/CHANGELOG.md)
- [Commits](https://github.com/webpack/loader-utils/compare/v1.4.0...v1.4.2)

---
updated-dependencies:
- dependency-name: loader-utils
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Fabian Braun <fsbraun@gmx.de>

1074 of 1547 branches covered (69.42%)

2565 of 3314 relevant lines covered (77.4%)

33.05 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.13
/cms/static/cms/js/modules/cms.base.js
1
/**
2
 * CMS.API.Helpers
3
 * Multiple helpers used across all CMS features
4
 */
5
import $ from 'jquery';
6
import URL from 'urijs';
7
import { once, debounce, throttle } from 'lodash';
8
import { showLoader, hideLoader } from './loader';
9

10
var _CMS = {
1✔
11
    API: {}
12
};
13

14
/**
15
 * @function _ns
16
 * @private
17
 * @param {String} events space separated event names to be namespaces
18
 * @returns {String} string containing space separated namespaced event names
19
 */
20
var _ns = function nameSpaceEvent(events) {
1✔
21
    return events
310✔
22
        .split(/\s+/g)
23
        .map(function(eventName) {
24
            return 'cms-' + eventName;
425✔
25
        })
26
        .join(' ');
27
};
28

29
// Handy shortcut to cache the window and the document objects
30
// in a jquery wrapper
31
export const $window = $(window);
1✔
32
export const $document = $(document);
1✔
33

34
/**
35
 * Creates always an unique identifier if called
36
 * @returns {Number} incremental numbers starting from 0
37
 */
38
export const uid = (function() {
1✔
39
    let i = 0;
1✔
40

41
    return () => ++i;
187✔
42
})();
43

44
/**
45
 * Checks if the current version of the CMS matches provided one
46
 *
47
 * @param {Object} settings
48
 * @param {String} settings.version CMS version
49
 * @returns {Boolean}
50
 */
51
export const currentVersionMatches = ({ version }) => {
1✔
52
    return version === __CMS_VERSION__;
6✔
53
};
54

55
/**
56
 * Provides various helpers that are mixed in all CMS classes.
57
 *
58
 * @class Helpers
59
 * @static
60
 * @module CMS
61
 * @submodule CMS.API
62
 * @namespace CMS.API
63
 */
64
export const Helpers = {
1✔
65
    /**
66
     * See {@link reloadBrowser}
67
     *
68
     * @property {Boolean} isRloading
69
     * @private
70
     */
71
    _isReloading: false,
72

73
    // aliasing the $window and the $document objects
74
    $window,
75
    $document,
76

77
    uid,
78

79
    once,
80
    debounce,
81
    throttle,
82

83
    /**
84
     * Redirects to a specific url or reloads browser.
85
     *
86
     * @method reloadBrowser
87
     * @param {String} url where to redirect. if equal to `REFRESH_PAGE` will reload page instead
88
     * @param {Number} timeout=0 timeout in ms
89
     * @param {Boolean} ajax if set to true first initiates **synchronous**
90
     *     ajax request to figure out if the browser should reload current page,
91
     *     move to another one, or do nothing.
92
     * @param {Object} [data] optional data to be passed instead of one provided by request config
93
     * @param {String} [data.model=CMS.config.request.model]
94
     * @param {String|Number} [data.pk=CMS.config.request.pk]
95
     * @returns {Boolean|void}
96
     */
97
    // eslint-disable-next-line max-params
98
    reloadBrowser: function(url, timeout, ajax, data) {
99
        var that = this;
15✔
100
        // is there a parent window?
101
        var win = this._getWindow();
15✔
102
        var parent = win.parent ? win.parent : win;
15✔
103

104
        that._isReloading = true;
15✔
105

106
        // if there is an ajax reload, prioritize
107
        if (ajax) {
15✔
108
            parent.CMS.API.locked = true;
7✔
109
            // check if the url has changed, if true redirect to the new path
110
            // this requires an ajax request
111
            $.ajax({
7✔
112
                async: false,
113
                type: 'GET',
114
                url: parent.CMS.config.request.url,
115
                data: data || {
14✔
116
                    model: parent.CMS.config.request.model,
117
                    pk: parent.CMS.config.request.pk
118
                },
119
                success: function(response) {
120
                    parent.CMS.API.locked = false;
7✔
121

122
                    if (response === '' && !url) {
7✔
123
                        // cancel if response is empty
124
                        return false;
1✔
125
                    } else if (parent.location.pathname !== response && response !== '') {
6✔
126
                        // api call to the backend to check if the current path is still the same
127
                        that.reloadBrowser(response);
2✔
128
                    } else if (url === 'REFRESH_PAGE') {
4✔
129
                        // if on_close provides REFRESH_PAGE, only do a reload
130
                        that.reloadBrowser();
1✔
131
                    } else if (url) {
3✔
132
                        // on_close can also provide a url, reload to the new destination
133
                        that.reloadBrowser(url);
2✔
134
                    }
135
                }
136
            });
137

138
            // cancel further operations
139
            return false;
7✔
140
        }
141

142
        // add timeout if provided
143
        parent.setTimeout(function() {
8✔
144
            if (url && url !== parent.location.href) {
8✔
145
                // location.reload() takes precedence over this, so we
146
                // don't want to reload the page if we need a redirect
147
                parent.location.href = url;
6✔
148
            } else {
149
                // ensure page is always reloaded #3413
150
                parent.location.reload();
2✔
151
            }
152
        }, timeout || 0);
15✔
153
    },
154

155
    /**
156
     * Overridable callback that is being called in close_frame.html when plugin is saved
157
     *
158
     * @function onPluginSave
159
     * @public
160
     */
161
    onPluginSave: function() {
162
        var data = this.dataBridge;
4✔
163
        var editedPlugin =
164
            data &&
4✔
165
            data.plugin_id &&
166
            window.CMS._instances.some(function(plugin) {
167
                return Number(plugin.options.plugin_id) === Number(data.plugin_id) && plugin.options.type === 'plugin';
2✔
168
            });
169
        var addedPlugin = !editedPlugin && data && data.plugin_id;
4✔
170

171
        if (editedPlugin || addedPlugin) {
4✔
172
            CMS.API.StructureBoard.invalidateState(addedPlugin ? 'ADD' : 'EDIT', data);
3✔
173
            return;
3✔
174
        }
175

176
        // istanbul ignore else
177
        if (!this._isReloading) {
1✔
178
            this.reloadBrowser(null, 300); // eslint-disable-line
1✔
179
        }
180
    },
181

182
    /**
183
     * Assigns an event handler to forms located in the toolbar
184
     * to prevent multiple submissions.
185
     *
186
     * @method preventSubmit
187
     */
188
    preventSubmit: function() {
189
        var forms = $('.cms-toolbar').find('form');
2✔
190
        var SUBMITTED_OPACITY = 0.5;
2✔
191

192
        forms.submit(function() {
2✔
193
            // show loader
194
            showLoader();
1✔
195
            // we cannot use disabled as the name action will be ignored
196
            $('input[type="submit"]')
1✔
197
                .on('click', function(e) {
198
                    e.preventDefault();
2✔
199
                })
200
                .css('opacity', SUBMITTED_OPACITY);
201
        });
202
    },
203

204
    /**
205
     * Sets csrf token header on ajax requests.
206
     *
207
     * @method csrf
208
     * @param {String} csrf_token
209
     */
210
    csrf: function(csrf_token) {
211
        $.ajaxSetup({
22✔
212
            beforeSend: function(xhr) {
213
                xhr.setRequestHeader('X-CSRFToken', csrf_token);
37✔
214
            }
215
        });
216
    },
217

218
    /**
219
     * Sends or retrieves a JSON from localStorage
220
     * or the session (through synchronous ajax request)
221
     * if localStorage is not available. Does not merge with
222
     * previous setSettings calls.
223
     *
224
     * @method setSettings
225
     * @param {Object} newSettings
226
     * @returns {Object}
227
     */
228
    setSettings: function(newSettings) {
229
        // merge settings
230
        var settings = JSON.stringify($.extend({}, window.CMS.config.settings, newSettings));
122✔
231

232
        // use local storage or session
233
        if (this._isStorageSupported) {
122✔
234
            // save within local storage
235
            localStorage.setItem('cms_cookie', settings);
114✔
236
        } else {
237
            // save within session
238
            CMS.API.locked = true;
8✔
239
            showLoader();
8✔
240

241
            $.ajax({
8✔
242
                async: false,
243
                type: 'POST',
244
                url: window.CMS.config.urls.settings,
245
                data: {
246
                    csrfmiddlewaretoken: window.CMS.config.csrf,
247
                    settings: settings
248
                },
249
                success: function(data) {
250
                    CMS.API.locked = false;
3✔
251
                    // determine if logged in or not
252
                    settings = data ? JSON.parse(data) : window.CMS.config.settings;
3✔
253
                    hideLoader();
3✔
254
                },
255
                error: function(jqXHR) {
256
                    CMS.API.Messages.open({
5✔
257
                        message: jqXHR.responseText + ' | ' + jqXHR.status + ' ' + jqXHR.statusText,
258
                        error: true
259
                    });
260
                }
261
            });
262
        }
263

264
        // save settings
265
        CMS.settings = typeof settings === 'object' ? settings : JSON.parse(settings);
122✔
266

267
        // ensure new settings are returned
268
        return CMS.settings;
122✔
269
    },
270

271
    /**
272
     * Gets user settings (from localStorage or the session)
273
     * in the same way as setSettings sets them.
274
     *
275
     * @method getSettings
276
     * @returns {Object}
277
     */
278
    getSettings: function() {
279
        var settings;
280

281

282
        // use local storage or session
283
        if (this._isStorageSupported) {
9✔
284
            // get from local storage
285
            settings = JSON.parse(localStorage.getItem('cms_cookie') || 'null');
6✔
286
        } else {
287
            showLoader();
3✔
288
            CMS.API.locked = true;
3✔
289
            // get from session
290
            $.ajax({
3✔
291
                async: false,
292
                type: 'GET',
293
                url: window.CMS.config.urls.settings,
294
                success: function(data) {
295
                    CMS.API.locked = false;
2✔
296
                    // determine if logged in or not
297
                    settings = data ? JSON.parse(data) : window.CMS.config.settings;
2✔
298
                    hideLoader();
2✔
299
                },
300
                error: function(jqXHR) {
301
                    CMS.API.Messages.open({
1✔
302
                        message: jqXHR.responseText + ' | ' + jqXHR.status + ' ' + jqXHR.statusText,
303
                        error: true
304
                    });
305
                }
306
            });
307
        }
308

309
        // edit_off is a random flag that should be available on the page, but sometimes can
310
        // be not set when settings are carried over from pagetree
311
        if (
9✔
312
            (!settings || !currentVersionMatches(settings))
15✔
313
        ) {
314
            settings = this.setSettings(window.CMS.config.settings);
7✔
315
        }
316

317
        // save settings
318
        CMS.settings = settings;
9✔
319

320
        // ensure new settings are returned
321
        return CMS.settings;
9✔
322
    },
323

324
    /**
325
     * Modifies the url with new params and sanitises
326
     * the ampersand within the url for #3404.
327
     *
328
     * @method makeURL
329
     * @param {String} url original url
330
     * @param {Array[]} [params] array of [`param`, `value`] arrays to update the url
331
     * @returns {String}
332
     */
333
    makeURL: function makeURL(url, params = []) {
55✔
334
        let newUrl = new URL(URL.decode(url.replace(/&amp;/g, '&')));
106✔
335

336
        params.forEach(pair => {
106✔
337
            const [key, value] = pair;
56✔
338

339
            newUrl.removeSearch(key);
56✔
340
            newUrl.addSearch(key, value);
56✔
341
        });
342

343
        return newUrl.toString();
106✔
344
    },
345

346
    /**
347
     * Browsers allow to "Prevent this page form creating additional
348
     * dialogs." checkbox which prevents further input from confirm messages.
349
     * This method falls back to "true" once the user chooses this option.
350
     *
351
     * @method secureConfirm
352
     * @param {String} message to be displayed
353
     * @returns {Boolean}
354
     */
355
    secureConfirm: function secureConfirm(message) {
356
        var start = Number(new Date());
3✔
357
        var result = confirm(message); // eslint-disable-line
3✔
358
        var end = Number(new Date());
3✔
359
        var MINIMUM_DELAY = 10;
3✔
360

361
        return end < start + MINIMUM_DELAY || result === true;
3✔
362
    },
363

364
    /**
365
     * Is localStorage truly supported?
366
     * Check is taken from modernizr.
367
     *
368
     * @property _isStorageSupported
369
     * @private
370
     * @type {Boolean}
371
     */
372
    _isStorageSupported: (function localStorageCheck() {
373
        var mod = 'modernizr';
1✔
374

375
        try {
1✔
376
            localStorage.setItem(mod, mod);
1✔
377
            localStorage.removeItem(mod);
1✔
378
            return true;
1✔
379
        } catch (e) {
380
            // istanbul ignore next
381
            return false;
382
        }
383
    })(),
384

385
    /**
386
     * Adds an event listener to the "CMS".
387
     *
388
     * @method addEventListener
389
     * @param {String} eventName string containing space separated event names
390
     * @param {Function} fn callback to run when the event happens
391
     * @returns {jQuery}
392
     */
393
    addEventListener: function addEventListener(eventName, fn) {
394
        return CMS._eventRoot && CMS._eventRoot.on(_ns(eventName), fn);
102✔
395
    },
396

397
    /**
398
     * Removes the event listener from the "CMS". If a callback is provided - removes only that callback.
399
     *
400
     * @method removeEventListener
401
     * @param {String} eventName string containing space separated event names
402
     * @param {Function} [fn] specific callback to be removed
403
     * @returns {jQuery}
404
     */
405
    removeEventListener: function removeEventListener(eventName, fn) {
406
        return CMS._eventRoot && CMS._eventRoot.off(_ns(eventName), fn);
43✔
407
    },
408

409
    /**
410
     * Dispatches an event
411
     * @method dispatchEvent
412
     * @param {String} eventName event name
413
     * @param {Object} payload whatever payload required for the consumer
414
     * @returns {$.Event} event that was just triggered
415
     */
416
    dispatchEvent: function dispatchEvent(eventName, payload) {
417
        var event = new $.Event(_ns(eventName));
169✔
418

419
        CMS._eventRoot.trigger(event, [payload]);
169✔
420
        return event;
169✔
421
    },
422

423
    /**
424
     * Prevents scrolling with touch in an element.
425
     *
426
     * @method preventTouchScrolling
427
     * @param {jQuery} element element where we are preventing the scroll
428
     * @param {String} namespace so we don't mix events from two different places on the same element
429
     */
430
    preventTouchScrolling: function preventTouchScrolling(element, namespace) {
431
        element.on('touchmove.cms.preventscroll.' + namespace, function(e) {
23✔
432
            e.preventDefault();
1✔
433
        });
434
    },
435

436
    /**
437
     * Allows scrolling with touch in an element.
438
     *
439
     * @method allowTouchScrolling
440
     * @param {jQuery} element element where we are allowing the scroll again
441
     * @param {String} namespace so we don't accidentally remove events from a different handler
442
     */
443
    allowTouchScrolling: function allowTouchScrolling(element, namespace) {
444
        element.off('touchmove.cms.preventscroll.' + namespace);
8✔
445
    },
446

447
    /**
448
     * Returns window object.
449
     *
450
     * @method _getWindow
451
     * @private
452
     * @returns {Window}
453
     */
454
    _getWindow: function() {
455
        return window;
214✔
456
    },
457

458
    /**
459
     * We need to update the url with cms_path param for undo/redo
460
     *
461
     * @function updateUrlWithPath
462
     * @private
463
     * @param {String} url url
464
     * @returns {String} modified url
465
     */
466
    updateUrlWithPath: function(url) {
467
        var win = this._getWindow();
38✔
468
        var path = win.location.pathname + win.location.search;
38✔
469

470
        return this.makeURL(url, [['cms_path', path]]);
38✔
471
    },
472

473
    /**
474
     * Get color scheme either from :root[data-color-scheme] or user system setting
475
     *
476
     * @method get_color_scheme
477
     * @public
478
     * @returns {String}
479
     */
480
    getColorScheme: function () {
481
        let state = $('html').attr('data-color-scheme');
3✔
482

483
        if (!state) {
3✔
484
            if (!CMS.settings) {
1!
485
                // Settings loaded? If not, pls. load.
486
                this.getSettings();
×
487
            }
488
            state = CMS.settings.color_scheme;
1✔
489
            if (!state && window.matchMedia) {
1!
490
                if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
×
491
                    state = 'dark'; // dark mode
×
492
                } else {
493
                    state = 'light';
×
494
                }
495
            }
496
        }
497

498
        return state;
3✔
499
    },
500

501
    /**
502
     * Sets the color scheme for the current document and all iframes contained.
503
     *
504
     * @method setColorScheme
505
     * @public
506
     * @param scheme {String}
507
     * @retiurns {void}
508
     */
509

510
    setColorScheme: function (scheme) {
511
        let body = $('html');
3✔
512

513
        if (!CMS.settings) {
3!
514
            // Settings loaded? If not, pls. load.
515
            this.getSettings();
×
516
        }
517
        CMS.settings.color_scheme = scheme;
3✔
518
        this.setSettings(CMS.settings);
3✔
519
        if (scheme === 'auto') {
3✔
520
            body.removeAttr('data-color-scheme');
1✔
521
            body.find('div.cms iframe').each(function(i, e) {
1✔
522
                delete e.contentDocument.documentElement.dataset.colorScheme;
×
523
            });
524
        } else {
525
            body.attr('data-color-scheme', scheme);
2✔
526
            body.find('div.cms iframe').each(function setFrameColorScheme(i, e) {
2✔
527
                if (e.contentDocument) {
×
528
                    e.contentDocument.documentElement.dataset.colorScheme = scheme;
×
529
                    // ckeditor (and potentially other apps) have iframes inside their admin forms
530
                    // also set color scheme there
531
                    $(e.contentDocument).find('iframe').each(setFrameColorScheme);
×
532
                }
533
            });
534
        }
535
    }
536
};
537

538
/**
539
 * Provides key codes for common keys.
540
 *
541
 * @module CMS
542
 * @submodule CMS.KEYS
543
 * @example
544
 *     if (e.keyCode === CMS.KEYS.ENTER) { ... };
545
 */
546
export const KEYS = {
1✔
547
    SHIFT: 16,
548
    TAB: 9,
549
    UP: 38,
550
    DOWN: 40,
551
    ENTER: 13,
552
    SPACE: 32,
553
    ESC: 27,
554
    CMD_LEFT: 91,
555
    CMD_RIGHT: 93,
556
    CMD_FIREFOX: 224,
557
    CTRL: 17
558
};
559

560
// shorthand for jQuery(document).ready();
561
$(function() {
1✔
562
    CMS._eventRoot = $('#cms-top');
1✔
563
    // autoinits
564
    Helpers.preventSubmit();
1✔
565
});
566

567
export default _CMS;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc