<commit_before>odoo.define('web_editor.wysiwyg.plugin.media', function (require) {
'use strict';

var core = require('web.core');
var weWidgets = require('wysiwyg.widgets');
var AbstractPlugin = require('web_editor.wysiwyg.plugin.abstract');
var registry = require('web_editor.wysiwyg.plugin.registry');
var Plugins = require('web_editor.wysiwyg.plugins');
var wysiwygTranslation = require('web_editor.wysiwyg.translation');
var wysiwygOptions = require('web_editor.wysiwyg.options');

var _t = core._t;

var dom = $.summernote.dom;
var ui = $.summernote.ui;

//--------------------------------------------------------------------------
// Media (for image, video, icon, document)
//--------------------------------------------------------------------------

/**
 * Return true if the node is a media (image, icon, document or video).
 *
 * @param {Node} node
 * @returns {Boolean}
 */
dom.isMedia = function (node) {
    return dom.isImg(node) ||
        dom.isIcon(node) ||
        dom.isDocument(node) ||
        dom.isVideo(node);
};

var MediaPlugin = AbstractPlugin.extend({
    events: {
        'summernote.mousedown': '_onMouseDown',
        'summernote.keydown': '_onKeydown',
        'summernote.keyup': '_onKeyup',
        'summernote.scroll': '_onScroll',
        'summernote.disable': '_onDisable',
        'summernote.change': '_onChange',
        'summernote.codeview.toggled': '_onToggled',
        'dblclick .note-editable': '_onDblclick',
    },

    //--------------------------------------------------------------------------
    // Public
    //--------------------------------------------------------------------------

    /**
     * Open the image dialog and listen to its saved/closed events.
     */
    showImageDialog: function () {
        this.context.invoke('editor.saveRange');
        var media = this.context.invoke('editor.restoreTarget');

        var mediaDialog = new weWidgets.MediaDialog(this.options.parent, {},
            $(media).clone()[0]
        );

        mediaDialog.on('saved', this, function (data) {
            this.insertMedia(media, data);
        });
        mediaDialog.on('closed', this, function () {
            this.context.invoke('editor.restoreRange');
        });
        mediaDialog.open();
    },
    /**
     * Remove the current target media and hide its popover.
     */
    removeMedia: function () {
        this.context.invoke('editor.beforeCommand');
        var target = this.context.invoke('editor.restoreTarget');
        var point = this.context.invoke('HelperPlugin.removeBlockNode', target);
        var rng = this.context.invoke('editor.setRange', point.node, point.offset);
        rng.normalize().select();
        this.context.invoke('editor.saveRange');
        this.context.invoke('editor.clearTarget');
        this.hidePopovers();
        this.context.invoke('editor.afterCommand');
    },
    /**
     * Update the target media and its popover.
     *
     * @param {Node} target
     */
    update: function (target) {
        if (!target) {
            return;
        }
        if (!this.options.displayPopover(target)) {
            return;
        }

        this.lastPos = this.context.invoke('HelperPlugin.makePoint', target, $(target).offset());

        this.context.triggerEvent('focusnode', target);

        if (!dom.isMedia(target)) {
            return;
        }

        this.context.invoke('editor.saveTarget', target);

        var $target = $(target);
        if (!$target.data('show_tooltip')) {
            $target.data('show_tooltip', true);
            setTimeout(function () {
                $target.tooltip({
                    title: _t('Double-click to edit'),
                    trigger: 'manuel',
                    container: this.document.body,
                }).tooltip('show');
                setTimeout(function () {
                    $target.tooltip('dispose');
                }, 2000);
            }, 400);
        }

        if (dom.isImg(target)) {
            this.context.invoke('ImagePlugin.show', target, this.mousePosition);
        } else if (dom.isIcon(target)) {
            this.context.invoke('IconPlugin.show', target, this.mousePosition);
        } else if (dom.isVideo(target)) {
            this.context.invoke('VideoPlugin.show', target, this.mousePosition);
        } else if (dom.isDocument(target)) {
            this.context.invoke('DocumentPlugin.show', target, this.mousePosition);
        }
    },
    /**
     * Hide all open popovers.
     * Warning: removes the saved target.
     */
    hidePopovers: function () {
        var media = this.context.invoke('editor.restoreTarget');
        this.context.invoke('HandlePlugin.hide');
        this.context.invoke('ImagePlugin.hide');
        this.context.invoke('IconPlugin.hide');
        this.context.invoke('VideoPlugin.hide');
        this.context.invoke('DocumentPlugin.hide');
        this.context.invoke('editor.saveTarget', media);
    },
    /**
     * Insert or replace a media.
     *
     * @param {Node} previous the media to replace, if any
     * @param {Object} data contains the media to insert
     */
    insertMedia: function (previous, data) {
        var newMedia = data.media;
        this._wrapCommand(function () {
            this.$editable.focus();
            var rng = this.context.invoke('editor.createRange');
            var point;

            if (newMedia.tagName === "IMG") {
                $(newMedia).one('load error abort', this.updatePopoverAfterEdit.bind(this, newMedia));
            }

            if (previous) {
                this.context.invoke('editor.clearTarget');
                var start = previous.parentNode;
                rng = this.context.invoke('editor.setRange', start, _.indexOf(start.childNodes, previous));
                if (previous.tagName === "IMG" && $(previous).hasClass('img-fluid')) {
                    $(newMedia).addClass('img img-fluid mx-auto');
                }

                if (dom.isVideo(previous) || dom.isVideo(newMedia)) {
                    var doNotInsertP = previous.tagName === newMedia.tagName;
                    point = this.context.invoke('HelperPlugin.removeBlockNode', previous, doNotInsertP);
                    if (!rng.sc.parentNode || !rng.sc.childNodes[rng.so]) {
                        rng = this.context.invoke('editor.setRange', point.node, point.offset);
                    }
                    previous = null;
                }
                rng.select();
                this.hidePopovers();
            }

            if (dom.isVideo(newMedia)) {
                this.context.invoke('HelperPlugin.insertBlockNode', newMedia);
            } else {
                rng = this.context.invoke('editor.createRange');
                point = rng.getStartPoint();
                if (!rng.isCollapsed()) {
                    point = this.context.invoke('HelperPlugin.deleteBetween', point, rng.getEndPoint());
                }<commit_msg>[FIX] web_editor: preserve image classes when replacing image<commit_after>odoo.define('web_editor.wysiwyg.plugin.media', function (require) {
'use strict';

var core = require('web.core');
var weWidgets = require('wysiwyg.widgets');
var AbstractPlugin = require('web_editor.wysiwyg.plugin.abstract');
var registry = require('web_editor.wysiwyg.plugin.registry');
var Plugins = require('web_editor.wysiwyg.plugins');
var wysiwygTranslation = require('web_editor.wysiwyg.translation');
var wysiwygOptions = require('web_editor.wysiwyg.options');

var _t = core._t;

var dom = $.summernote.dom;
var ui = $.summernote.ui;

//--------------------------------------------------------------------------
// Media (for image, video, icon, document)
//--------------------------------------------------------------------------

/**
 * Return true if the node is a media (image, icon, document or video).
 *
 * @param {Node} node
 * @returns {Boolean}
 */
dom.isMedia = function (node) {
    return dom.isImg(node) ||
        dom.isIcon(node) ||
        dom.isDocument(node) ||
        dom.isVideo(node);
};

var MediaPlugin = AbstractPlugin.extend({
    events: {
        'summernote.mousedown': '_onMouseDown',
        'summernote.keydown': '_onKeydown',
        'summernote.keyup': '_onKeyup',
        'summernote.scroll': '_onScroll',
        'summernote.disable': '_onDisable',
        'summernote.change': '_onChange',
        'summernote.codeview.toggled': '_onToggled',
        'dblclick .note-editable': '_onDblclick',
    },

    //--------------------------------------------------------------------------
    // Public
    //--------------------------------------------------------------------------

    /**
     * Open the image dialog and listen to its saved/closed events.
     */
    showImageDialog: function () {
        this.context.invoke('editor.saveRange');
        var media = this.context.invoke('editor.restoreTarget');

        var mediaDialog = new weWidgets.MediaDialog(this.options.parent, {},
            $(media).clone()[0]
        );

        mediaDialog.on('saved', this, function (data) {
            this.insertMedia(media, data);
        });
        mediaDialog.on('closed', this, function () {
            this.context.invoke('editor.restoreRange');
        });
        mediaDialog.open();
    },
    /**
     * Remove the current target media and hide its popover.
     */
    removeMedia: function () {
        this.context.invoke('editor.beforeCommand');
        var target = this.context.invoke('editor.restoreTarget');
        var point = this.context.invoke('HelperPlugin.removeBlockNode', target);
        var rng = this.context.invoke('editor.setRange', point.node, point.offset);
        rng.normalize().select();
        this.context.invoke('editor.saveRange');
        this.context.invoke('editor.clearTarget');
        this.hidePopovers();
        this.context.invoke('editor.afterCommand');
    },
    /**
     * Update the target media and its popover.
     *
     * @param {Node} target
     */
    update: function (target) {
        if (!target) {
            return;
        }
        if (!this.options.displayPopover(target)) {
            return;
        }

        this.lastPos = this.context.invoke('HelperPlugin.makePoint', target, $(target).offset());

        this.context.triggerEvent('focusnode', target);

        if (!dom.isMedia(target)) {
            return;
        }

        this.context.invoke('editor.saveTarget', target);

        var $target = $(target);
        if (!$target.data('show_tooltip')) {
            $target.data('show_tooltip', true);
            setTimeout(function () {
                $target.tooltip({
                    title: _t('Double-click to edit'),
                    trigger: 'manuel',
                    container: this.document.body,
                }).tooltip('show');
                setTimeout(function () {
                    $target.tooltip('dispose');
                }, 2000);
            }, 400);
        }

        if (dom.isImg(target)) {
            this.context.invoke('ImagePlugin.show', target, this.mousePosition);
        } else if (dom.isIcon(target)) {
            this.context.invoke('IconPlugin.show', target, this.mousePosition);
        } else if (dom.isVideo(target)) {
            this.context.invoke('VideoPlugin.show', target, this.mousePosition);
        } else if (dom.isDocument(target)) {
            this.context.invoke('DocumentPlugin.show', target, this.mousePosition);
        }
    },
    /**
     * Hide all open popovers.
     * Warning: removes the saved target.
     */
    hidePopovers: function () {
        var media = this.context.invoke('editor.restoreTarget');
        this.context.invoke('HandlePlugin.hide');
        this.context.invoke('ImagePlugin.hide');
        this.context.invoke('IconPlugin.hide');
        this.context.invoke('VideoPlugin.hide');
        this.context.invoke('DocumentPlugin.hide');
        this.context.invoke('editor.saveTarget', media);
    },
    /**
     * Insert or replace a media.
     *
     * @param {Node} previous the media to replace, if any
     * @param {Object} data contains the media to insert
     */
    insertMedia: function (previous, data) {
        var newMedia = data.media;
        this._wrapCommand(function () {
            this.$editable.focus();
            var rng = this.context.invoke('editor.createRange');
            var point;

            if (newMedia.tagName === "IMG") {
                $(newMedia).one('load error abort', this.updatePopoverAfterEdit.bind(this, newMedia));
            }

            if (previous) {
                this.context.invoke('editor.clearTarget');
                var start = previous.parentNode;
                rng = this.context.invoke('editor.setRange', start, _.indexOf(start.childNodes, previous));
                if (previous.tagName === "IMG" && newMedia.tagName === "IMG") {
                    $(newMedia).addClass(previous.className);
                }

                if (dom.isVideo(previous) || dom.isVideo(newMedia)) {
                    var doNotInsertP = previous.tagName === newMedia.tagName;
                    point = this.context.invoke('HelperPlugin.removeBlockNode', previous, doNotInsertP);
                    if (!rng.sc.parentNode || !rng.sc.childNodes[rng.so]) {
                        rng = this.context.invoke('editor.setRange', point.node, point.offset);
                    }
                    previous = null;
                }
                rng.select();
                this.hidePopovers();
            }

            if (dom.isVideo(newMedia)) {
                this.context.invoke('HelperPlugin.insertBlockNode', newMedia);
            } else {
                rng = this.context.invoke('editor.createRange');
                point = rng.getStartPoint();
                if (!rng.isCollapsed()) {
                    point = this.context.invoke('HelperPlugin.deleteBetween', point, rng.getEndPoint());
                }
{'fraction_non_alphanumeric': 0.17793212086495774, 'fraction_numerical': 0.0011456394099957038, 'mean_word_length': 1.8504081632653062, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4551357', 'n_tokens_mistral': 3793, 'n_tokens_neox': 3669, 'n_words': 696}