NodeBB/NodeBB

View on GitHub
public/src/admin/settings.js

Summary

Maintainability
A
0 mins
Test Coverage
'use strict';


define('admin/settings', [
    'uploader', 'mousetrap', 'hooks', 'alerts', 'settings', 'bootstrap',
], function (uploader, mousetrap, hooks, alerts, settings, bootstrap) {
    const Settings = {};

    Settings.populateTOC = function () {
        const headers = $('.settings-header');
        const tocEl = $('[component="settings/toc"]');
        const tocList = $('[component="settings/toc/list"]');
        const mainHader = $('[component="settings/main/header"]');

        if (headers.length > 1 && tocList.length) {
            headers.each(function (i) {
                const $this = $(this);
                const header = $this.text();
                const anchor = $this.parent().attr('id') || `section${i + 1}`;
                // for elements that don't have id use section{index}
                if (anchor.startsWith('section')) {
                    $this.parent().attr('id', anchor);
                }
                tocList.append(`<a class="btn-ghost-sm text-xs justify-content-start text-decoration-none" href="#${anchor}">${header}</a>`);
            });
            const offset = mainHader.outerHeight(true);
            // https://stackoverflow.com/a/11814275/583363
            tocList.find('a').on('click', function (event) {
                event.preventDefault();
                const href = $(this).attr('href');
                $(href)[0].scrollIntoView();
                window.location.hash = href;
                scrollBy(0, -offset);
                setTimeout(() => {
                    tocList.find('a').removeClass('active');
                    $(this).addClass('active');
                }, 10);
                return false;
            });

            new bootstrap.ScrollSpy($('#spy-container')[0], {
                target: '#settings-navbar',
                rootMargin: '-10% 0px -70%',
                smoothScroll: true,
            });

            const scrollTo = $(`${window.location.hash}`);
            if (scrollTo.length) {
                $('html, body').animate({
                    scrollTop: (scrollTo.offset().top - offset) + 'px',
                }, 400);
            }
            tocEl.removeClass('hidden');
        }
    };

    Settings.prepare = function (callback) {
        // Populate the fields on the page from the config
        const fields = $('#content [data-field]');
        const numFields = fields.length;
        const saveBtn = $('#save');
        const revertBtn = $('#revert');
        let x;
        let key;
        let inputType;
        let field;

        // Handle unsaved changes
        fields.on('change', function () {
            app.flags = app.flags || {};
            app.flags._unsaved = true;
        });
        const defaultInputs = ['text', 'hidden', 'password', 'textarea', 'number'];
        for (x = 0; x < numFields; x += 1) {
            field = fields.eq(x);
            key = field.attr('data-field');
            inputType = field.attr('type');
            if (app.config.hasOwnProperty(key)) {
                if (field.is('input') && inputType === 'checkbox') {
                    const checked = parseInt(app.config[key], 10) === 1;
                    field.prop('checked', checked);
                } else if (field.is('textarea') || field.is('select') || (field.is('input') && defaultInputs.indexOf(inputType) !== -1)) {
                    field.val(app.config[key]);
                }
            }
        }

        revertBtn.off('click').on('click', function () {
            ajaxify.refresh();
        });

        saveBtn.off('click').on('click', function (e) {
            e.preventDefault();

            const ok = settings.check(document.querySelectorAll('#content [data-field]'));
            if (!ok) {
                return;
            }

            saveFields(fields, function onFieldsSaved(err) {
                if (err) {
                    return alerts.alert({
                        alert_id: 'config_status',
                        timeout: 2500,
                        title: '[[admin/admin:changes-not-saved]]',
                        message: `[[admin/admin:changes-not-saved-message, ${err.message}]]`,
                        type: 'danger',
                    });
                }

                app.flags._unsaved = false;
                Settings.toggleSaveSuccess(saveBtn);
                hooks.fire('action:admin.settingsSaved');
            });
        });

        mousetrap.bind('ctrl+s', function (ev) {
            saveBtn.click();
            ev.preventDefault();
        });

        handleUploads();
        setupTagsInput();

        $('#clear-sitemap-cache').off('click').on('click', function () {
            socket.emit('admin.settings.clearSitemapCache', function () {
                alerts.success('Sitemap Cache Cleared!');
            });
            return false;
        });

        if (typeof callback === 'function') {
            callback();
        }

        setTimeout(function () {
            hooks.fire('action:admin.settingsLoaded');
        }, 0);
    };

    Settings.toggleSaveSuccess = function (saveBtn) {
        const saveBtnEl = saveBtn.get(0);
        if (saveBtnEl) {
            saveBtnEl.classList.toggle('saved', true);
            setTimeout(() => {
                saveBtnEl.classList.toggle('saved', false);
            }, 1500);
        }
    };

    function handleUploads() {
        $('#content input[data-action="upload"]').each(function () {
            const uploadBtn = $(this);
            uploadBtn.on('click', function () {
                uploader.show({
                    title: uploadBtn.attr('data-title'),
                    description: uploadBtn.attr('data-description'),
                    route: uploadBtn.attr('data-route'),
                    params: {},
                    showHelp: uploadBtn.attr('data-help') ? uploadBtn.attr('data-help') === 1 : undefined,
                    accept: uploadBtn.attr('data-accept'),
                }, function (image) {
                    $('#' + uploadBtn.attr('data-target')).val(image);
                });
            });
        });
    }

    function setupTagsInput() {
        $('[data-field-type="tagsinput"]').tagsinput({
            tagClass: 'badge bg-info',
            confirmKeys: [13, 44],
            trimValue: true,
        });
        app.flags._unsaved = false;
    }

    Settings.remove = function (key) {
        socket.emit('admin.config.remove', key);
    };

    function saveFields(fields, callback) {
        const data = {};

        fields.each(function () {
            const field = $(this);
            const key = field.attr('data-field');
            let value;
            let inputType;

            if (field.is('input')) {
                inputType = field.attr('type');
                switch (inputType) {
                    case 'text':
                    case 'password':
                    case 'hidden':
                    case 'textarea':
                    case 'number':
                        value = field.val();
                        break;

                    case 'checkbox':
                        value = field.prop('checked') ? '1' : '0';
                        break;
                }
            } else if (field.is('textarea') || field.is('select')) {
                value = field.val();
            }

            data[key] = value;
        });

        socket.emit('admin.config.setMultiple', data, function (err) {
            if (err) {
                return callback(err);
            }

            for (const field in data) {
                if (data.hasOwnProperty(field)) {
                    app.config[field] = data[field];
                }
            }

            callback();
        });
    }

    return Settings;
});