noesya/osuny

View on GitHub
app/assets/javascripts/application/plugins/summernote.js

Summary

Maintainability
A
3 hrs
Test Coverage
/*global $ */
window.summernoteManager = {
    configs: {},
    noteButton: function (context) {
        'use strict';
        var ui = $.summernote.ui,
            button = ui.button({
                contents: '<i class="fas fa-note-sticky"/>',
                tooltip: 'Note (beta)',
                className: 'note-btn-note',
                click: function () {
                    var text = context.invoke('editor.getSelectedText'),
                        // TODO find if it's a note or not
                        isANote = false,
                        note;
                    if (isANote) {
                        // TODO remove note
                    } else {
                        note = '<note>' + text + '</note>';
                        context.invoke('editor.pasteHTML', note);
                    }
                }
            });
        // return button as jquery object
        return button.render();
    },
    init: function () {
        'use strict';
        this.setConfigs();
        this.initEditors();
        this.monkeyPatchDropdownButtons();
    },

    setConfigs: function () {
        'use strict';
        this.setConfig('nothing',
            {
                toolbar: []
            },
            [],
            []);

        this.setConfig('link',
            {
                toolbar: [
                    ['insert', ['link', 'unlink']]
                ]
            },
            ['a'],
            ['href', 'target']);

        this.setConfig('mini',
            {
                toolbar: [
                    ['font', ['bold', 'italic']],
                    ['position', ['superscript']],
                    ['insert', ['link', 'unlink']],
                    ['view', ['codeview']]
                ]
            },
            ['b', 'strong', 'i', 'em', 'sup', 'a'],
            ['href', 'target']);

        this.setConfig('mini-list',
            {
                toolbar: [
                    ['font', ['bold', 'italic']],
                    ['position', ['superscript']],
                    ['para', ['ul', 'ol']],
                    ['insert', ['link', 'unlink']],
                    ['view', ['codeview']]
                ]
            },
            ['b', 'strong', 'i', 'em', 'sup', 'a', 'ul', 'ol', 'li'],
            ['href', 'target']);

        this.setConfig('mini-list-with-notes',
            {
                toolbar: [
                    ['font', ['bold', 'italic']],
                    ['position', ['superscript']],
                    ['para', ['ul', 'ol']],
                    ['insert', ['link', 'unlink', 'note']],
                    ['view', ['codeview']]
                ]
            },
            ['b', 'strong', 'i', 'em', 'sup', 'a', 'ul', 'ol', 'li', 'note'],
            ['href', 'target']);

        this.setConfig('default',
            {
                toolbar: [
                    ['style', ['style']],
                    ['font', ['bold', 'italic']],
                    ['position', ['superscript', 'subscript']],
                    ['para', ['ul', 'ol']],
                    ['view', ['codeview']]
                ],
                styleTags: [
                    'p',
                    'blockquote',
                    'pre',
                    'h2',
                    'h3',
                    'h4'
                ]
            },
            ['b', 'strong', 'i', 'em', 'sup', 'sub', 'a', 'ul', 'ol', 'li', 'p', 'blockquote', 'pre', 'h2', 'h3', 'h4'],
            ['href', 'target']);

        window.SUMMERNOTE_CONFIGS = this.configs;
    },

    setConfig: function (key, config, tags, attributes) {
        'use strict';
        config.followingToolbar = true;
        config.disableDragAndDrop = true;
        config.callbacks = {
            onPaste: this.pasteSanitizedClipboardContent.bind(this, tags, attributes)
        };
        this.configs[key] = config;
    },

    initEditors: function () {
        'use strict';
        $('[data-provider="summernote"]').each(this.initEditor.bind(this));
    },

    initEditor: function (_, element) {
        'use strict';
        var config = $(element).attr('data-summernote-config'),
            locale = $('#summernote-locale').data('locale'),
            options = {};
        config = config || 'default';
        options = this.configs[config];
        // if locale is undefined, summernote use default (en-US)
        options['lang'] = locale;
        $(element).summernote(options);
    },

    monkeyPatchDropdownButtons: function () {
        'use strict';
        // https://github.com/summernote/summernote/issues/4170
        $('button[data-toggle="dropdown"]').each(function () {
            $(this).removeAttr('data-toggle')
                .attr('data-bs-toggle', 'dropdown');
        });
    },

    pasteSanitizedClipboardContent: function (allowedTags, allowedAttributes, event) {
        'use strict';
        var text = event.originalEvent.clipboardData.getData('text/plain'),
            html = event.originalEvent.clipboardData.getData('text/html');

        event.preventDefault();
        if (html) {
            html = html.toString();
            html = this.cleanHtml(html);
            html = this.sanitizeTags(html, allowedTags);
            html = this.sanitizeAttributes(html, allowedAttributes);
            document.execCommand('insertHTML', false, html);
        } else {
            document.execCommand('insertText', false, text);
        }
    },

    cleanHtml: function (html) {
        'use strict';
        var cleanedHtml,
            previousHtml;
        // remove allMicrosoft Office tag
        cleanedHtml = html.replace(/<!\[if !supportLists[\s\S]*?endif\]>/g, '');
        // remove all html comments
        do {
            previousHtml = cleanedHtml;
            cleanedHtml = cleanedHtml.replace(/<!--[\s\S]*?-->/g, '');
        } while (cleanedHtml !== previousHtml);
        // remove all microsoft attributes,
        cleanedHtml = cleanedHtml.replace(/( class=(")?Mso[a-zA-Z]+(")?)/g, ' ');
        // ensure regular quote
        cleanedHtml = cleanedHtml.replace(/[\u2018\u2019\u201A]/g, '\'');
        // ensure regular double quote
        cleanedHtml = cleanedHtml.replace(/[\u201C\u201D\u201E]/g, '"');
        // ensure regular ellipsis
        cleanedHtml = cleanedHtml.replace(/\u2026/g, '...');
        // ensure regular hyphen
        cleanedHtml = cleanedHtml.replace(/[\u2013\u2014]/g, '-');
        return cleanedHtml;
    },

    sanitizeTags: function (html, allowedTags) {
        'use strict';
        var allowedTagsRegex = allowedTags.map(function (e) {
                return '(?!' + e + ')';
            }).join(''),
            tagStripper = new RegExp('</?' + allowedTagsRegex + '\\w*\\b[^>]*>', 'ig');

        return html.replace(tagStripper, '');
    },

    sanitizeAttributes: function (html, allowedAttributes) {
        'use strict';
        var div = document.createElement('div');
        div.innerHTML = html;
        this.sanitizeElementAttributes(div, allowedAttributes);
        return div.innerHTML;
    },

    sanitizeElementAttributes: function (elmt, allowedAttributes) {
        'use strict';
        var children = elmt.children,
            child,
            i,
            j;
        for (i = 0; i < children.length; i += 1) {
            child = children[i];
            for (j = child.attributes.length - 1; j >= 0; j -= 1) {
                if ($.inArray(child.attributes[j].name, allowedAttributes) < 0) {
                    child.removeAttributeNode(child.attributes[j]);
                }
            }

            if (child.children.length) {
                this.sanitizeElementAttributes(child, allowedAttributes);
            }
        }
    }
};

window.addEventListener('DOMContentLoaded', function () {
    'use strict';
    window.summernoteManager.init();
});