From 4f925af06fd3cebfcfb56eb947ab68c8a2ca4490 Mon Sep 17 00:00:00 2001
From: qsm-odoo
Date: Wed, 7 Jun 2017 17:45:15 +0200
Subject: [MOV] web_editor, website: reorganize files
---
addons/web_editor/static/src/js/ace.js | 693 ------
addons/web_editor/static/src/js/backend.js | 409 ----
addons/web_editor/static/src/js/backend/fields.js | 409 ++++
addons/web_editor/static/src/js/editor.js | 149 --
addons/web_editor/static/src/js/editor/editor.js | 149 ++
addons/web_editor/static/src/js/editor/rte.js | 637 ++++++
.../static/src/js/editor/rte.summernote.js | 983 ++++++++
.../static/src/js/editor/snippets.editor.js | 1154 ++++++++++
.../static/src/js/editor/snippets.options.js | 993 ++++++++
.../web_editor/static/src/js/editor/summernote.js | 2383 ++++++++++++++++++++
.../web_editor/static/src/js/editor/transcoder.js | 234 ++
.../web_editor/static/src/js/editor/translator.js | 319 +++
addons/web_editor/static/src/js/rte.js | 637 ------
addons/web_editor/static/src/js/rte.summernote.js | 983 --------
addons/web_editor/static/src/js/snippets.editor.js | 1154 ----------
.../web_editor/static/src/js/snippets.options.js | 993 --------
addons/web_editor/static/src/js/summernote.js | 2383 --------------------
addons/web_editor/static/src/js/tour.rte.js | 322 ---
addons/web_editor/static/src/js/tours/rte.js | 322 +++
addons/web_editor/static/src/js/transcoder.js | 234 --
addons/web_editor/static/src/js/translator.js | 319 ---
addons/web_editor/static/src/js/widgets.js | 1177 ----------
addons/web_editor/static/src/js/widgets/ace.js | 693 ++++++
addons/web_editor/static/src/js/widgets/widgets.js | 1177 ++++++++++
addons/web_editor/views/editor.xml | 24 +-
25 files changed, 9465 insertions(+), 9465 deletions(-)
delete mode 100644 addons/web_editor/static/src/js/ace.js
delete mode 100644 addons/web_editor/static/src/js/backend.js
create mode 100644 addons/web_editor/static/src/js/backend/fields.js
delete mode 100644 addons/web_editor/static/src/js/editor.js
create mode 100644 addons/web_editor/static/src/js/editor/editor.js
create mode 100644 addons/web_editor/static/src/js/editor/rte.js
create mode 100644 addons/web_editor/static/src/js/editor/rte.summernote.js
create mode 100644 addons/web_editor/static/src/js/editor/snippets.editor.js
create mode 100644 addons/web_editor/static/src/js/editor/snippets.options.js
create mode 100644 addons/web_editor/static/src/js/editor/summernote.js
create mode 100644 addons/web_editor/static/src/js/editor/transcoder.js
create mode 100644 addons/web_editor/static/src/js/editor/translator.js
delete mode 100644 addons/web_editor/static/src/js/rte.js
delete mode 100644 addons/web_editor/static/src/js/rte.summernote.js
delete mode 100644 addons/web_editor/static/src/js/snippets.editor.js
delete mode 100644 addons/web_editor/static/src/js/snippets.options.js
delete mode 100644 addons/web_editor/static/src/js/summernote.js
delete mode 100644 addons/web_editor/static/src/js/tour.rte.js
create mode 100644 addons/web_editor/static/src/js/tours/rte.js
delete mode 100644 addons/web_editor/static/src/js/transcoder.js
delete mode 100644 addons/web_editor/static/src/js/translator.js
delete mode 100644 addons/web_editor/static/src/js/widgets.js
create mode 100644 addons/web_editor/static/src/js/widgets/ace.js
create mode 100644 addons/web_editor/static/src/js/widgets/widgets.js
(limited to 'addons/web_editor')
diff --git a/addons/web_editor/static/src/js/ace.js b/addons/web_editor/static/src/js/ace.js
deleted file mode 100644
index b1b28d78e83..00000000000
--- a/addons/web_editor/static/src/js/ace.js
+++ /dev/null
@@ -1,693 +0,0 @@
-odoo.define('web_editor.ace', function (require) {
-'use strict';
-
-var ajax = require('web.ajax');
-var core = require('web.core');
-var Dialog = require("web.Dialog");
-var Widget = require('web.Widget');
-var base = require('web_editor.base');
-var local_storage = require('web.local_storage');
-var session = require("web.session");
-
-var qweb = core.qweb;
-var _t = core._t;
-
-ajax.loadXML('/web_editor/static/src/xml/ace.xml', qweb);
-
-function _getCheckReturn(isValid, errorLine, errorMessage) {
- return {
- isValid: isValid,
- error: isValid ? null : {
- line: errorLine,
- message: errorMessage,
- },
- };
-}
-
-function checkXML(xml) {
- if (typeof window.DOMParser != "undefined") {
- var xmlDoc = (new window.DOMParser()).parseFromString(xml, "text/xml");
- var error = xmlDoc.getElementsByTagName("parsererror");
- if (error.length > 0) {
- return _getCheckReturn(false, parseInt(error[0].innerHTML.match(/[Ll]ine[^\d]+(\d+)/)[1], 10), error[0].innerHTML);
- }
- } else if (typeof window.ActiveXObject != "undefined" && new window.ActiveXObject("Microsoft.XMLDOM")) {
- var xmlDocIE = new window.ActiveXObject("Microsoft.XMLDOM");
- xmlDocIE.async = "false";
- xmlDocIE.loadXML(xml);
- if (xmlDocIE.parseError.line > 0) {
- return _getCheckReturn(false, xmlDocIE.parseError.line, xmlDocIE.parseError.reason);
- }
- }
- return _getCheckReturn(true);
-}
-
-function formatXML(xml) {
- return window.vkbeautify.xml(xml, 4);
-}
-
-var checkLESS = (function () {
- var mapping = {
- '{': '}', '}': '{',
- '(': ')', ')': '(',
- '[': ']', ']': '[',
- };
- var openings = ['{', '(', '['];
- var closings = ['}', ')', ']'];
-
- return function (less) {
- var stack = [];
- var line = 1;
- for (var i = 0 ; i < less.length ; i++) {
- if (_.contains(openings, less[i])) {
- stack.push(less[i]);
- } else if (_.contains(closings, less[i])) {
- if (stack.pop() !== mapping[less[i]]) {
- return _getCheckReturn(false, line, _t("Unexpected ") + less[i]);
- }
- } else if (less[i] === '\n') {
- line++;
- }
- }
- if (stack.length > 0) {
- return _getCheckReturn(false, line, _t("Expected ") + mapping[stack.pop()]);
- }
- return _getCheckReturn(true);
- };
-})();
-
-function formatLESS(less) {
- return less;
-}
-
-/**
- * The ViewEditor Widget allow to visualize resources (by default, XML views) and edit them.
- */
-var ViewEditor = Widget.extend({
- template: 'web_editor.ace_view_editor',
- events: {
- "click .o_ace_type_switcher_choice": function (e) {
- e.preventDefault();
- this.switchType($(e.target).data("type"));
- },
- 'change .o_res_list': function () {
- this.displayResource(this.selectedResource());
- },
- 'click .js_include_bundles': function (e) {
- this.options.includeBundles = $(e.target).prop("checked");
- this.loadResources().then(this._updateViewSelectDOM.bind(this));
- },
- 'click .js_include_all_less': function (e) {
- this.options.includeAllLess = $(e.target).prop("checked");
- this.loadResources().then(this._updateViewSelectDOM.bind(this));
- },
- 'click button[data-action=save]': 'saveResources',
- "click button[data-action=\"reset\"]": function () {
- var self = this;
- Dialog.confirm(this, _t("If you reset this file, all your customizations will be lost as it will be reverted to the default file."), {
- title: _t("Careful !"),
- confirm_callback: function () {
- self.resetResource(self.selectedResource());
- },
- });
- },
- 'click button[data-action=format]': 'formatResource',
- 'click button[data-action=close]': 'do_hide',
- },
- /**
- * The init method should initialize the parameters of which information the ace editor will
- * have to load.
- * @param parent: the parent element of the editor widget.
- * @param viewKey: xml_id of the view whose linked resources are to be loaded.
- Also allow to receive the id directly.
- * @param options: an object containing some options
- * - initialResID: a specific view ID to load on start (otherwise the main view ID
- * associated with the specified viewKey will be used).
- * - includeBundles: whether or not the assets bundles templates needs to be loaded.
- */
- init: function (parent, viewKey, options) {
- this._super.apply(this, arguments);
-
- this.viewKey = viewKey;
- this.options = _.defaults({}, options, {
- position: 'right',
- doNotLoadViews: false,
- doNotLoadLess: false,
- includeBundles: false,
- includeAllLess: false,
- defaultBundlesRestriction: [],
- });
-
- this.resources = {xml: {}, less: {}};
- this.editingSessions = {xml: {}, less: {}};
- this.currentType = "xml";
-
- // Alias
- this.views = this.resources.xml;
- this.less = this.resources.less;
- },
- /**
- * The willStart method is in charge of loading everything the ace library needs to work.
- * It also loads the resources to visualize. See @loadResources.
- */
- willStart: function () {
- var js_def = ajax.loadJS('/web/static/lib/ace/ace.odoo-custom.js').then(function () {
- return $.when(
- ajax.loadJS('/web/static/lib/ace/mode-xml.js'),
- ajax.loadJS('/web/static/lib/ace/mode-less.js'),
- ajax.loadJS('/web/static/lib/ace/theme-monokai.js')
- );
- });
- return $.when(this._super.apply(this, arguments), js_def, this.loadResources());
- },
- /**
- * The start method is in charge of initializing the library and initial view once the DOM is
- * ready. It also initializes the resize feature of the ace editor.
- * @return a deferred which is resolved when the widget DOM content is fully loaded.
- */
- start: function () {
- this.$viewEditor = this.$("#ace-view-editor");
- this.$editor = this.$(".ace_editor");
-
- this.$typeSwitcherChoices = this.$(".o_ace_type_switcher_choice");
- this.$typeSwitcherBtn = this.$(".o_ace_type_switcher > .dropdown-toggle");
-
- this.$lists = {
- xml: this.$("#ace-view-list"),
- less: this.$("#ace-less-list")
- };
- this.$includeBundlesArea = this.$(".oe_include_bundles");
- this.$includeAllLessArea = this.$(".o_include_all_less");
- this.$viewID = this.$("#ace-view-id > span");
-
- this.$formatButton = this.$("button[data-action=\"format\"]");
- this.$resetButton = this.$("button[data-action=\"reset\"]");
-
- this.aceEditor = window.ace.edit(this.$viewEditor[0]);
- this.aceEditor.setTheme("ace/theme/monokai");
-
- var refX = 0;
- var resizing = false;
- var minWidth = 400;
- var debounceStoreEditorWidth = _.debounce(storeEditorWidth, 500);
-
- this._updateViewSelectDOM();
-
- var initResID;
- var initType;
- if (this.options.initialResID) {
- initResID = this.options.initialResID;
- initType = (_.isString(initResID) && initResID[0] === '/') ? "less" : "xml";
- } else {
- if (!this.options.doNotLoadLess) {
- initResID = this.sorted_less[0][1][0].url; // first bundle, less files, first one
- initType = "less";
- }
- if (!this.options.doNotLoadViews) {
- initResID = (typeof this.viewKey === "number" ? this.viewKey : _.findWhere(this.views, {xml_id: this.viewKey}).id);
- initType = "xml";
- }
- }
- if (initResID) {
- this.displayResource(initResID, initType);
- }
-
- if (!this.sorted_views.length || !this.sorted_less.length) {
- _.defer((function () {
- this.switchType(this.sorted_views.length ? "xml" : "less");
- this.$typeSwitcherBtn.parent(".btn-group").addClass("hidden");
- }).bind(this));
- }
-
- $(document).on("mouseup.ViewEditor", stopResizing.bind(this)).on("mousemove.ViewEditor", updateWidth.bind(this));
- if (this.options.position === 'left') {
- this.$('.ace_scroller').after($('').addClass('ace_resize_bar'));
- this.$('.ace_gutter').css({'cursor': 'default'});
- this.$el.on("mousedown.ViewEditor", ".ace_resize_bar", startResizing.bind(this));
- } else {
- this.$el.on("mousedown.ViewEditor", ".ace_gutter", startResizing.bind(this));
- }
-
- resizeEditor.call(this, readEditorWidth.call(this));
-
- return this._super.apply(this, arguments);
-
- function resizeEditor(target) {
- var width = Math.min(document.body.clientWidth, Math.max(parseInt(target, 10), minWidth));
- this.$editor.width(width);
- this.aceEditor.resize();
- this.$el.width(width);
- }
- function storeEditorWidth() {
- local_storage.setItem('ace_editor_width', this.$el.width());
- }
- function readEditorWidth() {
- var width = local_storage.getItem('ace_editor_width');
- return parseInt(width || 720, 10);
- }
- function startResizing(e) {
- refX = e.pageX;
- resizing = true;
- }
- function stopResizing() {
- resizing = false;
- }
- function updateWidth(e) {
- if (!resizing) return;
-
- var offset = e.pageX - refX;
- if (this.options.position === 'left') {
- offset = - offset;
- }
- var width = this.$el.width() - offset;
- refX = e.pageX;
- resizeEditor.call(this, width);
- debounceStoreEditorWidth.call(this);
- }
- },
- /**
- * The destroy method unbinds custom events binded to the document element.
- */
- destroy: function () {
- this._super.apply(this, arguments);
- this.$el.off(".ViewEditor");
- $(document).off(".ViewEditor");
- },
- /**
- * The loadResources method is in charge of loading data the ace editor will vizualize
- * and of processing them.
- * Default behavior is loading the activate views, index them and build their hierarchy.
- * @return a deferred which is resolved once everything is loaded and processed.
- */
- loadResources: function () {
- // Reset resources
- this.resources = {xml: {}, less: {}};
- this.editingSessions = {xml: {}, less: {}};
- this.views = this.resources.xml;
- this.less = this.resources.less;
-
- // Load resources
- return this._rpc({
- route: "/web_editor/get_assets_editor_resources",
- params: {
- key: this.viewKey,
- get_views: !this.options.doNotLoadViews,
- get_less: !this.options.doNotLoadLess,
- bundles: this.options.includeBundles,
- bundles_restriction: this.options.includeAllLess ? [] : this.options.defaultBundlesRestriction,
- },
- }).then((function (resources) {
- _process_views.call(this, resources.views);
- _process_less.call(this, resources.less);
- }).bind(this));
-
- function _process_views(views) {
- // Only keep the active views and index them by ID.
- _.extend(this.views, _.indexBy(_.filter(views, function (view) {
- return view.active;
- }), "id"));
-
- // Initialize a 0 level for each view and assign them an array containing their children.
- var self = this;
- var roots = [];
- _.each(this.views, function (view) {
- view.level = 0;
- view.children = [];
- });
- _.each(this.views, function (view) {
- var parentId = view.inherit_id[0];
- var parent = parentId && self.views[parentId];
- if (parent) {
- parent.children.push(view);
- } else {
- roots.push(view);
- }
- });
-
- // Assign the correct level based on children key and save a sorted array where
- // each view is followed by their children.
- this.sorted_views = [];
- function visit(view, level) {
- view.level = level;
- self.sorted_views.push(view);
- _.each(view.children, function (child) {
- visit(child, level + 1);
- });
- }
- _.each(roots, function (root) {
- visit(root, 0);
- });
- }
-
- function _process_less(less) {
- // The received less data is already sorted by bundle and DOM order
- this.sorted_less = less;
-
- // Store the URL ungrouped by bundle and use the URL as key (resource ID)
- var self = this;
- _.each(less, function (bundleInfos) {
- _.each(bundleInfos[1], function (info) { info.bundle_xmlid = bundleInfos[0].xmlid; });
- _.extend(self.less, _.indexBy(bundleInfos[1], "url"));
- });
- }
- },
- /**
- * The private _updateViewSelectDOM method purpose is to render the content of the view/file
- * select DOM element according to current widget data.
- */
- _updateViewSelectDOM: function () {
- var currentId = this.selectedResource();
-
- var self = this;
- this.$lists.xml.empty();
- _.each(this.sorted_views, function (view) {
- self.$lists.xml.append($("
", {
- value: view.id,
- text: view.name,
- selected: currentId === view.id,
- "data-level": view.level,
- "data-debug": view.xml_id,
- }));
- });
-
- this.$lists.less.empty();
- _.each(this.sorted_less, function (bundleInfos) {
- var $optgroup = $("
", {
- label: bundleInfos[0].name,
- }).appendTo(self.$lists.less);
- _.each(bundleInfos[1], function (lessInfo) {
- var name = lessInfo.url.substring(_.lastIndexOf(lessInfo.url, "/") + 1, lessInfo.url.length - 5);
- $optgroup.append($("
", {
- value: lessInfo.url,
- text: name,
- selected: currentId === lessInfo.url,
- "data-debug": lessInfo.url,
- "data-customized": lessInfo.customized
- }));
- });
- });
-
- this.$lists.xml.select2("destroy");
- this.$lists.xml.select2({
- formatResult: _formatDisplay,
- formatSelection: _formatDisplay,
- });
- this.$lists.less.select2("destroy");
- this.$lists.less.select2({
- formatResult: _formatDisplay,
- formatSelection: _formatDisplay,
- });
-
- function _formatDisplay(data) {
- var $elem = $(data.element);
-
- var $div = $("
", {
- text: data.text || "",
- style: "padding: 0 0 0 " + (24 * $elem.data("level")) + "px",
- });
-
- if ($elem.data("dirty") || $elem.data("customized")) {
- $div.prepend($("
", {
- class: "fa fa-floppy-o " + ($elem.data("dirty") ? "text-warning" : "text-success"),
- style: "margin-right: 8px;",
- }));
- }
-
- if (session.debug && $elem.data("debug")) {
- $div.append($("
", {
- text: " (" + $elem.data("debug") + ")",
- class: "text-muted",
- style: "font-size: 80%",
- }));
- }
-
- return $div;
- }
- },
- /**
- * The switchType method switches to the LESS or XML edition. Calling this method will adapt all DOM elements to
- * keep the editor consistent.
- * @param type: either "xml" or "less"
- */
- switchType: function (type) {
- this.currentType = type;
- this.$typeSwitcherBtn.html(this.$typeSwitcherChoices.filter("[data-type=\"" + type + "\"]").html());
- _.each(this.$lists, function ($list, _type) { $list.toggleClass("hidden", type !== _type); });
- this.$lists[type].change();
-
- this.$includeBundlesArea.toggleClass("hidden", this.currentType === "less" || !session.debug);
- this.$includeAllLessArea.toggleClass("hidden", this.currentType === "xml" || !session.debug || this.options.defaultBundlesRestriction.length === 0);
- this.$formatButton.toggleClass("hidden", this.currentType === "less");
- },
- /**
- * The selectedResource method returns the currently selected resource id (view ID or less file URL).
- * @return the currently resource id (view ID or less file URL)
- */
- selectedResource: function () {
- return this.$lists[this.currentType].select2("val");
- },
- /**
- * The displayResource method forces the view/less file identified by its ID/URL to be displayed in the editor.
- * The method will update the resource select DOM element as well.
- * @param resID: the ID/URL of the view/less file to display
- * @param type: the type of resource (either "xml" or "less")
- */
- displayResource: function (resID, type) {
- if (type) this.switchType(type);
-
- var editingSession = this.editingSessions[this.currentType][resID];
- if (!editingSession) {
- editingSession = this.editingSessions[this.currentType][resID] = this._buildEditingSession(resID);
- }
- this.aceEditor.setSession(editingSession);
-
- if (this.currentType === "xml") {
- this.$viewID.text(_.str.sprintf(_t("Template ID: %s"), this.views[resID].xml_id));
- } else {
- this.$viewID.text(_.str.sprintf(_t("Less file: %s"), resID));
- }
- this.$lists[this.currentType].select2("val", resID);
-
- this.$resetButton.toggleClass("hidden", this.currentType === "xml" || !this.less[resID].customized);
- },
- /**
- * The resetResource method forces the view/less file identified by its ID/URL to be reset to the way it was before
- * the user started editing it. TODO view reset is not supported yet
- * @param resID: the ID/URL of the view/less file to reset (default to the currently selected one)
- * @param type: the type of the resource to reset (default to the currently selected one)
- * @return a deferred which is resolved once the resource has been reset
- */
- resetResource: function (resID, type) {
- resID = resID || this.selectedResource();
- type = type || this.currentType;
-
- if (this.currentType === "xml") {
- return $.Defered().reject(_t("Reseting views is not supported yet")); // TODO
- } else {
- return this._rpc({
- route: "/web_editor/reset_less",
- params: {
- url: resID,
- bundle_xmlid: this.less[resID].bundle_xmlid,
- },
- });
- }
- },
- /**
- * The private _buildEditingSession method initializes a text editor for the specified resource.
- * @param resID: the ID/URL of the view/less file whose text editor it will be
- * @param type: the type of the given resource (default to the currently selected one)
- * @return an ace.EditSession object linked to the specified resID.
- */
- _buildEditingSession: function(resID, type) {
- var self = this;
- type = type || this.currentType;
- var editingSession = new window.ace.EditSession(this.resources[type][resID].arch);
- editingSession.setUseWorker(false);
- editingSession.setMode("ace/mode/" + (type || this.currentType));
- editingSession.setUndoManager(new window.ace.UndoManager());
- editingSession.on("change", function () {
- _.defer(function () {
- self._toggleDirtyInfo(resID);
- self._showErrorLine();
- });
- });
- return editingSession;
- },
- /**
- * The private _toggleDirtyInfo method update the select option DOM element associated with
- * a particular resID to indicate if the option is dirty or not.
- * @param resID: the ID/URL of the view/less file whose option has to be updated
- * @param type: the type of the given resource (default to the currently selected one)
- * @param isDirty: a boolean to indicate if the view is dirty or not ; default to content of UndoManager
- */
- _toggleDirtyInfo: function (resID, type, isDirty) {
- type = type || this.currentType;
-
- if (!resID || !this.editingSessions[type][resID]) return;
-
- var $option = this.$lists[type].find("[value='" + resID + "']");
- if (isDirty === undefined) {
- isDirty = this.editingSessions[type][resID].getUndoManager().hasUndo();
- }
- $option.data("dirty", isDirty);
- },
- /**
- * The formatResource method formats the current resource being vizualized.
- * TODO formatting LESS files is not supported yet.
- */
- formatResource: function () {
- var res = this.aceEditor.getValue();
- var check = (this.currentType === "xml" ? checkXML : checkLESS)(res);
- if (check.isValid) {
- this.aceEditor.setValue((this.currentType === "xml" ? formatXML : formatLESS)(res));
- } else {
- this._showErrorLine(check.error.line, check.error.message, this.selectedResource());
- }
- },
- /**
- * The saveResources method is in charge of saving every resource that has been modified.
- * If one cannot be saved, none is saved and an error message is displayed.
- * @return a deferred whose status indicates if the save is finished or if an error occured.
- */
- saveResources: function () {
- var toSave = {};
- var errorFound = false;
- _.each(this.editingSessions, (function (editingSessions, type) {
- if (errorFound) return;
-
- var dirtySessions = _.pick(editingSessions, function (session) {
- return session.getUndoManager().hasUndo();
- });
- toSave[type] = _.map(dirtySessions, function (session, resID) {
- return {
- id: parseInt(resID, 10) || resID,
- text: session.getValue(),
- };
- });
-
- this._showErrorLine();
- for (var i = 0 ; i < toSave[type].length && !errorFound ; i++) {
- var check = (type === "xml" ? checkXML : checkLESS)(toSave[type][i].text);
- if (!check.isValid) {
- this._showErrorLine(check.error.line, check.error.message, toSave[type][i].id, type);
- errorFound = toSave[type][i];
- }
- }
- }).bind(this));
- if (errorFound) return $.Deferred().reject(errorFound);
-
- var defs = [];
- _.each(toSave, (function (_toSave, type) {
- defs = defs.concat(_.map(_toSave, (type === "xml" ? this._saveView : this._saveLess).bind(this)));
- }).bind(this));
-
- return $.when.apply($, defs).fail((function (session, error) {
- Dialog.alert(this, "", {
- title: _t("Server error"),
- $content: $("
").html(
- _t("A server error occured. Please check you correctly signed in and that the file you are saving is well-formed.")
- + "
"
- + error
- )
- });
- }).bind(this));
- },
- /**
- * The private _saveView method is in charge of saving an unique XML view.
- * @param session: an object which contains the "id" and the "text" of the view to save.
- * @return a deferred whose status indicates if the save is finished or if an error occured.
- */
- _saveView: function (session) {
- var def = $.Deferred();
-
- var self = this;
- this._rpc({
- model: 'ir.ui.view',
- method: 'write',
- args: [[session.id], {arch: session.text}, _.extend(base.get_context(), {lang: null})],
- }).then(function () {
- self._toggleDirtyInfo(session.id, "xml", false);
- def.resolve();
- }, function (source, error) {
- def.reject(session, error);
- });
-
- return def;
- },
- /**
- * The private _saveLess method is in charge of saving an unique LESS file.
- * @param session: an object which contains the "id" (url) and the "text" of the view to save.
- * @return a deferred whose status indicates if the save is finished or if an error occured.
- */
- _saveLess: function (session) {
- var def = $.Deferred();
-
- var self = this;
- this._rpc({
- route: "/web_editor/save_less",
- params: {
- url: session.id,
- bundle_xmlid: this.less[session.id].bundle_xmlid,
- content: session.text,
- },
- }).then(function () {
- self._toggleDirtyInfo(session.id, "less", false);
- def.resolve();
- }, function (source, error) {
- def.reject(session, error);
- });
-
- return def;
- },
- /**
- * The private _showErrorLine method is designed to show a line which produced an error. Red color
- * is added to the editor, the cursor move to the line and a message is opened on click on the line
- * number. If the _showErrorLine is called without argument, the effects are removed.
- * @param line: the line number to highlight
- * @param message: the message to show on click on the line number
- * @param resID: the ID/URL of the view/less file whose line is to highlight
- * @param type: the type of the given resource
- */
- _showErrorLine: function (line, message, resID, type) {
- if (line === undefined || line <= 0) {
- if (this.$errorLine) {
- this.$errorLine.removeClass("o_error");
- this.$errorLine.off(".o_error");
- this.$errorLine = undefined;
- this.$errorContent.removeClass("o_error");
- this.$errorContent = undefined;
- }
- return;
- }
-
- if (type) this.switchType(type);
-
- if (this.selectedResource() === resID) {
- __showErrorLine.call(this, line);
- } else {
- var onChangeSession = (function () {
- this.aceEditor.off("changeSession", onChangeSession);
- _.delay(__showErrorLine.bind(this, line), 400);
- }).bind(this);
- this.aceEditor.on('changeSession', onChangeSession);
- this.displayResource(resID, this.currentType);
- }
-
- function __showErrorLine(line) {
- this.aceEditor.gotoLine(line);
- this.$errorLine = this.$viewEditor.find(".ace_gutter-cell").filter(function () {
- return parseInt($(this).text()) === line;
- }).addClass("o_error");
- this.$errorLine.addClass("o_error").on("click.o_error", function () {
- var $message = $("
").html(message);
- $message.text($message.text());
- Dialog.alert(this, "", {$content: $message});
- });
- this.$errorContent = this.$viewEditor.find(".ace_scroller").addClass("o_error");
- }
- },
-});
-
-return ViewEditor;
-});
diff --git a/addons/web_editor/static/src/js/backend.js b/addons/web_editor/static/src/js/backend.js
deleted file mode 100644
index 6b167c59edd..00000000000
--- a/addons/web_editor/static/src/js/backend.js
+++ /dev/null
@@ -1,409 +0,0 @@
-odoo.define('web_editor.backend', function (require) {
-'use strict';
-
-var AbstractField = require('web.AbstractField');
-var basic_fields = require('web.basic_fields');
-var config = require('web.config');
-var core = require('web.core');
-var session = require('web.session');
-var field_registry = require('web.field_registry');
-
-var transcoder = require('web_editor.transcoder');
-
-var TranslatableFieldMixin = basic_fields.TranslatableFieldMixin;
-
-var QWeb = core.qweb;
-var _t = core._t;
-
-
-/**
- * FieldTextHtmlSimple Widget
- * Intended to display HTML content. This widget uses the summernote editor
- * improved by odoo.
- *
- */
-var FieldTextHtmlSimple = basic_fields.DebouncedField.extend(TranslatableFieldMixin, {
- className: 'oe_form_field oe_form_field_html_text',
- supportedFieldTypes: ['html'],
-
- //--------------------------------------------------------------------------
- // Public
- //--------------------------------------------------------------------------
-
- /**
- * Summernote doesn't notify for changes done in code mode. We override
- * commitChanges to manually switch back to normal mode before committing
- * changes, so that the widget is aware of the changes done in code mode.
- *
- * @override
- */
- commitChanges: function () {
- // switch to WYSIWYG mode if currently in code mode to get all changes
- if (config.debug && this.mode === 'edit') {
- var layoutInfo = this.$textarea.data('layoutInfo');
- $.summernote.pluginEvents.codeview(undefined, undefined, layoutInfo, false);
- }
- this._super.apply(this, arguments);
- },
- /**
- * @override
- */
- isSet: function () {
- return this.value && this.value !== "
" && this.value.match(/\S/);
- },
- /**
- * Do not re-render this field if it was the origin of the onchange call.
- *
- * @override
- */
- reset: function (record, event) {
- this._reset(record, event);
- if (!event || event.target !== this) {
- if (this.mode === 'edit') {
- this.$content.html(this._textToHtml(this.value));
- } else {
- this._renderReadonly();
- }
- }
- return $.when();
- },
-
- //--------------------------------------------------------------------------
- // Private
- //--------------------------------------------------------------------------
-
- /**
- * @private
- * @returns {Object} the summernote configuration
- */
- _getSummernoteConfig: function () {
- var summernoteConfig = {
- focus: false,
- height: 180,
- toolbar: [
- ['style', ['style']],
- ['font', ['bold', 'italic', 'underline', 'clear']],
- ['fontsize', ['fontsize']],
- ['color', ['color']],
- ['para', ['ul', 'ol', 'paragraph']],
- ['table', ['table']],
- ['insert', ['link', 'picture']],
- ['history', ['undo', 'redo']]
- ],
- prettifyHtml: false,
- styleWithSpan: false,
- inlinemedia: ['p'],
- lang: "odoo",
- onChange: this._doDebouncedAction.bind(this),
- };
- if (config.debug) {
- summernoteConfig.toolbar.splice(7, 0, ['view', ['codeview']]);
- }
- return summernoteConfig;
- },
- /**
- * @override
- * @private
- */
- _getValue: function () {
- if (this.nodeOptions['style-inline']) {
- transcoder.class_to_style(this.$content);
- transcoder.font_to_img(this.$content);
- }
- return this.$content.html();
- },
- /**
- * @override
- * @private
- */
- _renderEdit: function () {
- this.$textarea = $('
');
+ $div.find('span').text($snippet.attr("name"));
+ $snippet.prepend($div);
+
+ // from t-snippet
+ var thumbnail = $("[data-oe-thumbnail]", this).data("oe-thumbnail");
+ if (thumbnail) {
+ $div.find('.oe_snippet_thumbnail_img').css('background-image', 'url(' + thumbnail + ')');
+ }
+ // end
+
+ var moduleID = $snippet.data('moduleId');
+ if (moduleID) {
+ $snippet.addClass('o_snippet_install');
+ var $installBtn = $(' ', {
+ class: 'btn btn-primary btn-sm o_install_btn',
+ target: '_blank',
+ href: '/web#id=' + moduleID + '&view_type=form&model=ir.module.module&action=base.open_module_tree',
+ text: _t("Install"),
+ });
+ $div.append($installBtn);
+ }
+ }
+ if (!$snippet.data("selector")) {
+ $("> *:not(.oe_snippet_thumbnail)", this).addClass('oe_snippet_body');
+ }
+ number++;
+ })
+ .not('[data-module-id]');
+
+ // hide scroll if no snippets defined
+ if (!number) {
+ this.$el.detach();
+ }
+ $("body").toggleClass("editor_has_snippets", !!number);
+
+ // select all default text to edit (if snippet default text)
+ this.add_default_snippet_text_classes();
+ $(document).on("click", ".o_default_snippet_text", function (event) {
+ $(event.target).selectContent();
+ });
+ $(document).on("keyup", function (event) {
+ var r = $.summernote.core.range.create();
+ $(r && r.sc).closest(".o_default_snippet_text").removeClass("o_default_snippet_text");
+ });
+
+ // clean t-oe
+ $html.find('[data-oe-model], [data-oe-type]').each(function () {
+ for (var k=0; k to get drop-near, drop-in
+ $snippet = $(this);
+ var $base_body = $snippet.find('.oe_snippet_body');
+ var $selector_siblings = $();
+ var $selector_children = $();
+ var temp = self.templateOptions;
+ for (var k in temp) {
+ if ($base_body.is(temp[k].base_selector)) {
+ if (temp[k]['drop-near']) {
+ if (!$selector_siblings) $selector_siblings = temp[k]['drop-near'].all();
+ else $selector_siblings = $selector_siblings.add(temp[k]['drop-near'].all());
+ }
+ if (temp[k]['drop-in']) {
+ if (!$selector_children) $selector_children = temp[k]['drop-in'].all();
+ else $selector_children = $selector_children.add(temp[k]['drop-in'].all());
+ }
+ }
+ }
+
+ $toInsert = $base_body.clone().data("name", $snippet.find(".oe_snippet_thumbnail_title").text());
+
+ if (!$selector_siblings.length && !$selector_children.length) {
+ console.warn($snippet.find(".oe_snippet_thumbnail_title").text() + " have not insert action: data-drop-near or data-drop-in");
+ return;
+ }
+
+ self.make_active(false);
+ self.activate_insertion_zones($selector_siblings, $selector_children);
+
+ $('.oe_drop_zone').droppable({
+ over: function () {
+ dropped = true;
+ $(this).first().after($toInsert).addClass('hidden');
+ },
+ out: function () {
+ var prev = $toInsert.prev();
+ if(this === prev[0]) {
+ dropped = false;
+ $toInsert.detach();
+ $(this).removeClass('hidden');
+ }
+ },
+ });
+ },
+ stop: function (ev, ui) {
+ $toInsert.removeClass('oe_snippet_body');
+
+ if (! dropped && self.$editable.find('.oe_drop_zone') && ui.position.top > 3 && ui.position.left + 50 > self.$el.outerWidth()) {
+ var el = self.$editable.find('.oe_drop_zone').nearest({x: ui.position.left, y: ui.position.top}).first();
+ if (el.length) {
+ el.after($toInsert);
+ dropped = true;
+ }
+ }
+
+ self.$editable.find('.oe_drop_zone').droppable('destroy').remove();
+
+ if (dropped) {
+
+ var prev = $toInsert.first()[0].previousSibling;
+ var next = $toInsert.last()[0].nextSibling;
+ var rte = self.getParent().rte;
+
+ if (prev) {
+ $toInsert.detach();
+ rte.historyRecordUndo($(prev));
+ $toInsert.insertAfter(prev);
+ } else if (next) {
+ $toInsert.detach();
+ rte.historyRecordUndo($(next));
+ $toInsert.insertBefore(next);
+ } else {
+ var $parent = $toInsert.parent();
+ $toInsert.detach();
+ rte.historyRecordUndo($parent);
+ $parent.prepend($toInsert);
+ }
+
+ $toInsert.closest(".o_editable").trigger("content_changed");
+
+ var $target = false;
+ $target = $toInsert;
+
+ setTimeout(function () {
+ self.$el.trigger('snippet-dropped', $target);
+
+ animation.start(true, $target);
+
+ self.call_for_all_snippets($target, function (editor, $snippet) {
+ _.defer(function () { editor.drop_and_build_snippet(); });
+ });
+ self.create_snippet_editor($target);
+ self.cover_target($target.data('overlay'), $target);
+ $target.closest(".o_editable").trigger("content_changed");
+
+ self.make_active($target);
+ },0);
+ } else {
+ $toInsert.remove();
+ }
+ },
+ });
+ },
+
+ // call a method on a snippet and all his children
+ call_for_all_snippets: function ($snippet, callback) {
+ var self = this;
+ $snippet.add(data.globalSelector.all($snippet)).each(function () {
+ var $snippet = $(this);
+ self.create_snippet_editor($snippet);
+ if ($snippet.data("snippet-editor")) {
+ callback.call(self, $snippet.data("snippet-editor"), $snippet);
+ }
+ });
+ },
+
+ // return the original snippet in the editor bar from a snippet id (string)
+ get_snippet_from_id: function (id) {
+ return $('.oe_snippet').filter(function () {
+ return $(this).data('option') === id;
+ }).first();
+ },
+
+ // Create element insertion drop zones. two css selectors can be provided
+ // selector.children -> will insert drop zones as direct child of the selected elements
+ // in case the selected elements have children themselves, dropzones will be interleaved
+ // with them.
+ // selector.siblings -> will insert drop zones after and before selected elements
+ activate_insertion_zones: function ($selector_siblings, $selector_children) {
+ var self = this;
+ var zone_template = $("
");
+
+ if ($selector_children) {
+ $selector_children.each(function () {
+ var $zone = $(this);
+ var css = window.getComputedStyle(this);
+ var float = css.float || css.cssFloat;
+ var $drop = zone_template.clone();
+
+ $zone.append($drop);
+ var node = $drop[0].previousSibling;
+ var test = !!(node && ((!node.tagName && node.textContent.match(/\S/)) || node.tagName === "BR"));
+ if (test) {
+ $drop.addClass("oe_vertical").css({
+ 'height': parseInt(window.getComputedStyle($zone[0]).lineHeight),
+ 'float': 'none',
+ 'display': 'inline-block'
+ });
+ } else if (float === "left" || float === "right") {
+ $drop.addClass("oe_vertical").css('height', Math.max(Math.min($zone.outerHeight(), $zone.children().last().outerHeight()), 30));
+ }
+
+ $drop = $drop.clone();
+
+ $zone.prepend($drop);
+ node = $drop[0].nextSibling;
+ test = !!(node && ((!node.tagName && node.textContent.match(/\S/)) || node.tagName === "BR"));
+ if (test) {
+ $drop.addClass("oe_vertical").css({
+ 'height': parseInt(window.getComputedStyle($zone[0]).lineHeight),
+ 'float': 'none',
+ 'display': 'inline-block'
+ });
+ } else if (float === "left" || float === "right") {
+ $drop.addClass("oe_vertical").css('height', Math.max(Math.min($zone.outerHeight(), $zone.children().first().outerHeight()), 30));
+ }
+ if (test) {
+ $drop.css({'float': 'none', 'display': 'inline-block'});
+ }
+ });
+
+ // add children near drop zone
+ $selector_siblings = $(_.uniq(($selector_siblings || $()).add($selector_children.children()).get()));
+ }
+
+ if ($selector_siblings) {
+ $selector_siblings.filter(':not(.oe_drop_zone):not(.oe_drop_clone)').each(function () {
+ var $zone = $(this);
+ var $drop;
+ var css = window.getComputedStyle(this);
+ var float = css.float || css.cssFloat;
+
+ if($zone.prev('.oe_drop_zone:visible').length === 0) {
+ $drop = zone_template.clone();
+ if (float === "left" || float === "right") {
+ $drop.addClass("oe_vertical").css('height', Math.max(Math.min($zone.outerHeight(), $zone.prev().outerHeight() || Infinity), 30));
+ }
+ $zone.before($drop);
+ }
+ if($zone.next('.oe_drop_zone:visible').length === 0) {
+ $drop = zone_template.clone();
+ if (float === "left" || float === "right") {
+ $drop.addClass("oe_vertical").css('height', Math.max(Math.min($zone.outerHeight(), $zone.next().outerHeight() || Infinity), 30));
+ }
+ $zone.after($drop);
+ }
+ });
+ }
+
+ var count;
+ do {
+ count = 0;
+ $zones = self.$editable.find('.oe_drop_zone > .oe_drop_zone').remove(); // no recursive zones
+ count += $zones.length;
+ $zones.remove();
+ } while (count > 0);
+
+ // Cleaning consecutive zone and up zones placed between floating or inline elements. We do not like these kind of zones.
+ var $zones = self.$editable.find('.oe_drop_zone:not(.oe_vertical)');
+ $zones.each(function () {
+ var zone = $(this);
+ var prev = zone.prev();
+ var next = zone.next();
+ // remove consecutive zone
+ if (prev.is('.oe_drop_zone') || next.is('.oe_drop_zone')) {
+ zone.remove();
+ return;
+ }
+ var float_prev = prev.css('float') || 'none';
+ var float_next = next.css('float') || 'none';
+ var disp_prev = prev.css('display') || null;
+ var disp_next = next.css('display') || null;
+ if( (float_prev === 'left' || float_prev === 'right')
+ && (float_next === 'left' || float_next === 'right') ) {
+ zone.remove();
+ }else if( !( disp_prev === null
+ || disp_next === null
+ || disp_prev === 'block'
+ || disp_next === 'block' )) {
+ zone.remove();
+ }
+ });
+ },
+
+ // generate drop zones covering the elements selected by the selector
+ // we generate overlay drop zones only to get an idea of where the snippet are, the drop
+ activate_overlay_zones: function ($targets) {
+ var self = this;
+
+ // filter out invisible elements
+ $targets = $targets.filter(':visible:hasVisibility:hasOpacity');
+
+ $targets.each(function () {
+ var $target = $(this);
+ if (!$target.data('overlay')) {
+ var $zone = $(qweb.render('web_editor.snippet_overlay'));
+
+ // fix for pointer-events: none with ie9
+ if (document.body && document.body.addEventListener) {
+ $zone.on("click mousedown mousedown", function passThrough(event) {
+ event.preventDefault();
+ $target.each(function () {
+ // check if clicked point (taken from event) is inside element
+ event.srcElement = this;
+ $(this).trigger(event.type);
+ });
+ return false;
+ });
+ }
+
+ $zone.appendTo('#oe_manipulators');
+ $zone.data('target', $target);
+ $target.data('overlay', $zone);
+
+ var timer;
+ $target.closest('.o_editable').on("content_changed", function (event) {
+ clearTimeout(timer);
+ timer = setTimeout(function () {
+ if ($target.data('overlay') && $target.data('overlay').hasClass("oe_active")) {
+ self.cover_target($target.data('overlay'), $target);
+ }
+ }, 50);
+ });
+
+ var resize = function () {
+ if ($zone.parent().length) {
+ self.cover_target($zone, $target);
+ } else {
+ $('body').off("resize", resize);
+ }
+ };
+ $('body').on("resize", resize);
+ }
+ self.cover_target($target.data('overlay'), $target);
+ });
+ return $targets;
+ }
+});
+
+/**
+ * Snippet editor Editor class.
+ * Management of the overlay and option list for a snippet.
+ */
+data.Editor = Class.extend({
+ init: function (BuildingBlock, dom) {
+ this.buildingBlock = BuildingBlock;
+ this.$target = $(dom);
+ this.$overlay = this.$target.data('overlay');
+
+ // Initialize parent button
+ this.init_parent_options();
+
+ // Load overlay options content
+ this.load_style_options();
+
+ // Initialize move/clone/remove buttons
+ if (!this.$target.parent().is(':o_editable')) {
+ this.$overlay.find('.oe_snippet_move, .oe_snippet_clone, .oe_snippet_remove').remove();
+ } else {
+ this.$overlay.on('click', '.oe_snippet_clone', _.bind(this.on_clone, this));
+ this.$overlay.on('click', '.oe_snippet_remove', _.bind(this.on_remove, this));
+ this._drag_and_drop();
+ }
+ },
+
+ getName: function () {
+ if (this.$target.data("name") !== undefined) {
+ return this.$target.data("name");
+ }
+ if (this.$target.parent(".row").length) {
+ return _t("Column");
+ }
+ return _t("Block");
+ },
+
+ // activate drag and drop for the snippets in the snippet toolbar
+ _drag_and_drop: function () {
+ var self = this;
+ this.dropped = false;
+ this.$overlay.draggable({
+ greedy: true,
+ appendTo: 'body',
+ cursor: "move",
+ handle: ".oe_snippet_move",
+ cursorAt: {
+ left: 18,
+ top: 14
+ },
+ helper: function () {
+ var $clone = $(this).clone().css({width: "24px", height: "24px", border: 0});
+ $clone.find(".oe_overlay_options >:not(:contains(.oe_snippet_move)), .oe_handle").remove();
+ $clone.find(":not(.glyphicon)").css({position: 'absolute', top: 0, left: 0});
+ $clone.appendTo("body").removeClass("hidden");
+ return $clone;
+ },
+ start: _.bind(self._drag_and_drop_start, self),
+ stop: _.bind(self._drag_and_drop_stop, self)
+ });
+ },
+ _drag_and_drop_after_insert_dropzone: function () {},
+ _drag_and_drop_active_drop_zone: function ($zones) {
+ var self = this;
+ $zones.droppable({
+ over: function () {
+ $(".oe_drop_zone.hide").removeClass("hide");
+ $(this).addClass("hide").first().after(self.$target);
+ self.dropped = true;
+ },
+ out: function () {
+ $(this).removeClass("hide");
+ self.$target.detach();
+ self.dropped = false;
+ },
+ });
+ },
+ _drag_and_drop_start: function () {
+ var self = this;
+ self.buildingBlock.editor_busy = true;
+ self.size = {
+ width: self.$target.width(),
+ height: self.$target.height()
+ };
+ self.$target.after("
");
+ self.$target.detach();
+ self.$overlay.addClass("hidden");
+
+ var $selector_siblings;
+ for (var i = 0 ; i < self.selector_siblings.length ; i++) {
+ if (!$selector_siblings) $selector_siblings = self.selector_siblings[i].all();
+ else $selector_siblings = $selector_siblings.add(self.selector_siblings[i].all());
+ }
+ var $selector_children;
+ for (i = 0 ; i < self.selector_children.length ; i++) {
+ if (!$selector_children) $selector_children = self.selector_children[i].all();
+ else $selector_children = $selector_children.add(self.selector_children[i].all());
+ }
+
+ self.buildingBlock.make_active(false);
+ self.buildingBlock.activate_insertion_zones($selector_siblings, $selector_children);
+
+ $("body").addClass('move-important');
+
+ self._drag_and_drop_after_insert_dropzone();
+ self._drag_and_drop_active_drop_zone($('.oe_drop_zone'));
+ },
+ _drag_and_drop_stop: function () {
+ var self = this;
+
+ $(".oe_drop_zone").droppable('destroy').remove();
+
+ var prev = this.$target.first()[0].previousSibling;
+ var next = this.$target.last()[0].nextSibling;
+ var $parent = this.$target.parent();
+
+ var $clone = $(".oe_drop_clone");
+ if (prev === $clone[0]) {
+ prev = $clone[0].previousSibling;
+ } else if (next === $clone[0]) {
+ next = $clone[0].nextSibling;
+ }
+ $clone.after(this.$target);
+
+ this.$overlay.removeClass("hidden");
+ $("body").removeClass('move-important');
+ $clone.remove();
+
+ if (this.dropped) {
+ this.buildingBlock.getParent().rte.historyRecordUndo(this.$target);
+
+ if (prev) {
+ this.$target.insertAfter(prev);
+ } else if (next) {
+ this.$target.insertBefore(next);
+ } else {
+ $parent.prepend(this.$target);
+ }
+
+ for (var i in this.styles) {
+ this.styles[i].on_move();
+ }
+ }
+
+ self.buildingBlock.editor_busy = false;
+
+ self.init_parent_options();
+ _.defer(function () {
+ self.buildingBlock.cover_target(self.$target.data('overlay'), self.$target);
+ });
+ },
+
+ load_style_options: function () {
+ var self = this;
+ var $styles = this.$overlay.find('.oe_options');
+ var $ul = $styles.find('ul:first');
+ this.styles = {};
+ this.selector_siblings = [];
+ this.selector_children = [];
+
+ var i = 0;
+ $ul.append($(" ", {"class": "dropdown-header o_main_header", text: this.getName()}).data("editor", this));
+ _.each(this.buildingBlock.templateOptions, function (val, option_id) {
+ if (!val.selector.is(self.$target)) {
+ return;
+ }
+ if (val['drop-near']) self.selector_siblings.push(val['drop-near']);
+ if (val['drop-in']) self.selector_children.push(val['drop-in']);
+
+ var option = val['option'];
+ self.styles[option] = new (options.registry[option] || options.Class)(self.buildingBlock, self, self.$target, option_id);
+ $ul.append(self.styles[option].$el.addClass("snippet-option-" + option));
+ self.styles[option].start();
+ self.styles[option].__order = i++;
+ });
+ $ul.append($(" ", {"class": "divider"}));
+
+ var $parents = this.$target.parents();
+ _.each($parents, function (parent) {
+ var parentEditor = $(parent).data("snippet-editor");
+ if (parentEditor) {
+ for (var styleName in parentEditor.styles) {
+ if (!parentEditor.styles[styleName].preventChildPropagation) {
+ $ul.append($(" ", {"class": "dropdown-header o_parent_editor_header", text: parentEditor.getName()}).data("editor", parentEditor));
+ break;
+ }
+ }
+ }
+ });
+
+ if (!this.selector_siblings.length && !this.selector_children.length) {
+ this.$overlay.find(".oe_snippet_move, .oe_snippet_clone").addClass('hidden');
+ }
+
+ this.$overlay.find('[data-toggle="dropdown"]').dropdown();
+ },
+
+ /**
+ * The init_parent_options method initializes the "go to parent" button and create the editor options
+ * management for them if they do not already have one.
+ */
+ init_parent_options: function () {
+ var self = this;
+ var $button = this.$overlay.find('.oe_snippet_parent');
+ var $parent = data.globalSelector.closest(this.$target.parent());
+ if (!$parent.data("snippet-editor")) {
+ this.buildingBlock.create_snippet_editor($parent);
+ }
+
+ $button.toggleClass("hidden", $parent.length === 0);
+ $button.off("click").on('click', function (event) {
+ event.preventDefault();
+ _.defer(function () {
+ self.buildingBlock.make_active($parent);
+ });
+ });
+ },
+
+ on_clone: function (event) {
+ event.preventDefault();
+ var $clone = this.$target.clone(false);
+
+ this.buildingBlock.getParent().rte.historyRecordUndo(this.$target);
+
+ this.$target.after($clone);
+ this.buildingBlock.call_for_all_snippets($clone, function (editor, $snippet) {
+ for (var i in editor.styles) {
+ editor.styles[i].on_clone($snippet, {
+ isCurrent: ($snippet.is($clone)),
+ });
+ }
+ });
+ return false;
+ },
+
+ on_remove: function (event) {
+ if (event !== undefined) {
+ event.preventDefault();
+ this.buildingBlock.getParent().rte.historyRecordUndo(this.$target);
+ }
+
+ this.on_blur();
+
+ var index = _.indexOf(this.buildingBlock.snippets, this.$target.get(0));
+ this.buildingBlock.call_for_all_snippets(this.$target, function (editor, $snippet) {
+ for (var i in editor.styles) {
+ editor.styles[i].on_remove();
+ }
+ });
+ delete this.buildingBlock.snippets[index];
+
+ var $parent = this.$target.parent();
+ this.$target.find("*").andSelf().tooltip("destroy");
+ this.$target.remove();
+ this.$overlay.remove();
+
+ var node = $parent[0];
+ if (node && node.firstChild) {
+ $.summernote.core.dom.removeSpace(node, node.firstChild, 0, node.lastChild, 1);
+ if (!node.firstChild.tagName && node.firstChild.textContent === " ") {
+ node.removeChild(node.firstChild);
+ }
+ }
+
+ if($parent.closest(":data(\"snippet-editor\")").length) {
+ while (!$parent.data("snippet-editor")) {
+ var $nextParent = $parent.parent();
+ if ($parent.children().length === 0 && $parent.text().trim() === "" && !$parent.hasClass("oe_structure")) {
+ $parent.remove();
+ }
+ $parent = $nextParent;
+ }
+ if ($parent.children().length === 0 && $parent.text().trim() === "" && !$parent.hasClass("oe_structure")) {
+ _.defer(function () {
+ $parent.data("snippet-editor").on_remove();
+ });
+ }
+ }
+
+ // clean editor if they are image or table in deleted content
+ $(".note-control-selection").hide();
+ $('.o_table_handler').remove();
+
+ this.buildingBlock.$el.trigger("snippet-removed");
+
+ return false;
+ },
+
+ /*
+ * drop_and_build_snippet
+ * This method is called just after that a thumbnail is drag and dropped into a drop zone
+ * (after the insertion of this.$body, if this.$body exists)
+ */
+ drop_and_build_snippet: function () {
+ for (var i in this.styles) {
+ this.styles[i].drop_and_build_snippet();
+ }
+ },
+
+ /* on_focus
+ * This method is called when the user click inside the snippet in the dom
+ */
+ on_focus: function () {
+ this._on_focus_blur(true);
+ },
+
+ /* on_focus
+ * This method is called when the user click outside the snippet in the dom, after a focus
+ */
+ on_blur: function () {
+ this._on_focus_blur(false);
+ },
+
+ _on_focus_blur: function (focus) {
+ var do_action = (focus ? _do_action_focus : _do_action_blur);
+
+ // Attach own and parent options on the current overlay
+ var $style_button = this.$overlay.find(".oe_options");
+ var $ul = $style_button.find("ul:first");
+ var $headers = $ul.find(".dropdown-header:data(editor)");
+ _.each($headers, (function (el) {
+ var $el = $(el);
+ var styles = _.values($el.data("editor").styles);
+ if ($el.data("editor") !== this) {
+ styles = _.filter(styles, function (option) { return !option.preventChildPropagation; });
+ }
+
+ var count = 0;
+ _.each(_.sortBy(styles, "__order").reverse(), function (style) {
+ if (do_action(style, $el)) {
+ count++;
+ }
+ });
+ $el.toggleClass("hidden", count === 0);
+ }).bind(this));
+
+ // Activate the overlay
+ $style_button.toggleClass("hidden", $ul.children(":not(.o_main_header):not(.divider):not(.hidden)").length === 0);
+ this.$overlay.toggleClass("oe_active", !!focus);
+
+ function _do_action_focus(style, $dest) {
+ style.$el.insertAfter($dest);
+ style.on_focus();
+ return (style.$el.length > 0);
+ }
+ function _do_action_blur(style, $dest) {
+ style.$el.detach();
+ style.on_blur();
+ return false;
+ }
+ },
+});
+
+/**
+ * Add the ability on the main editor class to instantiate the snippet editor component
+ */
+editor.Class.include({
+ init: function () {
+ var self = this;
+ var res = this._super.apply(this, arguments);
+ var $editable = this.rte.editable();
+ this.buildingBlock = new data.Class(this, $editable);
+ this.buildingBlock.on("snippets:ready", this, function () {
+ self.trigger("snippets:ready");
+ });
+ return res;
+ },
+ start: function () {
+ this.buildingBlock.insertAfter(this.$el);
+ this.rte.editable().find("*").off('mousedown mouseup click');
+ return this._super.apply(this, arguments);
+ },
+ save: function () {
+ this.buildingBlock.clean_for_save();
+ return this._super.apply(this, arguments);
+ },
+});
+
+/**
+ * Add the ability the restart the animations
+ */
+editor.Class.include({
+ start: function () {
+ return this._super.apply(this, arguments).then(function () {
+ animation.start(true);
+ });
+ },
+});
+
+return data;
+});
diff --git a/addons/web_editor/static/src/js/editor/snippets.options.js b/addons/web_editor/static/src/js/editor/snippets.options.js
new file mode 100644
index 00000000000..68301312cd5
--- /dev/null
+++ b/addons/web_editor/static/src/js/editor/snippets.options.js
@@ -0,0 +1,993 @@
+odoo.define('web_editor.snippets.options', function (require) {
+ 'use strict';
+
+ var ajax = require('web.ajax');
+ var Class = require('web.Class');
+ var core = require('web.core');
+ var Dialog = require('web.Dialog');
+ var base = require('web_editor.base');
+ var editor = require('web_editor.editor');
+ var widget = require('web_editor.widget');
+
+ var qweb = core.qweb;
+ var _t = core._t;
+
+ /**
+ * The SnippetOption class handles one option for one snippet. The registry returned by
+ * this module contains the names of the specialized SnippetOption which can be reference
+ * thanks to the data-js key of the full options web_editor template.
+ */
+ var SnippetOption = Class.extend({
+ init: function (BuildingBlock, editor, $target, option_id) {
+ this.buildingBlock = BuildingBlock;
+ this.editor = editor;
+ this.$target = $target;
+
+ this.option = option_id;
+ var option = this.buildingBlock.templateOptions[option_id];
+ this.$el = option.$el.children("li").clone();
+ this.data = option.$el.data();
+
+ this.$overlay = this.$target.data('overlay') || $();
+ },
+
+ // helper for this.$target.find
+ $: function () {
+ return this.$target.find.apply(this.$target, arguments);
+ },
+
+ _bind_li_menu: function () {
+ this.$el.filter("li:hasData").find('a:first')
+ .off('mouseenter click')
+ .on('mouseenter click', _.bind(_mouse, this));
+
+ this.$el
+ .off('mouseenter click', "li:hasData a")
+ .on('mouseenter click', "li:hasData a", _.bind(_mouse, this));
+
+ this.$el.off('mouseleave')
+ .on('mouseleave', _.bind(this.reset, this));
+
+ this.$el.off('mouseleave', "ul")
+ .on('mouseleave', "ul", _.bind(this.reset, this));
+
+ this.methodsToReset = [];
+
+ function _mouse(event) {
+ var $next = $(event.currentTarget).parent();
+
+ // triggers preview or apply methods if a menu item has been clicked
+ this.select(event.type === "click" ? "click" : "over", $next);
+ if (event.type === 'click') {
+ this.set_active();
+ this.$target.trigger("snippet-option-change", [this]);
+ } else {
+ this.$target.trigger("snippet-option-preview", [this]);
+ }
+ }
+ },
+
+ /**
+ * The set_active method tweaks the option dropdown to show the selected value
+ * according to the state of the $target the option customizes.
+ */
+ set_active: function () {
+ var self = this;
+ this.$el.find('[data-toggle_class], [data-select_class]')
+ .addBack('[data-toggle_class], [data-select_class]')
+ .removeClass("active")
+ .filter(function () {
+ var $elem = $(this);
+ var className = $elem.data("toggle_class") || $elem.data("select_class");
+ return self.$target.hasClass(className);
+ })
+ .addClass("active");
+ },
+
+ start: function () {
+ this.set_active();
+ this.$target.on('snippet-option-reset', _.bind(this.set_active, this));
+ this._bind_li_menu();
+ },
+
+ on_focus : function () {
+ this._bind_li_menu();
+ },
+
+ on_blur : function () {},
+ on_clone: function ($clone) {},
+ on_move: function () {},
+ on_remove: function () {},
+
+ drop_and_build_snippet: function () {},
+
+ reset: function () {
+ var self = this;
+ var lis = this.$el.find("li.active").addBack('.active').get();
+ _.each(lis.reverse(), function (li) {
+ var $li = $(li);
+ for (var k in self.methodsToReset) {
+ var method = self.methodsToReset[k];
+ if ($li.closest('[data-' + method + ']').length) {
+ delete self.methodsToReset[k];
+ }
+ }
+ self.select("reset", $li);
+ });
+
+ for (var k in this.methodsToReset) {
+ var method = this.methodsToReset[k];
+ if (method) {
+ this[method]("reset", null);
+ }
+ }
+ this.methodsToReset = [];
+ this.$target.trigger("snippet-option-reset", [this]);
+ },
+
+ /**
+ * Handle action type on a particuler $li (sub)option of the option.
+ * @param type action type to handle ("click", "over", "reset")
+ * @param $li the suboption DOM element which is concerned by the action
+ */
+ select: function (type, $li) {
+ // (sub)options can say they respond to only one action type
+ if ($li.data('only') && type !== $li.data('only')) {
+ return;
+ }
+ // If the type is click, the user selected the option for good (so reset and record action)
+ if (type === "click") {
+ this.reset();
+ this.buildingBlock.getParent().rte.historyRecordUndo(this.$target);
+ }
+
+ // Search for methods (data-...) (i.e. data-toggle_class) on the selected (sub)option and its parents
+ var el = $li[0];
+ var methods = [];
+ do {
+ var m = _.omit(el.dataset, ["oeId", "oeModel", "oeField", "oeXpath", "oeSourceId", "only"]);
+ if (_.keys(m).length) {
+ methods.push([el, m]);
+ }
+ el = el.parentNode;
+ } while (this.$el.parent().has(el).length);
+
+ // Call the found method in the right order (parents -> child) and save them for later reset
+ var self = this;
+ _.each(methods.reverse(), function (data) {
+ var $el = $(data[0]);
+ var methods = data[1];
+
+ for (var k in methods) {
+ if (self[k]) {
+ if (type !== "reset" && self.methodsToReset.indexOf(k) === -1) {
+ self.methodsToReset.push(k);
+ }
+ self[k](type, methods[k], $el);
+ } else {
+ console.error("'" + self.option + "' snippet option have not method '" + k + "'");
+ }
+ }
+ });
+ },
+
+ /**
+ * Default option methods toggle_class and selected_class handle the two behavior of having a list
+ * of option classes to toggle or not and having a list of option classes where only one must be chosen.
+ */
+ toggle_class: function (type, value, $li) {
+ var $lis = this.$el.find("[data-toggle_class]").addBack("[data-toggle_class]");
+
+ function map($lis) {
+ return $lis.map(function () {return $(this).data("toggle_class");}).get().join(" ");
+ }
+ var classes = map($lis);
+ var active_classes = map($lis.filter('.active, :has(.active)'));
+
+ this.$target.removeClass(classes).addClass(active_classes);
+ if (type !== 'reset') {
+ this.$target.toggleClass(value);
+ }
+ },
+ select_class: function (type, value, $li) {
+ var $lis = this.$el.find("[data-select_class]").addBack("[data-select_class]");
+
+ var classes = $lis.map(function () {return $(this).data("select_class");}).get().join(" ");
+
+ this.$target.removeClass(classes);
+ if (value) {
+ this.$target.addClass(value);
+ }
+ },
+
+ /**
+ * Helper method to evaluate the value expression directly as a function
+ */
+ eval: function (type, value, $li) {
+ var fn = new Function("node", "type", "value", "$li", value);
+ fn.call(this, this, type, value, $li);
+ },
+
+ clean_for_save: editor.dummy
+ });
+ var registry = {};
+
+ /* ----- default options ---- */
+
+ registry.marginAndResize = SnippetOption.extend({
+ preventChildPropagation: true,
+
+ start: function () {
+ var self = this;
+ this._super();
+
+ var resize_values = this.getSize();
+ if (resize_values.n) this.$overlay.find(".oe_handle.n").removeClass("readonly");
+ if (resize_values.s) this.$overlay.find(".oe_handle.s").removeClass("readonly");
+ if (resize_values.e) this.$overlay.find(".oe_handle.e").removeClass("readonly");
+ if (resize_values.w) this.$overlay.find(".oe_handle.w").removeClass("readonly");
+ if (resize_values.size) this.$overlay.find(".oe_handle.size").removeClass("readonly");
+
+ var $auto_size = this.$overlay.find(".oe_handle.size .auto_size");
+ var $fixed_size = this.$overlay.find(".oe_handle.size .size");
+
+ var auto_sized = isNaN(parseInt(self.$target.prop("style").height));
+ $fixed_size.toggleClass("active", !auto_sized);
+ $auto_size.toggleClass("active", auto_sized);
+
+ $fixed_size.add(this.$overlay.find(".oe_handle:not(.size)")).on('mousedown', function (event) {
+ event.preventDefault();
+
+ var $handle = $(this);
+
+ var resize_values = self.getSize();
+ var compass = false;
+ var XY = false;
+ if ($handle.hasClass('n')) {
+ compass = 'n';
+ XY = 'Y';
+ }
+ else if ($handle.hasClass('s')) {
+ compass = 's';
+ XY = 'Y';
+ }
+ else if ($handle.hasClass('e')) {
+ compass = 'e';
+ XY = 'X';
+ }
+ else if ($handle.hasClass('w')) {
+ compass = 'w';
+ XY = 'X';
+ }
+ else if ($handle.hasClass('size')) {
+ compass = 'size';
+ XY = 'Y';
+ }
+
+ var resize = resize_values[compass];
+ if (!resize) return;
+
+
+ if (compass === 'size') {
+ var offset = self.$target.offset().top;
+ if (self.$target.css("background").match(/rgba\(0, 0, 0, 0\)/)) {
+ self.$target.addClass("resize_editor_busy");
+ }
+ } else {
+ var xy = event['page'+XY];
+ var current = resize[2] || 0;
+ var margin_dir = {s:'bottom', n: 'top', w: 'left', e: 'right'}[compass];
+ var real_margin = parseInt(self.$target.css('margin-'+margin_dir));
+ _.each(resize[0], function (val, key) {
+ if (self.$target.hasClass(val)) {
+ current = key;
+ } else if (resize[1][key] === real_margin) {
+ current = key;
+ }
+ });
+ var begin = current;
+ var beginClass = self.$target.attr("class");
+ var regClass = new RegExp("\\s*" + resize[0][begin].replace(/[-]*[0-9]+/, '[-]*[0-9]+'), 'g');
+ }
+
+ self.buildingBlock.editor_busy = true;
+
+ var cursor = $handle.css("cursor")+'-important';
+ var $body = $(document.body);
+ $body.addClass(cursor);
+
+ var body_mousemove = function (event) {
+ event.preventDefault();
+ if (compass === 'size') {
+ var dy = event.pageY-offset;
+ dy = dy - dy%resize;
+ if (dy <= 0) dy = resize;
+ self.$target.css("height", dy+"px");
+ self.$target.css("overflow", "hidden");
+ self.on_resize(compass, null, dy);
+ self.buildingBlock.cover_target(self.$overlay, self.$target);
+ return;
+ }
+ var dd = event['page'+XY] - xy + resize[1][begin];
+ var next = current+1 === resize[1].length ? current : (current+1);
+ var prev = current ? (current-1) : 0;
+
+ var change = false;
+ if (dd > (2*resize[1][next] + resize[1][current])/3) {
+ self.$target.attr("class", (self.$target.attr("class")||'').replace(regClass, ''));
+ self.$target.addClass(resize[0][next]);
+ current = next;
+ change = true;
+ }
+ if (prev !== current && dd < (2*resize[1][prev] + resize[1][current])/3) {
+ self.$target.attr("class", (self.$target.attr("class")||'').replace(regClass, ''));
+ self.$target.addClass(resize[0][prev]);
+ current = prev;
+ change = true;
+ }
+
+ if (change) {
+ self.on_resize(compass, beginClass, current);
+ self.buildingBlock.cover_target(self.$overlay, self.$target);
+ self.adapt_margin_preview_size();
+ $handle.addClass('oe_handle_change');
+ }
+ };
+
+ var body_mouseup = function () {
+ $body.unbind('mousemove', body_mousemove);
+ $body.unbind('mouseup', body_mouseup);
+ $body.removeClass(cursor);
+ setTimeout(function () {
+ self.buildingBlock.editor_busy = false;
+ if (begin !== current) {
+ self.buildingBlock.getParent().rte.historyRecordUndo(self.$target, 'resize_' + XY);
+ }
+ },0);
+ self.$target.removeClass("resize_editor_busy");
+ $handle.removeClass('oe_handle_change');
+
+ if (compass === "size") {
+ $fixed_size.addClass("active");
+ $auto_size.removeClass("active");
+ } else {
+ self._highlight_margins_preview(300);
+ }
+ };
+ $body.mousemove(body_mousemove);
+ $body.mouseup(body_mouseup);
+ });
+ $auto_size.on('click', function () {
+ self.$target.css("height", "");
+ self.$target.css("overflow", "");
+ self.buildingBlock.getParent().rte.historyRecordUndo(self.$target, 'resize_Y');
+ self.buildingBlock.cover_target(self.$overlay, self.$target);
+
+ $fixed_size.removeClass("active");
+ $auto_size.addClass("active");
+
+ return false;
+ });
+ },
+ getSize: function () {
+ this.grid = {};
+ return this.grid;
+ },
+
+ on_focus : function () {
+ this._super();
+ this.change_cursor();
+ this.adapt_margin_preview_size();
+ },
+
+ change_cursor : function () {
+ var _class = this.$target.attr("class") || "";
+ var $handle_s = this.$overlay.find('.oe_handle.s');
+ var $handle_n = this.$overlay.find('.oe_handle.n');
+ var $handle_w = this.$overlay.find('.oe_handle.w');
+
+ var col = _class.match(/col-md-([0-9-]+)/i);
+ col = col ? +col[1] : 0;
+
+ var offset = _class.match(/col-md-offset-([0-9-]+)/i);
+ offset = offset ? +offset[1] : 0;
+
+ var overlay_class = this.$overlay.attr("class").replace(/(^|\s+)block-[^\s]*/gi, '');
+ if (col+offset >= 12) overlay_class+= " block-e-right";
+ if (col === 1) overlay_class+= " block-w-right block-e-left";
+ if (offset === 0) overlay_class+= " block-w-left";
+ $handle_w.toggleClass('oe_handle_centered', offset > 0).toggleClass('o_handle_edited', offset >= 1 );
+
+ var mb = _class.match(/mb([0-9-]+)/i);
+ mb = mb ? +mb[1] : parseInt(this.$target.css('margin-bottom'));
+ if (mb >= 128) overlay_class+= " block-s-bottom";
+ else if (!mb) overlay_class+= " block-s-top";
+ $handle_s.toggleClass('oe_handle_centered', mb >= 32).toggleClass('o_handle_edited', mb > 0 );
+
+ var mt = _class.match(/mt([0-9-]+)/i);
+ mt = mt ? +mt[1] : parseInt(this.$target.css('margin-top'));
+ if (mt >= 128) overlay_class+= " block-n-top";
+ else if (!mt) overlay_class+= " block-n-bottom";
+ $handle_n.toggleClass('oe_handle_centered', mt >= 32).toggleClass('o_handle_edited', mt > 0 );
+
+ this.$overlay.attr("class", overlay_class);
+ },
+
+ adapt_margin_preview_size : function () {
+ var self = this,
+ ml = this.$target.css('margin-left');
+ _.each(this.$overlay.find(".oe_handle.n, .oe_handle.s"), function (handle) {
+ var $handle = $(handle);
+ var direction = $handle.hasClass('n') ? 'top': 'bottom';
+ $handle.height(self.$target.css('margin-' + direction ));
+ });
+ this.$overlay.find(".oe_handle.w").css({
+ width: ml,
+ left: '-' + ml
+ });
+ },
+
+ _highlight_margins_preview: function (timing) {
+ var handlers = this.$overlay.find(".oe_handle.n, .oe_handle.s, .oe_handle.w");
+ handlers.addClass('oe_handle_active').delay(timing).queue(function(){
+ handlers.removeClass('oe_handle_active').dequeue();
+ });
+ },
+
+ /* on_resize
+ * called when the box is resizing and the class change, before the cover_target
+ * @compass: resize direction : 'n', 's', 'e', 'w'
+ * @beginClass: attributes class at the begin
+ * @current: curent increment in this.grid
+ */
+ on_resize: function () {
+ this.change_cursor();
+ }
+ });
+
+ registry["margin-y"] = registry.marginAndResize.extend({
+ preventChildPropagation: true,
+
+ getSize: function () {
+ this.grid = this._super();
+ var grid = [0,4,8,16,32,48,64,92,128];
+ this.grid = {
+ // list of class (Array), grid (Array), default value (INT)
+ n: [_.map(grid, function (v) {return 'mt'+v;}), grid],
+ s: [_.map(grid, function (v) {return 'mb'+v;}), grid],
+ // INT if the user can resize the snippet (resizing per INT px)
+ size: null
+ };
+ return this.grid;
+ },
+ });
+
+ registry.resize = registry.marginAndResize.extend({
+ preventChildPropagation: true,
+
+ getSize: function () {
+ this.grid = this._super();
+ this.grid.size = 8;
+ return this.grid;
+ },
+ });
+
+ /**
+ * The colorpicker option is designed to change the background color class of a snippet. This class change the
+ * default background color and text color of the snippet content.
+ */
+ registry.colorpicker = SnippetOption.extend({
+ start: function () {
+ var self = this;
+ var res = this._super.apply(this, arguments);
+
+ if (!this.$el.find('.colorpicker').length) {
+ var $pt = $(qweb.render('web_editor.snippet.option.colorpicker'));
+ var $clpicker = $(qweb.render('web_editor.colorpicker'));
+
+ // Retrieve excluded palettes list
+ var excluded = [];
+ if (this.data.paletteExclude) {
+ excluded = this.data.paletteExclude.replace(/ /g, '').split(',');
+ }
+ // Apply a custom title if specified
+ if (this.data.paletteTitle) {
+ $pt.find('.note-palette-title').text(this.data.paletteTitle);
+ }
+
+ var $toggles = $pt.find('.o_colorpicker_section_menu');
+ var $tabs = $pt.find('.o_colorpicker_section_tabs');
+
+ // Remove excluded palettes
+ _.each(excluded, function (exc) {
+ $clpicker.find('[data-name="' + exc + '"]').remove();
+ });
+
+ var $sections = $clpicker.find('.o_colorpicker_section');
+
+ if ($sections.length > 1) { // Multi-palette layout
+ $sections.each(function () {
+ var $section = $(this);
+ var id = 'o_palette_' + $section.data('name') + _.uniqueId();
+
+ var $li = $(' ')
+ .append($(' ', {href: '#' + id})
+ .append($(' ', {'class': $section.data('iconClass') || '', html: $section.data('iconContent') || ''})));
+ $toggles.append($li);
+
+ $tabs.append($section.addClass('tab-pane').attr('id', id));
+ });
+
+ // If a default palette is defined, make it active
+ if (this.data.paletteDefault) {
+ var $palette_def = $tabs.find('div[data-name="' + self.data.paletteDefault + '"]');
+ var pos = $tabs.find('> div').index($palette_def);
+
+ $toggles.children('li').eq(pos).addClass('active');
+ $palette_def.addClass('active');
+ } else {
+ $toggles.find('li').first().addClass('active');
+ $tabs.find('div').first().addClass('active');
+ }
+
+ $toggles.on('click mouseover', '> li > a', function (e) {
+ e.preventDefault();
+ e.stopPropagation();
+ $(this).tab('show');
+ });
+ } else if ($sections.length === 1) { // Unique palette layout
+ $tabs.addClass('o_unique_palette').append($sections.addClass('tab-pane active'));
+ } else {
+ $toggles.parent().empty().append($clpicker);
+ }
+
+ this.$el.find('li').append($pt);
+ }
+ if (this.$el.data('area')) {
+ this.$target = this.$target.find(this.$el.data('area'));
+ this.$el.removeData('area').removeAttr('area');
+ }
+
+ var classes = [];
+ this.$el.find(".colorpicker button").each(function () {
+ var $color = $(this);
+ if (!$color.data("color")) {
+ return;
+ }
+
+ var className = 'bg-' + $color.data('color');
+ $color.addClass(className);
+ if (self.$target.hasClass(className)) {
+ self.color = className;
+ $color.addClass("selected");
+ }
+ classes.push(className);
+ });
+ this.classes = classes.join(" ");
+
+ this.bind_events();
+ return res;
+ },
+ bind_events: function () {
+ var self = this;
+ var $colors = this.$el.find(".colorpicker button");
+ $colors
+ .mouseenter(function (e) {
+ self.$target.removeClass(self.classes);
+ var color = $(this).data("color");
+ if (color) {
+ self.$target.addClass('bg-' + color);
+ }
+ self.$target.trigger("background-color-event", e.type);
+ })
+ .mouseleave(function (e) {
+ self.$target.removeClass(self.classes);
+ var $selected = $colors.filter(".selected");
+ var color = $selected.length && $selected.data("color");
+ if (color) {
+ self.$target.addClass('bg-' + color);
+ }
+ self.$target.trigger("background-color-event", e.type);
+ })
+ .click(function (e) {
+ $colors.removeClass("selected");
+ $(this).addClass("selected");
+ self.$target.closest(".o_editable").trigger("content_changed");
+ self.$target.trigger("background-color-event", e.type);
+ });
+
+ this.$el.find('.note-color-reset').on('click', function () {
+ self.$target.removeClass(self.classes);
+ $colors.removeClass("selected");
+ });
+ }
+ });
+
+ /**
+ * The background option is designed to change the background image of a snippet.
+ */
+ registry.background = SnippetOption.extend({
+ start: function () {
+ var res = this._super.apply(this, arguments);
+ this.bind_bg_events();
+ return res;
+ },
+
+ bind_bg_events: function () {
+ this.$target.off(".background-option")
+ .on("background-color-event.background-option", (function (e, type) {
+ e.stopPropagation();
+ if (e.currentTarget !== e.target) return;
+ this.$el.find("li[data-background=''] > a").trigger(type);
+ }).bind(this));
+ },
+ background: function (type, value, $li) {
+ if (value && value.length) {
+ this.$target.css("background-image", "url(" + value + ")");
+ this.$target.addClass("oe_img_bg");
+ } else {
+ this.$target.css("background-image", "");
+ this.$target.removeClass("oe_img_bg oe_custom_bg");
+ }
+ },
+ select_class: function (type, value, $li) {
+ this.background(type, '', $li);
+ this._super(type, value ? (value + " oe_img_bg") : value, $li);
+ },
+ choose_image: function (type, value, $li) {
+ if(type !== "click") {
+ return;
+ }
+
+ // Put fake image in the DOM, edit it and use it as background-image
+ var $image = $(" ", {"class": "hidden", "src": value}).appendTo(this.$target);
+
+ var _editor = new widget.MediaDialog(null, {}, null, $image[0]).open();
+ _editor.$('[href="#editor-media-video"], [href="#editor-media-icon"]').addClass('hidden');
+
+ _editor.on('save', this, function () {
+ var value = $image.attr("src");
+ this.background(type, value, $li);
+ this.$target.addClass("oe_custom_bg");
+ this.set_active();
+ this.$target.trigger("snippet-option-change", [this]);
+ });
+ _editor.on('closed', this, function () {
+ $image.remove();
+ });
+ },
+ set_active: function () {
+ this._super.apply(this, arguments);
+
+ var src = this.$target.css("background-image").replace(/url\(['"]*|['"]*\)|^none$/g, "");
+ if (this.$target.hasClass('oe_custom_bg')) {
+ this.$el.find('li[data-choose_image]').data("background", src).attr("data-background", src);
+ }
+
+ this.$el.find('li[data-background]')
+ .removeClass("active")
+ .filter(function () {
+ var bgOption = $(this).data("background");
+ return (bgOption === "" && src === "" || bgOption !== "" && src.indexOf(bgOption) >= 0);
+ })
+ .addClass("active");
+ }
+ });
+
+ /**
+ * The background_position option is designed to change the background image position of a snippet.
+ */
+ registry.background_position = SnippetOption.extend({
+ start: function () {
+ this._super.apply(this, arguments);
+ this.on_focus();
+ var self = this;
+ this.$target.on("snippet-option-change", function () {
+ self.on_focus();
+ });
+ },
+ on_focus: function () {
+ this._super.apply(this, arguments);
+ this.$el.toggleClass('hidden', this.$target.css('background-image') === 'none');
+ },
+ background_position: function (type, value, $li) {
+ if (type != 'click') { return; }
+ var self = this;
+
+ this.previous_state = [this.$target.attr('class'), this.$target.css('background-size'), this.$target.css('background-position')];
+
+ this.bg_pos = self.$target.css('background-position').split(' ');
+ this.bg_siz = self.$target.css('background-size').split(' ');
+
+ this.modal = new Dialog(null, {
+ title: _t("Background Image Sizing"),
+ $content: $(qweb.render('web_editor.dialog.background_position')),
+ buttons: [
+ {text: _t("Ok"), classes: "btn-primary", close: true, click: _.bind(this.save, this)},
+ {text: _t("Discard"), close: true, click: _.bind(this.discard, this)},
+ ],
+ }).open();
+
+ this.modal.opened().then(function () {
+ // Fetch data form $target
+ var value = ((self.$target.hasClass('o_bg_img_opt_contain'))? 'contain' : ((self.$target.hasClass('o_bg_img_opt_custom'))? 'custom' : 'cover'));
+ self.modal.$("> label > input[value=" + value + "]").prop('checked', true);
+
+ if(self.$target.hasClass("o_bg_img_opt_repeat")) {
+ self.modal.$("#o_bg_img_opt_contain_repeat").prop('checked', true);
+ self.modal.$("#o_bg_img_opt_custom_repeat").val('o_bg_img_opt_repeat');
+ } else if (self.$target.hasClass("o_bg_img_opt_repeat_x")) {
+ self.modal.$("#o_bg_img_opt_custom_repeat").val('o_bg_img_opt_repeat_x');
+ } else if (self.$target.hasClass("o_bg_img_opt_repeat_y")) {
+ self.modal.$("#o_bg_img_opt_custom_repeat").val('o_bg_img_opt_repeat_y');
+ }
+
+ if(self.bg_pos.length > 1) {
+ self.bg_pos = {
+ x: self.bg_pos[0],
+ y: self.bg_pos[1],
+ };
+ self.modal.$("#o_bg_img_opt_custom_pos_x").val(self.bg_pos.x.replace('%', ''));
+ self.modal.$("#o_bg_img_opt_custom_pos_y").val(self.bg_pos.y.replace('%', ''));
+ }
+ if(self.bg_siz.length > 1) {
+ self.modal.$("#o_bg_img_opt_custom_size_x").val(self.bg_siz[0].replace('%', ''));
+ self.modal.$("#o_bg_img_opt_custom_size_y").val(self.bg_siz[1].replace('%', ''));
+ }
+
+ // Focus Point
+ self.$focus = self.modal.$(".o_focus_point");
+ self.update_pos_information();
+
+ var img_url = /\(['"]?([^'"]+)['"]?\)/g.exec(self.$target.css('background-image'));
+ img_url = (img_url && img_url[1]) || '';
+ var $img = $(' ', {'class': 'img img-responsive', src: img_url});
+ $img.on('load', function () {
+ self.bind_img_events($img);
+ });
+ $img.prependTo(self.modal.$(".o_bg_img_opt_object"));
+
+ // Bind events
+ self.modal.$el.on('change', '> label > input', function (e) {
+ self.modal.$('> .o_bg_img_opt').addClass('o_hidden')
+ .filter("[data-value=" + e.target.value + "]")
+ .removeClass('o_hidden');
+ });
+ self.modal.$el.on('change', 'input, select', function (e) {
+ self.save();
+ });
+ self.modal.$("> label > input:checked").trigger('change');
+ });
+ },
+ bind_img_events: function ($img) {
+ var self = this;
+
+ var mousedown = false;
+ $img.on('mousedown', function (e) {
+ mousedown = true;
+ });
+ $img.on('mousemove', function (e) {
+ if (mousedown) {
+ _update(e);
+ }
+ });
+ $img.on('mouseup', function (e) {
+ self.$focus.addClass('o_with_transition');
+ _update(e);
+ setTimeout(function () {
+ self.$focus.removeClass('o_with_transition');
+ }, 200);
+ mousedown = false;
+ });
+
+ function _update(e) {
+ var posX = e.pageX - $(e.target).offset().left;
+ var posY = e.pageY - $(e.target).offset().top;
+ self.bg_pos = {
+ x: clip_value(posX/$img.width()*100).toFixed(2) + '%',
+ y: clip_value(posY/$img.height()*100).toFixed(2) + '%',
+ };
+ self.update_pos_information();
+ self.save();
+ }
+
+ function clip_value(value) {
+ return Math.max(0, Math.min(value, 100));
+ }
+ },
+ update_pos_information: function () {
+ this.modal.$(".o_bg_img_opt_ui_info .o_x").text(this.bg_pos.x);
+ this.modal.$(".o_bg_img_opt_ui_info .o_y").text(this.bg_pos.y);
+ this.$focus.css({
+ left: this.bg_pos.x,
+ top: this.bg_pos.y,
+ });
+ },
+ save: function () {
+ this.clean();
+
+ var bg_img_size = this.modal.$('> :not(label):not(.o_hidden)').data('value') || 'cover';
+ switch (bg_img_size) {
+ case "cover":
+ this.$target.css('background-position', this.bg_pos.x + ' ' + this.bg_pos.y);
+ break;
+ case "contain":
+ this.$target.addClass('o_bg_img_opt_contain');
+ this.$target.toggleClass('o_bg_img_opt_repeat', this.modal.$("#o_bg_img_opt_contain_repeat").prop("checked"));
+ break;
+ case "custom":
+ this.$target.addClass('o_bg_img_opt_custom');
+ var sizeX = this.modal.$("#o_bg_img_opt_custom_size_x").val();
+ var sizeY = this.modal.$("#o_bg_img_opt_custom_size_y").val();
+ var posX = this.modal.$("#o_bg_img_opt_custom_pos_x").val();
+ var posY = this.modal.$("#o_bg_img_opt_custom_pos_y").val();
+ this.$target.addClass(this.modal.$("#o_bg_img_opt_custom_repeat").val())
+ .css({
+ 'background-size': ((sizeX)? sizeX + '%' : 'auto') + " " + ((sizeY)? sizeY + '%' : 'auto'),
+ 'background-position': ((posX)? posX + '%' : 'auto') + " " + ((posY)? posY + '%' : 'auto'),
+ });
+ break;
+ }
+ },
+ discard: function () {
+ this.clean();
+ if (this.previous_state) {
+ this.$target.addClass(this.previous_state[0]).css({
+ 'background-size': this.previous_state[1],
+ 'background-position': this.previous_state[2],
+ });
+ }
+ },
+ clean: function () {
+ this.$target.removeClass('o_bg_img_opt_contain o_bg_img_opt_custom o_bg_img_opt_repeat o_bg_img_opt_repeat_x o_bg_img_opt_repeat_y')
+ .css({
+ 'background-size': '',
+ 'background-position': '',
+ });
+ },
+ });
+
+ /* t-field options */
+
+ registry.many2one = SnippetOption.extend({
+ start: function () {
+ var self = this;
+
+ this.Model = this.$target.data('oe-many2one-model');
+ this.ID = +this.$target.data('oe-many2one-id');
+
+ // create search button and bind search bar
+ this.$btn = $(qweb.render("web_editor.many2one.button"))
+ .insertAfter(this.$overlay.find('.oe_options'));
+
+ this.$ul = this.$btn.find("ul");
+ this.$search = this.$ul.find('li:first');
+ this.$search.find('input').on('mousedown click mouseup keyup keydown', function (e) {
+ e.stopPropagation();
+ });
+
+ // move menu item
+ setTimeout(function () {
+ if (self.$overlay.find('.oe_options').hasClass('hidden')) {
+ self.$btn.css('height', '0').find('> a').addClass('hidden');
+ self.$ul.show().css({
+ 'top': '-24px', 'margin': '0', 'padding': '2px 0', 'position': 'relative'
+ });
+ } else {
+ self.$btn.find('a').on('click', function (e) {
+ self.clear();
+ });
+ }
+ },0);
+
+ // bind search input
+ this.$search.find('input')
+ .focus()
+ .on('keyup', function (e) {
+ self.find_existing($(this).val());
+ });
+
+ // bind result
+ this.$ul.on('click', "li:not(:first) a", function (e) {
+ self.select_record(this);
+ });
+ },
+
+ on_focus: function () {
+ this.$target.attr('contentEditable', 'false');
+ this.clear();
+ this._super.apply(this, arguments);
+ },
+
+ clear: function () {
+ var self = this;
+ this.$search.siblings().remove();
+ self.$search.find('input').val("");
+ setTimeout(function () {
+ self.$search.find('input').focus();
+ },0);
+ },
+
+ find_existing: function (name) {
+ var self = this;
+ var domain = [];
+ if (!name || !name.length) {
+ self.$search.siblings().remove();
+ return;
+ }
+ if (isNaN(+name)) {
+ if (this.Model !== "res.partner") {
+ domain.push(['name', 'ilike', name]);
+ } else {
+ domain.push('|', ['name', 'ilike', name], ['email', 'ilike', name]);
+ }
+ } else {
+ domain.push(['id', '=', name]);
+ }
+
+ ajax.jsonRpc('/web/dataset/call_kw', 'call', {
+ model: this.Model,
+ method: 'search_read',
+ args: [domain, this.Model === "res.partner" ? ['name', 'display_name', 'city', 'country_id'] : ['name', 'display_name']],
+ kwargs: {
+ order: 'name DESC',
+ limit: 5,
+ context: base.get_context(),
+ }
+ }).then(function (result) {
+ self.$search.siblings().remove();
+ self.$search.after(qweb.render("web_editor.many2one.search",{contacts:result}));
+ });
+ },
+
+ get_contact_rendering: function (options) {
+ return ajax.jsonRpc('/web/dataset/call_kw', 'call', {
+ model: 'ir.qweb.field.contact',
+ method: 'get_record_to_html',
+ args: [[this.ID]],
+ kwargs: {
+ options: options,
+ context: base.get_context(),
+ }
+ });
+ },
+
+ select_record: function (li) {
+ var self = this;
+
+ this.ID = +$(li).data("id");
+ this.$target.attr('data-oe-many2one-id', this.ID).data('oe-many2one-id', this.ID);
+
+ this.buildingBlock.getParent().rte.historyRecordUndo(this.$target);
+ this.$target.trigger('content_changed');
+
+ if (self.$target.data('oe-type') === "contact") {
+ $('[data-oe-contact-options]')
+ .filter('[data-oe-model="'+self.$target.data('oe-model')+'"]')
+ .filter('[data-oe-id="'+self.$target.data('oe-id')+'"]')
+ .filter('[data-oe-field="'+self.$target.data('oe-field')+'"]')
+ .filter('[data-oe-contact-options!="'+self.$target.data('oe-contact-options')+'"]')
+ .add(self.$target)
+ .attr('data-oe-many2one-id', self.ID).data('oe-many2one-id', self.ID)
+ .each(function () {
+ var $node = $(this);
+ self.get_contact_rendering($node.data('oe-contact-options'))
+ .then(function (html) {
+ $node.html(html);
+ });
+ });
+ } else {
+ self.$target.html($(li).data("name"));
+ }
+
+ setTimeout(function () {
+ self.buildingBlock.make_active(false);
+ },0);
+ }
+ });
+
+ return {
+ Class: SnippetOption,
+ registry: registry,
+ };
+});
diff --git a/addons/web_editor/static/src/js/editor/summernote.js b/addons/web_editor/static/src/js/editor/summernote.js
new file mode 100644
index 00000000000..c2e2ea54dca
--- /dev/null
+++ b/addons/web_editor/static/src/js/editor/summernote.js
@@ -0,0 +1,2383 @@
+odoo.define('web_editor.summernote', function (require) {
+'use strict';
+
+var core = require('web.core');
+require('summernote/summernote'); // wait that summernote is loaded
+
+var _t = core._t;
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////
+/* Summernote Lib (neek hack to make accessible: method and object) */
+
+var agent = $.summernote.core.agent;
+var dom = $.summernote.core.dom;
+var range = $.summernote.core.range;
+var list = $.summernote.core.list;
+var key = $.summernote.core.key;
+var eventHandler = $.summernote.eventHandler;
+var editor = eventHandler.modules.editor;
+var renderer = $.summernote.renderer;
+var options = $.summernote.options;
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////
+/* Add method to Summernote*/
+
+dom.hasContentAfter = function (node) {
+ var next;
+ if(dom.isEditable(node)) return;
+ while (node.nextSibling) {
+ next = node.nextSibling;
+ if (next.tagName || dom.isVisibleText(next) || dom.isBR(next)) return next;
+ node = next;
+ }
+};
+dom.hasContentBefore = function (node) {
+ var prev;
+ if(dom.isEditable(node)) return;
+ while (node.previousSibling) {
+ prev = node.previousSibling;
+ if (prev.tagName || dom.isVisibleText(prev) || dom.isBR(prev)) return prev;
+ node = prev;
+ }
+};
+dom.ancestorHaveNextSibling = function (node, pred) {
+ pred = pred || dom.hasContentAfter;
+ while (!dom.isEditable(node) && (!node.nextSibling || !pred(node))) { node = node.parentNode; }
+ return node;
+};
+dom.ancestorHavePreviousSibling = function (node, pred) {
+ pred = pred || dom.hasContentBefore;
+ while (!dom.isEditable(node) && (!node.previousSibling || !pred(node))) { node = node.parentNode; }
+ return node;
+};
+dom.nextElementSibling = function (node) {
+ while (node) {
+ node = node.nextSibling;
+ if (node && node.tagName) {
+ break;
+ }
+ }
+ return node;
+};
+dom.previousElementSibling = function (node) {
+ while (node) {
+ node = node.previousSibling;
+ if (node && node.tagName) {
+ break;
+ }
+ }
+ return node;
+};
+dom.lastChild = function (node) {
+ while (node.lastChild) { node = node.lastChild; }
+ return node;
+};
+dom.firstChild = function (node) {
+ while (node.firstChild) { node = node.firstChild; }
+ return node;
+};
+dom.lastElementChild = function (node, deep) {
+ node = deep ? dom.lastChild(node) : node.lastChild;
+ return !node || node.tagName ? node : dom.previousElementSibling(node);
+};
+dom.firstElementChild = function (node, deep) {
+ node = deep ? dom.firstChild(node) : node.firstChild;
+ return !node || node.tagName ? node : dom.nextElementSibling(node);
+};
+dom.isEqual = function (prev, cur) {
+ if (prev.tagName !== cur.tagName) {
+ return false;
+ }
+ if ((prev.attributes ? prev.attributes.length : 0) !== (cur.attributes ? cur.attributes.length : 0)) {
+ return false;
+ }
+
+ function strip(text) {
+ return text && text.replace(/^\s+|\s+$/g, '').replace(/\s+/g, ' ');
+ }
+ var att, att2;
+ loop_prev:
+ for(var a in prev.attributes) {
+ att = prev.attributes[a];
+ for(var b in cur.attributes) {
+ att2 = cur.attributes[b];
+ if (att.name === att2.name) {
+ if (strip(att.value) != strip(att2.value)) return false;
+ continue loop_prev;
+ }
+ }
+ return false;
+ }
+ return true;
+};
+/**
+ * Checks that the node only has a @style, not e.g. @class or whatever
+ */
+dom.hasOnlyStyle = function (node) {
+ for (var i = 0; i < node.attributes.length; i++) {
+ var attr = node.attributes[i];
+ if (attr.attributeName !== 'style') {
+ return false;
+ }
+ }
+ return true;
+};
+dom.hasProgrammaticStyle = function (node) {
+ var styles = ["float", "display", "position", "top", "left", "right", "bottom"];
+ for (var i = 0; i < node.style.length; i++) {
+ var style = node.style[i];
+ if (styles.indexOf(style) !== -1) {
+ return true;
+ }
+ }
+ return false;
+};
+dom.mergeFilter = function (prev, cur, parent) {
+ // merge text nodes
+ if (prev && (dom.isText(prev) || (['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'LI', 'P'].indexOf(prev.tagName) !== -1 && prev !== cur.parentNode)) && dom.isText(cur)) {
+ return true;
+ }
+ if (prev && prev.tagName === "P" && dom.isText(cur)) {
+ return true;
+ }
+ if (prev && dom.isText(cur) && !dom.isVisibleText(cur) && (dom.isText(prev) || dom.isVisibleText(prev))) {
+ return true;
+ }
+ if (prev && !dom.isBR(prev) && dom.isEqual(prev, cur) &&
+ ((prev.tagName && dom.getComputedStyle(prev).display === "inline" &&
+ cur.tagName && dom.getComputedStyle(cur).display === "inline"))) {
+ return true;
+ }
+ if (dom.isEqual(parent, cur) &&
+ ((parent.tagName && dom.getComputedStyle(parent).display === "inline" &&
+ cur.tagName && dom.getComputedStyle(cur).display === "inline"))) {
+ return true;
+ }
+ if (parent && cur.tagName === "FONT" && (!cur.firstChild || (!cur.attributes.getNamedItem('style') && !cur.className.length))) {
+ return true;
+ }
+ // On backspace, webkit browsers create a with a bunch of
+ // inline styles "remembering" where they come from.
+ // chances are we had e.g.
+ // foo
+ // bar
+ // merged the lines getting this in webkit
+ // foobar
+ if (parent && cur.tagName === "SPAN" && dom.hasOnlyStyle(cur) && !dom.hasProgrammaticStyle(cur)) {
+ return true;
+ }
+};
+dom.doMerge = function (prev, cur) {
+ if (prev.tagName) {
+ if (prev.childNodes.length && !prev.textContent.match(/\S/) && dom.firstElementChild(prev) && dom.isBR(dom.firstElementChild(prev))) {
+ prev.removeChild(dom.firstElementChild(prev));
+ }
+ if (cur.tagName) {
+ while (cur.firstChild) {
+ prev.appendChild(cur.firstChild);
+ }
+ cur.parentNode.removeChild(cur);
+ } else {
+ prev.appendChild(cur);
+ }
+ } else {
+ if (cur.tagName) {
+ var deep = cur;
+ while (deep.tagName && deep.firstChild) {deep = deep.firstChild;}
+ prev.appendData(deep.textContent);
+ cur.parentNode.removeChild(cur);
+ } else {
+ prev.appendData(cur.textContent);
+ cur.parentNode.removeChild(cur);
+ }
+ }
+};
+dom.merge = function (node, begin, so, end, eo, mergeFilter, all) {
+ mergeFilter = mergeFilter || dom.mergeFilter;
+ var _merged = false;
+ var add = all || false;
+
+ if (!begin) {
+ begin = node;
+ while(begin.firstChild) {begin = begin.firstChild;}
+ so = 0;
+ } else if (begin.tagName && begin.childNodes[so]) {
+ begin = begin.childNodes[so];
+ so = 0;
+ }
+ if (!end) {
+ end = node;
+ while(end.lastChild) {end = end.lastChild;}
+ eo = end.textContent.length-1;
+ } else if (end.tagName) {
+
+ } else if (end.tagName && end.childNodes[so]) {
+ end = end.childNodes[so];
+ so = 0;
+ }
+
+ begin = dom.firstChild(begin);
+ if (dom.isText(begin) && so > begin.textContent.length) {
+ so = 0;
+ }
+ end = dom.firstChild(end);
+ if (dom.isText(end) && eo > end.textContent.length) {
+ eo = 0;
+ }
+
+ function __merge (node) {
+ var merged = false;
+ var prev;
+ for (var k=0; k 1 && dom.isText(cur) && !dom.isVisibleText(cur)) {
+ removed = true;
+ if (cur === begin) {
+ t_begin = dom.hasContentBefore(dom.ancestorHavePreviousSibling(cur));
+ if(t_begin) {
+ so = 0;
+ begin = dom.lastChild(t_begin);
+ }
+ }
+ if (cur === end) {
+ t_end = dom.hasContentAfter(dom.ancestorHaveNextSibling(cur));
+ if(t_end) {
+ eo = 1;
+ end = dom.firstChild(t_end);
+ if (dom.isText(end)) {
+ eo = end.textContent.length;
+ }
+ }
+ }
+ cur.parentNode.removeChild(cur);
+ begin = dom.lastChild(begin);
+ end = dom.lastChild(end);
+ k--;
+ continue;
+ }
+
+ // convert HTML space
+ if (dom.isText(cur)) {
+ var text;
+ var exp1 = /[\t\n\r ]+/g;
+ var exp2 = /(?!([ ]|\u00A0)|^)\u00A0(?!([ ]|\u00A0)|$)/g;
+ if (cur === begin) {
+ var temp = cur.textContent.substr(0, so);
+ var _temp = temp.replace(exp1, ' ').replace(exp2, ' ');
+ so -= temp.length - _temp.length;
+ }
+ if (cur === end) {
+ var temp = cur.textContent.substr(0, eo);
+ var _temp = temp.replace(exp1, ' ').replace(exp2, ' ');
+ eo -= temp.length - _temp.length;
+ }
+ var text = cur.textContent.replace(exp1, ' ').replace(exp2, ' ');
+ removed = removed || cur.textContent.length !== text.length;
+ cur.textContent = text;
+ }
+ }
+ })(node);
+
+ return {
+ removed: removed,
+ sc: begin,
+ ec: end,
+ so: !dom.isBR(begin) && so > 0 ? so : 0,
+ eo: dom.isBR(end) ? 0 : eo
+ };
+};
+dom.node = function (node) {
+ return node.tagName ? node : node.parentNode;
+};
+dom.removeBetween = function (sc, so, ec, eo, towrite) {
+ if (ec.tagName) {
+ if (ec.childNodes[eo]) {
+ ec = ec.childNodes[eo];
+ eo = 0;
+ } else {
+ ec = dom.lastChild(ec);
+ eo = dom.nodeLength(ec);
+ }
+ }
+ if (sc.tagName) {
+ sc = sc.childNodes[so] || dom.firstChild(ec);
+ so = 0;
+ if (!dom.hasContentBefore(sc) && towrite) {
+ sc.parentNode.insertBefore(document.createTextNode('\u00A0'), sc);
+ }
+ }
+ if (!eo && sc !== ec) {
+ ec = dom.lastChild(dom.hasContentBefore(dom.ancestorHavePreviousSibling(ec)) || ec);
+ eo = ec.textContent.length;
+ }
+
+ var ancestor = dom.commonAncestor(sc.tagName ? sc.parentNode : sc, ec.tagName ? ec.parentNode : ec) || dom.ancestor(sc, dom.isEditable);
+
+ if (!dom.isContentEditable(ancestor)) {
+ return {
+ sc: sc,
+ so: so,
+ ec: sc,
+ eo: eo
+ };
+ }
+
+ if (ancestor.tagName) {
+ var ancestor_sc = sc;
+ var ancestor_ec = ec;
+ while (ancestor !== ancestor_sc && ancestor !== ancestor_sc.parentNode) { ancestor_sc = ancestor_sc.parentNode; }
+ while (ancestor !== ancestor_ec && ancestor !== ancestor_ec.parentNode) { ancestor_ec = ancestor_ec.parentNode; }
+
+
+ var node = dom.node(sc);
+ if (!dom.isNotBreakable(node) && !dom.isVoid(sc)) {
+ sc = dom.splitTree(ancestor_sc, {'node': sc, 'offset': so});
+ }
+ var before = dom.hasContentBefore(dom.ancestorHavePreviousSibling(sc));
+
+ var after;
+ if (ec.textContent.slice(eo, Infinity).match(/\S|\u00A0/)) {
+ after = dom.splitTree(ancestor_ec, {'node': ec, 'offset': eo});
+ } else {
+ after = dom.hasContentAfter(dom.ancestorHaveNextSibling(ec));
+ }
+
+ var nodes = dom.listBetween(sc, ec);
+
+ var ancestor_first_last = function (node) {
+ return node === before || node === after;
+ };
+
+ for (var i=0; i")[0];
+ node.appendChild(sc);
+ sc = br;
+ so = 0;
+ } else if (!ancestor.children.length && !ancestor.textContent.match(/\S|\u00A0/)) {
+ sc = $(" ")[0];
+ so = 0;
+ $(ancestor).prepend(sc);
+ } else if (dom.isText(sc)) {
+ var text = sc.textContent.replace(/[ \t\n\r]+$/, '\u00A0');
+ so = Math.min(so, text.length);
+ sc.textContent = text;
+ }
+
+ } else {
+
+ var text = ancestor.textContent;
+ ancestor.textContent = text.slice(0, so) + text.slice(eo, Infinity).replace(/^[ \t\n\r]+/, '\u00A0');
+
+ }
+
+ eo = so;
+ if(!dom.isBR(sc) && !dom.isVisibleText(sc) && !dom.isText(dom.hasContentBefore(sc)) && !dom.isText(dom.hasContentAfter(sc))) {
+ ancestor = dom.node(sc);
+ var text = document.createTextNode('\u00A0');
+ $(sc).before(text);
+ sc = text;
+ so = 0;
+ eo = 1;
+ }
+ return {
+ sc: sc,
+ so: so,
+ ec: sc,
+ eo: eo
+ };
+};
+dom.indent = function (node) {
+ var style = dom.isCell(node) ? 'paddingLeft' : 'marginLeft';
+ var margin = parseFloat(node.style[style] || 0)+1.5;
+ node.style[style] = margin + "em";
+ return margin;
+};
+dom.outdent = function (node) {
+ var style = dom.isCell(node) ? 'paddingLeft' : 'marginLeft';
+ var margin = parseFloat(node.style[style] || 0)-1.5;
+ node.style[style] = margin > 0 ? margin + "em" : "";
+ return margin;
+};
+dom.scrollIntoViewIfNeeded = function (node) {
+ var node = dom.node(node);
+
+ var $span;
+ if (dom.isBR(node)) {
+ $span = $(' ').text('\u00A0');
+ $(node).after($span);
+ node = $span[0];
+ }
+
+ if (node.scrollIntoViewIfNeeded) {
+ node.scrollIntoViewIfNeeded(false);
+ } else {
+ var offsetParent = node.offsetParent;
+ while (offsetParent) {
+ var elY = 0;
+ var elH = node.offsetHeight;
+ var parent = node;
+
+ while (offsetParent && parent) {
+ elY += node.offsetTop;
+
+ // get if a parent have a scrollbar
+ parent = node.parentNode;
+ while (parent != offsetParent &&
+ (parent.tagName === "BODY" || ["auto", "scroll"].indexOf(dom.getComputedStyle(parent).overflowY) === -1)) {
+ parent = parent.parentNode;
+ }
+ node = parent;
+
+ if (parent !== offsetParent) {
+ elY -= parent.offsetTop;
+ parent = null;
+ }
+
+ offsetParent = node.offsetParent;
+ }
+
+ if ((node.tagName === "BODY" || ["auto", "scroll"].indexOf(dom.getComputedStyle(node).overflowY) !== -1) &&
+ (node.scrollTop + node.clientHeight) < (elY + elH)) {
+ node.scrollTop = (elY + elH) - node.clientHeight;
+ }
+ }
+ }
+
+ if ($span) {
+ $span.remove();
+ }
+
+ return;
+};
+dom.moveTo = function (node, target, before) {
+ var nodes = [];
+ while (node.firstChild) {
+ nodes.push(node.firstChild);
+ if (before) {
+ target.insertBefore(node.firstChild, before);
+ } else {
+ target.appendChild(node.firstChild);
+ }
+ }
+ node.parentNode.removeChild(node);
+ return nodes;
+};
+dom.isMergable = function (node) {
+ return node.tagName && "h1 h2 h3 h4 h5 h6 p b bold i u code sup strong small li a ul ol font".indexOf(node.tagName.toLowerCase()) !== -1;
+};
+dom.isSplitable = function (node) {
+ return node.tagName && "h1 h2 h3 h4 h5 h6 p b bold i u code sup strong small li a font".indexOf(node.tagName.toLowerCase()) !== -1;
+};
+dom.isRemovableEmptyNode = function (node) {
+ return "h1 h2 h3 h4 h5 h6 p b bold i u code sup strong small li a ul ol font span br".indexOf(node.tagName.toLowerCase()) !== -1;
+};
+dom.isForbiddenNode = function (node) {
+ return node.tagName === "BR" || $(node).is(".fa, img");
+};
+dom.listBetween = function (sc, ec) {
+ var nodes = [];
+ var ancestor = dom.commonAncestor(sc, ec);
+ dom.walkPoint({'node': sc, 'offset': 0}, {'node': ec, 'offset': 0}, function (point) {
+ if (ancestor !== point.node || ancestor === sc || ancestor === ec) {
+ nodes.push(point.node);
+ }
+ });
+ return list.unique(nodes);
+};
+dom.isNotBreakable = function (node) {
+ // avoid triple click => crappy dom
+ return !dom.isText(node) && !dom.isBR(dom.firstChild(node)) && dom.isVoid(dom.firstChild(node));
+};
+
+dom.isContentEditable = function (node) {
+ return $(node).closest('[contenteditable]').is('[contenteditable="true"]');
+};
+
+dom.isPre = function (node) {
+ return node.tagName === "PRE";
+};
+dom.isFont = function (node) {
+ var nodeName = node && node.nodeName.toUpperCase();
+ return node && (nodeName === "FONT" ||
+ (nodeName === "SPAN" && (
+ node.className.match(/(^|\s)fa(\s|$)/i) ||
+ node.className.match(/(^|\s)(text|bg)-/i) ||
+ (node.attributes.style && node.attributes.style.value.match(/(^|\s)(color|background-color|font-size):/i)))) );
+};
+dom.isVisibleText = function (textNode) {
+ return !!textNode.textContent.match(/\S|\u00A0/);
+};
+
+/* avoid thinking HTML comment are visible */
+var old_isVisiblePoint = dom.isVisiblePoint;
+dom.isVisiblePoint = function (point) {
+ return point.node.nodeType !== 8 && old_isVisiblePoint.apply(this, arguments);
+};
+
+/**
+ * order the style of the node to compare 2 nodes and remove attribute if empty
+ *
+ * @param {Node} node
+ * @return {String} className
+ */
+dom.orderStyle = function (node) {
+ var style = node.getAttribute('style');
+ if (!style) return null;
+ style = style.replace(/[\s\n\r]+/, ' ').replace(/^ ?;? ?| ?;? ?$/g, '').replace(/ ?; ?/g, ';');
+ if (!style.length) {
+ node.removeAttribute("style");
+ return null;
+ }
+ style = style.split(";");
+ style.sort();
+ style = style.join("; ")+";";
+ node.setAttribute('style', style);
+ return style;
+};
+/**
+ * order the class of the node to compare 2 nodes and remove attribute if empty
+ *
+ * @param {Node} node
+ * @return {String} className
+ */
+dom.orderClass = function (node) {
+ var className = node.getAttribute && node.getAttribute('class');
+ if (!className) return null;
+ className = className.replace(/[\s\n\r]+/, ' ').replace(/^ | $/g, '').replace(/ +/g, ' ');
+ if (!className.length) {
+ node.removeAttribute("class");
+ return null;
+ }
+ className = className.split(" ");
+ className.sort();
+ className = className.join(" ");
+ node.setAttribute('class', className);
+ return className;
+};
+/**
+ * return the current node (if text node, return parentNode)
+ *
+ * @param {Node} node
+ * @return {Node} node
+ */
+dom.node = function (node) {
+ return dom.isText(node) ? node.parentNode : node;
+};
+/**
+ * move all childNode to an other node
+ *
+ * @param {Node} from
+ * @param {Node} to
+ */
+dom.moveContent = function (from, to) {
+ if (from === to) {
+ return;
+ }
+ if (from.parentNode === to) {
+ while (from.lastChild) {
+ dom.insertAfter(from.lastChild, from);
+ }
+ } else {
+ while (from.firstChild && from.firstChild != to) {
+ to.appendChild(from.firstChild);
+ }
+ }
+};
+
+dom.getComputedStyle = function (node) {
+ return node.nodeType == Node.COMMENT_NODE ? {} : window.getComputedStyle(node);
+};
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+range.WrappedRange.prototype.reRange = function (keep_end, isNotBreakable) {
+ var sc = this.sc;
+ var so = this.so;
+ var ec = this.ec;
+ var eo = this.eo;
+ isNotBreakable = isNotBreakable || dom.isNotBreakable;
+
+ // search the first snippet editable node
+ var start = keep_end ? ec : sc;
+ while (start) {
+ if (isNotBreakable(start, sc, so, ec, eo)) {
+ break;
+ }
+ start = start.parentNode;
+ }
+
+ // check if the end caret have the same node
+ var lastFilterEnd;
+ var end = keep_end ? sc : ec;
+ while (end) {
+ if (start === end) {
+ break;
+ }
+ if (isNotBreakable(end, sc, so, ec, eo)) {
+ lastFilterEnd = end;
+ }
+ end = end.parentNode;
+ }
+ if (lastFilterEnd) {
+ end = lastFilterEnd;
+ }
+ if (!end) {
+ end = document.getElementsByTagName('body')[0];
+ }
+
+ // if same node, keep range
+ if (start === end || !start) {
+ return this;
+ }
+
+ // reduce or extend the range to don't break a isNotBreakable area
+ if ($.contains(start, end)) {
+
+ if (keep_end) {
+ sc = dom.lastChild(dom.hasContentBefore(dom.ancestorHavePreviousSibling(end)) || sc);
+ so = sc.textContent.length;
+ } else if (!eo) {
+ ec = dom.lastChild(dom.hasContentBefore(dom.ancestorHavePreviousSibling(end)) || ec);
+ eo = ec.textContent.length;
+ } else {
+ ec = dom.firstChild(dom.hasContentAfter(dom.ancestorHaveNextSibling(end)) || ec);
+ eo = 0;
+ }
+ } else {
+
+ if (keep_end) {
+ sc = dom.firstChild(start);
+ so = 0;
+ } else {
+ ec = dom.lastChild(start);
+ eo = ec.textContent.length;
+ }
+ }
+
+ return new range.WrappedRange(sc, so, ec, eo);
+};
+// isOnImg: judge whether range is an image node or not
+range.WrappedRange.prototype.isOnImg = function () {
+ var nb = 0;
+ var image;
+ var startPoint = {node: this.sc.childNodes.length && this.sc.childNodes[this.so] || this.sc};
+ startPoint.offset = startPoint.node === this.sc ? this.so : 0;
+ var endPoint = {node: this.ec.childNodes.length && this.ec.childNodes[this.eo] || this.ec};
+ endPoint.offset = endPoint.node === this.ec ? this.eo : 0;
+
+ if (dom.isImg(startPoint.node)) {
+ nb ++;
+ image = startPoint.node;
+ }
+ dom.walkPoint(startPoint, endPoint, function (point) {
+ if (!dom.isText(endPoint.node) && point.node === endPoint.node && point.offset === endPoint.offset) {
+ return;
+ }
+ var node = point.node.childNodes.length && point.node.childNodes[point.offset] || point.node;
+ var offset = node === point.node ? point.offset : 0;
+ var isImg = dom.ancestor(node, dom.isImg);
+ if (!isImg && ((!dom.isBR(node) && !dom.isText(node)) || (offset && node.textContent.length !== offset && node.textContent.match(/\S|\u00A0/)))) {
+ nb++;
+ }
+ if (isImg && image !== isImg) {
+ image = isImg;
+ nb ++;
+ }
+ });
+ return nb === 1 && image;
+};
+range.WrappedRange.prototype.deleteContents = function (towrite) {
+ var prevBP = dom.removeBetween(this.sc, this.so, this.ec, this.eo, towrite);
+
+ $(dom.node(prevBP.sc)).trigger("click"); // trigger click to disable and reanable editor and image handler
+ return new range.WrappedRange(
+ prevBP.sc,
+ prevBP.so,
+ prevBP.ec,
+ prevBP.eo
+ );
+};
+range.WrappedRange.prototype.clean = function (mergeFilter, all) {
+ var node = dom.node(this.sc === this.ec ? this.sc : this.commonAncestor());
+ node = node || $(this.sc).closest('[contenteditable]')[0];
+ if (node.childNodes.length <=1) {
+ return this;
+ }
+
+ var merge = dom.merge(node, this.sc, this.so, this.ec, this.eo, mergeFilter, all);
+ var rem = dom.removeSpace(node.parentNode, merge.sc, merge.so, merge.ec, merge.eo);
+
+ if (merge.merged || rem.removed) {
+ return range.create(rem.sc, rem.so, rem.ec, rem.eo);
+ }
+ return this;
+};
+range.WrappedRange.prototype.remove = function (mergeFilter) {
+};
+range.WrappedRange.prototype.isOnCellFirst = function () {
+ var node = dom.ancestor(this.sc, function (node) {return ["LI", "DIV", "TD","TH"].indexOf(node.tagName) !== -1;});
+ return node && ["TD","TH"].indexOf(node.tagName) !== -1;
+};
+range.WrappedRange.prototype.isContentEditable = function () {
+ return dom.isContentEditable(this.sc) && (this.sc === this.ec || dom.isContentEditable(this.ec));
+};
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+renderer.tplButtonInfo.fontsize = function (lang, options) {
+ var items = options.fontSizes.reduce(function (memo, v) {
+ return memo + '' +
+ ' ' + v +
+ ' ';
+ }, '');
+
+ var sLabel = '11 ';
+ return renderer.getTemplate().button(sLabel, {
+ title: lang.font.size,
+ dropdown: ''
+ });
+};
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////
+/* add some text commands */
+
+key.nameFromCode[46] = 'DELETE';
+key.nameFromCode[27] = 'ESCAPE';
+
+options.keyMap.pc['BACKSPACE'] = 'backspace';
+options.keyMap.pc['DELETE'] = 'delete';
+options.keyMap.pc['ENTER'] = 'enter';
+options.keyMap.pc['ESCAPE'] = 'cancel';
+options.keyMap.mac['SHIFT+TAB'] = 'untab';
+options.keyMap.pc['UP'] = 'up';
+options.keyMap.pc['DOWN'] = 'down';
+
+options.keyMap.mac['BACKSPACE'] = 'backspace';
+options.keyMap.mac['DELETE'] = 'delete';
+options.keyMap.mac['ENTER'] = 'enter';
+options.keyMap.mac['ESCAPE'] = 'cancel';
+options.keyMap.mac['UP'] = 'up';
+options.keyMap.mac['DOWN'] = 'down';
+
+options.styleTags = ['p', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote'];
+
+$.summernote.pluginEvents.insertTable = function (event, editor, layoutInfo, sDim) {
+ var $editable = layoutInfo.editable();
+ var dimension = sDim.split('x');
+ var r = range.create();
+ if (!r) return;
+ r = r.deleteContents(true);
+
+ var table = editor.table.createTable(dimension[0], dimension[1]);
+ var parent = r.sc;
+ while (dom.isText(parent.parentNode) || dom.isRemovableEmptyNode(parent.parentNode)) {
+ parent = parent.parentNode;
+ }
+ var node = dom.splitTree(parent, {'node': r.sc, 'offset': r.so}) || r.sc;
+ node.parentNode.insertBefore(table, node);
+
+ if ($(node).text() === '' || node.textContent === '\u00A0') {
+ node.parentNode.removeChild(node);
+ }
+
+ editor.afterCommand($editable);
+ event.preventDefault();
+ return false;
+};
+$.summernote.pluginEvents.tab = function (event, editor, layoutInfo, outdent) {
+ var $editable = layoutInfo.editable();
+ $editable.data('NoteHistory').recordUndo($editable, 'tab');
+ var r = range.create();
+ var outdent = outdent || false;
+ event.preventDefault();
+
+ if (r && (dom.ancestor(r.sc, dom.isCell) || dom.ancestor(r.ec, dom.isCell))) {
+ if (r.isCollapsed() && r.isOnCell() && r.isOnCellFirst()) {
+ var td = dom.ancestor(r.sc, dom.isCell);
+ if (!outdent && !dom.nextElementSibling(td) && !dom.nextElementSibling(td.parentNode)) {
+ var last = dom.lastChild(td);
+ range.create(last, dom.nodeLength(last), last, dom.nodeLength(last)).select();
+ $.summernote.pluginEvents.enter(event, editor, layoutInfo);
+ } else if (outdent && !dom.previousElementSibling(td) && !$(td.parentNode).text().match(/\S/)) {
+ $.summernote.pluginEvents.backspace(event, editor, layoutInfo);
+ } else {
+ editor.table.tab(r, outdent);
+ }
+ } else {
+ $.summernote.pluginEvents.indent(event, editor, layoutInfo, outdent);
+ }
+ } else if (r && r.isCollapsed()) {
+ if (!r.sc.textContent.slice(0,r.so).match(/\S/) && r.isOnList()) {
+ if (outdent) {
+ $.summernote.pluginEvents.outdent(event, editor, layoutInfo);
+ } else {
+ $.summernote.pluginEvents.indent(event, editor, layoutInfo);
+ }
+ } else {
+ if (!outdent) {
+ if(dom.isText(r.sc)) {
+ var next = r.sc.splitText(r.so);
+ } else {
+ var next = document.createTextNode('')
+ $(r.sc.childNodes[r.so]).before(next);
+ }
+ editor.typing.insertTab($editable, r, options.tabsize);
+ r = range.create(next, 0, next, 0);
+ r = dom.merge(r.sc.parentNode, r.sc, r.so, r.ec, r.eo, null, true);
+ range.create(r.sc, r.so, r.ec, r.eo).select();
+ } else {
+ r = dom.merge(r.sc.parentNode, r.sc, r.so, r.ec, r.eo, null, true);
+ r = range.create(r.sc, r.so, r.ec, r.eo);
+ var next = r.sc.splitText(r.so);
+ r.sc.textContent = r.sc.textContent.replace(/(\u00A0)+$/g, '');
+ next.textContent = next.textContent.replace(/^(\u00A0)+/g, '');
+ range.create(r.sc, r.sc.textContent.length, r.sc, r.sc.textContent.length).select();
+ }
+ }
+ }
+ return false;
+};
+$.summernote.pluginEvents.untab = function (event, editor, layoutInfo) {
+ return $.summernote.pluginEvents.tab(event, editor, layoutInfo, true);
+};
+$.summernote.pluginEvents.up = function (event, editor, layoutInfo) {
+ var r = range.create();
+ var node = dom.firstChild(r.sc.childNodes[r.so] || r.sc);
+ if (!r.isOnCell() || (!dom.isCell(node) && dom.hasContentBefore(node) && (!dom.isBR(dom.hasContentBefore(node)) || !dom.isText(node) || dom.isVisibleText(node) || dom.hasContentBefore(dom.hasContentBefore(node))))) {
+ return;
+ }
+ event.preventDefault();
+ var td = dom.ancestor(r.sc, dom.isCell);
+ var tr = td.parentNode;
+ var target = tr.previousElementSibling && tr.previousElementSibling.children[_.indexOf(tr.children, td)];
+ if (!target) {
+ target = (dom.ancestorHavePreviousSibling(tr) || tr).previousSibling;
+ }
+ if (target) {
+ range.create(dom.lastChild(target), dom.lastChild(target).textContent.length).select();
+ }
+};
+$.summernote.pluginEvents.down = function (event, editor, layoutInfo) {
+ var r = range.create();
+ var node = dom.firstChild(r.sc.childNodes[r.so] || r.sc);
+ if (!r.isOnCell() || (!dom.isCell(node) && dom.hasContentAfter(node) && (!dom.isBR(dom.hasContentAfter(node)) || !dom.isText(node) || dom.isVisibleText(node) || dom.hasContentAfter(dom.hasContentAfter(node))))) {
+ return;
+ }
+ event.preventDefault();
+ var td = dom.ancestor(r.sc, dom.isCell);
+ var tr = td.parentNode;
+ var target = tr.nextElementSibling && tr.nextElementSibling.children[_.indexOf(tr.children, td)];
+ if (!target) {
+ target = (dom.ancestorHaveNextSibling(tr) || tr).nextSibling;
+ }
+ if (target) {
+ range.create(dom.firstChild(target), 0).select();
+ }
+};
+$.summernote.pluginEvents.enter = function (event, editor, layoutInfo) {
+ var $editable = layoutInfo.editable();
+ $editable.data('NoteHistory').recordUndo($editable, 'enter');
+
+ var r = range.create();
+ if (!r.isContentEditable()) {
+ event.preventDefault();
+ return false;
+ }
+ if (!r.isCollapsed()) {
+ r = r.deleteContents();
+ r.select();
+ }
+
+ var br = $(" ")[0];
+
+ var contentBefore = r.sc.textContent.slice(0,r.so).match(/\S|\u00A0/);
+ if (!contentBefore && dom.isText(r.sc)) {
+ var node = r.sc.previousSibling;
+ while (!contentBefore && node && dom.isText(node)) {
+ contentBefore = dom.isVisibleText(node);
+ node = node.previousSibling;
+ }
+ }
+
+ var node = dom.node(r.sc);
+ var exist = r.sc.childNodes[r.so] || r.sc;
+ exist = dom.isVisibleText(exist) || dom.isBR(exist) ? exist : dom.hasContentAfter(exist) || (dom.hasContentBefore(exist) || exist);
+
+ // table: add a tr
+ var td = dom.ancestor(node, dom.isCell);
+ if (td && !dom.nextElementSibling(node) && !dom.nextElementSibling(td) && !dom.nextElementSibling(td.parentNode) && (!dom.isText(r.sc) || !r.sc.textContent.slice(r.so).match(/\S|\u00A0/))) {
+ var $node = $(td.parentNode);
+ var $clone = $node.clone();
+ $clone.children().html(dom.blank);
+ $node.after($clone);
+ var node = dom.firstElementChild($clone[0]) || $clone[0];
+ range.create(node, 0, node, 0).select();
+ dom.scrollIntoViewIfNeeded(br);
+ event.preventDefault();
+ return false;
+ }
+
+ var last = node;
+ while (node && dom.isSplitable(node) && !dom.isList(node)) {
+ last = node;
+ node = node.parentNode;
+ }
+
+ if (last === node && !dom.isBR(node)) {
+ node = r.insertNode(br, true);
+ if (isFormatNode(last.firstChild) && $(last).closest(options.styleTags.join(',')).length) {
+ dom.moveContent(last.firstChild, last);
+ last.removeChild(last.firstChild);
+ }
+ do {
+ node = dom.hasContentAfter(node);
+ } while (node && dom.isBR(node));
+
+ // create an other br because the user can't see the new line with only br in a block
+ if (!node && (!br.nextElementSibling || !dom.isBR(br.nextElementSibling))) {
+ $(br).before($(" ")[0]);
+ }
+ node = br.nextSibling || br;
+ } else if (last === node && dom.isBR(node)) {
+ $(node).after(br);
+ node = br;
+ } else if (!r.so && r.isOnList() && !r.sc.textContent.length && !dom.ancestor(r.sc, dom.isLi).nextElementSibling) {
+ // double enter on the end of a list = new line out of the list
+ $('
').append(br).insertAfter(dom.ancestor(r.sc, dom.isList));
+ node = br;
+ } else if (dom.isBR(exist) && $(r.sc).closest('blockquote, pre').length && !dom.hasContentAfter($(exist.parentNode).closest('blockquote *, pre *').length ? exist.parentNode : exist)) {
+ // double enter on the end of a blockquote & pre = new line out of the list
+ $('
').append(br).insertAfter($(r.sc).closest('blockquote, pre'));
+ node = br;
+ } else if (last === r.sc) {
+ if (dom.isBR(last)) {
+ last = last.parentNode;
+ }
+ var $node = $(last);
+ var $clone = $node.clone().text("");
+ $node.after($clone);
+ node = dom.node(dom.firstElementChild($clone[0]) || $clone[0]);
+ $(node).html(br);
+ node = br;
+ } else {
+ node = dom.splitTree(last, {'node': r.sc, 'offset': r.so}) || r.sc;
+ if (!contentBefore) {
+ var cur = dom.node(dom.lastChild(node.previousSibling));
+ if (!dom.isBR(cur)) {
+ $(cur).html(br);
+ }
+ }
+ if (!dom.isVisibleText(node)) {
+ node = dom.firstChild(node);
+ $(dom.node( dom.isBR(node) ? node.parentNode : node )).html(br);
+ node = br;
+ }
+ }
+
+ if (node) {
+ node = dom.firstChild(node);
+ if (dom.isBR(node)) {
+ range.createFromNode(node).select();
+ } else {
+ range.create(node,0).select();
+ }
+ dom.scrollIntoViewIfNeeded(node);
+ }
+ event.preventDefault();
+ return false;
+};
+$.summernote.pluginEvents.visible = function (event, editor, layoutInfo) {
+ var $editable = layoutInfo.editable();
+ $editable.data('NoteHistory').recordUndo($editable, "visible");
+
+ var r = range.create();
+ if (!r) return;
+
+ if (!r.isCollapsed()) {
+ if (dom.isCell(dom.node(r.sc)) || dom.isCell(dom.node(r.ec))) {
+ remove_table_content(r);
+ r = range.create(r.ec, 0).select();
+ }
+ r.select();
+ }
+
+ // don't write in forbidden tag (like span for font awsome)
+ var node = dom.firstChild(r.sc.tagName && r.so ? r.sc.childNodes[r.so] || r.sc : r.sc);
+ while (node.parentNode) {
+ if (dom.isForbiddenNode(node)) {
+ var text = node.previousSibling;
+ if (text && dom.isText(text) && dom.isVisibleText(text)) {
+ range.create(text, text.textContent.length, text, text.textContent.length).select();
+ } else {
+ text = node.parentNode.insertBefore(document.createTextNode( "." ), node);
+ range.create(text, 1, text, 1).select();
+ setTimeout(function () {
+ var text = range.create().sc;
+ text.textContent = text.textContent.replace(/^./, '');
+ range.create(text, text.textContent.length, text, text.textContent.length).select();
+ },0);
+ }
+ break;
+ }
+ node = node.parentNode;
+ }
+
+ return true;
+};
+
+function summernote_keydown_clean (field) {
+ setTimeout(function () {
+ var r = range.create();
+ if (!r) return;
+ var node = r[field];
+ while (dom.isText(node)) {node = node.parentNode;}
+ node = node.parentNode;
+ var data = dom.merge(node, r.sc, r.so, r.ec, r.eo, null, true);
+ data = dom.removeSpace(node.parentNode, data.sc, data.so, data.sc, data.so);
+
+ range.create(data.sc, data.so, data.sc, data.so).select();
+ },0);
+}
+
+function remove_table_content(r) {
+ var ancestor = r.commonAncestor();
+ var nodes = dom.listBetween(r.sc, r.ec);
+ if (dom.isText(r.sc)) {
+ r.sc.textContent = r.sc.textContent.slice(0, r.so);
+ }
+ if (dom.isText(r.ec)) {
+ r.ec.textContent = r.ec.textContent.slice(r.eo);
+ }
+ for (var i in nodes) {
+ var node = nodes[i];
+ if (node === r.sc || node === r.ec || $.contains(node, r.sc) || $.contains(node, r.ec)) {
+ continue;
+ } else if (dom.isCell(node)) {
+ $(node).html(" ");
+ } else if(node.parentNode) {
+ do {
+ var parent = node.parentNode;
+ parent.removeChild(node);
+ node = parent;
+ } while(!dom.isVisibleText(node) && !dom.firstElementChild(node) &&
+ !dom.isCell(node) &&
+ node.parentNode && !$(node.parentNode).hasClass('o_editable'));
+ }
+ }
+ return false;
+}
+
+$.summernote.pluginEvents.delete = function (event, editor, layoutInfo) {
+ var $editable = layoutInfo.editable();
+ $editable.data('NoteHistory').recordUndo($editable, "delete");
+
+ var r = range.create();
+ if (!r) return;
+ if (!r.isContentEditable()) {
+ event.preventDefault();
+ return false;
+ }
+ if (!r.isCollapsed()) {
+ if (dom.isCell(dom.node(r.sc)) || dom.isCell(dom.node(r.ec))) {
+ remove_table_content(r);
+ range.create(r.ec, 0).select();
+ } else {
+ r = r.deleteContents();
+ r.select();
+ }
+ event.preventDefault();
+ return false;
+ }
+
+ var target = r.ec;
+ var offset = r.eo;
+ if (target.tagName && target.childNodes[offset]) {
+ target = target.childNodes[offset];
+ offset = 0;
+ }
+
+ var node = dom.node(target);
+ var data = dom.merge(node, target, offset, target, offset, null, true);
+ data = dom.removeSpace(node.parentNode, data.sc, data.so, data.ec, data.eo);
+ r = range.create(data.sc, data.so);
+ r.select();
+ target = r.sc;
+ offset = r.so;
+
+ while (!dom.hasContentAfter(node) && !dom.hasContentBefore(node) && !dom.isImg(node)) {node = node.parentNode;}
+
+ var contentAfter = target.textContent.slice(offset,Infinity).match(/\S|\u00A0/);
+ var content = target.textContent.replace(/[ \t\r\n]+$/, '');
+ var temp;
+ var temp2;
+
+ // media
+ if (dom.isImg(node) || (!contentAfter && dom.isImg(dom.hasContentAfter(node)))) {
+ var parent;
+ var index;
+ var r;
+ if (!dom.isImg(node)) {
+ node = dom.hasContentAfter(node);
+ }
+ while (dom.isImg(node)) {
+ parent = node.parentNode;
+ index = dom.position(node);
+ if (index>0) {
+ var next = node.previousSibling;
+ r = range.create(next, next.textContent.length);
+ } else {
+ r = range.create(parent, 0);
+ }
+ if (!dom.hasContentAfter(node) && !dom.hasContentBefore(node)) {
+ parent.appendChild($(' ')[0]);
+ }
+ parent.removeChild(node);
+ node = parent;
+ r.select();
+ }
+ }
+ // empty tag
+ else if (!content.length && target.tagName && dom.isRemovableEmptyNode(dom.isBR(target) ? target.parentNode : target)) {
+ if (node === $editable[0] || $.contains(node, $editable[0])) {
+ event.preventDefault();
+ return false;
+ }
+ var before = false;
+ var next = dom.hasContentAfter(dom.ancestorHaveNextSibling(node));
+ if (!dom.isContentEditable(next)) {
+ before = true;
+ next = dom.hasContentBefore(dom.ancestorHavePreviousSibling(node));
+ }
+ dom.removeSpace(next.parentNode, next, 0, next, 0); // clean before jump for not select invisible space between 2 tag
+ next = dom.firstChild(next);
+ node.parentNode.removeChild(node);
+ range.create(next, before ? next.textContent.length : 0).select();
+ }
+ // normal feature if same tag and not the end
+ else if (contentAfter) {
+ return true;
+ }
+ // merge with the next text node
+ else if (dom.isText(target) && (temp = dom.hasContentAfter(target)) && dom.isText(temp)) {
+ return true;
+ }
+ //merge with the next block
+ else if ((temp = dom.ancestorHaveNextSibling(target)) &&
+ !r.isOnCell() &&
+ dom.isMergable(temp) &&
+ dom.isMergable(temp2 = dom.hasContentAfter(temp)) &&
+ temp.tagName === temp2.tagName &&
+ (temp.tagName !== "LI" || !$('ul,ol', temp).length) && (temp2.tagName !== "LI" || !$('ul,ol', temp2).length) && // protect li
+ !dom.isNotBreakable(temp) &&
+ !dom.isNotBreakable(temp2)) {
+ dom.autoMerge(target, false);
+ var next = dom.firstChild(dom.hasContentAfter(dom.ancestorHaveNextSibling(target)));
+ if (dom.isBR(next)) {
+ if(dom.position(next) == 0) {
+ range.create(next.parentNode, 0).select();
+ }
+ else {
+ range.create(next.previousSibling, next.previousSibling.textContent.length).select();
+ }
+ next.parentNode.removeChild(next);
+ } else {
+ range.create(next, 0).select();
+ }
+ }
+ // jump to next node for delete
+ else if ((temp = dom.ancestorHaveNextSibling(target)) && (temp2 = dom.hasContentAfter(temp)) && dom.isContentEditable(temp2)) {
+
+ dom.removeSpace(temp2.parentNode, temp2, 0, temp, 0); // clean before jump for not select invisible space between 2 tag
+ temp2 = dom.firstChild(temp2);
+
+ r = range.create(temp2, 0);
+ r.select();
+
+ if ((dom.isText(temp) || dom.getComputedStyle(temp).display === "inline") && (dom.isText(temp2) || dom.getComputedStyle(temp2).display === "inline")) {
+ if (dom.isText(temp2)) {
+ temp2.textContent = temp2.textContent.replace(/^\s*\S/, '');
+ } else {
+ $.summernote.pluginEvents.delete(event, editor, layoutInfo);
+ }
+ }
+ }
+
+ $(dom.node(r.sc)).trigger("click"); // trigger click to disable and reanable editor and image handler
+ event.preventDefault();
+ return false;
+};
+$.summernote.pluginEvents.backspace = function (event, editor, layoutInfo) {
+ var $editable = layoutInfo.editable();
+ $editable.data('NoteHistory').recordUndo($editable, "backspace");
+
+ var r = range.create();
+ if (!r) return;
+ if (!r.isContentEditable()) {
+ event.preventDefault();
+ return false;
+ }
+ if (!r.isCollapsed()) {
+ if (dom.isCell(dom.node(r.sc)) || dom.isCell(dom.node(r.ec))) {
+ remove_table_content(r);
+ range.create(r.sc, dom.nodeLength(r.sc)).select();
+ } else {
+ r = r.deleteContents();
+ r.select();
+ }
+ event.preventDefault();
+ return false;
+ }
+
+ var target = r.sc;
+ var offset = r.so;
+ if (target.tagName && target.childNodes[offset]) {
+ target = target.childNodes[offset];
+ offset = 0;
+ }
+
+ var node = dom.node(target);
+ var data = dom.merge(node, target, offset, target, offset, null, true);
+ data = dom.removeSpace(node.parentNode, data.sc, data.so, data.ec, data.eo);
+ r = dom.isVoid(data.sc) ? range.createFromNode(data.sc) : range.create(data.sc, data.so);
+ r.select();
+ target = r.sc;
+ offset = r.so;
+ if (target.tagName && target.childNodes[offset]) {
+ target = target.childNodes[offset];
+ offset = 0;
+ node = dom.node(target);
+ }
+
+ while (node.parentNode && !dom.hasContentAfter(node) && !dom.hasContentBefore(node) && !dom.isImg(node)) {node = node.parentNode;}
+
+ var contentBefore = target.textContent.slice(0,offset).match(/\S|\u00A0/);
+ var content = target.textContent.replace(/[ \t\r\n]+$/, '');
+ var temp;
+ var temp2;
+
+ // delete media
+ if (dom.isImg(node) || (!contentBefore && dom.isImg(dom.hasContentBefore(node)))) {
+ if (!dom.isImg(node)) {
+ node = dom.hasContentBefore(node);
+ }
+ range.createFromNode(node).select();
+ $.summernote.pluginEvents.delete(event, editor, layoutInfo);
+ }
+ // table tr td
+ else if (r.isOnCell() && !offset && (target === (temp = dom.ancestor(target, dom.isCell)) || target === temp.firstChild || (dom.isText(temp.firstChild) && !dom.isVisibleText(temp.firstChild) && target === temp.firstChild.nextSibling))) {
+ if (dom.previousElementSibling(temp)) {
+ var td = dom.previousElementSibling(temp);
+ node = td.lastChild || td;
+ } else {
+ var tr = temp.parentNode;
+ var prevTr = dom.previousElementSibling(tr);
+ if (!$(temp.parentNode).text().match(/\S|\u00A0/)) {
+ if (prevTr) {
+ node = dom.lastChild(dom.lastElementChild(prevTr));
+ } else {
+ node = dom.lastChild(dom.hasContentBefore(dom.ancestorHavePreviousSibling(tr)) || $editable.get(0));
+ }
+ $(tr).empty();
+ if(!$(tr).closest('table').has('td, th').length) {
+ $(tr).closest('table').remove();
+ }
+ $(tr).remove();
+ range.create(node, node.textContent.length, node, node.textContent.length).select();
+ } else {
+ node = dom.lastElementChild(prevTr).lastChild || dom.lastElementChild(prevTr);
+ }
+ }
+ if (dom.isBR(node)) {
+ range.createFromNode(node).select();
+ } else {
+ range.create(node, dom.nodeLength(node)).select();
+ }
+ }
+ // empty tag
+ else if (!content.length && target.tagName && dom.isRemovableEmptyNode(target)) {
+ if (node === $editable[0] || $.contains(node, $editable[0])) {
+ event.preventDefault();
+ return false;
+ }
+ var before = true;
+ var prev = dom.hasContentBefore(dom.ancestorHavePreviousSibling(node));
+ if (!dom.isContentEditable(prev)) {
+ before = false;
+ prev = dom.hasContentAfter(dom.ancestorHaveNextSibling(node));
+ }
+ dom.removeSpace(prev.parentNode, prev, 0, prev, 0); // clean before jump for not select invisible space between 2 tag
+ prev = dom.lastChild(prev);
+ node.parentNode.removeChild(node);
+ range.createFromNode(prev).select();
+ range.create(prev, before ? prev.textContent.length : 0).select();
+ }
+ // normal feature if same tag and not the begin
+ else if (contentBefore) {
+ return true;
+ }
+ // merge with the previous text node
+ else if (dom.isText(target) && (temp = dom.hasContentBefore(target)) && (dom.isText(temp) || dom.isBR(temp))) {
+ return true;
+ }
+ //merge with the previous block
+ else if ((temp = dom.ancestorHavePreviousSibling(target)) &&
+ dom.isMergable(temp) &&
+ dom.isMergable(temp2 = dom.hasContentBefore(temp)) &&
+ temp.tagName === temp2.tagName &&
+ (temp.tagName !== "LI" || !$('ul,ol', temp).length) && (temp2.tagName !== "LI" || !$('ul,ol', temp2).length) && // protect li
+ !dom.isNotBreakable(temp) &&
+ !dom.isNotBreakable(temp2)) {
+ var prev = dom.firstChild(target);
+ dom.autoMerge(target, true);
+ range.create(prev, 0).select();
+ }
+ // jump to previous node for delete
+ else if ((temp = dom.ancestorHavePreviousSibling(target)) && (temp2 = dom.hasContentBefore(temp)) && dom.isContentEditable(temp2)) {
+
+ dom.removeSpace(temp2.parentNode, temp2, 0, temp, 0); // clean before jump for not select invisible space between 2 tag
+ temp2 = dom.lastChild(temp2);
+
+ r = range.create(temp2, temp2.textContent.length, temp2, temp2.textContent.length);
+ r.select();
+
+ if ((dom.isText(temp) || dom.getComputedStyle(temp).display === "inline") && (dom.isText(temp2) || dom.getComputedStyle(temp2).display === "inline")) {
+ if (dom.isText(temp2)) {
+ temp2.textContent = temp2.textContent.replace(/\S\s*$/, '');
+ } else {
+ $.summernote.pluginEvents.backspace(event, editor, layoutInfo);
+ }
+ }
+ }
+
+ var r = range.create();
+ if (r) {
+ $(dom.node(r.sc)).trigger("click"); // trigger click to disable and reanable editor and image handler
+ dom.scrollIntoViewIfNeeded(r.sc.parentNode.previousElementSibling || r.sc);
+ }
+
+ event.preventDefault();
+ return false;
+};
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////
+/* add list command (create a uggly dom for chrome) */
+
+function isFormatNode(node) {
+ return node.tagName && options.styleTags.indexOf(node.tagName.toLowerCase()) !== -1;
+}
+
+$.summernote.pluginEvents.insertUnorderedList = function (event, editor, layoutInfo, sorted) {
+ var $editable = layoutInfo.editable();
+ $editable.data('NoteHistory').recordUndo($editable);
+
+ var parent;
+ var r = range.create();
+ if (!r) return;
+ var node = r.sc;
+ while (node && node !== $editable[0]) {
+
+ parent = node.parentNode;
+ if (node.tagName === (sorted ? "UL" : "OL")) {
+
+ var ul = document.createElement(sorted ? "ol" : "ul");
+ ul.className = node.className;
+ parent.insertBefore(ul, node);
+ while (node.firstChild) {
+ ul.appendChild(node.firstChild);
+ }
+ parent.removeChild(node);
+ r.select();
+ return;
+
+ } else if (node.tagName === (sorted ? "OL" : "UL")) {
+
+ var lis = [];
+ for (var i=0; i' : sTagName;
+ document.execCommand('FormatBlock', false, sTagName);
+ return;
+ }
+
+ // fix by odoo because if you select a style in a li with no p tag all the ul is wrapped by the style tag
+ var nodes = dom.listBetween(r.sc, r.ec);
+ for (var i=0; i 0.05) {
+ font.style.fontSize = size + "px";
+ }
+ }
+ }
+ }
+
+ // remove empty values
+ // we must remove the value in 2 steps (applay inherit then remove) because some
+ // browser like chrome have some time an error for the rendering and/or keep inherit
+ for (var i=0; i0 && (font = dom.ancestor(nodes[i-1], dom.isFont))) {
+ className2 = font.getAttribute('class');
+ style2 = font.getAttribute('style');
+ if (node !== font && className == className2 && style == style2) {
+ remove(node, font);
+ nodes.splice(i,1);
+ i--;
+ continue;
+ }
+ }
+ }
+ }
+
+ range.create(startPoint.node, startPoint.offset, endPoint.node, endPoint.offset).select();
+};
+$.summernote.pluginEvents.fontSize = function (event, editor, layoutInfo, value) {
+ var $editable = layoutInfo.editable();
+ event.preventDefault();
+ $.summernote.pluginEvents.applyFont(event, editor, layoutInfo, null, null, value);
+ editor.afterCommand($editable);
+};
+$.summernote.pluginEvents.color = function (event, editor, layoutInfo, sObjColor) {
+ var $editable = layoutInfo.editable();
+ var oColor = JSON.parse(sObjColor);
+ var foreColor = oColor.foreColor, backColor = oColor.backColor;
+
+ if (foreColor) { $.summernote.pluginEvents.foreColor(event, editor, layoutInfo, foreColor); }
+ if (backColor) { $.summernote.pluginEvents.backColor(event, editor, layoutInfo, backColor); }
+};
+$.summernote.pluginEvents.foreColor = function (event, editor, layoutInfo, foreColor) {
+ var $editable = layoutInfo.editable();
+ $.summernote.pluginEvents.applyFont(event, editor, layoutInfo, foreColor, null, null);
+ editor.afterCommand($editable);
+};
+$.summernote.pluginEvents.backColor = function (event, editor, layoutInfo, backColor) {
+ var $editable = layoutInfo.editable();
+ var r = range.create();
+ if (!r) return;
+ if (r.isCollapsed() && r.isOnCell()) {
+ var cell = dom.ancestor(r.sc, dom.isCell);
+ cell.className = cell.className.replace(new RegExp('(^|\\s+)bg-[^\\s]+(\\s+|$)', 'gi'), '');
+ cell.style.backgroundColor = "";
+ if (backColor.indexOf('bg-') !== -1) {
+ cell.className += ' ' + backColor;
+ } else if (backColor !== 'inherit') {
+ cell.style.backgroundColor = backColor;
+ }
+ return;
+ }
+ $.summernote.pluginEvents.applyFont(event, editor, layoutInfo, null, backColor, null);
+ editor.afterCommand($editable);
+};
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+options.onCreateLink = function (sLinkUrl) {
+ if (sLinkUrl.indexOf('mailto:') === 0) {
+ // pass
+ } else if (sLinkUrl.indexOf('@') !== -1 && sLinkUrl.indexOf(':') === -1) {
+ sLinkUrl = 'mailto:' + sLinkUrl;
+ } else if (sLinkUrl.indexOf('://') === -1 && sLinkUrl.indexOf('/') !== 0 && sLinkUrl.indexOf('#') !== 0) {
+ sLinkUrl = 'http://' + sLinkUrl;
+ }
+ return sLinkUrl;
+};
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////
+/* table */
+
+function summernote_table_scroll (event) {
+ var r = range.create();
+ if (r && r.isOnCell()) {
+ $('.o_table_handler').remove();
+ }
+}
+function summernote_table_update (oStyle) {
+ var r = range.create();
+ if (!oStyle.range || !r || !r.isOnCell() || !r.isOnCellFirst()) {
+ $('.o_table_handler').remove();
+ return;
+ }
+ var table = dom.ancestor(oStyle.range.sc, dom.isTable);
+ if (!table) { // if the editable tag is inside the table
+ return;
+ }
+ var $editable = $(table).closest('.o_editable');
+
+ $('.o_table_handler').remove();
+
+ var $dels = $();
+ var $adds = $();
+ var $tds = $('tr:first', table).children();
+ $tds.each(function () {
+ var $td = $(this);
+ var pos = $td.offset();
+
+ var $del = $(' ').appendTo('body');
+ $del.data('td', this);
+ $dels = $dels.add($del);
+ $del.css({
+ left: ((pos.left + $td.outerWidth()/2)-6) + "px",
+ top: (pos.top-6) + "px"
+ });
+
+ var $add = $(' ').appendTo('body');
+ $add.data('td', this);
+ $adds = $adds.add($add);
+ $add.css({
+ left: (pos.left-6) + "px",
+ top: (pos.top-6) + "px"
+ });
+ });
+
+ var $last = $tds.last();
+ var pos = $last.offset();
+ var $add = $(' ').appendTo('body');
+ $adds = $adds.add($add);
+ $add.css({
+ left: (pos.left+$last.outerWidth()-6) + "px",
+ top: (pos.top-6) + "px"
+ });
+
+ var $table = $(table);
+ $dels.data('table', table).on('mousedown', function (event) {
+ var td = $(this).data('td');
+ $editable.data('NoteHistory').recordUndo($editable);
+
+ var newTd;
+ if ($(td).siblings().length) {
+ var eq = $(td).index();
+ $table.find('tr').each(function () {
+ $('> td:eq('+eq+')', this).remove();
+ });
+ newTd = $table.find('tr:first > td:eq('+eq+'), tr:first > td:last').first();
+ } else {
+ var prev = dom.lastChild(dom.hasContentBefore(dom.ancestorHavePreviousSibling($table[0])));
+ $table.remove();
+ $('.o_table_handler').remove();
+ r = range.create(prev, prev.textContent.length);
+ r.select();
+ $(r.sc).trigger('mouseup');
+ return;
+ }
+
+ $('.o_table_handler').remove();
+ range.create(newTd[0], 0, newTd[0], 0).select();
+ newTd.trigger('mouseup');
+ });
+ $adds.data('table', table).on('mousedown', function (event) {
+ var td = $(this).data('td');
+ $editable.data('NoteHistory').recordUndo($editable);
+
+ var newTd;
+ if (td) {
+ var eq = $(td).index();
+ $table.find('tr').each(function () {
+ $('td:eq('+eq+')', this).before(''+dom.blank+' ');
+ });
+ newTd = $table.find('tr:first td:eq('+eq+')');
+ } else {
+ $table.find('tr').each(function () {
+ $(this).append(''+dom.blank+' ');
+ });
+ newTd = $table.find('tr:first td:last');
+ }
+
+ $('.o_table_handler').remove();
+ range.create(newTd[0], 0, newTd[0], 0).select();
+ newTd.trigger('mouseup');
+ });
+
+ $dels.css({
+ 'position': 'absolute',
+ 'cursor': 'pointer',
+ 'background-color': '#fff',
+ 'color': '#ff0000'
+ });
+ $adds.css({
+ 'position': 'absolute',
+ 'cursor': 'pointer',
+ 'background-color': '#fff',
+ 'color': '#00ff00'
+ });
+}
+var fn_popover_update = eventHandler.modules.popover.update;
+eventHandler.modules.popover.update = function ($popover, oStyle, isAirMode) {
+ fn_popover_update.call(this, $popover, oStyle, isAirMode);
+ if(!!(isAirMode ? $popover : $popover.parent()).find('.note-table').length) {
+ summernote_table_update(oStyle);
+ }
+};
+
+// override summernote clipboard functionality
+eventHandler.modules.clipboard.attach = function(layoutInfo) {
+ var $editable = layoutInfo.editable();
+ $editable.on('paste', function(e) {
+ var clipboardData = ((e.originalEvent || e).clipboardData || window.clipboardData);
+ // Change nothing if pasting html (copy from text editor / web / ...) or
+ // if clipboardData is not available (IE / ...)
+ if (clipboardData && clipboardData.types && clipboardData.types.length === 1 && clipboardData.types[0] === "text/plain") {
+ e.preventDefault();
+ $editable.data('NoteHistory').recordUndo($editable); // FIXME
+ var pastedText = clipboardData.getData("text/plain");
+ // Try removing linebreaks which are not really linebreaks (in a PDF,
+ // when a sentence goes over the next line, copying it considers it
+ // a linebreak for example).
+ var formattedText = pastedText.replace(/([\w-])\r?\n([\w-])/g, "$1 $2").trim();
+ document.execCommand("insertText", false, formattedText);
+ }
+ });
+};
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+var fn_attach = eventHandler.attach;
+eventHandler.attach = function (oLayoutInfo, options) {
+ var $editable = oLayoutInfo.editor().hasClass('note-editable') ? oLayoutInfo.editor() : oLayoutInfo.editor().find('.note-editable');
+ fn_attach.call(this, oLayoutInfo, options);
+ $editable.on("scroll", summernote_table_scroll);
+};
+var fn_detach = eventHandler.detach;
+eventHandler.detach = function (oLayoutInfo, options) {
+ var $editable = oLayoutInfo.editor().hasClass('note-editable') ? oLayoutInfo.editor() : oLayoutInfo.editor().find('.note-editable');
+ fn_detach.call(this, oLayoutInfo, options);
+ $editable.off("scroll", summernote_table_scroll);
+ $('.o_table_handler').remove();
+};
+
+options.icons.image.image = "file-image-o";
+$.summernote.lang['en-US'].image.image = "File / Image";
+
+return $.summernote;
+
+});
diff --git a/addons/web_editor/static/src/js/editor/transcoder.js b/addons/web_editor/static/src/js/editor/transcoder.js
new file mode 100644
index 00000000000..6b42e028b3a
--- /dev/null
+++ b/addons/web_editor/static/src/js/editor/transcoder.js
@@ -0,0 +1,234 @@
+odoo.define('web_editor.transcoder', function (require) {
+'use strict';
+
+var widget = require('web_editor.widget');
+
+var rulesCache = [];
+var getMatchedCSSRules = function (a) {
+ if (!rulesCache.length) {
+ var sheets = document.styleSheets;
+ for(var i = sheets.length-1; i >= 0 ; i--) {
+ var rules;
+ if (sheets[i].rules) {
+ rules = sheets[i].rules;
+ } else {
+ //try...catch because Firefox not able to enumerate document.styleSheets[].cssRules[] for cross-domain stylesheets.
+ try {
+ rules = sheets[i].cssRules;
+ } catch(e) {
+ console.warn("Can't read the css rules of: " + sheets[i].href, e);
+ continue;
+ }
+ rules = sheets[i].cssRules;
+ }
+ if (rules) {
+ for(var r = rules.length-1; r >= 0; r--) {
+ var selectorText = rules[r].selectorText;
+ if (selectorText &&
+ rules[r].cssText &&
+ selectorText !== "*" &&
+ selectorText.indexOf(":hover") === -1 &&
+ selectorText.indexOf(":before") === -1 &&
+ selectorText.indexOf(":after") === -1 &&
+ selectorText.indexOf(":active") === -1 &&
+ selectorText.indexOf(":link") === -1 &&
+ selectorText.indexOf("::") === -1 &&
+ selectorText.indexOf("\"") === -1 &&
+ selectorText.indexOf("'") === -1) {
+ var st = selectorText.split(/\s*,\s*/);
+ for (var k=0; k property: [top/right/bottom/left value];
+ style[p] = style[p+'-top'];
+ }
+ else {
+ // keep => property: [top value] [right value] [bottom value] [left value];
+ style[p] = (style[p+'-top'] || 0) + ' ' + (style[p+'-right'] || 0) + ' ' + (style[p+'-bottom'] || 0) + ' ' + (style[p+'-left'] || 0);
+ if (style[p].indexOf('inherit') !== -1 || style[p].indexOf('initial') !== -1) {
+ // keep => property-top: [top value]; property-right: [right value]; property-bottom: [bottom value]; property-left: [left value];
+ delete style[p];
+ return;
+ }
+ }
+ delete style[p+'-top'];
+ delete style[p+'-right'];
+ delete style[p+'-bottom'];
+ delete style[p+'-left'];
+ }
+ });
+
+ // text-decoration rule is decomposed in -line, -color and -style. This is
+ // however not supported by many browser/mail clients and the editor does
+ // not allow to change -color and -style rule anyway
+ if (style['text-decoration-line']) {
+ style['text-decoration'] = style['text-decoration-line'];
+ delete style['text-decoration-line'];
+ delete style['text-decoration-color'];
+ delete style['text-decoration-style'];
+ }
+
+ return style;
+};
+
+// convert font awsome into image
+var font_to_img = function ($editable) {
+ $editable.find(".fa").each(function () {
+ var $font = $(this);
+ var icon, content;
+ _.find(widget.fontIcons, function (font) {
+ return _.find(widget.getCssSelectors(font.parser), function (css) {
+ if ($font.is(css[0].replace(/::?before/g, ''))) {
+ icon = css[2].split("-").shift();
+ content = css[1].match(/content:\s*['"]?(.)['"]?/)[1];
+ return true;
+ }
+ });
+ });
+ if (content) {
+ var color = $font.css("color").replace(/\s/g, '');
+ $font.replaceWith($(" ", {
+ "src": _.str.sprintf('/web_editor/font_to_img/%s/%s/%s', content.charCodeAt(0), window.encodeURI(color), Math.max(1, $font.height())),
+ "data-class": $font.attr("class"),
+ "data-style": $font.attr("style"),
+ "class": $font.attr("class").replace(new RegExp("(^|\\s+)" + icon + "(-[^\\s]+)?", "gi"), ''), // remove inline font-awsome style
+ "style": $font.attr("style")
+ }).css({height: "auto", width: "auto"}));
+ } else {
+ $font.remove();
+ }
+ });
+};
+// convert image into font awsome
+var img_to_font = function ($editable) {
+ $editable.find("img[src*='/web_editor/font_to_img/']").each(function () {
+ var $img = $(this);
+ $img.replaceWith($(" ", {
+ "class": $img.data("class"),
+ "style": $img.data("style")
+ }));
+ });
+};
+
+// convert class into inline style to send by mail
+var class_to_style = function ($editable) {
+ if (!rulesCache.length) {
+ getMatchedCSSRules($editable[0]);
+ }
+ $editable.find('*').each(function () {
+ var $target = $(this);
+ var css = getMatchedCSSRules(this);
+ var style = $target.attr("style") || "";
+ _.each(css, function (v,k) {
+ if (!(new RegExp('(^|;)\s*' + k).test(style))) {
+ style = k+":"+v+";"+style;
+ }
+ });
+ if (_.isEmpty(style)) {
+ $target.removeAttr("style");
+ } else {
+ $target.attr("style", style);
+ }
+ });
+};
+// convert style into inline class from mail
+var style_to_class = function ($editable) {
+ getMatchedCSSRules($editable[0]);
+
+ var $c = $(' ').appendTo("body");
+
+ $editable.find('*').each(function () {
+ var $target = $(this);
+ var css = getMatchedCSSRules(this);
+ var style = "";
+ _.each(css, function (v,k) {
+ if (!(new RegExp('(^|;)\s*' + k).test(style))) {
+ style = k+":"+v+";"+style;
+ }
+ });
+ css = ($c.attr("style", style).attr("style") || "").split(/\s*;\s*/);
+ style = $target.attr("style") || "";
+ _.each(css, function (v) {
+ style = style.replace(v, '');
+ });
+ style = style.replace(/;+(\s;)*/g, ';').replace(/^;/g, '');
+ if (style !== '') {
+ $target.attr("style", style);
+ } else {
+ $target.removeAttr("style");
+ }
+ });
+ $c.remove();
+};
+
+return {
+ 'font_to_img': font_to_img,
+ 'img_to_font': img_to_font,
+ 'class_to_style': class_to_style,
+ 'style_to_class': style_to_class,
+};
+
+});
diff --git a/addons/web_editor/static/src/js/editor/translator.js b/addons/web_editor/static/src/js/editor/translator.js
new file mode 100644
index 00000000000..e82ebbb5b81
--- /dev/null
+++ b/addons/web_editor/static/src/js/editor/translator.js
@@ -0,0 +1,319 @@
+odoo.define('web_editor.translate', function (require) {
+'use strict';
+
+var core = require('web.core');
+var ajax = require('web.ajax');
+var Widget = require('web.Widget');
+var base = require('web_editor.base');
+var rte = require('web_editor.rte');
+var editor_widget = require('web_editor.widget');
+
+var _t = core._t;
+
+var translatable = !!$('html').data('translatable');
+var edit_translations = !!$('html').data('edit_translations');
+
+var RTE_Translate = rte.Class.extend({
+ saveElement: function ($el, context) {
+ // remove multi edition
+ var key = $el.data('oe-translation-id') ? 'translation:'+$el.data('oe-translation-id') :
+ ($el.data('oe-model') ? $el.data('oe-model')+":"+$el.data('oe-id')+":"+$el.data('oe-field')+":"+$el.data('oe-type')+":"+$el.data('oe-expression') : false);
+ if (!key || this.__saved[key]) return true;
+ this.__saved[key] = true;
+
+ if ($el.data('oe-translation-id')) {
+ var translation_content = this.getEscapedElement($el).html();
+
+ return ajax.jsonRpc('/web/dataset/call', 'call', {
+ model: 'ir.translation',
+ method: 'save_html',
+ args: [
+ [+$el.data('oe-translation-id')],
+ translation_content,
+ context || base.get_context()
+ ],
+ });
+ } else {
+ var markup = this.getEscapedElement($el).prop('outerHTML');
+
+ return ajax.jsonRpc('/web/dataset/call', 'call', {
+ model: 'ir.ui.view',
+ method: 'save',
+ args: [
+ $el.data('oe-id'),
+ markup,
+ $el.data('oe-xpath') || null,
+ context || base.get_context()
+ ],
+ });
+ }
+ },
+});
+
+var Translate_Modal = editor_widget.Dialog.extend({
+ init: function (p, options, parent, node) {
+ this._super(p, _.extend({}, {
+ title: _t("Translate Attribute"),
+ buttons: [
+ {text: _t("Close"), classes: "btn-primary o_save_button", close: true, click: this.save}
+ ]
+ }, options || {}));
+ this.parent = parent;
+ this.$target = $(node);
+ this.translation = $(node).data('translation');
+ },
+ start: function () {
+ var self = this;
+ var def = this._super.apply(this, arguments);
+ var $group = $("
", {"class": "form-group"}).appendTo(this.$el);
+ _.each(this.translation, function (node, attr) {
+ var $node = $(node);
+ var $label = $(' ').text(attr);
+ var $input = $(' ').val($node.html());
+ $input.on('change keyup', function () {
+ var value = $input.val();
+ $node.html(value).trigger('change', node);
+ $node.data('$node').attr($node.data('attribute'), value).trigger('translate');
+ self.parent.rte_changed(node);
+ });
+ $group.append($label).append($input);
+ });
+ return def;
+ }
+});
+
+var Translate = Widget.extend({
+ events: {
+ 'click [data-action="save"]': 'save_and_reload',
+ 'click [data-action="cancel"]': 'cancel',
+ },
+ template: 'web_editor.editorbar',
+ init: function (parent, $target, lang) {
+ this.parent = parent;
+ this.lang = lang || base.get_context().lang;
+ this.setTarget($target);
+ this._super.apply(this, arguments);
+
+ this.rte = new RTE_Translate(this, this.config);
+ this.rte.on('change', this, this.rte_changed);
+ },
+ start: function () {
+ this._super.apply(this, arguments);
+ this.$('#web_editor-toolbars').remove();
+ return this.edit();
+ },
+ setTarget: function ($target) {
+ var $edit = $target.find('[data-oe-translation-id], [data-oe-model][data-oe-id][data-oe-field]');
+ $edit.filter(':has([data-oe-translation-id], [data-oe-model][data-oe-id][data-oe-field])').attr('data-oe-readonly', true);
+
+ this.$target = $edit.not('[data-oe-readonly]');
+
+ // attributes
+
+ var attrs = ['placeholder', 'title', 'alt'];
+ _.each(attrs, function (attr) {
+ $target.find('['+attr+'*="data-oe-translation-id="]').filter(':empty, input, select, textarea, img').each(function () {
+ var $node = $(this);
+ var translation = $node.data('translation') || {};
+ var trans = $node.attr(attr);
+ var match = trans.match(/]*data-oe-translation-id="([0-9]+)"[^>]*>(.*)<\/span>/);
+ var $trans = $(trans).addClass('hidden o_editable o_editable_translatable_attribute').appendTo('body');
+ $trans.data('$node', $node).data('attribute', attr);
+ translation[attr] = $trans[0];
+ $node.attr(attr, match[2]);
+
+ var select2 = $node.data('select2');
+ if (select2) {
+ select2.blur();
+ $node.on('translate', function () {
+ select2.blur();
+ });
+ $node = select2.container.find('input');
+ }
+ $node.addClass('o_translatable_attribute').data('translation', translation);
+ });
+ });
+ this.$target_attr = $target.find('.o_translatable_attribute');
+ this.$target_attribute = $('.o_editable_translatable_attribute');
+ },
+ find: function (selector) {
+ return selector ? this.$target.find(selector).addBack().filter(selector) : this.$target;
+ },
+ edit: function () {
+ var flag = false;
+ window.onbeforeunload = function (event) {
+ if ($('.o_editable.o_dirty').length && !flag) {
+ flag = true;
+ setTimeout(function () {flag=false;},0);
+ return _t('This document is not saved!');
+ }
+ };
+ this.$target.addClass("o_editable");
+ this.rte.start();
+ this.translations = [];
+ this.markTranslatableNodes();
+ this.onTranslateReady();
+ },
+ onTranslateReady: function () {
+ this.$el.show();
+ this.trigger("edit");
+ },
+ rte_changed: function (node) {
+ var $node = $(node);
+ $node.find("p").each(function () { // remove
element which might have been inserted because of copy-paste
+ var $p = $(this);
+ $p.after($p.html()).remove();
+ });
+ var trans = this.getTranlationObject($node[0]);
+ $node.toggleClass('o_dirty', trans.value !== $node.html().replace(/[ \t\n\r]+/, ' '));
+ },
+ getTranlationObject: function (node) {
+ var $node = $(node);
+ var id = +$node.data('oe-translation-id');
+ if (!id) {
+ id = $node.data('oe-model')+','+$node.data('oe-id')+','+$node.data('oe-field');
+ }
+ var trans = _.find(this.translations, function (trans) {
+ return trans.id === id;
+ });
+ if (!trans) {
+ this.translations.push(trans = {'id': id});
+ }
+ return trans;
+ },
+ __unbind_click: function (event) {
+ if (event.ctrlKey || !$(event.target).is(':o_editable')) {
+ return;
+ }
+ event.preventDefault();
+ event.stopPropagation();
+ },
+ __translate_attribute: function (event) {
+ if (event.ctrlKey) {
+ return;
+ }
+ event.preventDefault();
+ event.stopPropagation();
+ if (event.type !== 'mousedown') {
+ return;
+ }
+
+ new Translate_Modal(null, {}, event.data, event.target).open();
+ },
+ markTranslatableNodes: function (node) {
+ var self = this;
+ this.$target.add(this.$target_attribute).each(function () {
+ var $node = $(this);
+ var trans = self.getTranlationObject(this);
+ trans.value = (trans.value ? trans.value : $node.html() ).replace(/[ \t\n\r]+/, ' ');
+ });
+ this.$target.parent().prependEvent('click.translator', this, this.__unbind_click);
+
+ // attributes
+
+ this.$target_attr.each(function () {
+ var $node = $(this);
+ var translation = $node.data('translation');
+ _.each(translation, function (node, attr) {
+ var trans = self.getTranlationObject(node);
+ trans.value = (trans.value ? trans.value : $node.html() ).replace(/[ \t\n\r]+/, ' ');
+ $node.attr('data-oe-translation-state', (trans.state || 'to_translate'));
+ });
+ });
+
+ this.$target_attr.prependEvent('mousedown.translator click.translator mouseup.translator', this, this.__translate_attribute);
+
+ console.info('Click on CTRL when you click in an translatable area to have the default behavior');
+ },
+ unarkTranslatableNode: function () {
+ this.$target.removeClass('o_editable').removeAttr('contentEditable');
+ this.$target.parent().off('.translator');
+ this.$target_attr.off('.translator');
+ },
+ save_and_reload: function () {
+ return this.save().then(function () {
+ window.location.href = window.location.href.replace(/&?edit_translations(=[^&]*)?/g, '');
+ });
+ },
+ save: function () {
+ var context = base.get_context();
+ context.lang = this.lang;
+ return this.rte.save(context);
+ },
+ cancel: function () {
+ var self = this;
+ this.rte.cancel();
+ this.$target.each(function () {
+ $(this).html(self.getTranlationObject(this).value);
+ });
+ this.unarkTranslatableNode();
+ this.trigger("cancel");
+ this.$el.hide();
+ window.onbeforeunload = null;
+ },
+ destroy: function () {
+ this.cancel();
+ this.$el.remove();
+ this._super.apply(this, arguments);
+ },
+
+ config: function ($editable) {
+ if ($editable.data('oe-model')) {
+ return {
+ 'airMode' : true,
+ 'focus': false,
+ 'airPopover': [
+ ['history', ['undo', 'redo']],
+ ],
+ 'styleWithSpan': false,
+ 'inlinemedia' : ['p'],
+ 'lang': "odoo",
+ 'onChange': function (html, $editable) {
+ $editable.trigger("content_changed");
+ }
+ };
+ }
+ return {
+ 'airMode' : true,
+ 'focus': false,
+ 'airPopover': [
+ ['font', ['bold', 'italic', 'underline', 'clear']],
+ ['fontsize', ['fontsize']],
+ ['color', ['color']],
+ ['history', ['undo', 'redo']],
+ ],
+ 'styleWithSpan': false,
+ 'inlinemedia' : ['p'],
+ 'lang': "odoo",
+ 'onChange': function (html, $editable) {
+ $editable.trigger("content_changed");
+ }
+ };
+ }
+});
+
+
+if (edit_translations) {
+ base.ready().then(function () {
+ data.instance = new Translate(this, $('#wrapwrap'));
+ data.instance.prependTo(document.body);
+
+ $('a[href*=edit_translations]').each(function () {
+ this.href = this.href.replace(/[$?]edit_translations[^&?]+/, '');
+ });
+ $('form[action*=edit_translations]').each(function () {
+ this.action = this.action.replace(/[$?]edit_translations[^&?]+/, '');
+ });
+
+ $('title').html($('title').html().replace(/<span data-oe-model.+?>(.+?)<\/span>/, '\$1'));
+ });
+}
+
+var data = {
+ 'translatable': translatable,
+ 'edit_translations': edit_translations,
+ 'Class': Translate,
+};
+return data;
+
+});
diff --git a/addons/web_editor/static/src/js/rte.js b/addons/web_editor/static/src/js/rte.js
deleted file mode 100644
index 92ab133263a..00000000000
--- a/addons/web_editor/static/src/js/rte.js
+++ /dev/null
@@ -1,637 +0,0 @@
-odoo.define('web_editor.rte', function (require) {
-'use strict';
-
-var ajax = require('web.ajax');
-var core = require('web.core');
-var concurrency = require('web.concurrency');
-var Widget = require('web.Widget');
-var summernote = require('web_editor.summernote');
-var base = require('web_editor.base');
-
-var _t = core._t;
-
-//////////////////////////////////////////////////////////////////////////////////////////////////////////
-/* Summernote Lib (neek change to make accessible: method and object) */
-
-var dom = summernote.core.dom;
-var range = summernote.core.range;
-
-//////////////////////////////////////////////////////////////////////////////////////////////////////////
-/* Change History to have a global History for all summernote instances */
-
-var History = function History ($editable) {
- var aUndo = [];
- var pos = 0;
-
- this.makeSnap = function (event, rng) {
- rng = rng || range.create();
- var elEditable = $(rng && rng.sc).closest('.o_editable')[0];
- if (!elEditable) {
- return false;
- }
- return {
- event: event,
- editable: elEditable,
- contents: elEditable.innerHTML,
- bookmark: rng && rng.bookmark(elEditable),
- scrollTop: $(elEditable).scrollTop()
- };
- };
-
- this.applySnap = function (oSnap) {
- var $editable = $(oSnap.editable);
-
- if (document.documentMode) {
- $editable.removeAttr("contentEditable").removeProp("contentEditable");
- }
-
- $editable.html(oSnap.contents).scrollTop(oSnap.scrollTop);
- $(".oe_overlay").remove();
- $(".note-control-selection").hide();
-
- $editable.trigger("content_changed");
-
- try {
- var r = oSnap.editable.innerHTML === "" ? range.create(oSnap.editable, 0) : range.createFromBookmark(oSnap.editable, oSnap.bookmark);
- r.select();
- } catch(e) {
- console.error(e);
- return;
- }
-
- $(document).trigger("click");
- $(".o_editable *").filter(function () {
- var $el = $(this);
- if($el.data('snippet-editor')) {
- $el.removeData();
- }
- });
-
-
- _.defer(function () {
- var target = dom.isBR(r.sc) ? r.sc.parentNode : dom.node(r.sc);
- if (!target) {
- return;
- }
-
- $editable.trigger("applySnap");
-
- var evt = document.createEvent("MouseEvents");
- evt.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, target);
- target.dispatchEvent(evt);
-
- $editable.trigger("keyup");
- });
- };
-
- this.undo = function () {
- if (!pos) { return; }
- var _toSnap = toSnap;
- if (_toSnap) {
- this.saveSnap();
- }
- if (!aUndo[pos] && (!aUndo[pos] || aUndo[pos].event !== 'undo')) {
- var temp = this.makeSnap('undo');
- if (temp && (!pos || temp.contents !== aUndo[pos-1].contents)) {
- aUndo[pos] = temp;
- } else {
- pos--;
- }
- } else if (_toSnap) {
- pos--;
- }
- this.applySnap(aUndo[Math.max(--pos,0)]);
- while (pos && (aUndo[pos].event === "blur" || (aUndo[pos+1].editable === aUndo[pos].editable && aUndo[pos+1].contents === aUndo[pos].contents))) {
- this.applySnap(aUndo[--pos]);
- }
- };
- this.hasUndo = function () {
- return (toSnap && (toSnap.event !== "blur" && toSnap.event !== "activate" && toSnap.event !== "undo")) ||
- !!_.find(aUndo.slice(0, pos+1), function (undo) {
- return undo.event !== "blur" && undo.event !== "activate" && undo.event !== "undo";
- });
- };
-
- this.getEditableHasUndo = function () {
- var editable = [];
- if ((toSnap && (toSnap.event !== "blur" && toSnap.event !== "activate" && toSnap.event !== "undo"))) {
- editable.push(toSnap.editable);
- }
- _.each(aUndo.slice(0, pos+1), function (undo) {
- if (undo.event !== "blur" && undo.event !== "activate" && undo.event !== "undo") {
- editable.push(undo.editable);
- }
- });
- return _.uniq(editable);
- };
-
- this.redo = function () {
- if (!aUndo[pos+1]) { return; }
- this.applySnap(aUndo[++pos]);
- while (aUndo[pos+1] && aUndo[pos].event === "active") {
- this.applySnap(aUndo[pos++]);
- }
- };
- this.hasRedo = function () {
- return aUndo.length > pos+1;
- };
-
- var toSnap;
- this.recordUndo = function ($editable, event, internal_history) {
- var self = this;
- if (!$editable) {
- var rng = range.create();
- if(!rng) return;
- $editable = $(rng.sc).closest(".o_editable");
- }
-
- if (aUndo[pos] && (event === "applySnap" || event === "activate")) {
- return;
- }
-
- if (!internal_history) {
- if (!event || !toSnap || !aUndo[pos-1] || toSnap.event === "activate") { // don't trigger change for all keypress
- setTimeout(function () {
- $editable.trigger("content_changed");
- },0);
- }
- }
-
- if (aUndo[pos]) {
- pos = Math.min(pos, aUndo.length);
- aUndo.splice(Math.max(pos,1), aUndo.length);
- }
-
- // => make a snap when the user change editable zone (because: don't make snap for each keydown)
- if (toSnap && (toSnap.split || !event || toSnap.event !== event || toSnap.editable !== $editable[0])) {
- this.saveSnap();
- }
-
- if (pos && aUndo[pos-1].editable !== $editable[0]) {
- var snap = this.makeSnap('blur', range.create(aUndo[pos-1].editable, 0));
- pos++;
- aUndo.push(snap);
- }
-
- if (range.create()) {
- toSnap = self.makeSnap(event);
- } else {
- toSnap = false;
- }
- };
-
- this.splitNext = function () {
- if (toSnap) {
- toSnap.split = true;
- }
- };
-
- this.saveSnap = function () {
- if (toSnap) {
- if (!aUndo[pos]) {
- pos++;
- }
- aUndo.push(toSnap);
- delete toSnap.split;
- toSnap = null;
- }
- };
-};
-var history = new History();
-
-//////////////////////////////////////////////////////////////////////////////////////////////////////////
-// add focusIn to jQuery to allow to move caret into a div of a contentEditable area
-
-$.extend($.expr[':'],{
- o_editable: function (node,i,m) {
- while (node) {
- if (node.className && _.isString(node.className)) {
- if (node.className.indexOf('o_not_editable')!==-1 ) {
- return false;
- }
- if (node.className.indexOf('o_editable')!==-1 ) {
- return true;
- }
- }
- node = node.parentNode;
- }
- return false;
- }
-});
-
-$.fn.extend({
- focusIn: function () {
- if (this.length) {
- range.create(dom.firstChild(this[0]), 0).select();
- }
- return this;
- },
- focusInEnd: function () {
- if (this.length) {
- var last = dom.lastChild(this[0]);
- range.create(last, dom.nodeLength(last)).select();
- }
- return this;
- },
- selectContent: function () {
- if (this.length) {
- var next = dom.lastChild(this[0]);
- range.create(dom.firstChild(this[0]), 0, next, next.textContent.length).select();
- }
- return this;
- }
-});
-
-//////////////////////////////////////////////////////////////////////////////////////////////////////////
-
-/* ----- RICH TEXT EDITOR ---- */
-
-var RTE = Widget.extend({
- init: function (EditorBar, config) {
- var self = this;
- this.EditorBar = EditorBar;
- data.rte = this;
- this._super.apply(this, arguments);
-
- this.init_bootstrap_carousel = $.fn.carousel;
- this.edit_bootstrap_carousel = function () {
- var res = self.init_bootstrap_carousel.apply(this, arguments);
- // off bootstrap keydown event to remove event.preventDefault()
- // and allow to change cursor position
- $(this).off('keydown.bs.carousel');
- return res;
- };
-
- if (config) {
- this.config = config;
- }
- },
- /**
- * Add a record undo to history
- * @param {DOM} target where the dom is changed is editable zone
- */
- historyRecordUndo: function ($target, event, internal_history) {
- var rng = range.create();
- var $editable = $(rng && rng.sc).closest(".o_editable");
- if (!rng || !$editable.length) {
- $editable = $($target).closest(".o_editable");
- rng = range.create($target.closest("*")[0],0);
- } else {
- rng = $editable.data('range') || rng;
- }
- rng.select();
- history.recordUndo($editable, event, internal_history);
- },
- /**
- * Makes the page editable
- *
- * @param {Boolean} [restart=false] in case the edition was already set
- * up once and is being re-enabled.
- * @returns {$.Deferred} deferred indicating when the RTE is ready
- */
- start: function () {
- var self = this;
-
- this.saving_mutex = new concurrency.Mutex();
-
- $.fn.carousel = this.edit_bootstrap_carousel;
-
- $(document).on("keydown.rte", this, this.onKeydown.bind(this));
- $(document).on("mousedown.rte activate.rte", this, this.onMousedown.bind(this));
- $(document).on("mouseup.rte", this, this.onMouseup.bind(this));
-
- $('.o_not_editable').attr("contentEditable", false);
-
- var $editable = this.editable();
-
- $editable.addClass('o_editable')
- .data('rte', this)
- .each(function () {
- var $node = $(this);
-
- // add class to display inline-block for empty t-field
- if (window.getComputedStyle(this).display === "inline" && $node.data('oe-type') !== "image") {
- $node.addClass('o_is_inline_editable');
- }
- $node.data('initInnerHTML', $node.html());
- });
-
- // start element observation
- $(document).on('content_changed', '.o_editable', function (event) {
- self.trigger('change', this);
- $(this).addClass('o_dirty');
- });
-
- $('#wrapwrap, .o_editable').on('click.rte', '*', this, this.onClick.bind(this));
-
- $('body').addClass("editor_enable");
-
- $(document)
- .tooltip({
- selector: '[data-oe-readonly]',
- container: 'body',
- trigger: 'hover',
- delay: { "show": 1000, "hide": 100 },
- placement: 'bottom',
- title: _t("Readonly field")
- })
- .on('click', function () {
- $(this).tooltip('hide');
- });
-
- $(document).trigger('mousedown');
- this.trigger('rte:start');
- },
-
- save: function (context) {
- var self = this;
-
- this.__saved = {}; // list of allready saved views and data
-
- var defs = $('.o_dirty')
- .removeAttr('contentEditable')
- .removeClass('o_dirty oe_carlos_danger o_is_inline_editable')
- .map(function () {
- var $el = $(this);
-
- $el.find('[class]').filter(function () {
- if (!this.className.match(/\S/)) {
- this.removeAttribute("class");
- }
- });
-
- // TODO: Add a queue with concurrency limit in webclient
- // https://github.com/medikoo/deferred/blob/master/lib/ext/function/gate.js
- return self.saving_mutex.exec(function () {
- return self.saveElement($el, context)
- .then(undefined, function (thing, response) {
- // because ckeditor regenerates all the dom,
- // we can't just setup the popover here as
- // everything will be destroyed by the DOM
- // regeneration. Add markings instead, and
- // returns a new rejection with all relevant
- // info
- var id = _.uniqueId('carlos_danger_');
- $el.addClass('o_dirty oe_carlos_danger ' + id);
- return $.Deferred().reject({
- id: id,
- error: response.data,
- });
- });
- });
- }).get();
-
- return $.when.apply(null, defs).then(function () {
- window.onbeforeunload = null;
- }, function (failed) {
- // If there were errors, re-enable edition
- self.stop();
- self.start();
- // jquery's deferred being a pain in the ass
- if (!_.isArray(failed)) { failed = [failed]; }
-
- _(failed).each(function (failure) {
- var html = failure.error.exception_type === "except_osv";
- if (html) {
- var msg = $("
").text(failure.error.message).html();
- var data = msg.substring(3,msg.length-2).split(/', u'/);
- failure.error.message = '' + data[0] + ' ' + data[1];
- }
- $('.o_editable.' + failure.id)
- .removeClass(failure.id)
- .popover({
- html: html,
- trigger: 'hover',
- content: failure.error.message,
- placement: 'auto top',
- })
- // Force-show popovers so users will notice them.
- .popover('show');
- });
- });
- },
-
- /**
- * Get HTML cloned element with text nodes escaped for XML storage
- */
- getEscapedElement: function ($el) {
- var escaped_el = $el.clone();
- var to_escape = escaped_el.find('*').addBack();
- to_escape = to_escape.not(to_escape.filter('object,iframe,script,style,[data-oe-model][data-oe-model!="ir.ui.view"]').find('*').addBack());
- to_escape.contents().each(function () {
- if(this.nodeType === 3) {
- this.nodeValue = $('
').text(this.nodeValue).html();
- }
- });
- return escaped_el;
- },
-
- saveElement: function ($el, context) {
- // remove multi edition
- if ($el.data('oe-model')) {
- var key = $el.data('oe-model')+":"+$el.data('oe-id')+":"+$el.data('oe-field')+":"+$el.data('oe-type')+":"+$el.data('oe-expression');
- if (this.__saved[key]) return $.when();
- this.__saved[key] = true;
- }
- var markup = this.getEscapedElement($el).prop('outerHTML');
-
- return ajax.jsonRpc('/web/dataset/call', 'call', {
- model: 'ir.ui.view',
- method: 'save',
- args: [
- $el.data('oe-id'),
- markup,
- $el.data('oe-xpath') || null,
- _.omit(context || base.get_context(), 'lang')
- ],
- });
- },
-
- stop: function () {
- if (this.$last) {
- this.$last.destroy();
- this.$last = null;
- }
-
- $.fn.carousel = this.init_bootstrap_carousel;
-
- $(document).off(".rte");
- $('#wrapwrap, .o_editable').off('.rte');
-
- $('.o_not_editable').removeAttr("contentEditable");
- $(document).off('content_changed').removeClass('o_is_inline_editable').removeData('rte');
- $(document).tooltip('destroy');
- $('body').removeClass("editor_enable");
- this.trigger('rte:stop');
- },
-
- cancel: function () {
- $('.o_editable').each(function () {
- var $node = $(this);
- $node.data('initInnerHTML', $node.html());
- });
- this.stop();
- },
-
- onClick: function (e) {
- e.preventDefault();
- },
-
- // handler for cancel editor
- onKeydown: function (event) {
- if (event.keyCode === 27 && !$('.modal-content:visible').length) {
- _.defer(function () {
- $('#editor-top-navbar [data-action="cancel"]').click();
- var $modal = $('.modal-content > .modal-body').parents(".modal:first");
- $modal.off('keyup.dismiss.bs.modal');
- _.delay(function () {
- $modal.on('keyup.dismiss.bs.modal', function () {
- $(this).modal('hide');
- });
- }, 500);
- });
- }
- },
-
- // activate editor
- onMousedown: function (event) {
- var $target = $(event.target);
- var $editable = $target.closest('.o_editable');
-
- if (!$editable.length) {
- return;
- }
-
- if ($target.is('a')) {
- /**
- * Remove content editable everywhere and add it on the link only so that characters can be added
- * and removed at the start and at the end of it.
- */
- $target.attr('contenteditable', true);
- _.defer(function () {
- $editable.not($target).attr('contenteditable', false);
- $target.focus();
- });
-
- // Once clicked outside, remove contenteditable on link and reactive all
- $(document).on('mousedown.reactivate_contenteditable', function (e) {
- if ($target.is(e.target)) return;
- $target.removeAttr('contenteditable');
- $editable.attr('contenteditable', true);
- $(document).off('mousedown.reactivate_contenteditable');
- });
- }
-
- if (this && this.$last && (!$editable.length || this.$last[0] !== $editable[0])) {
- var $destroy = this.$last;
- history.splitNext();
-
- _.delay(function () {
- var id = $destroy.data('note-id');
- $destroy.destroy().removeData('note-id').removeAttr('data-note-id');
- $('#note-popover-'+id+', #note-handle-'+id+', #note-dialog-'+id+'').remove();
- }, 150); // setTimeout to remove flickering when change to editable zone (re-create an editor)
- this.$last = null;
- }
- if ($editable.length && (!this.$last || this.$last[0] !== $editable[0]) &&
- ($target.closest('[contenteditable]').attr('contenteditable') || "").toLowerCase() !== 'false') {
- $editable.summernote(this.config($editable));
-
- $editable.data('NoteHistory', history);
- this.$last = $editable;
-
- // firefox & IE fix
- try {
- document.execCommand('enableObjectResizing', false, false);
- document.execCommand('enableInlineTableEditing', false, false);
- document.execCommand('2D-position', false, false);
- } catch (e) {}
- document.body.addEventListener('resizestart', function (evt) {evt.preventDefault(); return false;});
- document.body.addEventListener('movestart', function (evt) {evt.preventDefault(); return false;});
- document.body.addEventListener('dragstart', function (evt) {evt.preventDefault(); return false;});
-
- if (!range.create()) {
- $editable.focusIn();
- }
-
- if (dom.isImg($target[0])) {
- $target.trigger('mousedown'); // for activate selection on picture
- }
-
- this.onEnableEditableArea($editable);
- }
- },
-
- onEnableEditableArea: function ($editable) {
- },
-
- onMouseup: function (event) {
- var $target = $(event.target);
- var $editable = $target.closest('.o_editable');
-
- if (!$editable.length) {
- return;
- }
-
- var self = this;
- _.defer(function () {
- self.historyRecordUndo($target, 'activate', true);
- });
-
- // To Fix Google Chrome Tripleclick Issue, which selects the ending
- // whitespace characters (so Tripleclicking then typing text will remove
- // the whole paragraph instead of its content).
- // http://stackoverflow.com/questions/38467334/why-does-google-chrome-always-add-space-after-selected-text
- if ($.browser.chrome === true && event.originalEvent.detail === 3) {
- var currentSelection = range.create();
- if (currentSelection.sc.parentNode === currentSelection.ec) {
- range.create(currentSelection.sc, currentSelection.so, currentSelection.sc, currentSelection.sc.length).select();
- }
- }
- },
-
- editable: function () {
- return $('#wrapwrap [data-oe-model]')
- .not('.o_not_editable')
- .filter(function () {
- return !$(this).closest('.o_not_editable').length;
- })
- .not('link, script')
- .not('[data-oe-readonly]')
- .not('img[data-oe-field="arch"], br[data-oe-field="arch"], input[data-oe-field="arch"]')
- .not('.oe_snippet_editor')
- .add('.o_editable');
- },
-
- config: function ($editable) {
- return {
- 'airMode' : true,
- 'focus': false,
- 'airPopover': [
- ['style', ['style']],
- ['font', ['bold', 'italic', 'underline', 'clear']],
- ['fontsize', ['fontsize']],
- ['color', ['color']],
- ['para', ['ul', 'ol', 'paragraph']],
- ['table', ['table']],
- ['insert', ['link', 'picture']],
- ['history', ['undo', 'redo']],
- ],
- 'styleWithSpan': false,
- 'inlinemedia' : ['p'],
- 'lang': "odoo",
- 'onChange': function (html, $editable) {
- $editable.trigger("content_changed");
- }
- };
- },
-});
-
-
-var data = {
- history: history,
- Class: RTE
-};
-return data;
-
-});
diff --git a/addons/web_editor/static/src/js/rte.summernote.js b/addons/web_editor/static/src/js/rte.summernote.js
deleted file mode 100644
index 979211cd4d7..00000000000
--- a/addons/web_editor/static/src/js/rte.summernote.js
+++ /dev/null
@@ -1,983 +0,0 @@
-odoo.define('web_editor.rte.summernote', function (require) {
-'use strict';
-
-var core = require('web.core');
-var ajax = require('web.ajax');
-var base = require('web_editor.base');
-var widgets = require('web_editor.widget');
-var rte = require('web_editor.rte');
-
-var QWeb = core.qweb;
-var _t = core._t;
-
-//////////////////////////////////////////////////////////////////////////////////////////////////////////
-
-ajax.jsonRpc('/web/dataset/call', 'call', {
- 'model': 'ir.ui.view',
- 'method': 'read_template',
- 'args': ['web_editor.colorpicker', base.get_context()]
-}).done(function (data) {
- QWeb.add_template(data);
-});
-
-//////////////////////////////////////////////////////////////////////////////////////////////////////////
-/* Summernote Lib (neek change to make accessible: method and object) */
-
-var dom = $.summernote.core.dom;
-var range = $.summernote.core.range;
-var eventHandler = $.summernote.eventHandler;
-var renderer = $.summernote.renderer;
-// var options = $.summernote.options;
-
-var tplButton = renderer.getTemplate().button;
-var tplIconButton = renderer.getTemplate().iconButton;
-var tplDropdown = renderer.getTemplate().dropdown;
-
-//////////////////////////////////////////////////////////////////////////////////////////////////////////
-/* update and change the popovers content, and add history button */
-
-var fn_createPalette = renderer.createPalette;
-renderer.createPalette = function ($container, options) {
- fn_createPalette.call(this, $container, options);
-
- if (!QWeb.has_template('web_editor.colorpicker')) {
- return;
- }
-
- var $clpicker = $(QWeb.render('web_editor.colorpicker'));
-
- var groups;
- if ($clpicker.is("colorpicker")) {
- groups = _.map($clpicker.children(), function (el) {
- return $(el).find("button").empty();
- });
- } else {
- groups = [$clpicker.find("button").empty()];
- }
-
- var html = "" + _t("Theme colors") + " " + _.map(groups, function ($group) {
- var $row = $("
", {"class": "note-color-row mb8"}).append($group);
- var $after_breaks = $row.find(".o_small + :not(.o_small)");
- if ($after_breaks.length === 0) {
- $after_breaks = $row.find(":nth-child(8n+9)");
- }
- $after_breaks.addClass("o_clear");
- return $row[0].outerHTML;
- }).join("") + "" + _t("Common colors") + " ";
- var $palettes = $container.find(".note-color .note-color-palette");
- $palettes.prepend(html);
-
- var $bg = $palettes.filter(":even").find("button:not(.note-color-btn)").addClass("note-color-btn");
- var $fore = $palettes.filter(":odd").find("button:not(.note-color-btn)").addClass("note-color-btn");
- $bg.each(function () {
- var $el = $(this);
- var className = 'bg-' + $el.data('color');
- $el.attr('data-event', 'backColor').attr('data-value', className).addClass(className);
- });
- $fore.each(function () {
- var $el = $(this);
- var className = 'text-' + $el.data('color');
- $el.attr('data-event', 'foreColor').attr('data-value', className).addClass('bg-' + $el.data('color'));
- });
-};
-
-var fn_tplPopovers = renderer.tplPopovers;
-renderer.tplPopovers = function (lang, options) {
- var $popover = $(fn_tplPopovers.call(this, lang, options));
-
- var $imagePopover = $popover.find('.note-image-popover');
- var $linkPopover = $popover.find('.note-link-popover');
- var $airPopover = $popover.find('.note-air-popover');
-
- if (window === window.top) {
- $popover.children().addClass("hidden-xs");
- }
-
- //////////////// image popover
-
- // add center button for images
- $(tplIconButton('fa fa-align-center', {
- title: _t('Center'),
- event: 'floatMe',
- value: 'center'
- })).insertAfter($imagePopover.find('[data-event="floatMe"][data-value="left"]'));
- $imagePopover.find('button[data-event="removeMedia"]').parent().remove();
- $imagePopover.find('button[data-event="floatMe"][data-value="none"]').remove();
-
- // padding button
- var $padding = $('
');
- $padding.insertBefore($imagePopover.find('.btn-group:first'));
- var dropdown_content = [
- ''+_t('None')+' ',
- ''+_t('Small')+' ',
- ''+_t('Medium')+' ',
- ''+_t('Large')+' ',
- ''+_t('Xl')+' ',
- ];
- $(tplIconButton('fa fa-plus-square-o', {
- title: _t('Padding'),
- dropdown: tplDropdown(dropdown_content)
- })).appendTo($padding);
-
- // circle, boxed... options became toggled
- $imagePopover.find('[data-event="imageShape"]:not([data-value])').remove();
- var $button = $(tplIconButton('fa fa-sun-o', {
- title: _t('Shadow'),
- event: 'imageShape',
- value: 'shadow'
- })).insertAfter($imagePopover.find('[data-event="imageShape"][data-value="img-circle"]'));
-
- // add spin for fa
- var $spin = $('
').insertAfter($button.parent());
- $(tplIconButton('fa fa-refresh', {
- title: _t('Spin'),
- event: 'imageShape',
- value: 'fa-spin'
- })).appendTo($spin);
-
- // resize for fa
- var $resizefa = $('
')
- .insertAfter($imagePopover.find('.btn-group:has([data-event="resize"])'));
- for (var size=1; size<=5; size++) {
- $(tplButton(''+size+'x ', {
- title: size+"x",
- event: 'resizefa',
- value: size+''
- })).appendTo($resizefa);
- }
- var $colorfa = $airPopover.find('.note-color').clone();
- $colorfa.find("ul.dropdown-menu").css('min-width', '172px');
- $resizefa.after($colorfa);
-
- // show dialog box and delete
- var $imageprop = $('
');
- $imageprop.appendTo($imagePopover.find('.popover-content'));
- $(tplIconButton('fa fa-file-image-o', {
- title: _t('Edit'),
- event: 'showImageDialog'
- })).appendTo($imageprop);
- $(tplIconButton('fa fa-trash-o', {
- title: _t('Remove'),
- event: 'delete'
- })).appendTo($imageprop);
-
- $imagePopover.find('.popover-content').append($airPopover.find(".note-history").clone());
-
- $imagePopover.find('[data-event="showImageDialog"]').before($airPopover.find('[data-event="showLinkDialog"]').clone());
-
- var $alt = $('
');
- $alt.appendTo($imagePopover.find('.popover-content'));
- $alt.append('' + _t('Description') + ': ');
-
- //////////////// link popover
-
- $linkPopover.find('.popover-content').append($airPopover.find(".note-history").clone());
-
- $linkPopover.find('button[data-event="showLinkDialog"] i').attr("class", "fa fa-link");
- $linkPopover.find('button[data-event="unlink"]').before($airPopover.find('button[data-event="showImageDialog"]').clone());
-
- //////////////// text/air popover
-
- //// highlight the text format
- $airPopover.find('.note-style .dropdown-toggle').on('mousedown', function () {
- var $format = $airPopover.find('[data-event="formatBlock"]');
- var node = range.create().sc;
- var formats = $format.map(function () { return $(this).data("value"); }).get();
- while (node && (!node.tagName || (!node.tagName || formats.indexOf(node.tagName.toLowerCase()) === -1))) {
- node = node.parentNode;
- }
- $format.parent().removeClass('active');
- $format.filter('[data-value="'+(node ? node.tagName.toLowerCase() : "p")+'"]')
- .parent().addClass("active");
- });
-
- //////////////// tooltip
-
- setTimeout(function () {
- $airPopover.add($linkPopover).add($imagePopover).find("button")
- .tooltip('destroy')
- .tooltip({
- container: 'body',
- trigger: 'hover',
- placement: 'bottom'
- }).on('click', function () {$(this).tooltip('hide');});
- });
-
- return $popover;
-};
-
-var fn_boutton_update = eventHandler.modules.popover.button.update;
-eventHandler.modules.popover.button.update = function ($container, oStyle) {
- // stop animation when edit content
- var previous = $(".note-control-selection").data('target');
- if (previous) {
- $(previous).css({"-webkit-animation-play-state": "", "animation-play-state": "", "-webkit-transition": "", "transition": "", "-webkit-animation": "", "animation": ""});
- }
- // end
-
- fn_boutton_update.call(this, $container, oStyle);
-
- $container.find('.note-color').removeClass("hidden");
-
- if (oStyle.image) {
- $container.find('[data-event]').parent().removeClass("active");
-
- $container.find('a[data-event="padding"][data-value="small"]').parent().toggleClass("active", $(oStyle.image).hasClass("padding-small"));
- $container.find('a[data-event="padding"][data-value="medium"]').parent().toggleClass("active", $(oStyle.image).hasClass("padding-medium"));
- $container.find('a[data-event="padding"][data-value="large"]').parent().toggleClass("active", $(oStyle.image).hasClass("padding-large"));
- $container.find('a[data-event="padding"][data-value="xl"]').parent().toggleClass("active", $(oStyle.image).hasClass("padding-xl"));
- $container.find('a[data-event="padding"][data-value=""]').parent().toggleClass("active", !$container.find('.active a[data-event="padding"]').length);
-
- if (dom.isImgFont(oStyle.image)) {
-
- $container.find('.btn-group:not(.only_fa):has(button[data-event="resize"],button[data-value="img-thumbnail"])').addClass("hidden");
- $container.find('.only_fa').removeClass("hidden");
- $container.find('button[data-event="resizefa"][data-value="2"]').toggleClass("active", $(oStyle.image).hasClass("fa-2x"));
- $container.find('button[data-event="resizefa"][data-value="3"]').toggleClass("active", $(oStyle.image).hasClass("fa-3x"));
- $container.find('button[data-event="resizefa"][data-value="4"]').toggleClass("active", $(oStyle.image).hasClass("fa-4x"));
- $container.find('button[data-event="resizefa"][data-value="5"]').toggleClass("active", $(oStyle.image).hasClass("fa-5x"));
- $container.find('button[data-event="resizefa"][data-value="1"]').toggleClass("active", !$container.find('.active[data-event="resizefa"]').length);
-
- $container.find('button[data-event="imageShape"][data-value="fa-spin"]').toggleClass("active", $(oStyle.image).hasClass("fa-spin"));
- $container.find('button[data-event="imageShape"][data-value="shadow"]').toggleClass("active", $(oStyle.image).hasClass("shadow"));
-
- } else {
-
- $container.find('.hidden:not(.only_fa)').removeClass("hidden");
- $container.find('.only_fa').addClass("hidden");
- var width = ($(oStyle.image).attr('style') || '').match(/(^|;|\s)width:\s*([0-9]+%)/);
- if (width) {
- width = width[2];
- }
- $container.find('button[data-event="resize"][data-value="auto"]').toggleClass("active", width !== "100%" && width !== "50%" && width !== "25%");
- $container.find('button[data-event="resize"][data-value="1"]').toggleClass("active", width === "100%");
- $container.find('button[data-event="resize"][data-value="0.5"]').toggleClass("active", width === "50%");
- $container.find('button[data-event="resize"][data-value="0.25"]').toggleClass("active", width === "25%");
-
- $container.find('button[data-event="imageShape"][data-value="shadow"]').toggleClass("active", $(oStyle.image).hasClass("shadow"));
-
- if (!$(oStyle.image).is("img")) {
- $container.find('.btn-group:has(button[data-event="imageShape"])').addClass("hidden");
- }
-
- $container.find('.note-color').addClass("hidden");
-
- }
-
- $container.find('button[data-event="floatMe"][data-value="left"]').toggleClass("active", $(oStyle.image).hasClass("pull-left"));
- $container.find('button[data-event="floatMe"][data-value="center"]').toggleClass("active", $(oStyle.image).hasClass("center-block"));
- $container.find('button[data-event="floatMe"][data-value="right"]').toggleClass("active", $(oStyle.image).hasClass("pull-right"));
-
- $(oStyle.image).trigger('attributes_change');
- }
-};
-
-var fn_popover_update = eventHandler.modules.popover.update;
-eventHandler.modules.popover.update = function ($popover, oStyle, isAirMode) {
- var $imagePopover = $popover.find('.note-image-popover');
- var $linkPopover = $popover.find('.note-link-popover');
- var $airPopover = $popover.find('.note-air-popover');
-
- fn_popover_update.call(this, $popover, oStyle, isAirMode);
-
- if (oStyle.image) {
- if (oStyle.image.parentNode.className.match(/(^|\s)media_iframe_video(\s|$)/i)) {
- oStyle.image = oStyle.image.parentNode;
- }
- var alt = $(oStyle.image).attr("alt");
-
- $imagePopover.find('.o_image_alt').text( (alt || "").replace(/"/g, '"') ).parent().toggle(oStyle.image.tagName === "IMG");
- $imagePopover.show();
-
- // for video tag (non-void) we select the range over the tag,
- // for other media types we get the first descendant leaf element
- var target_node = oStyle.image;
- if (!oStyle.image.className.match(/(^|\s)media_iframe_video(\s|$)/i)) {
- target_node = dom.firstChild(target_node);
- }
- range.createFromNode(target_node).select();
- // save range on the editor so it is not lost if restored
- eventHandler.modules.editor.saveRange(dom.makeLayoutInfo(target_node).editable());
- } else {
- $(".note-control-selection").hide();
- }
-
- if (oStyle.image || (oStyle.range && (!oStyle.range.isCollapsed() || (oStyle.range.sc.tagName && !dom.isAnchor(oStyle.range.sc)))) || (oStyle.image && !$(oStyle.image).closest('a').length)) {
- $linkPopover.hide();
- oStyle.anchor = false;
- }
-
- if (oStyle.image || oStyle.anchor || (oStyle.range && !$(oStyle.range.sc).closest('.note-editable').length)) {
- $airPopover.hide();
- } else {
- $airPopover.show();
- }
-};
-
-var fn_handle_update = eventHandler.modules.handle.update;
-eventHandler.modules.handle.update = function ($handle, oStyle, isAirMode) {
- fn_handle_update.call(this, $handle, oStyle, isAirMode);
- if (oStyle.image) {
- $handle.find('.note-control-selection').hide();
- }
-};
-
-//////////////////////////////////////////////////////////////////////////////////////////////////////////
-/* hack for image and link editor */
-
-function getImgTarget ($editable) {
- var $handle = $editable ? dom.makeLayoutInfo($editable).handle() : undefined;
- return $(".note-control-selection", $handle).data('target');
-}
-eventHandler.modules.editor.padding = function ($editable, sValue) {
- var $target = $(getImgTarget($editable));
- var paddings = "small medium large xl".split(/\s+/);
- $editable.data('NoteHistory').recordUndo();
- if (sValue.length) {
- paddings.splice(paddings.indexOf(sValue),1);
- $target.toggleClass('padding-'+sValue);
- }
- $target.removeClass("padding-" + paddings.join(" padding-"));
-};
-eventHandler.modules.editor.resize = function ($editable, sValue) {
- var $target = $(getImgTarget($editable));
- $editable.data('NoteHistory').recordUndo();
- var width = ($target.attr('style') || '').match(/(^|;|\s)width:\s*([0-9]+)%/);
- if (width) {
- width = width[2]/100;
- }
- $target.css('width', (width !== sValue && sValue !== "auto") ? (sValue * 100) + '%' : '');
-};
-eventHandler.modules.editor.resizefa = function ($editable, sValue) {
- var $target = $(getImgTarget($editable));
- $editable.data('NoteHistory').recordUndo();
- $target.attr('class', $target.attr('class').replace(/\s*fa-[0-9]+x/g, ''));
- if (+sValue > 1) {
- $target.addClass('fa-'+sValue+'x');
- }
-};
-eventHandler.modules.editor.floatMe = function ($editable, sValue) {
- var $target = $(getImgTarget($editable));
- $editable.data('NoteHistory').recordUndo();
- switch (sValue) {
- case 'center': $target.toggleClass('center-block').removeClass('pull-right pull-left'); break;
- case 'left': $target.toggleClass('pull-left').removeClass('pull-right center-block'); break;
- case 'right': $target.toggleClass('pull-right').removeClass('pull-left center-block'); break;
- }
-};
-eventHandler.modules.editor.imageShape = function ($editable, sValue) {
- var $target = $(getImgTarget($editable));
- $editable.data('NoteHistory').recordUndo();
- $target.toggleClass(sValue);
-};
-
-eventHandler.modules.linkDialog.showLinkDialog = function ($editable, $dialog, linkInfo) {
- $editable.data('range').select();
- $editable.data('NoteHistory').recordUndo();
-
- var editor = new widgets.LinkDialog(null, {}, $editable, linkInfo).open();
-
- var def = new $.Deferred();
- editor.on("save", this, function (linkInfo) {
- linkInfo.range.select();
- $editable.data('range', linkInfo.range);
- def.resolve(linkInfo);
- $editable.trigger('keyup');
- $('.note-popover .note-link-popover').show();
- });
- editor.on("cancel", this, function () {
- def.reject();
- });
- return def;
-};
-eventHandler.modules.imageDialog.showImageDialog = function ($editable) {
- var r = $editable.data('range');
- if (r.sc.tagName && r.sc.childNodes.length) {
- r.sc = r.sc.childNodes[r.so];
- }
- new widgets.MediaDialog(null, {}, $editable, dom.isImg(r.sc) ? r.sc : null).open();
- return new $.Deferred().reject();
-};
-$.summernote.pluginEvents.alt = function (event, editor, layoutInfo, sorted) {
- var $editable = layoutInfo.editable();
- var $selection = layoutInfo.handle().find('.note-control-selection');
- var media = $selection.data('target');
- new widgets.alt(null, {}, $editable, media).open();
-};
-
-//////////////////////////////////////////////////////////////////////////////////////////////////////////
-
-var fn_is_void = dom.isVoid || function () {};
-dom.isVoid = function (node) {
- return fn_is_void(node) || dom.isImgFont(node) || (node && node.className && node.className.match(/(^|\s)media_iframe_video(\s|$)/i));
-};
-var fn_is_img = dom.isImg || function () {};
-dom.isImg = function (node) {
- return fn_is_img(node) || dom.isImgFont(node) || (node && (node.nodeName === "IMG" || (node.className && node.className.match(/(^|\s)(media_iframe_video|o_image)(\s|$)/i)) ));
-};
-var fn_is_forbidden_node = dom.isForbiddenNode || function () {};
-dom.isForbiddenNode = function (node) {
- return fn_is_forbidden_node(node) || $(node).is(".media_iframe_video");
-};
-var fn_is_img_font = dom.isImgFont || function () {};
-dom.isImgFont = function (node) {
- if (fn_is_img_font(node)) return true;
-
- var nodeName = node && node.nodeName.toUpperCase();
- var className = (node && node.className || "");
- if (node && (nodeName === "SPAN" || nodeName === "I") && className.length) {
- var classNames = className.split(/\s+/);
- for (var k=0; k
')[0];
- $node.append( p );
- range.createFromNode(p.firstChild).select();
- }
- return res;
-};
-
-function prettify_html(html) {
- html = html.trim();
- var result = '',
- level = 0,
- get_space = function (level) {
- var i = level, space = '';
- while (i--) space += ' ';
- return space;
- },
- reg = /^<\/?(a|span|font|strong|u|i|strong|b)(\s|>)/i,
- inline_level = Infinity,
- tokens = _.compact(_.flatten(_.map(html.split(/), function (value) {
- value = value.replace(/\s+/g, ' ').split(/>/);
- value[0] = /\S/.test(value[0]) ? '<' + value[0] + '>' : '';
- return value;
- })));
-
- // reduce => merge inline style + text
-
- for (var i = 0, l = tokens.length; i < l; i++) {
- var token = tokens[i];
- var inline_tag = reg.test(token);
- var inline = inline_tag || inline_level <= level;
-
- if (token[0] === '<' && token[1] === '/') {
- if (inline_tag && inline_level === level) {
- inline_level = Infinity;
- }
- level--;
- }
-
- if (!inline && !/\S/.test(token)) {
- continue;
- }
- if (!inline || (token[1] !== '/' && inline_level > level)) {
- result += get_space(level);
- }
-
- if (token[0] === '<' && token[1] !== '/') {
- level++;
- if (inline_tag && inline_level > level) {
- inline_level = level;
- }
- }
-
- if (token.match(/^<(img|hr|br)/)) {
- level--;
- }
-
- // don't trim inline content (which could change appearance)
- if (!inline) {
- token = token.trim();
- }
-
- result += token.replace(/\s+/, ' ');
-
- if (inline_level > level) {
- result += '\n';
- }
- }
- return result;
-}
-
-/*
- * This override when clicking on the 'Code View' button has two aims:
- *
- * - have our own code view implementation for FieldTextHtml
- * - add an 'enable' paramater to call the function directly and allow us to
- * disable (false) or enable (true) the code view mode.
- */
-$.summernote.pluginEvents.codeview = function (event, editor, layoutInfo, enable) {
- if (layoutInfo === undefined) {
- return;
- }
- if (layoutInfo.toolbar) {
- // if editor inline (FieldTextHtmlSimple)
- var is_activated = $.summernote.eventHandler.modules.codeview.isActivated(layoutInfo);
- if (is_activated === enable) {
- return;
- }
- return eventHandler.modules.codeview.toggle(layoutInfo);
- } else {
- // if editor iframe (FieldTextHtml)
- var $editor = layoutInfo.editor();
- var $textarea = $editor.prev('textarea');
- if ($textarea.is('textarea') === enable) {
- return;
- }
-
- if (!$textarea.length) {
- // init and create texarea
- var html = prettify_html($editor.prop("innerHTML"));
- $editor.parent().css({
- 'position': 'absolute',
- 'top': 0,
- 'bottom': 0,
- 'left': 0,
- 'right': 0
- });
- $textarea = $(' ').css({
- 'margin': '0 -4px',
- 'padding': '0 4px',
- 'border': 0,
- 'top': '51px',
- 'left': '620px',
- 'width': '100%',
- 'font-family': 'sans-serif',
- 'font-size': '13px',
- 'height': '98%',
- 'white-space': 'pre',
- 'word-wrap': 'normal'
- }).val(html).data('init', html);
- $editor.before($textarea);
- $editor.hide();
- } else {
- // save changes
- $editor.prop('innerHTML', $textarea.val().replace(/\s*\n\s*/g, '')).trigger('content_changed');
- $textarea.remove();
- $editor.show();
- }
- }
-};
-
-//////////////////////////////////////////////////////////////////////////////////////////////////////////
-/* fix ie and re-range to don't break snippet*/
-
-var initial_data = {};
-function reRangeSelectKey (event) {
- initial_data.range = null;
- if (event.shiftKey && event.keyCode >= 37 && event.keyCode <= 40 && !$(event.target).is("input, textarea, select")) {
- var r = range.create();
- if (r) {
- var rng = r.reRange(event.keyCode <= 38);
- if (r !== rng) {
- rng.select();
- }
- }
- }
-}
-function reRangeSelect (event, dx, dy) {
- var r = range.create();
- if (!r || r.isCollapsed()) return;
-
- // check if the user move the caret on up or down
- var data = r.reRange(dy < 0 || (dy === 0 && dx < 0));
-
- if (data.sc !== r.sc || data.so !== r.so || data.ec !== r.ec || data.eo !== r.eo) {
- setTimeout(function () {
- data.select();
- $(data.sc.parentNode).closest('.note-popover');
- },0);
- }
-
- $(data.sc).closest('.o_editable').data('range', r);
- return r;
-}
-function summernote_mouseup (event) {
- if ($(event.target).closest("#web_editor-top-navbar, .note-popover").length) {
- return;
- }
- // don't rerange if simple click
- if (initial_data.event) {
- var dx = event.clientX - (event.shiftKey && initial_data.rect ? initial_data.rect.left : initial_data.event.clientX);
- var dy = event.clientY - (event.shiftKey && initial_data.rect ? initial_data.rect.top : initial_data.event.clientY);
- if (10 < Math.pow(dx, 2)+Math.pow(dy, 2)) {
- reRangeSelect(event, dx, dy);
- }
- }
-
- if (!$(event.target).closest(".o_editable").length) {
- return;
- }
- if (!initial_data.range || !event.shiftKey) {
- setTimeout(function () {
- initial_data.range = range.create();
- },0);
- }
-}
-var remember_selection;
-function summernote_mousedown (event) {
- rte.history.splitNext();
-
- var $editable = $(event.target).closest(".o_editable, .note-editor");
- var r;
-
- if (document.documentMode) {
- summernote_ie_fix(event, function (node) { return node.tagName === "DIV" || node.tagName === "IMG" || (node.dataset && node.dataset.oeModel); });
- } else if (last_div && event.target !== last_div) {
- if (last_div.tagName === "A") {
- summernote_ie_fix(event, function (node) { return node.dataset && node.dataset.oeModel; });
- } else if ($editable.length) {
- if (summernote_ie_fix(event, function (node) { return node.tagName === "A"; })) {
- r = range.create();
- r.select();
- }
- }
- }
-
- // restore range if range lost after clicking on non-editable area
- r = range.create();
- var editables = $(".o_editable[contenteditable], .note-editable[contenteditable]");
- var r_editable = editables.has((r||{}).sc);
- if (!r_editable.closest('.note-editor').is($editable) && !r_editable.filter('.o_editable').is(editables)) {
- var saved_editable = editables.has((remember_selection||{}).sc);
- if($editable.length && !saved_editable.closest('.o_editable, .note-editor').is($editable)) {
- remember_selection = range.create(dom.firstChild($editable[0]), 0);
- } else if(!saved_editable.length) {
- remember_selection = undefined;
- }
- if(remember_selection) {
- try {
- remember_selection.select();
- } catch (e) {
- console.warn(e);
- }
- }
- } else if(r_editable.length) {
- remember_selection = r;
- }
-
- initial_data.event = event;
-
- // keep selection when click with shift
- if (event.shiftKey && $editable.length) {
- if (initial_data.range) {
- initial_data.range.select();
- }
- var rect = r && r.getClientRects();
- initial_data.rect = rect && rect.length ? rect[0] : { top: 0, left: 0 };
- }
-}
-
-var last_div;
-var last_div_change;
-var last_editable;
-function summernote_ie_fix (event, pred) {
- var editable;
- var div;
- var node = event.target;
- while(node.parentNode) {
- if (!div && pred(node)) {
- div = node;
- }
- if(last_div !== node && (node.getAttribute('contentEditable')==='false' || node.className && (node.className.indexOf('o_not_editable') !== -1))) {
- break;
- }
- if (node.className && node.className.indexOf('o_editable') !== -1) {
- if (!div) {
- div = node;
- }
- editable = node;
- break;
- }
- node = node.parentNode;
- }
-
- if (!editable) {
- $(last_div_change).removeAttr("contentEditable").removeProp("contentEditable");
- $(last_editable).attr("contentEditable", "true").prop("contentEditable", "true");
- last_div_change = null;
- last_editable = null;
- return;
- }
-
- if (div === last_div) {
- return;
- }
-
- last_div = div;
-
- $(last_div_change).removeAttr("contentEditable").removeProp("contentEditable");
-
- if (last_editable !== editable) {
- if ($(editable).is("[contentEditable='true']")) {
- $(editable).removeAttr("contentEditable").removeProp("contentEditable");
- last_editable = editable;
- } else {
- last_editable = null;
- }
- }
- if (!$(div).attr("contentEditable") && !$(div).is("[data-oe-type='many2one'], [data-oe-type='contact']")) {
- $(div).attr("contentEditable", "true").prop("contentEditable", "true");
- last_div_change = div;
- } else {
- last_div_change = null;
- }
- return editable !== div ? div : null;
-}
-
-var fn_attach = eventHandler.attach;
-eventHandler.attach = function (oLayoutInfo, options) {
- fn_attach.call(this, oLayoutInfo, options);
-
- oLayoutInfo.editor().on('dragstart', 'img', function (e) { e.preventDefault(); });
- $(document).on('mousedown', summernote_mousedown).on('mouseup', summernote_mouseup);
- oLayoutInfo.editor().off('click').on('click', function (e) {e.preventDefault();}); // if the content editable is a link
-
- /**
- * Open Media Dialog on double click on an image/video/icon.
- * Shows a tooltip on click to say to the user he can double click.
- */
- create_dblclick_feature("img, .media_iframe_video, i.fa, span.fa, a.o_image", function () {
- eventHandler.modules.imageDialog.show(oLayoutInfo);
- });
-
- /**
- * Open Link Dialog on double click on a link/button.
- * Shows a tooltip on click to say to the user he can double click.
- */
- create_dblclick_feature("a[href], .btn", function () {
- eventHandler.modules.linkDialog.show(oLayoutInfo);
- });
-
- if(oLayoutInfo.editor().is('[data-oe-model][data-oe-type="image"]')) {
- oLayoutInfo.editor().on('click', 'img', function (event) {
- $(event.target).trigger("dblclick");
- });
- }
- oLayoutInfo.editable().on('mousedown', function (e) {
- if(dom.isImg(e.target)) {
- range.createFromNode(e.target).select();
- }
- });
- $(document).on("keyup", reRangeSelectKey);
-
- var clone_data = false;
- var $node = oLayoutInfo.editor();
- if ($node.data('oe-model') || $node.data('oe-translation-id')) {
- $node.on('content_changed', function () {
- var $nodes = $('[data-oe-model], [data-oe-translation-id]')
- .filter(function () { return this !== $node[0];});
-
- if ($node.data('oe-model')) {
- $nodes = $nodes.filter('[data-oe-model="'+$node.data('oe-model')+'"]')
- .filter('[data-oe-id="'+$node.data('oe-id')+'"]')
- .filter('[data-oe-field="'+$node.data('oe-field')+'"]');
- }
- if ($node.data('oe-translation-id')) $nodes = $nodes.filter('[data-oe-translation-id="'+$node.data('oe-translation-id')+'"]');
- if ($node.data('oe-type')) $nodes = $nodes.filter('[data-oe-type="'+$node.data('oe-type')+'"]');
- if ($node.data('oe-expression')) $nodes = $nodes.filter('[data-oe-expression="'+$node.data('oe-expression')+'"]');
- if ($node.data('oe-xpath')) $nodes = $nodes.filter('[data-oe-xpath="'+$node.data('oe-xpath')+'"]');
- if ($node.data('oe-contact-options')) $nodes = $nodes.filter('[data-oe-contact-options="'+$node.data('oe-contact-options')+'"]');
-
- var nodes = $node.get();
-
- if ($node.data('oe-type') === "many2one") {
- $nodes = $nodes.add($('[data-oe-model]')
- .filter(function () { return this !== $node[0] && nodes.indexOf(this) === -1; })
- .filter('[data-oe-many2one-model="'+$node.data('oe-many2one-model')+'"]')
- .filter('[data-oe-many2one-id="'+$node.data('oe-many2one-id')+'"]')
- .filter('[data-oe-type="many2one"]'));
-
- $nodes = $nodes.add($('[data-oe-model]')
- .filter(function () { return this !== $node[0] && nodes.indexOf(this) === -1; })
- .filter('[data-oe-model="'+$node.data('oe-many2one-model')+'"]')
- .filter('[data-oe-id="'+$node.data('oe-many2one-id')+'"]')
- .filter('[data-oe-field="name"]'));
- }
-
- if (!clone_data) {
- clone_data = true;
- $nodes.html(this.innerHTML);
- clone_data = false;
- }
- });
- }
-
- var custom_toolbar = oLayoutInfo.toolbar ? oLayoutInfo.toolbar() : undefined;
- var $toolbar = $(oLayoutInfo.popover()).add(custom_toolbar);
- $('button[data-event="undo"], button[data-event="redo"]', $toolbar).attr('disabled', true);
-
- $(oLayoutInfo.editor())
- .add(oLayoutInfo.handle())
- .add(oLayoutInfo.popover())
- .add(custom_toolbar)
- .on('click content_changed', function () {
- $('button[data-event="undo"]', $toolbar).attr('disabled', !oLayoutInfo.editable().data('NoteHistory').hasUndo());
- $('button[data-event="redo"]', $toolbar).attr('disabled', !oLayoutInfo.editable().data('NoteHistory').hasRedo());
- });
-
- function create_dblclick_feature(selector, callback) {
- var show_tooltip = true;
-
- oLayoutInfo.editor().on("dblclick", selector, function (e) {
- if ($(e.target).closest(".note-toolbar").length) return; // prevent icon edition of top bar for default summernote
- show_tooltip = false;
- callback();
- e.stopImmediatePropagation();
- });
-
- oLayoutInfo.editor().on("click", selector, function (e) {
- var $target = $(e.target);
- if ($target.closest(".note-toolbar").length) return; // prevent icon edition of top bar for default summernote
- show_tooltip = true;
- setTimeout(function () {
- if (!show_tooltip) return;
- $target.tooltip({title: _t('Double-click to edit'), trigger: 'manuel', container: 'body'}).tooltip('show');
- setTimeout(function () {
- $target.tooltip('destroy');
- }, 800);
- }, 400);
- });
- }
-};
-var fn_detach = eventHandler.detach;
-eventHandler.detach = function (oLayoutInfo, options) {
- fn_detach.call(this, oLayoutInfo, options);
- oLayoutInfo.editable().off('mousedown');
- oLayoutInfo.editor().off("dragstart");
- oLayoutInfo.editor().off('click');
- $(document).off('mousedown', summernote_mousedown);
- $(document).off('mouseup', summernote_mouseup);
- oLayoutInfo.editor().off("dblclick");
- $(document).off("keyup", reRangeSelectKey);
-};
-
-//////////////////////////////////////////////////////////////////////////////////////////////////////////
-/* Translation for odoo */
-
-$.summernote.lang.odoo = {
- font: {
- bold: _t('Bold'),
- italic: _t('Italic'),
- underline: _t('Underline'),
- strikethrough: _t('Strikethrough'),
- subscript: _t('Subscript'),
- superscript: _t('Superscript'),
- clear: _t('Remove Font Style'),
- height: _t('Line Height'),
- name: _t('Font Family'),
- size: _t('Font Size')
- },
- image: {
- image: _t('File / Image'),
- insert: _t('Insert Image'),
- resizeFull: _t('Resize Full'),
- resizeHalf: _t('Resize Half'),
- resizeQuarter: _t('Resize Quarter'),
- floatLeft: _t('Float Left'),
- floatRight: _t('Float Right'),
- floatNone: _t('Float None'),
- dragImageHere: _t('Drag an image here'),
- selectFromFiles: _t('Select from files'),
- url: _t('Image URL'),
- remove: _t('Remove Image')
- },
- link: {
- link: _t('Link'),
- insert: _t('Insert Link'),
- unlink: _t('Unlink'),
- edit: _t('Edit'),
- textToDisplay: _t('Text to display'),
- url: _t('To what URL should this link go?'),
- openInNewWindow: _t('Open in new window')
- },
- video: {
- video: _t('Video'),
- videoLink: _t('Video Link'),
- insert: _t('Insert Video'),
- url: _t('Video URL?'),
- providers: _t('(YouTube, Vimeo, Vine, Instagram, DailyMotion or Youku)')
- },
- table: {
- table: _t('Table')
- },
- hr: {
- insert: _t('Insert Horizontal Rule')
- },
- style: {
- style: _t('Style'),
- normal: _t('Normal'),
- blockquote: _t('Quote'),
- pre: _t('Code'),
- h1: _t('Header 1'),
- h2: _t('Header 2'),
- h3: _t('Header 3'),
- h4: _t('Header 4'),
- h5: _t('Header 5'),
- h6: _t('Header 6')
- },
- lists: {
- unordered: _t('Unordered list'),
- ordered: _t('Ordered list')
- },
- options: {
- help: _t('Help'),
- fullscreen: _t('Full Screen'),
- codeview: _t('Code View')
- },
- paragraph: {
- paragraph: _t('Paragraph'),
- outdent: _t('Outdent'),
- indent: _t('Indent'),
- left: _t('Align left'),
- center: _t('Align center'),
- right: _t('Align right'),
- justify: _t('Justify full')
- },
- color: {
- recent: _t('Recent Color'),
- more: _t('More Color'),
- background: _t('Background Color'),
- foreground: _t('Font Color'),
- transparent: _t('Transparent'),
- setTransparent: _t('Set transparent'),
- reset: _t('Reset'),
- resetToDefault: _t('Reset to default')
- },
- shortcut: {
- shortcuts: _t('Keyboard shortcuts'),
- close: _t('Close'),
- textFormatting: _t('Text formatting'),
- action: _t('Action'),
- paragraphFormatting: _t('Paragraph formatting'),
- documentStyle: _t('Document Style')
- },
- history: {
- undo: _t('Undo'),
- redo: _t('Redo')
- }
-};
-});
diff --git a/addons/web_editor/static/src/js/snippets.editor.js b/addons/web_editor/static/src/js/snippets.editor.js
deleted file mode 100644
index a0bfb8b300c..00000000000
--- a/addons/web_editor/static/src/js/snippets.editor.js
+++ /dev/null
@@ -1,1154 +0,0 @@
-odoo.define('web_editor.snippet.editor', function (require) {
-'use strict';
-
-var Class = require('web.Class');
-var ajax = require('web.ajax');
-var core = require('web.core');
-var dom = require('web.dom');
-var Widget = require('web.Widget');
-var editor = require('web_editor.editor');
-var animation = require('web_editor.snippets.animation');
-var options = require('web_editor.snippets.options');
-
-var qweb = core.qweb;
-var _t = core._t;
-
-ajax.loadXML('/web_editor/static/src/xml/snippets.xml', qweb);
-
-var data = {};
-
-/* ----- SNIPPET SELECTOR ---- */
-
-data.globalSelector = {
- closest: function () { return $(); },
- all: function () { return $(); },
- is: function () { return false; },
-};
-
-/**
- * Snippet editor main class.
- * Management of drag&drop and focus/blur of snippets.
- * Previously named "BuildingBlock".
- */
-data.Class = Widget.extend({
- id: "oe_snippets",
- activeSnippets: [],
- init: function (parent, $editable) {
- this.$editable = $editable;
-
- this._super.apply(this, arguments);
- if(!$('#oe_manipulators').length) {
- $("
", {id: "oe_manipulators", 'class': "o_css_editor"}).appendTo('body');
- }
- this.$active_snipped_id = false;
- this.snippets = [];
-
- data.instance = this;
- },
- start: function () {
- var self = this;
-
- $(window).resize(function () {
- _.defer(function () { $(document).click(); });
- });
-
- this.fetch_snippet_templates();
- this.bind_snippet_click_editor();
-
- $(document).on('click', '.dropdown-submenu a[tabindex]', function (e) {
- e.preventDefault();
- });
-
- var _isNotBreakable = $.summernote.core.dom.isNotBreakable;
- $.summernote.core.dom.isNotBreakable = function (node) {
- return _isNotBreakable(node) || $(node).is('div') || data.globalSelector.is($(node));
- };
-
- $(window).on('resize', function () {
- if (self.$active_snipped_id && self.$active_snipped_id.data("snippet-editor")) {
- self.cover_target(self.$active_snipped_id.data("snippet-editor").$overlay, self.$active_snipped_id);
- }
- });
-
- $(document).on('mousemove', function () {
- if (self.$active_snipped_id && self.$active_snipped_id.data("snippet-editor")) {
- self.$active_snipped_id.data("snippet-editor").$overlay.removeClass('o_keypress');
- }
- });
- $(document).on('keydown', function (event) {
- if (self.$active_snipped_id && self.$active_snipped_id.data("snippet-editor")) {
- self.$active_snipped_id.data("snippet-editor").$overlay.addClass('o_keypress');
- }
- if ((event.metaKey || (event.ctrlKey && !event.altKey)) && event.shiftKey && event.keyCode >= 48 && event.keyCode <= 57) {
- self.$('.scroll:first > ul li:eq('+(event.keyCode-49)+') a').trigger("click");
- event.preventDefault();
- }
- });
- },
-
- _get_snippet_url: function () {
- return '/web_editor/snippets';
- },
- _add_check_selector : function (selector, no_check, is_children) {
- var self = this;
- selector = selector.split(/\s*,/).join(":not(.o_snippet_not_selectable), ") + ":not(.o_snippet_not_selectable)";
-
- if (no_check) {
- return {
- closest: function ($from, parentNode) {
- return $from.closest(selector, parentNode);
- },
- all: function ($from) {
- return $from ? dom.cssFind($from, selector) : $(selector);
- },
- is: function ($from) {
- return $from.is(selector);
- }
- };
- } else {
- selector = selector.split(/\s*,/).join(":o_editable, ") + ":o_editable";
- return {
- closest: function ($from, parentNode) {
- var parents = self.$editable.get();
- return $from.closest(selector, parentNode).filter(function () {
- var node = this;
- while (node.parentNode) {
- if (parents.indexOf(node)!==-1) {
- return true;
- }
- node = node.parentNode;
- }
- return false;
- });
- },
- all: is_children ? function ($from) {
- return dom.cssFind($from || self.$editable, selector);
- } : function ($from) {
- $from = $from || self.$editable;
- return $from.filter(selector).add(dom.cssFind($from, selector));
- },
- is: function ($from) {
- return $from.is(selector);
- }
- };
- }
- },
-
- fetch_snippet_templates: function () {
- var self = this;
- var url = this._get_snippet_url();
- if (!url || !url.length) {
- this.$el.detach();
- return;
- }
- return ajax.jsonRpc(url, 'call', {}).then(function (html) {
- self.compute_snippet_templates(html);
- self.trigger("snippets:ready");
- }, function () {
- console.warn('Snippets template not found:', url);
- });
- },
- compute_snippet_templates: function (html) {
- var self = this;
- var $html = $(html);
- var $scroll = $html.siblings("#o_scroll");
-
- if (!$scroll.length) {
- throw new Error("Wrong snippets xml definition");
- }
-
- // t-snippet
- $html.find('[data-oe-type="snippet"]').each(function () {
- $(this).children().attr('data-oe-type', "snippet").attr('data-oe-thumbnail', $(this).data('oe-thumbnail'));
- });
- // end
-
- self.templateOptions = [];
- var selector = [];
- var $styles = $html.find("[data-js], [data-selector]");
- $styles.each(function () {
- var $style = $(this);
- var no_check = $style.data('no-check');
- var option_id = $style.data('js');
- var option = {
- 'option' : option_id,
- 'base_selector': $style.data('selector'),
- 'selector': self._add_check_selector($style.data('selector'), no_check),
- '$el': $style,
- 'drop-near': $style.data('drop-near') && self._add_check_selector($style.data('drop-near'), no_check, true),
- 'drop-in': $style.data('drop-in') && self._add_check_selector($style.data('drop-in'), no_check),
- 'data': $style.data()
- };
- self.templateOptions.push(option);
- selector.push(option.selector);
- });
-
- $styles.addClass("hidden");
- data.globalSelector.closest = function ($from) {
- var $temp;
- var $target;
- var len = selector.length;
- for (var i = 0; i'+
- '
'+
- ' '+
- '');
- $div.find('span').text($snippet.attr("name"));
- $snippet.prepend($div);
-
- // from t-snippet
- var thumbnail = $("[data-oe-thumbnail]", this).data("oe-thumbnail");
- if (thumbnail) {
- $div.find('.oe_snippet_thumbnail_img').css('background-image', 'url(' + thumbnail + ')');
- }
- // end
-
- var moduleID = $snippet.data('moduleId');
- if (moduleID) {
- $snippet.addClass('o_snippet_install');
- var $installBtn = $(' ', {
- class: 'btn btn-primary btn-sm o_install_btn',
- target: '_blank',
- href: '/web#id=' + moduleID + '&view_type=form&model=ir.module.module&action=base.open_module_tree',
- text: _t("Install"),
- });
- $div.append($installBtn);
- }
- }
- if (!$snippet.data("selector")) {
- $("> *:not(.oe_snippet_thumbnail)", this).addClass('oe_snippet_body');
- }
- number++;
- })
- .not('[data-module-id]');
-
- // hide scroll if no snippets defined
- if (!number) {
- this.$el.detach();
- }
- $("body").toggleClass("editor_has_snippets", !!number);
-
- // select all default text to edit (if snippet default text)
- this.add_default_snippet_text_classes();
- $(document).on("click", ".o_default_snippet_text", function (event) {
- $(event.target).selectContent();
- });
- $(document).on("keyup", function (event) {
- var r = $.summernote.core.range.create();
- $(r && r.sc).closest(".o_default_snippet_text").removeClass("o_default_snippet_text");
- });
-
- // clean t-oe
- $html.find('[data-oe-model], [data-oe-type]').each(function () {
- for (var k=0; k to get drop-near, drop-in
- $snippet = $(this);
- var $base_body = $snippet.find('.oe_snippet_body');
- var $selector_siblings = $();
- var $selector_children = $();
- var temp = self.templateOptions;
- for (var k in temp) {
- if ($base_body.is(temp[k].base_selector)) {
- if (temp[k]['drop-near']) {
- if (!$selector_siblings) $selector_siblings = temp[k]['drop-near'].all();
- else $selector_siblings = $selector_siblings.add(temp[k]['drop-near'].all());
- }
- if (temp[k]['drop-in']) {
- if (!$selector_children) $selector_children = temp[k]['drop-in'].all();
- else $selector_children = $selector_children.add(temp[k]['drop-in'].all());
- }
- }
- }
-
- $toInsert = $base_body.clone().data("name", $snippet.find(".oe_snippet_thumbnail_title").text());
-
- if (!$selector_siblings.length && !$selector_children.length) {
- console.warn($snippet.find(".oe_snippet_thumbnail_title").text() + " have not insert action: data-drop-near or data-drop-in");
- return;
- }
-
- self.make_active(false);
- self.activate_insertion_zones($selector_siblings, $selector_children);
-
- $('.oe_drop_zone').droppable({
- over: function () {
- dropped = true;
- $(this).first().after($toInsert).addClass('hidden');
- },
- out: function () {
- var prev = $toInsert.prev();
- if(this === prev[0]) {
- dropped = false;
- $toInsert.detach();
- $(this).removeClass('hidden');
- }
- },
- });
- },
- stop: function (ev, ui) {
- $toInsert.removeClass('oe_snippet_body');
-
- if (! dropped && self.$editable.find('.oe_drop_zone') && ui.position.top > 3 && ui.position.left + 50 > self.$el.outerWidth()) {
- var el = self.$editable.find('.oe_drop_zone').nearest({x: ui.position.left, y: ui.position.top}).first();
- if (el.length) {
- el.after($toInsert);
- dropped = true;
- }
- }
-
- self.$editable.find('.oe_drop_zone').droppable('destroy').remove();
-
- if (dropped) {
-
- var prev = $toInsert.first()[0].previousSibling;
- var next = $toInsert.last()[0].nextSibling;
- var rte = self.getParent().rte;
-
- if (prev) {
- $toInsert.detach();
- rte.historyRecordUndo($(prev));
- $toInsert.insertAfter(prev);
- } else if (next) {
- $toInsert.detach();
- rte.historyRecordUndo($(next));
- $toInsert.insertBefore(next);
- } else {
- var $parent = $toInsert.parent();
- $toInsert.detach();
- rte.historyRecordUndo($parent);
- $parent.prepend($toInsert);
- }
-
- $toInsert.closest(".o_editable").trigger("content_changed");
-
- var $target = false;
- $target = $toInsert;
-
- setTimeout(function () {
- self.$el.trigger('snippet-dropped', $target);
-
- animation.start(true, $target);
-
- self.call_for_all_snippets($target, function (editor, $snippet) {
- _.defer(function () { editor.drop_and_build_snippet(); });
- });
- self.create_snippet_editor($target);
- self.cover_target($target.data('overlay'), $target);
- $target.closest(".o_editable").trigger("content_changed");
-
- self.make_active($target);
- },0);
- } else {
- $toInsert.remove();
- }
- },
- });
- },
-
- // call a method on a snippet and all his children
- call_for_all_snippets: function ($snippet, callback) {
- var self = this;
- $snippet.add(data.globalSelector.all($snippet)).each(function () {
- var $snippet = $(this);
- self.create_snippet_editor($snippet);
- if ($snippet.data("snippet-editor")) {
- callback.call(self, $snippet.data("snippet-editor"), $snippet);
- }
- });
- },
-
- // return the original snippet in the editor bar from a snippet id (string)
- get_snippet_from_id: function (id) {
- return $('.oe_snippet').filter(function () {
- return $(this).data('option') === id;
- }).first();
- },
-
- // Create element insertion drop zones. two css selectors can be provided
- // selector.children -> will insert drop zones as direct child of the selected elements
- // in case the selected elements have children themselves, dropzones will be interleaved
- // with them.
- // selector.siblings -> will insert drop zones after and before selected elements
- activate_insertion_zones: function ($selector_siblings, $selector_children) {
- var self = this;
- var zone_template = $("
");
-
- if ($selector_children) {
- $selector_children.each(function () {
- var $zone = $(this);
- var css = window.getComputedStyle(this);
- var float = css.float || css.cssFloat;
- var $drop = zone_template.clone();
-
- $zone.append($drop);
- var node = $drop[0].previousSibling;
- var test = !!(node && ((!node.tagName && node.textContent.match(/\S/)) || node.tagName === "BR"));
- if (test) {
- $drop.addClass("oe_vertical").css({
- 'height': parseInt(window.getComputedStyle($zone[0]).lineHeight),
- 'float': 'none',
- 'display': 'inline-block'
- });
- } else if (float === "left" || float === "right") {
- $drop.addClass("oe_vertical").css('height', Math.max(Math.min($zone.outerHeight(), $zone.children().last().outerHeight()), 30));
- }
-
- $drop = $drop.clone();
-
- $zone.prepend($drop);
- node = $drop[0].nextSibling;
- test = !!(node && ((!node.tagName && node.textContent.match(/\S/)) || node.tagName === "BR"));
- if (test) {
- $drop.addClass("oe_vertical").css({
- 'height': parseInt(window.getComputedStyle($zone[0]).lineHeight),
- 'float': 'none',
- 'display': 'inline-block'
- });
- } else if (float === "left" || float === "right") {
- $drop.addClass("oe_vertical").css('height', Math.max(Math.min($zone.outerHeight(), $zone.children().first().outerHeight()), 30));
- }
- if (test) {
- $drop.css({'float': 'none', 'display': 'inline-block'});
- }
- });
-
- // add children near drop zone
- $selector_siblings = $(_.uniq(($selector_siblings || $()).add($selector_children.children()).get()));
- }
-
- if ($selector_siblings) {
- $selector_siblings.filter(':not(.oe_drop_zone):not(.oe_drop_clone)').each(function () {
- var $zone = $(this);
- var $drop;
- var css = window.getComputedStyle(this);
- var float = css.float || css.cssFloat;
-
- if($zone.prev('.oe_drop_zone:visible').length === 0) {
- $drop = zone_template.clone();
- if (float === "left" || float === "right") {
- $drop.addClass("oe_vertical").css('height', Math.max(Math.min($zone.outerHeight(), $zone.prev().outerHeight() || Infinity), 30));
- }
- $zone.before($drop);
- }
- if($zone.next('.oe_drop_zone:visible').length === 0) {
- $drop = zone_template.clone();
- if (float === "left" || float === "right") {
- $drop.addClass("oe_vertical").css('height', Math.max(Math.min($zone.outerHeight(), $zone.next().outerHeight() || Infinity), 30));
- }
- $zone.after($drop);
- }
- });
- }
-
- var count;
- do {
- count = 0;
- $zones = self.$editable.find('.oe_drop_zone > .oe_drop_zone').remove(); // no recursive zones
- count += $zones.length;
- $zones.remove();
- } while (count > 0);
-
- // Cleaning consecutive zone and up zones placed between floating or inline elements. We do not like these kind of zones.
- var $zones = self.$editable.find('.oe_drop_zone:not(.oe_vertical)');
- $zones.each(function () {
- var zone = $(this);
- var prev = zone.prev();
- var next = zone.next();
- // remove consecutive zone
- if (prev.is('.oe_drop_zone') || next.is('.oe_drop_zone')) {
- zone.remove();
- return;
- }
- var float_prev = prev.css('float') || 'none';
- var float_next = next.css('float') || 'none';
- var disp_prev = prev.css('display') || null;
- var disp_next = next.css('display') || null;
- if( (float_prev === 'left' || float_prev === 'right')
- && (float_next === 'left' || float_next === 'right') ) {
- zone.remove();
- }else if( !( disp_prev === null
- || disp_next === null
- || disp_prev === 'block'
- || disp_next === 'block' )) {
- zone.remove();
- }
- });
- },
-
- // generate drop zones covering the elements selected by the selector
- // we generate overlay drop zones only to get an idea of where the snippet are, the drop
- activate_overlay_zones: function ($targets) {
- var self = this;
-
- // filter out invisible elements
- $targets = $targets.filter(':visible:hasVisibility:hasOpacity');
-
- $targets.each(function () {
- var $target = $(this);
- if (!$target.data('overlay')) {
- var $zone = $(qweb.render('web_editor.snippet_overlay'));
-
- // fix for pointer-events: none with ie9
- if (document.body && document.body.addEventListener) {
- $zone.on("click mousedown mousedown", function passThrough(event) {
- event.preventDefault();
- $target.each(function () {
- // check if clicked point (taken from event) is inside element
- event.srcElement = this;
- $(this).trigger(event.type);
- });
- return false;
- });
- }
-
- $zone.appendTo('#oe_manipulators');
- $zone.data('target', $target);
- $target.data('overlay', $zone);
-
- var timer;
- $target.closest('.o_editable').on("content_changed", function (event) {
- clearTimeout(timer);
- timer = setTimeout(function () {
- if ($target.data('overlay') && $target.data('overlay').hasClass("oe_active")) {
- self.cover_target($target.data('overlay'), $target);
- }
- }, 50);
- });
-
- var resize = function () {
- if ($zone.parent().length) {
- self.cover_target($zone, $target);
- } else {
- $('body').off("resize", resize);
- }
- };
- $('body').on("resize", resize);
- }
- self.cover_target($target.data('overlay'), $target);
- });
- return $targets;
- }
-});
-
-/**
- * Snippet editor Editor class.
- * Management of the overlay and option list for a snippet.
- */
-data.Editor = Class.extend({
- init: function (BuildingBlock, dom) {
- this.buildingBlock = BuildingBlock;
- this.$target = $(dom);
- this.$overlay = this.$target.data('overlay');
-
- // Initialize parent button
- this.init_parent_options();
-
- // Load overlay options content
- this.load_style_options();
-
- // Initialize move/clone/remove buttons
- if (!this.$target.parent().is(':o_editable')) {
- this.$overlay.find('.oe_snippet_move, .oe_snippet_clone, .oe_snippet_remove').remove();
- } else {
- this.$overlay.on('click', '.oe_snippet_clone', _.bind(this.on_clone, this));
- this.$overlay.on('click', '.oe_snippet_remove', _.bind(this.on_remove, this));
- this._drag_and_drop();
- }
- },
-
- getName: function () {
- if (this.$target.data("name") !== undefined) {
- return this.$target.data("name");
- }
- if (this.$target.parent(".row").length) {
- return _t("Column");
- }
- return _t("Block");
- },
-
- // activate drag and drop for the snippets in the snippet toolbar
- _drag_and_drop: function () {
- var self = this;
- this.dropped = false;
- this.$overlay.draggable({
- greedy: true,
- appendTo: 'body',
- cursor: "move",
- handle: ".oe_snippet_move",
- cursorAt: {
- left: 18,
- top: 14
- },
- helper: function () {
- var $clone = $(this).clone().css({width: "24px", height: "24px", border: 0});
- $clone.find(".oe_overlay_options >:not(:contains(.oe_snippet_move)), .oe_handle").remove();
- $clone.find(":not(.glyphicon)").css({position: 'absolute', top: 0, left: 0});
- $clone.appendTo("body").removeClass("hidden");
- return $clone;
- },
- start: _.bind(self._drag_and_drop_start, self),
- stop: _.bind(self._drag_and_drop_stop, self)
- });
- },
- _drag_and_drop_after_insert_dropzone: function () {},
- _drag_and_drop_active_drop_zone: function ($zones) {
- var self = this;
- $zones.droppable({
- over: function () {
- $(".oe_drop_zone.hide").removeClass("hide");
- $(this).addClass("hide").first().after(self.$target);
- self.dropped = true;
- },
- out: function () {
- $(this).removeClass("hide");
- self.$target.detach();
- self.dropped = false;
- },
- });
- },
- _drag_and_drop_start: function () {
- var self = this;
- self.buildingBlock.editor_busy = true;
- self.size = {
- width: self.$target.width(),
- height: self.$target.height()
- };
- self.$target.after("
");
- self.$target.detach();
- self.$overlay.addClass("hidden");
-
- var $selector_siblings;
- for (var i = 0 ; i < self.selector_siblings.length ; i++) {
- if (!$selector_siblings) $selector_siblings = self.selector_siblings[i].all();
- else $selector_siblings = $selector_siblings.add(self.selector_siblings[i].all());
- }
- var $selector_children;
- for (i = 0 ; i < self.selector_children.length ; i++) {
- if (!$selector_children) $selector_children = self.selector_children[i].all();
- else $selector_children = $selector_children.add(self.selector_children[i].all());
- }
-
- self.buildingBlock.make_active(false);
- self.buildingBlock.activate_insertion_zones($selector_siblings, $selector_children);
-
- $("body").addClass('move-important');
-
- self._drag_and_drop_after_insert_dropzone();
- self._drag_and_drop_active_drop_zone($('.oe_drop_zone'));
- },
- _drag_and_drop_stop: function () {
- var self = this;
-
- $(".oe_drop_zone").droppable('destroy').remove();
-
- var prev = this.$target.first()[0].previousSibling;
- var next = this.$target.last()[0].nextSibling;
- var $parent = this.$target.parent();
-
- var $clone = $(".oe_drop_clone");
- if (prev === $clone[0]) {
- prev = $clone[0].previousSibling;
- } else if (next === $clone[0]) {
- next = $clone[0].nextSibling;
- }
- $clone.after(this.$target);
-
- this.$overlay.removeClass("hidden");
- $("body").removeClass('move-important');
- $clone.remove();
-
- if (this.dropped) {
- this.buildingBlock.getParent().rte.historyRecordUndo(this.$target);
-
- if (prev) {
- this.$target.insertAfter(prev);
- } else if (next) {
- this.$target.insertBefore(next);
- } else {
- $parent.prepend(this.$target);
- }
-
- for (var i in this.styles) {
- this.styles[i].on_move();
- }
- }
-
- self.buildingBlock.editor_busy = false;
-
- self.init_parent_options();
- _.defer(function () {
- self.buildingBlock.cover_target(self.$target.data('overlay'), self.$target);
- });
- },
-
- load_style_options: function () {
- var self = this;
- var $styles = this.$overlay.find('.oe_options');
- var $ul = $styles.find('ul:first');
- this.styles = {};
- this.selector_siblings = [];
- this.selector_children = [];
-
- var i = 0;
- $ul.append($(" ", {"class": "dropdown-header o_main_header", text: this.getName()}).data("editor", this));
- _.each(this.buildingBlock.templateOptions, function (val, option_id) {
- if (!val.selector.is(self.$target)) {
- return;
- }
- if (val['drop-near']) self.selector_siblings.push(val['drop-near']);
- if (val['drop-in']) self.selector_children.push(val['drop-in']);
-
- var option = val['option'];
- self.styles[option] = new (options.registry[option] || options.Class)(self.buildingBlock, self, self.$target, option_id);
- $ul.append(self.styles[option].$el.addClass("snippet-option-" + option));
- self.styles[option].start();
- self.styles[option].__order = i++;
- });
- $ul.append($(" ", {"class": "divider"}));
-
- var $parents = this.$target.parents();
- _.each($parents, function (parent) {
- var parentEditor = $(parent).data("snippet-editor");
- if (parentEditor) {
- for (var styleName in parentEditor.styles) {
- if (!parentEditor.styles[styleName].preventChildPropagation) {
- $ul.append($(" ", {"class": "dropdown-header o_parent_editor_header", text: parentEditor.getName()}).data("editor", parentEditor));
- break;
- }
- }
- }
- });
-
- if (!this.selector_siblings.length && !this.selector_children.length) {
- this.$overlay.find(".oe_snippet_move, .oe_snippet_clone").addClass('hidden');
- }
-
- this.$overlay.find('[data-toggle="dropdown"]').dropdown();
- },
-
- /**
- * The init_parent_options method initializes the "go to parent" button and create the editor options
- * management for them if they do not already have one.
- */
- init_parent_options: function () {
- var self = this;
- var $button = this.$overlay.find('.oe_snippet_parent');
- var $parent = data.globalSelector.closest(this.$target.parent());
- if (!$parent.data("snippet-editor")) {
- this.buildingBlock.create_snippet_editor($parent);
- }
-
- $button.toggleClass("hidden", $parent.length === 0);
- $button.off("click").on('click', function (event) {
- event.preventDefault();
- _.defer(function () {
- self.buildingBlock.make_active($parent);
- });
- });
- },
-
- on_clone: function (event) {
- event.preventDefault();
- var $clone = this.$target.clone(false);
-
- this.buildingBlock.getParent().rte.historyRecordUndo(this.$target);
-
- this.$target.after($clone);
- this.buildingBlock.call_for_all_snippets($clone, function (editor, $snippet) {
- for (var i in editor.styles) {
- editor.styles[i].on_clone($snippet, {
- isCurrent: ($snippet.is($clone)),
- });
- }
- });
- return false;
- },
-
- on_remove: function (event) {
- if (event !== undefined) {
- event.preventDefault();
- this.buildingBlock.getParent().rte.historyRecordUndo(this.$target);
- }
-
- this.on_blur();
-
- var index = _.indexOf(this.buildingBlock.snippets, this.$target.get(0));
- this.buildingBlock.call_for_all_snippets(this.$target, function (editor, $snippet) {
- for (var i in editor.styles) {
- editor.styles[i].on_remove();
- }
- });
- delete this.buildingBlock.snippets[index];
-
- var $parent = this.$target.parent();
- this.$target.find("*").andSelf().tooltip("destroy");
- this.$target.remove();
- this.$overlay.remove();
-
- var node = $parent[0];
- if (node && node.firstChild) {
- $.summernote.core.dom.removeSpace(node, node.firstChild, 0, node.lastChild, 1);
- if (!node.firstChild.tagName && node.firstChild.textContent === " ") {
- node.removeChild(node.firstChild);
- }
- }
-
- if($parent.closest(":data(\"snippet-editor\")").length) {
- while (!$parent.data("snippet-editor")) {
- var $nextParent = $parent.parent();
- if ($parent.children().length === 0 && $parent.text().trim() === "" && !$parent.hasClass("oe_structure")) {
- $parent.remove();
- }
- $parent = $nextParent;
- }
- if ($parent.children().length === 0 && $parent.text().trim() === "" && !$parent.hasClass("oe_structure")) {
- _.defer(function () {
- $parent.data("snippet-editor").on_remove();
- });
- }
- }
-
- // clean editor if they are image or table in deleted content
- $(".note-control-selection").hide();
- $('.o_table_handler').remove();
-
- this.buildingBlock.$el.trigger("snippet-removed");
-
- return false;
- },
-
- /*
- * drop_and_build_snippet
- * This method is called just after that a thumbnail is drag and dropped into a drop zone
- * (after the insertion of this.$body, if this.$body exists)
- */
- drop_and_build_snippet: function () {
- for (var i in this.styles) {
- this.styles[i].drop_and_build_snippet();
- }
- },
-
- /* on_focus
- * This method is called when the user click inside the snippet in the dom
- */
- on_focus: function () {
- this._on_focus_blur(true);
- },
-
- /* on_focus
- * This method is called when the user click outside the snippet in the dom, after a focus
- */
- on_blur: function () {
- this._on_focus_blur(false);
- },
-
- _on_focus_blur: function (focus) {
- var do_action = (focus ? _do_action_focus : _do_action_blur);
-
- // Attach own and parent options on the current overlay
- var $style_button = this.$overlay.find(".oe_options");
- var $ul = $style_button.find("ul:first");
- var $headers = $ul.find(".dropdown-header:data(editor)");
- _.each($headers, (function (el) {
- var $el = $(el);
- var styles = _.values($el.data("editor").styles);
- if ($el.data("editor") !== this) {
- styles = _.filter(styles, function (option) { return !option.preventChildPropagation; });
- }
-
- var count = 0;
- _.each(_.sortBy(styles, "__order").reverse(), function (style) {
- if (do_action(style, $el)) {
- count++;
- }
- });
- $el.toggleClass("hidden", count === 0);
- }).bind(this));
-
- // Activate the overlay
- $style_button.toggleClass("hidden", $ul.children(":not(.o_main_header):not(.divider):not(.hidden)").length === 0);
- this.$overlay.toggleClass("oe_active", !!focus);
-
- function _do_action_focus(style, $dest) {
- style.$el.insertAfter($dest);
- style.on_focus();
- return (style.$el.length > 0);
- }
- function _do_action_blur(style, $dest) {
- style.$el.detach();
- style.on_blur();
- return false;
- }
- },
-});
-
-/**
- * Add the ability on the main editor class to instantiate the snippet editor component
- */
-editor.Class.include({
- init: function () {
- var self = this;
- var res = this._super.apply(this, arguments);
- var $editable = this.rte.editable();
- this.buildingBlock = new data.Class(this, $editable);
- this.buildingBlock.on("snippets:ready", this, function () {
- self.trigger("snippets:ready");
- });
- return res;
- },
- start: function () {
- this.buildingBlock.insertAfter(this.$el);
- this.rte.editable().find("*").off('mousedown mouseup click');
- return this._super.apply(this, arguments);
- },
- save: function () {
- this.buildingBlock.clean_for_save();
- return this._super.apply(this, arguments);
- },
-});
-
-/**
- * Add the ability the restart the animations
- */
-editor.Class.include({
- start: function () {
- return this._super.apply(this, arguments).then(function () {
- animation.start(true);
- });
- },
-});
-
-return data;
-});
diff --git a/addons/web_editor/static/src/js/snippets.options.js b/addons/web_editor/static/src/js/snippets.options.js
deleted file mode 100644
index 68301312cd5..00000000000
--- a/addons/web_editor/static/src/js/snippets.options.js
+++ /dev/null
@@ -1,993 +0,0 @@
-odoo.define('web_editor.snippets.options', function (require) {
- 'use strict';
-
- var ajax = require('web.ajax');
- var Class = require('web.Class');
- var core = require('web.core');
- var Dialog = require('web.Dialog');
- var base = require('web_editor.base');
- var editor = require('web_editor.editor');
- var widget = require('web_editor.widget');
-
- var qweb = core.qweb;
- var _t = core._t;
-
- /**
- * The SnippetOption class handles one option for one snippet. The registry returned by
- * this module contains the names of the specialized SnippetOption which can be reference
- * thanks to the data-js key of the full options web_editor template.
- */
- var SnippetOption = Class.extend({
- init: function (BuildingBlock, editor, $target, option_id) {
- this.buildingBlock = BuildingBlock;
- this.editor = editor;
- this.$target = $target;
-
- this.option = option_id;
- var option = this.buildingBlock.templateOptions[option_id];
- this.$el = option.$el.children("li").clone();
- this.data = option.$el.data();
-
- this.$overlay = this.$target.data('overlay') || $();
- },
-
- // helper for this.$target.find
- $: function () {
- return this.$target.find.apply(this.$target, arguments);
- },
-
- _bind_li_menu: function () {
- this.$el.filter("li:hasData").find('a:first')
- .off('mouseenter click')
- .on('mouseenter click', _.bind(_mouse, this));
-
- this.$el
- .off('mouseenter click', "li:hasData a")
- .on('mouseenter click', "li:hasData a", _.bind(_mouse, this));
-
- this.$el.off('mouseleave')
- .on('mouseleave', _.bind(this.reset, this));
-
- this.$el.off('mouseleave', "ul")
- .on('mouseleave', "ul", _.bind(this.reset, this));
-
- this.methodsToReset = [];
-
- function _mouse(event) {
- var $next = $(event.currentTarget).parent();
-
- // triggers preview or apply methods if a menu item has been clicked
- this.select(event.type === "click" ? "click" : "over", $next);
- if (event.type === 'click') {
- this.set_active();
- this.$target.trigger("snippet-option-change", [this]);
- } else {
- this.$target.trigger("snippet-option-preview", [this]);
- }
- }
- },
-
- /**
- * The set_active method tweaks the option dropdown to show the selected value
- * according to the state of the $target the option customizes.
- */
- set_active: function () {
- var self = this;
- this.$el.find('[data-toggle_class], [data-select_class]')
- .addBack('[data-toggle_class], [data-select_class]')
- .removeClass("active")
- .filter(function () {
- var $elem = $(this);
- var className = $elem.data("toggle_class") || $elem.data("select_class");
- return self.$target.hasClass(className);
- })
- .addClass("active");
- },
-
- start: function () {
- this.set_active();
- this.$target.on('snippet-option-reset', _.bind(this.set_active, this));
- this._bind_li_menu();
- },
-
- on_focus : function () {
- this._bind_li_menu();
- },
-
- on_blur : function () {},
- on_clone: function ($clone) {},
- on_move: function () {},
- on_remove: function () {},
-
- drop_and_build_snippet: function () {},
-
- reset: function () {
- var self = this;
- var lis = this.$el.find("li.active").addBack('.active').get();
- _.each(lis.reverse(), function (li) {
- var $li = $(li);
- for (var k in self.methodsToReset) {
- var method = self.methodsToReset[k];
- if ($li.closest('[data-' + method + ']').length) {
- delete self.methodsToReset[k];
- }
- }
- self.select("reset", $li);
- });
-
- for (var k in this.methodsToReset) {
- var method = this.methodsToReset[k];
- if (method) {
- this[method]("reset", null);
- }
- }
- this.methodsToReset = [];
- this.$target.trigger("snippet-option-reset", [this]);
- },
-
- /**
- * Handle action type on a particuler $li (sub)option of the option.
- * @param type action type to handle ("click", "over", "reset")
- * @param $li the suboption DOM element which is concerned by the action
- */
- select: function (type, $li) {
- // (sub)options can say they respond to only one action type
- if ($li.data('only') && type !== $li.data('only')) {
- return;
- }
- // If the type is click, the user selected the option for good (so reset and record action)
- if (type === "click") {
- this.reset();
- this.buildingBlock.getParent().rte.historyRecordUndo(this.$target);
- }
-
- // Search for methods (data-...) (i.e. data-toggle_class) on the selected (sub)option and its parents
- var el = $li[0];
- var methods = [];
- do {
- var m = _.omit(el.dataset, ["oeId", "oeModel", "oeField", "oeXpath", "oeSourceId", "only"]);
- if (_.keys(m).length) {
- methods.push([el, m]);
- }
- el = el.parentNode;
- } while (this.$el.parent().has(el).length);
-
- // Call the found method in the right order (parents -> child) and save them for later reset
- var self = this;
- _.each(methods.reverse(), function (data) {
- var $el = $(data[0]);
- var methods = data[1];
-
- for (var k in methods) {
- if (self[k]) {
- if (type !== "reset" && self.methodsToReset.indexOf(k) === -1) {
- self.methodsToReset.push(k);
- }
- self[k](type, methods[k], $el);
- } else {
- console.error("'" + self.option + "' snippet option have not method '" + k + "'");
- }
- }
- });
- },
-
- /**
- * Default option methods toggle_class and selected_class handle the two behavior of having a list
- * of option classes to toggle or not and having a list of option classes where only one must be chosen.
- */
- toggle_class: function (type, value, $li) {
- var $lis = this.$el.find("[data-toggle_class]").addBack("[data-toggle_class]");
-
- function map($lis) {
- return $lis.map(function () {return $(this).data("toggle_class");}).get().join(" ");
- }
- var classes = map($lis);
- var active_classes = map($lis.filter('.active, :has(.active)'));
-
- this.$target.removeClass(classes).addClass(active_classes);
- if (type !== 'reset') {
- this.$target.toggleClass(value);
- }
- },
- select_class: function (type, value, $li) {
- var $lis = this.$el.find("[data-select_class]").addBack("[data-select_class]");
-
- var classes = $lis.map(function () {return $(this).data("select_class");}).get().join(" ");
-
- this.$target.removeClass(classes);
- if (value) {
- this.$target.addClass(value);
- }
- },
-
- /**
- * Helper method to evaluate the value expression directly as a function
- */
- eval: function (type, value, $li) {
- var fn = new Function("node", "type", "value", "$li", value);
- fn.call(this, this, type, value, $li);
- },
-
- clean_for_save: editor.dummy
- });
- var registry = {};
-
- /* ----- default options ---- */
-
- registry.marginAndResize = SnippetOption.extend({
- preventChildPropagation: true,
-
- start: function () {
- var self = this;
- this._super();
-
- var resize_values = this.getSize();
- if (resize_values.n) this.$overlay.find(".oe_handle.n").removeClass("readonly");
- if (resize_values.s) this.$overlay.find(".oe_handle.s").removeClass("readonly");
- if (resize_values.e) this.$overlay.find(".oe_handle.e").removeClass("readonly");
- if (resize_values.w) this.$overlay.find(".oe_handle.w").removeClass("readonly");
- if (resize_values.size) this.$overlay.find(".oe_handle.size").removeClass("readonly");
-
- var $auto_size = this.$overlay.find(".oe_handle.size .auto_size");
- var $fixed_size = this.$overlay.find(".oe_handle.size .size");
-
- var auto_sized = isNaN(parseInt(self.$target.prop("style").height));
- $fixed_size.toggleClass("active", !auto_sized);
- $auto_size.toggleClass("active", auto_sized);
-
- $fixed_size.add(this.$overlay.find(".oe_handle:not(.size)")).on('mousedown', function (event) {
- event.preventDefault();
-
- var $handle = $(this);
-
- var resize_values = self.getSize();
- var compass = false;
- var XY = false;
- if ($handle.hasClass('n')) {
- compass = 'n';
- XY = 'Y';
- }
- else if ($handle.hasClass('s')) {
- compass = 's';
- XY = 'Y';
- }
- else if ($handle.hasClass('e')) {
- compass = 'e';
- XY = 'X';
- }
- else if ($handle.hasClass('w')) {
- compass = 'w';
- XY = 'X';
- }
- else if ($handle.hasClass('size')) {
- compass = 'size';
- XY = 'Y';
- }
-
- var resize = resize_values[compass];
- if (!resize) return;
-
-
- if (compass === 'size') {
- var offset = self.$target.offset().top;
- if (self.$target.css("background").match(/rgba\(0, 0, 0, 0\)/)) {
- self.$target.addClass("resize_editor_busy");
- }
- } else {
- var xy = event['page'+XY];
- var current = resize[2] || 0;
- var margin_dir = {s:'bottom', n: 'top', w: 'left', e: 'right'}[compass];
- var real_margin = parseInt(self.$target.css('margin-'+margin_dir));
- _.each(resize[0], function (val, key) {
- if (self.$target.hasClass(val)) {
- current = key;
- } else if (resize[1][key] === real_margin) {
- current = key;
- }
- });
- var begin = current;
- var beginClass = self.$target.attr("class");
- var regClass = new RegExp("\\s*" + resize[0][begin].replace(/[-]*[0-9]+/, '[-]*[0-9]+'), 'g');
- }
-
- self.buildingBlock.editor_busy = true;
-
- var cursor = $handle.css("cursor")+'-important';
- var $body = $(document.body);
- $body.addClass(cursor);
-
- var body_mousemove = function (event) {
- event.preventDefault();
- if (compass === 'size') {
- var dy = event.pageY-offset;
- dy = dy - dy%resize;
- if (dy <= 0) dy = resize;
- self.$target.css("height", dy+"px");
- self.$target.css("overflow", "hidden");
- self.on_resize(compass, null, dy);
- self.buildingBlock.cover_target(self.$overlay, self.$target);
- return;
- }
- var dd = event['page'+XY] - xy + resize[1][begin];
- var next = current+1 === resize[1].length ? current : (current+1);
- var prev = current ? (current-1) : 0;
-
- var change = false;
- if (dd > (2*resize[1][next] + resize[1][current])/3) {
- self.$target.attr("class", (self.$target.attr("class")||'').replace(regClass, ''));
- self.$target.addClass(resize[0][next]);
- current = next;
- change = true;
- }
- if (prev !== current && dd < (2*resize[1][prev] + resize[1][current])/3) {
- self.$target.attr("class", (self.$target.attr("class")||'').replace(regClass, ''));
- self.$target.addClass(resize[0][prev]);
- current = prev;
- change = true;
- }
-
- if (change) {
- self.on_resize(compass, beginClass, current);
- self.buildingBlock.cover_target(self.$overlay, self.$target);
- self.adapt_margin_preview_size();
- $handle.addClass('oe_handle_change');
- }
- };
-
- var body_mouseup = function () {
- $body.unbind('mousemove', body_mousemove);
- $body.unbind('mouseup', body_mouseup);
- $body.removeClass(cursor);
- setTimeout(function () {
- self.buildingBlock.editor_busy = false;
- if (begin !== current) {
- self.buildingBlock.getParent().rte.historyRecordUndo(self.$target, 'resize_' + XY);
- }
- },0);
- self.$target.removeClass("resize_editor_busy");
- $handle.removeClass('oe_handle_change');
-
- if (compass === "size") {
- $fixed_size.addClass("active");
- $auto_size.removeClass("active");
- } else {
- self._highlight_margins_preview(300);
- }
- };
- $body.mousemove(body_mousemove);
- $body.mouseup(body_mouseup);
- });
- $auto_size.on('click', function () {
- self.$target.css("height", "");
- self.$target.css("overflow", "");
- self.buildingBlock.getParent().rte.historyRecordUndo(self.$target, 'resize_Y');
- self.buildingBlock.cover_target(self.$overlay, self.$target);
-
- $fixed_size.removeClass("active");
- $auto_size.addClass("active");
-
- return false;
- });
- },
- getSize: function () {
- this.grid = {};
- return this.grid;
- },
-
- on_focus : function () {
- this._super();
- this.change_cursor();
- this.adapt_margin_preview_size();
- },
-
- change_cursor : function () {
- var _class = this.$target.attr("class") || "";
- var $handle_s = this.$overlay.find('.oe_handle.s');
- var $handle_n = this.$overlay.find('.oe_handle.n');
- var $handle_w = this.$overlay.find('.oe_handle.w');
-
- var col = _class.match(/col-md-([0-9-]+)/i);
- col = col ? +col[1] : 0;
-
- var offset = _class.match(/col-md-offset-([0-9-]+)/i);
- offset = offset ? +offset[1] : 0;
-
- var overlay_class = this.$overlay.attr("class").replace(/(^|\s+)block-[^\s]*/gi, '');
- if (col+offset >= 12) overlay_class+= " block-e-right";
- if (col === 1) overlay_class+= " block-w-right block-e-left";
- if (offset === 0) overlay_class+= " block-w-left";
- $handle_w.toggleClass('oe_handle_centered', offset > 0).toggleClass('o_handle_edited', offset >= 1 );
-
- var mb = _class.match(/mb([0-9-]+)/i);
- mb = mb ? +mb[1] : parseInt(this.$target.css('margin-bottom'));
- if (mb >= 128) overlay_class+= " block-s-bottom";
- else if (!mb) overlay_class+= " block-s-top";
- $handle_s.toggleClass('oe_handle_centered', mb >= 32).toggleClass('o_handle_edited', mb > 0 );
-
- var mt = _class.match(/mt([0-9-]+)/i);
- mt = mt ? +mt[1] : parseInt(this.$target.css('margin-top'));
- if (mt >= 128) overlay_class+= " block-n-top";
- else if (!mt) overlay_class+= " block-n-bottom";
- $handle_n.toggleClass('oe_handle_centered', mt >= 32).toggleClass('o_handle_edited', mt > 0 );
-
- this.$overlay.attr("class", overlay_class);
- },
-
- adapt_margin_preview_size : function () {
- var self = this,
- ml = this.$target.css('margin-left');
- _.each(this.$overlay.find(".oe_handle.n, .oe_handle.s"), function (handle) {
- var $handle = $(handle);
- var direction = $handle.hasClass('n') ? 'top': 'bottom';
- $handle.height(self.$target.css('margin-' + direction ));
- });
- this.$overlay.find(".oe_handle.w").css({
- width: ml,
- left: '-' + ml
- });
- },
-
- _highlight_margins_preview: function (timing) {
- var handlers = this.$overlay.find(".oe_handle.n, .oe_handle.s, .oe_handle.w");
- handlers.addClass('oe_handle_active').delay(timing).queue(function(){
- handlers.removeClass('oe_handle_active').dequeue();
- });
- },
-
- /* on_resize
- * called when the box is resizing and the class change, before the cover_target
- * @compass: resize direction : 'n', 's', 'e', 'w'
- * @beginClass: attributes class at the begin
- * @current: curent increment in this.grid
- */
- on_resize: function () {
- this.change_cursor();
- }
- });
-
- registry["margin-y"] = registry.marginAndResize.extend({
- preventChildPropagation: true,
-
- getSize: function () {
- this.grid = this._super();
- var grid = [0,4,8,16,32,48,64,92,128];
- this.grid = {
- // list of class (Array), grid (Array), default value (INT)
- n: [_.map(grid, function (v) {return 'mt'+v;}), grid],
- s: [_.map(grid, function (v) {return 'mb'+v;}), grid],
- // INT if the user can resize the snippet (resizing per INT px)
- size: null
- };
- return this.grid;
- },
- });
-
- registry.resize = registry.marginAndResize.extend({
- preventChildPropagation: true,
-
- getSize: function () {
- this.grid = this._super();
- this.grid.size = 8;
- return this.grid;
- },
- });
-
- /**
- * The colorpicker option is designed to change the background color class of a snippet. This class change the
- * default background color and text color of the snippet content.
- */
- registry.colorpicker = SnippetOption.extend({
- start: function () {
- var self = this;
- var res = this._super.apply(this, arguments);
-
- if (!this.$el.find('.colorpicker').length) {
- var $pt = $(qweb.render('web_editor.snippet.option.colorpicker'));
- var $clpicker = $(qweb.render('web_editor.colorpicker'));
-
- // Retrieve excluded palettes list
- var excluded = [];
- if (this.data.paletteExclude) {
- excluded = this.data.paletteExclude.replace(/ /g, '').split(',');
- }
- // Apply a custom title if specified
- if (this.data.paletteTitle) {
- $pt.find('.note-palette-title').text(this.data.paletteTitle);
- }
-
- var $toggles = $pt.find('.o_colorpicker_section_menu');
- var $tabs = $pt.find('.o_colorpicker_section_tabs');
-
- // Remove excluded palettes
- _.each(excluded, function (exc) {
- $clpicker.find('[data-name="' + exc + '"]').remove();
- });
-
- var $sections = $clpicker.find('.o_colorpicker_section');
-
- if ($sections.length > 1) { // Multi-palette layout
- $sections.each(function () {
- var $section = $(this);
- var id = 'o_palette_' + $section.data('name') + _.uniqueId();
-
- var $li = $(' ')
- .append($(' ', {href: '#' + id})
- .append($(' ', {'class': $section.data('iconClass') || '', html: $section.data('iconContent') || ''})));
- $toggles.append($li);
-
- $tabs.append($section.addClass('tab-pane').attr('id', id));
- });
-
- // If a default palette is defined, make it active
- if (this.data.paletteDefault) {
- var $palette_def = $tabs.find('div[data-name="' + self.data.paletteDefault + '"]');
- var pos = $tabs.find('> div').index($palette_def);
-
- $toggles.children('li').eq(pos).addClass('active');
- $palette_def.addClass('active');
- } else {
- $toggles.find('li').first().addClass('active');
- $tabs.find('div').first().addClass('active');
- }
-
- $toggles.on('click mouseover', '> li > a', function (e) {
- e.preventDefault();
- e.stopPropagation();
- $(this).tab('show');
- });
- } else if ($sections.length === 1) { // Unique palette layout
- $tabs.addClass('o_unique_palette').append($sections.addClass('tab-pane active'));
- } else {
- $toggles.parent().empty().append($clpicker);
- }
-
- this.$el.find('li').append($pt);
- }
- if (this.$el.data('area')) {
- this.$target = this.$target.find(this.$el.data('area'));
- this.$el.removeData('area').removeAttr('area');
- }
-
- var classes = [];
- this.$el.find(".colorpicker button").each(function () {
- var $color = $(this);
- if (!$color.data("color")) {
- return;
- }
-
- var className = 'bg-' + $color.data('color');
- $color.addClass(className);
- if (self.$target.hasClass(className)) {
- self.color = className;
- $color.addClass("selected");
- }
- classes.push(className);
- });
- this.classes = classes.join(" ");
-
- this.bind_events();
- return res;
- },
- bind_events: function () {
- var self = this;
- var $colors = this.$el.find(".colorpicker button");
- $colors
- .mouseenter(function (e) {
- self.$target.removeClass(self.classes);
- var color = $(this).data("color");
- if (color) {
- self.$target.addClass('bg-' + color);
- }
- self.$target.trigger("background-color-event", e.type);
- })
- .mouseleave(function (e) {
- self.$target.removeClass(self.classes);
- var $selected = $colors.filter(".selected");
- var color = $selected.length && $selected.data("color");
- if (color) {
- self.$target.addClass('bg-' + color);
- }
- self.$target.trigger("background-color-event", e.type);
- })
- .click(function (e) {
- $colors.removeClass("selected");
- $(this).addClass("selected");
- self.$target.closest(".o_editable").trigger("content_changed");
- self.$target.trigger("background-color-event", e.type);
- });
-
- this.$el.find('.note-color-reset').on('click', function () {
- self.$target.removeClass(self.classes);
- $colors.removeClass("selected");
- });
- }
- });
-
- /**
- * The background option is designed to change the background image of a snippet.
- */
- registry.background = SnippetOption.extend({
- start: function () {
- var res = this._super.apply(this, arguments);
- this.bind_bg_events();
- return res;
- },
-
- bind_bg_events: function () {
- this.$target.off(".background-option")
- .on("background-color-event.background-option", (function (e, type) {
- e.stopPropagation();
- if (e.currentTarget !== e.target) return;
- this.$el.find("li[data-background=''] > a").trigger(type);
- }).bind(this));
- },
- background: function (type, value, $li) {
- if (value && value.length) {
- this.$target.css("background-image", "url(" + value + ")");
- this.$target.addClass("oe_img_bg");
- } else {
- this.$target.css("background-image", "");
- this.$target.removeClass("oe_img_bg oe_custom_bg");
- }
- },
- select_class: function (type, value, $li) {
- this.background(type, '', $li);
- this._super(type, value ? (value + " oe_img_bg") : value, $li);
- },
- choose_image: function (type, value, $li) {
- if(type !== "click") {
- return;
- }
-
- // Put fake image in the DOM, edit it and use it as background-image
- var $image = $(" ", {"class": "hidden", "src": value}).appendTo(this.$target);
-
- var _editor = new widget.MediaDialog(null, {}, null, $image[0]).open();
- _editor.$('[href="#editor-media-video"], [href="#editor-media-icon"]').addClass('hidden');
-
- _editor.on('save', this, function () {
- var value = $image.attr("src");
- this.background(type, value, $li);
- this.$target.addClass("oe_custom_bg");
- this.set_active();
- this.$target.trigger("snippet-option-change", [this]);
- });
- _editor.on('closed', this, function () {
- $image.remove();
- });
- },
- set_active: function () {
- this._super.apply(this, arguments);
-
- var src = this.$target.css("background-image").replace(/url\(['"]*|['"]*\)|^none$/g, "");
- if (this.$target.hasClass('oe_custom_bg')) {
- this.$el.find('li[data-choose_image]').data("background", src).attr("data-background", src);
- }
-
- this.$el.find('li[data-background]')
- .removeClass("active")
- .filter(function () {
- var bgOption = $(this).data("background");
- return (bgOption === "" && src === "" || bgOption !== "" && src.indexOf(bgOption) >= 0);
- })
- .addClass("active");
- }
- });
-
- /**
- * The background_position option is designed to change the background image position of a snippet.
- */
- registry.background_position = SnippetOption.extend({
- start: function () {
- this._super.apply(this, arguments);
- this.on_focus();
- var self = this;
- this.$target.on("snippet-option-change", function () {
- self.on_focus();
- });
- },
- on_focus: function () {
- this._super.apply(this, arguments);
- this.$el.toggleClass('hidden', this.$target.css('background-image') === 'none');
- },
- background_position: function (type, value, $li) {
- if (type != 'click') { return; }
- var self = this;
-
- this.previous_state = [this.$target.attr('class'), this.$target.css('background-size'), this.$target.css('background-position')];
-
- this.bg_pos = self.$target.css('background-position').split(' ');
- this.bg_siz = self.$target.css('background-size').split(' ');
-
- this.modal = new Dialog(null, {
- title: _t("Background Image Sizing"),
- $content: $(qweb.render('web_editor.dialog.background_position')),
- buttons: [
- {text: _t("Ok"), classes: "btn-primary", close: true, click: _.bind(this.save, this)},
- {text: _t("Discard"), close: true, click: _.bind(this.discard, this)},
- ],
- }).open();
-
- this.modal.opened().then(function () {
- // Fetch data form $target
- var value = ((self.$target.hasClass('o_bg_img_opt_contain'))? 'contain' : ((self.$target.hasClass('o_bg_img_opt_custom'))? 'custom' : 'cover'));
- self.modal.$("> label > input[value=" + value + "]").prop('checked', true);
-
- if(self.$target.hasClass("o_bg_img_opt_repeat")) {
- self.modal.$("#o_bg_img_opt_contain_repeat").prop('checked', true);
- self.modal.$("#o_bg_img_opt_custom_repeat").val('o_bg_img_opt_repeat');
- } else if (self.$target.hasClass("o_bg_img_opt_repeat_x")) {
- self.modal.$("#o_bg_img_opt_custom_repeat").val('o_bg_img_opt_repeat_x');
- } else if (self.$target.hasClass("o_bg_img_opt_repeat_y")) {
- self.modal.$("#o_bg_img_opt_custom_repeat").val('o_bg_img_opt_repeat_y');
- }
-
- if(self.bg_pos.length > 1) {
- self.bg_pos = {
- x: self.bg_pos[0],
- y: self.bg_pos[1],
- };
- self.modal.$("#o_bg_img_opt_custom_pos_x").val(self.bg_pos.x.replace('%', ''));
- self.modal.$("#o_bg_img_opt_custom_pos_y").val(self.bg_pos.y.replace('%', ''));
- }
- if(self.bg_siz.length > 1) {
- self.modal.$("#o_bg_img_opt_custom_size_x").val(self.bg_siz[0].replace('%', ''));
- self.modal.$("#o_bg_img_opt_custom_size_y").val(self.bg_siz[1].replace('%', ''));
- }
-
- // Focus Point
- self.$focus = self.modal.$(".o_focus_point");
- self.update_pos_information();
-
- var img_url = /\(['"]?([^'"]+)['"]?\)/g.exec(self.$target.css('background-image'));
- img_url = (img_url && img_url[1]) || '';
- var $img = $(' ', {'class': 'img img-responsive', src: img_url});
- $img.on('load', function () {
- self.bind_img_events($img);
- });
- $img.prependTo(self.modal.$(".o_bg_img_opt_object"));
-
- // Bind events
- self.modal.$el.on('change', '> label > input', function (e) {
- self.modal.$('> .o_bg_img_opt').addClass('o_hidden')
- .filter("[data-value=" + e.target.value + "]")
- .removeClass('o_hidden');
- });
- self.modal.$el.on('change', 'input, select', function (e) {
- self.save();
- });
- self.modal.$("> label > input:checked").trigger('change');
- });
- },
- bind_img_events: function ($img) {
- var self = this;
-
- var mousedown = false;
- $img.on('mousedown', function (e) {
- mousedown = true;
- });
- $img.on('mousemove', function (e) {
- if (mousedown) {
- _update(e);
- }
- });
- $img.on('mouseup', function (e) {
- self.$focus.addClass('o_with_transition');
- _update(e);
- setTimeout(function () {
- self.$focus.removeClass('o_with_transition');
- }, 200);
- mousedown = false;
- });
-
- function _update(e) {
- var posX = e.pageX - $(e.target).offset().left;
- var posY = e.pageY - $(e.target).offset().top;
- self.bg_pos = {
- x: clip_value(posX/$img.width()*100).toFixed(2) + '%',
- y: clip_value(posY/$img.height()*100).toFixed(2) + '%',
- };
- self.update_pos_information();
- self.save();
- }
-
- function clip_value(value) {
- return Math.max(0, Math.min(value, 100));
- }
- },
- update_pos_information: function () {
- this.modal.$(".o_bg_img_opt_ui_info .o_x").text(this.bg_pos.x);
- this.modal.$(".o_bg_img_opt_ui_info .o_y").text(this.bg_pos.y);
- this.$focus.css({
- left: this.bg_pos.x,
- top: this.bg_pos.y,
- });
- },
- save: function () {
- this.clean();
-
- var bg_img_size = this.modal.$('> :not(label):not(.o_hidden)').data('value') || 'cover';
- switch (bg_img_size) {
- case "cover":
- this.$target.css('background-position', this.bg_pos.x + ' ' + this.bg_pos.y);
- break;
- case "contain":
- this.$target.addClass('o_bg_img_opt_contain');
- this.$target.toggleClass('o_bg_img_opt_repeat', this.modal.$("#o_bg_img_opt_contain_repeat").prop("checked"));
- break;
- case "custom":
- this.$target.addClass('o_bg_img_opt_custom');
- var sizeX = this.modal.$("#o_bg_img_opt_custom_size_x").val();
- var sizeY = this.modal.$("#o_bg_img_opt_custom_size_y").val();
- var posX = this.modal.$("#o_bg_img_opt_custom_pos_x").val();
- var posY = this.modal.$("#o_bg_img_opt_custom_pos_y").val();
- this.$target.addClass(this.modal.$("#o_bg_img_opt_custom_repeat").val())
- .css({
- 'background-size': ((sizeX)? sizeX + '%' : 'auto') + " " + ((sizeY)? sizeY + '%' : 'auto'),
- 'background-position': ((posX)? posX + '%' : 'auto') + " " + ((posY)? posY + '%' : 'auto'),
- });
- break;
- }
- },
- discard: function () {
- this.clean();
- if (this.previous_state) {
- this.$target.addClass(this.previous_state[0]).css({
- 'background-size': this.previous_state[1],
- 'background-position': this.previous_state[2],
- });
- }
- },
- clean: function () {
- this.$target.removeClass('o_bg_img_opt_contain o_bg_img_opt_custom o_bg_img_opt_repeat o_bg_img_opt_repeat_x o_bg_img_opt_repeat_y')
- .css({
- 'background-size': '',
- 'background-position': '',
- });
- },
- });
-
- /* t-field options */
-
- registry.many2one = SnippetOption.extend({
- start: function () {
- var self = this;
-
- this.Model = this.$target.data('oe-many2one-model');
- this.ID = +this.$target.data('oe-many2one-id');
-
- // create search button and bind search bar
- this.$btn = $(qweb.render("web_editor.many2one.button"))
- .insertAfter(this.$overlay.find('.oe_options'));
-
- this.$ul = this.$btn.find("ul");
- this.$search = this.$ul.find('li:first');
- this.$search.find('input').on('mousedown click mouseup keyup keydown', function (e) {
- e.stopPropagation();
- });
-
- // move menu item
- setTimeout(function () {
- if (self.$overlay.find('.oe_options').hasClass('hidden')) {
- self.$btn.css('height', '0').find('> a').addClass('hidden');
- self.$ul.show().css({
- 'top': '-24px', 'margin': '0', 'padding': '2px 0', 'position': 'relative'
- });
- } else {
- self.$btn.find('a').on('click', function (e) {
- self.clear();
- });
- }
- },0);
-
- // bind search input
- this.$search.find('input')
- .focus()
- .on('keyup', function (e) {
- self.find_existing($(this).val());
- });
-
- // bind result
- this.$ul.on('click', "li:not(:first) a", function (e) {
- self.select_record(this);
- });
- },
-
- on_focus: function () {
- this.$target.attr('contentEditable', 'false');
- this.clear();
- this._super.apply(this, arguments);
- },
-
- clear: function () {
- var self = this;
- this.$search.siblings().remove();
- self.$search.find('input').val("");
- setTimeout(function () {
- self.$search.find('input').focus();
- },0);
- },
-
- find_existing: function (name) {
- var self = this;
- var domain = [];
- if (!name || !name.length) {
- self.$search.siblings().remove();
- return;
- }
- if (isNaN(+name)) {
- if (this.Model !== "res.partner") {
- domain.push(['name', 'ilike', name]);
- } else {
- domain.push('|', ['name', 'ilike', name], ['email', 'ilike', name]);
- }
- } else {
- domain.push(['id', '=', name]);
- }
-
- ajax.jsonRpc('/web/dataset/call_kw', 'call', {
- model: this.Model,
- method: 'search_read',
- args: [domain, this.Model === "res.partner" ? ['name', 'display_name', 'city', 'country_id'] : ['name', 'display_name']],
- kwargs: {
- order: 'name DESC',
- limit: 5,
- context: base.get_context(),
- }
- }).then(function (result) {
- self.$search.siblings().remove();
- self.$search.after(qweb.render("web_editor.many2one.search",{contacts:result}));
- });
- },
-
- get_contact_rendering: function (options) {
- return ajax.jsonRpc('/web/dataset/call_kw', 'call', {
- model: 'ir.qweb.field.contact',
- method: 'get_record_to_html',
- args: [[this.ID]],
- kwargs: {
- options: options,
- context: base.get_context(),
- }
- });
- },
-
- select_record: function (li) {
- var self = this;
-
- this.ID = +$(li).data("id");
- this.$target.attr('data-oe-many2one-id', this.ID).data('oe-many2one-id', this.ID);
-
- this.buildingBlock.getParent().rte.historyRecordUndo(this.$target);
- this.$target.trigger('content_changed');
-
- if (self.$target.data('oe-type') === "contact") {
- $('[data-oe-contact-options]')
- .filter('[data-oe-model="'+self.$target.data('oe-model')+'"]')
- .filter('[data-oe-id="'+self.$target.data('oe-id')+'"]')
- .filter('[data-oe-field="'+self.$target.data('oe-field')+'"]')
- .filter('[data-oe-contact-options!="'+self.$target.data('oe-contact-options')+'"]')
- .add(self.$target)
- .attr('data-oe-many2one-id', self.ID).data('oe-many2one-id', self.ID)
- .each(function () {
- var $node = $(this);
- self.get_contact_rendering($node.data('oe-contact-options'))
- .then(function (html) {
- $node.html(html);
- });
- });
- } else {
- self.$target.html($(li).data("name"));
- }
-
- setTimeout(function () {
- self.buildingBlock.make_active(false);
- },0);
- }
- });
-
- return {
- Class: SnippetOption,
- registry: registry,
- };
-});
diff --git a/addons/web_editor/static/src/js/summernote.js b/addons/web_editor/static/src/js/summernote.js
deleted file mode 100644
index c2e2ea54dca..00000000000
--- a/addons/web_editor/static/src/js/summernote.js
+++ /dev/null
@@ -1,2383 +0,0 @@
-odoo.define('web_editor.summernote', function (require) {
-'use strict';
-
-var core = require('web.core');
-require('summernote/summernote'); // wait that summernote is loaded
-
-var _t = core._t;
-
-//////////////////////////////////////////////////////////////////////////////////////////////////////////
-/* Summernote Lib (neek hack to make accessible: method and object) */
-
-var agent = $.summernote.core.agent;
-var dom = $.summernote.core.dom;
-var range = $.summernote.core.range;
-var list = $.summernote.core.list;
-var key = $.summernote.core.key;
-var eventHandler = $.summernote.eventHandler;
-var editor = eventHandler.modules.editor;
-var renderer = $.summernote.renderer;
-var options = $.summernote.options;
-
-//////////////////////////////////////////////////////////////////////////////////////////////////////////
-/* Add method to Summernote*/
-
-dom.hasContentAfter = function (node) {
- var next;
- if(dom.isEditable(node)) return;
- while (node.nextSibling) {
- next = node.nextSibling;
- if (next.tagName || dom.isVisibleText(next) || dom.isBR(next)) return next;
- node = next;
- }
-};
-dom.hasContentBefore = function (node) {
- var prev;
- if(dom.isEditable(node)) return;
- while (node.previousSibling) {
- prev = node.previousSibling;
- if (prev.tagName || dom.isVisibleText(prev) || dom.isBR(prev)) return prev;
- node = prev;
- }
-};
-dom.ancestorHaveNextSibling = function (node, pred) {
- pred = pred || dom.hasContentAfter;
- while (!dom.isEditable(node) && (!node.nextSibling || !pred(node))) { node = node.parentNode; }
- return node;
-};
-dom.ancestorHavePreviousSibling = function (node, pred) {
- pred = pred || dom.hasContentBefore;
- while (!dom.isEditable(node) && (!node.previousSibling || !pred(node))) { node = node.parentNode; }
- return node;
-};
-dom.nextElementSibling = function (node) {
- while (node) {
- node = node.nextSibling;
- if (node && node.tagName) {
- break;
- }
- }
- return node;
-};
-dom.previousElementSibling = function (node) {
- while (node) {
- node = node.previousSibling;
- if (node && node.tagName) {
- break;
- }
- }
- return node;
-};
-dom.lastChild = function (node) {
- while (node.lastChild) { node = node.lastChild; }
- return node;
-};
-dom.firstChild = function (node) {
- while (node.firstChild) { node = node.firstChild; }
- return node;
-};
-dom.lastElementChild = function (node, deep) {
- node = deep ? dom.lastChild(node) : node.lastChild;
- return !node || node.tagName ? node : dom.previousElementSibling(node);
-};
-dom.firstElementChild = function (node, deep) {
- node = deep ? dom.firstChild(node) : node.firstChild;
- return !node || node.tagName ? node : dom.nextElementSibling(node);
-};
-dom.isEqual = function (prev, cur) {
- if (prev.tagName !== cur.tagName) {
- return false;
- }
- if ((prev.attributes ? prev.attributes.length : 0) !== (cur.attributes ? cur.attributes.length : 0)) {
- return false;
- }
-
- function strip(text) {
- return text && text.replace(/^\s+|\s+$/g, '').replace(/\s+/g, ' ');
- }
- var att, att2;
- loop_prev:
- for(var a in prev.attributes) {
- att = prev.attributes[a];
- for(var b in cur.attributes) {
- att2 = cur.attributes[b];
- if (att.name === att2.name) {
- if (strip(att.value) != strip(att2.value)) return false;
- continue loop_prev;
- }
- }
- return false;
- }
- return true;
-};
-/**
- * Checks that the node only has a @style, not e.g. @class or whatever
- */
-dom.hasOnlyStyle = function (node) {
- for (var i = 0; i < node.attributes.length; i++) {
- var attr = node.attributes[i];
- if (attr.attributeName !== 'style') {
- return false;
- }
- }
- return true;
-};
-dom.hasProgrammaticStyle = function (node) {
- var styles = ["float", "display", "position", "top", "left", "right", "bottom"];
- for (var i = 0; i < node.style.length; i++) {
- var style = node.style[i];
- if (styles.indexOf(style) !== -1) {
- return true;
- }
- }
- return false;
-};
-dom.mergeFilter = function (prev, cur, parent) {
- // merge text nodes
- if (prev && (dom.isText(prev) || (['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'LI', 'P'].indexOf(prev.tagName) !== -1 && prev !== cur.parentNode)) && dom.isText(cur)) {
- return true;
- }
- if (prev && prev.tagName === "P" && dom.isText(cur)) {
- return true;
- }
- if (prev && dom.isText(cur) && !dom.isVisibleText(cur) && (dom.isText(prev) || dom.isVisibleText(prev))) {
- return true;
- }
- if (prev && !dom.isBR(prev) && dom.isEqual(prev, cur) &&
- ((prev.tagName && dom.getComputedStyle(prev).display === "inline" &&
- cur.tagName && dom.getComputedStyle(cur).display === "inline"))) {
- return true;
- }
- if (dom.isEqual(parent, cur) &&
- ((parent.tagName && dom.getComputedStyle(parent).display === "inline" &&
- cur.tagName && dom.getComputedStyle(cur).display === "inline"))) {
- return true;
- }
- if (parent && cur.tagName === "FONT" && (!cur.firstChild || (!cur.attributes.getNamedItem('style') && !cur.className.length))) {
- return true;
- }
- // On backspace, webkit browsers create a with a bunch of
- // inline styles "remembering" where they come from.
- // chances are we had e.g.
- // foo
- // bar
- // merged the lines getting this in webkit
- // foobar
- if (parent && cur.tagName === "SPAN" && dom.hasOnlyStyle(cur) && !dom.hasProgrammaticStyle(cur)) {
- return true;
- }
-};
-dom.doMerge = function (prev, cur) {
- if (prev.tagName) {
- if (prev.childNodes.length && !prev.textContent.match(/\S/) && dom.firstElementChild(prev) && dom.isBR(dom.firstElementChild(prev))) {
- prev.removeChild(dom.firstElementChild(prev));
- }
- if (cur.tagName) {
- while (cur.firstChild) {
- prev.appendChild(cur.firstChild);
- }
- cur.parentNode.removeChild(cur);
- } else {
- prev.appendChild(cur);
- }
- } else {
- if (cur.tagName) {
- var deep = cur;
- while (deep.tagName && deep.firstChild) {deep = deep.firstChild;}
- prev.appendData(deep.textContent);
- cur.parentNode.removeChild(cur);
- } else {
- prev.appendData(cur.textContent);
- cur.parentNode.removeChild(cur);
- }
- }
-};
-dom.merge = function (node, begin, so, end, eo, mergeFilter, all) {
- mergeFilter = mergeFilter || dom.mergeFilter;
- var _merged = false;
- var add = all || false;
-
- if (!begin) {
- begin = node;
- while(begin.firstChild) {begin = begin.firstChild;}
- so = 0;
- } else if (begin.tagName && begin.childNodes[so]) {
- begin = begin.childNodes[so];
- so = 0;
- }
- if (!end) {
- end = node;
- while(end.lastChild) {end = end.lastChild;}
- eo = end.textContent.length-1;
- } else if (end.tagName) {
-
- } else if (end.tagName && end.childNodes[so]) {
- end = end.childNodes[so];
- so = 0;
- }
-
- begin = dom.firstChild(begin);
- if (dom.isText(begin) && so > begin.textContent.length) {
- so = 0;
- }
- end = dom.firstChild(end);
- if (dom.isText(end) && eo > end.textContent.length) {
- eo = 0;
- }
-
- function __merge (node) {
- var merged = false;
- var prev;
- for (var k=0; k 1 && dom.isText(cur) && !dom.isVisibleText(cur)) {
- removed = true;
- if (cur === begin) {
- t_begin = dom.hasContentBefore(dom.ancestorHavePreviousSibling(cur));
- if(t_begin) {
- so = 0;
- begin = dom.lastChild(t_begin);
- }
- }
- if (cur === end) {
- t_end = dom.hasContentAfter(dom.ancestorHaveNextSibling(cur));
- if(t_end) {
- eo = 1;
- end = dom.firstChild(t_end);
- if (dom.isText(end)) {
- eo = end.textContent.length;
- }
- }
- }
- cur.parentNode.removeChild(cur);
- begin = dom.lastChild(begin);
- end = dom.lastChild(end);
- k--;
- continue;
- }
-
- // convert HTML space
- if (dom.isText(cur)) {
- var text;
- var exp1 = /[\t\n\r ]+/g;
- var exp2 = /(?!([ ]|\u00A0)|^)\u00A0(?!([ ]|\u00A0)|$)/g;
- if (cur === begin) {
- var temp = cur.textContent.substr(0, so);
- var _temp = temp.replace(exp1, ' ').replace(exp2, ' ');
- so -= temp.length - _temp.length;
- }
- if (cur === end) {
- var temp = cur.textContent.substr(0, eo);
- var _temp = temp.replace(exp1, ' ').replace(exp2, ' ');
- eo -= temp.length - _temp.length;
- }
- var text = cur.textContent.replace(exp1, ' ').replace(exp2, ' ');
- removed = removed || cur.textContent.length !== text.length;
- cur.textContent = text;
- }
- }
- })(node);
-
- return {
- removed: removed,
- sc: begin,
- ec: end,
- so: !dom.isBR(begin) && so > 0 ? so : 0,
- eo: dom.isBR(end) ? 0 : eo
- };
-};
-dom.node = function (node) {
- return node.tagName ? node : node.parentNode;
-};
-dom.removeBetween = function (sc, so, ec, eo, towrite) {
- if (ec.tagName) {
- if (ec.childNodes[eo]) {
- ec = ec.childNodes[eo];
- eo = 0;
- } else {
- ec = dom.lastChild(ec);
- eo = dom.nodeLength(ec);
- }
- }
- if (sc.tagName) {
- sc = sc.childNodes[so] || dom.firstChild(ec);
- so = 0;
- if (!dom.hasContentBefore(sc) && towrite) {
- sc.parentNode.insertBefore(document.createTextNode('\u00A0'), sc);
- }
- }
- if (!eo && sc !== ec) {
- ec = dom.lastChild(dom.hasContentBefore(dom.ancestorHavePreviousSibling(ec)) || ec);
- eo = ec.textContent.length;
- }
-
- var ancestor = dom.commonAncestor(sc.tagName ? sc.parentNode : sc, ec.tagName ? ec.parentNode : ec) || dom.ancestor(sc, dom.isEditable);
-
- if (!dom.isContentEditable(ancestor)) {
- return {
- sc: sc,
- so: so,
- ec: sc,
- eo: eo
- };
- }
-
- if (ancestor.tagName) {
- var ancestor_sc = sc;
- var ancestor_ec = ec;
- while (ancestor !== ancestor_sc && ancestor !== ancestor_sc.parentNode) { ancestor_sc = ancestor_sc.parentNode; }
- while (ancestor !== ancestor_ec && ancestor !== ancestor_ec.parentNode) { ancestor_ec = ancestor_ec.parentNode; }
-
-
- var node = dom.node(sc);
- if (!dom.isNotBreakable(node) && !dom.isVoid(sc)) {
- sc = dom.splitTree(ancestor_sc, {'node': sc, 'offset': so});
- }
- var before = dom.hasContentBefore(dom.ancestorHavePreviousSibling(sc));
-
- var after;
- if (ec.textContent.slice(eo, Infinity).match(/\S|\u00A0/)) {
- after = dom.splitTree(ancestor_ec, {'node': ec, 'offset': eo});
- } else {
- after = dom.hasContentAfter(dom.ancestorHaveNextSibling(ec));
- }
-
- var nodes = dom.listBetween(sc, ec);
-
- var ancestor_first_last = function (node) {
- return node === before || node === after;
- };
-
- for (var i=0; i")[0];
- node.appendChild(sc);
- sc = br;
- so = 0;
- } else if (!ancestor.children.length && !ancestor.textContent.match(/\S|\u00A0/)) {
- sc = $(" ")[0];
- so = 0;
- $(ancestor).prepend(sc);
- } else if (dom.isText(sc)) {
- var text = sc.textContent.replace(/[ \t\n\r]+$/, '\u00A0');
- so = Math.min(so, text.length);
- sc.textContent = text;
- }
-
- } else {
-
- var text = ancestor.textContent;
- ancestor.textContent = text.slice(0, so) + text.slice(eo, Infinity).replace(/^[ \t\n\r]+/, '\u00A0');
-
- }
-
- eo = so;
- if(!dom.isBR(sc) && !dom.isVisibleText(sc) && !dom.isText(dom.hasContentBefore(sc)) && !dom.isText(dom.hasContentAfter(sc))) {
- ancestor = dom.node(sc);
- var text = document.createTextNode('\u00A0');
- $(sc).before(text);
- sc = text;
- so = 0;
- eo = 1;
- }
- return {
- sc: sc,
- so: so,
- ec: sc,
- eo: eo
- };
-};
-dom.indent = function (node) {
- var style = dom.isCell(node) ? 'paddingLeft' : 'marginLeft';
- var margin = parseFloat(node.style[style] || 0)+1.5;
- node.style[style] = margin + "em";
- return margin;
-};
-dom.outdent = function (node) {
- var style = dom.isCell(node) ? 'paddingLeft' : 'marginLeft';
- var margin = parseFloat(node.style[style] || 0)-1.5;
- node.style[style] = margin > 0 ? margin + "em" : "";
- return margin;
-};
-dom.scrollIntoViewIfNeeded = function (node) {
- var node = dom.node(node);
-
- var $span;
- if (dom.isBR(node)) {
- $span = $(' ').text('\u00A0');
- $(node).after($span);
- node = $span[0];
- }
-
- if (node.scrollIntoViewIfNeeded) {
- node.scrollIntoViewIfNeeded(false);
- } else {
- var offsetParent = node.offsetParent;
- while (offsetParent) {
- var elY = 0;
- var elH = node.offsetHeight;
- var parent = node;
-
- while (offsetParent && parent) {
- elY += node.offsetTop;
-
- // get if a parent have a scrollbar
- parent = node.parentNode;
- while (parent != offsetParent &&
- (parent.tagName === "BODY" || ["auto", "scroll"].indexOf(dom.getComputedStyle(parent).overflowY) === -1)) {
- parent = parent.parentNode;
- }
- node = parent;
-
- if (parent !== offsetParent) {
- elY -= parent.offsetTop;
- parent = null;
- }
-
- offsetParent = node.offsetParent;
- }
-
- if ((node.tagName === "BODY" || ["auto", "scroll"].indexOf(dom.getComputedStyle(node).overflowY) !== -1) &&
- (node.scrollTop + node.clientHeight) < (elY + elH)) {
- node.scrollTop = (elY + elH) - node.clientHeight;
- }
- }
- }
-
- if ($span) {
- $span.remove();
- }
-
- return;
-};
-dom.moveTo = function (node, target, before) {
- var nodes = [];
- while (node.firstChild) {
- nodes.push(node.firstChild);
- if (before) {
- target.insertBefore(node.firstChild, before);
- } else {
- target.appendChild(node.firstChild);
- }
- }
- node.parentNode.removeChild(node);
- return nodes;
-};
-dom.isMergable = function (node) {
- return node.tagName && "h1 h2 h3 h4 h5 h6 p b bold i u code sup strong small li a ul ol font".indexOf(node.tagName.toLowerCase()) !== -1;
-};
-dom.isSplitable = function (node) {
- return node.tagName && "h1 h2 h3 h4 h5 h6 p b bold i u code sup strong small li a font".indexOf(node.tagName.toLowerCase()) !== -1;
-};
-dom.isRemovableEmptyNode = function (node) {
- return "h1 h2 h3 h4 h5 h6 p b bold i u code sup strong small li a ul ol font span br".indexOf(node.tagName.toLowerCase()) !== -1;
-};
-dom.isForbiddenNode = function (node) {
- return node.tagName === "BR" || $(node).is(".fa, img");
-};
-dom.listBetween = function (sc, ec) {
- var nodes = [];
- var ancestor = dom.commonAncestor(sc, ec);
- dom.walkPoint({'node': sc, 'offset': 0}, {'node': ec, 'offset': 0}, function (point) {
- if (ancestor !== point.node || ancestor === sc || ancestor === ec) {
- nodes.push(point.node);
- }
- });
- return list.unique(nodes);
-};
-dom.isNotBreakable = function (node) {
- // avoid triple click => crappy dom
- return !dom.isText(node) && !dom.isBR(dom.firstChild(node)) && dom.isVoid(dom.firstChild(node));
-};
-
-dom.isContentEditable = function (node) {
- return $(node).closest('[contenteditable]').is('[contenteditable="true"]');
-};
-
-dom.isPre = function (node) {
- return node.tagName === "PRE";
-};
-dom.isFont = function (node) {
- var nodeName = node && node.nodeName.toUpperCase();
- return node && (nodeName === "FONT" ||
- (nodeName === "SPAN" && (
- node.className.match(/(^|\s)fa(\s|$)/i) ||
- node.className.match(/(^|\s)(text|bg)-/i) ||
- (node.attributes.style && node.attributes.style.value.match(/(^|\s)(color|background-color|font-size):/i)))) );
-};
-dom.isVisibleText = function (textNode) {
- return !!textNode.textContent.match(/\S|\u00A0/);
-};
-
-/* avoid thinking HTML comment are visible */
-var old_isVisiblePoint = dom.isVisiblePoint;
-dom.isVisiblePoint = function (point) {
- return point.node.nodeType !== 8 && old_isVisiblePoint.apply(this, arguments);
-};
-
-/**
- * order the style of the node to compare 2 nodes and remove attribute if empty
- *
- * @param {Node} node
- * @return {String} className
- */
-dom.orderStyle = function (node) {
- var style = node.getAttribute('style');
- if (!style) return null;
- style = style.replace(/[\s\n\r]+/, ' ').replace(/^ ?;? ?| ?;? ?$/g, '').replace(/ ?; ?/g, ';');
- if (!style.length) {
- node.removeAttribute("style");
- return null;
- }
- style = style.split(";");
- style.sort();
- style = style.join("; ")+";";
- node.setAttribute('style', style);
- return style;
-};
-/**
- * order the class of the node to compare 2 nodes and remove attribute if empty
- *
- * @param {Node} node
- * @return {String} className
- */
-dom.orderClass = function (node) {
- var className = node.getAttribute && node.getAttribute('class');
- if (!className) return null;
- className = className.replace(/[\s\n\r]+/, ' ').replace(/^ | $/g, '').replace(/ +/g, ' ');
- if (!className.length) {
- node.removeAttribute("class");
- return null;
- }
- className = className.split(" ");
- className.sort();
- className = className.join(" ");
- node.setAttribute('class', className);
- return className;
-};
-/**
- * return the current node (if text node, return parentNode)
- *
- * @param {Node} node
- * @return {Node} node
- */
-dom.node = function (node) {
- return dom.isText(node) ? node.parentNode : node;
-};
-/**
- * move all childNode to an other node
- *
- * @param {Node} from
- * @param {Node} to
- */
-dom.moveContent = function (from, to) {
- if (from === to) {
- return;
- }
- if (from.parentNode === to) {
- while (from.lastChild) {
- dom.insertAfter(from.lastChild, from);
- }
- } else {
- while (from.firstChild && from.firstChild != to) {
- to.appendChild(from.firstChild);
- }
- }
-};
-
-dom.getComputedStyle = function (node) {
- return node.nodeType == Node.COMMENT_NODE ? {} : window.getComputedStyle(node);
-};
-
-
-//////////////////////////////////////////////////////////////////////////////////////////////////////////
-
-range.WrappedRange.prototype.reRange = function (keep_end, isNotBreakable) {
- var sc = this.sc;
- var so = this.so;
- var ec = this.ec;
- var eo = this.eo;
- isNotBreakable = isNotBreakable || dom.isNotBreakable;
-
- // search the first snippet editable node
- var start = keep_end ? ec : sc;
- while (start) {
- if (isNotBreakable(start, sc, so, ec, eo)) {
- break;
- }
- start = start.parentNode;
- }
-
- // check if the end caret have the same node
- var lastFilterEnd;
- var end = keep_end ? sc : ec;
- while (end) {
- if (start === end) {
- break;
- }
- if (isNotBreakable(end, sc, so, ec, eo)) {
- lastFilterEnd = end;
- }
- end = end.parentNode;
- }
- if (lastFilterEnd) {
- end = lastFilterEnd;
- }
- if (!end) {
- end = document.getElementsByTagName('body')[0];
- }
-
- // if same node, keep range
- if (start === end || !start) {
- return this;
- }
-
- // reduce or extend the range to don't break a isNotBreakable area
- if ($.contains(start, end)) {
-
- if (keep_end) {
- sc = dom.lastChild(dom.hasContentBefore(dom.ancestorHavePreviousSibling(end)) || sc);
- so = sc.textContent.length;
- } else if (!eo) {
- ec = dom.lastChild(dom.hasContentBefore(dom.ancestorHavePreviousSibling(end)) || ec);
- eo = ec.textContent.length;
- } else {
- ec = dom.firstChild(dom.hasContentAfter(dom.ancestorHaveNextSibling(end)) || ec);
- eo = 0;
- }
- } else {
-
- if (keep_end) {
- sc = dom.firstChild(start);
- so = 0;
- } else {
- ec = dom.lastChild(start);
- eo = ec.textContent.length;
- }
- }
-
- return new range.WrappedRange(sc, so, ec, eo);
-};
-// isOnImg: judge whether range is an image node or not
-range.WrappedRange.prototype.isOnImg = function () {
- var nb = 0;
- var image;
- var startPoint = {node: this.sc.childNodes.length && this.sc.childNodes[this.so] || this.sc};
- startPoint.offset = startPoint.node === this.sc ? this.so : 0;
- var endPoint = {node: this.ec.childNodes.length && this.ec.childNodes[this.eo] || this.ec};
- endPoint.offset = endPoint.node === this.ec ? this.eo : 0;
-
- if (dom.isImg(startPoint.node)) {
- nb ++;
- image = startPoint.node;
- }
- dom.walkPoint(startPoint, endPoint, function (point) {
- if (!dom.isText(endPoint.node) && point.node === endPoint.node && point.offset === endPoint.offset) {
- return;
- }
- var node = point.node.childNodes.length && point.node.childNodes[point.offset] || point.node;
- var offset = node === point.node ? point.offset : 0;
- var isImg = dom.ancestor(node, dom.isImg);
- if (!isImg && ((!dom.isBR(node) && !dom.isText(node)) || (offset && node.textContent.length !== offset && node.textContent.match(/\S|\u00A0/)))) {
- nb++;
- }
- if (isImg && image !== isImg) {
- image = isImg;
- nb ++;
- }
- });
- return nb === 1 && image;
-};
-range.WrappedRange.prototype.deleteContents = function (towrite) {
- var prevBP = dom.removeBetween(this.sc, this.so, this.ec, this.eo, towrite);
-
- $(dom.node(prevBP.sc)).trigger("click"); // trigger click to disable and reanable editor and image handler
- return new range.WrappedRange(
- prevBP.sc,
- prevBP.so,
- prevBP.ec,
- prevBP.eo
- );
-};
-range.WrappedRange.prototype.clean = function (mergeFilter, all) {
- var node = dom.node(this.sc === this.ec ? this.sc : this.commonAncestor());
- node = node || $(this.sc).closest('[contenteditable]')[0];
- if (node.childNodes.length <=1) {
- return this;
- }
-
- var merge = dom.merge(node, this.sc, this.so, this.ec, this.eo, mergeFilter, all);
- var rem = dom.removeSpace(node.parentNode, merge.sc, merge.so, merge.ec, merge.eo);
-
- if (merge.merged || rem.removed) {
- return range.create(rem.sc, rem.so, rem.ec, rem.eo);
- }
- return this;
-};
-range.WrappedRange.prototype.remove = function (mergeFilter) {
-};
-range.WrappedRange.prototype.isOnCellFirst = function () {
- var node = dom.ancestor(this.sc, function (node) {return ["LI", "DIV", "TD","TH"].indexOf(node.tagName) !== -1;});
- return node && ["TD","TH"].indexOf(node.tagName) !== -1;
-};
-range.WrappedRange.prototype.isContentEditable = function () {
- return dom.isContentEditable(this.sc) && (this.sc === this.ec || dom.isContentEditable(this.ec));
-};
-
-//////////////////////////////////////////////////////////////////////////////////////////////////////////
-
-renderer.tplButtonInfo.fontsize = function (lang, options) {
- var items = options.fontSizes.reduce(function (memo, v) {
- return memo + '' +
- ' ' + v +
- ' ';
- }, '');
-
- var sLabel = '11 ';
- return renderer.getTemplate().button(sLabel, {
- title: lang.font.size,
- dropdown: ''
- });
-};
-
-//////////////////////////////////////////////////////////////////////////////////////////////////////////
-/* add some text commands */
-
-key.nameFromCode[46] = 'DELETE';
-key.nameFromCode[27] = 'ESCAPE';
-
-options.keyMap.pc['BACKSPACE'] = 'backspace';
-options.keyMap.pc['DELETE'] = 'delete';
-options.keyMap.pc['ENTER'] = 'enter';
-options.keyMap.pc['ESCAPE'] = 'cancel';
-options.keyMap.mac['SHIFT+TAB'] = 'untab';
-options.keyMap.pc['UP'] = 'up';
-options.keyMap.pc['DOWN'] = 'down';
-
-options.keyMap.mac['BACKSPACE'] = 'backspace';
-options.keyMap.mac['DELETE'] = 'delete';
-options.keyMap.mac['ENTER'] = 'enter';
-options.keyMap.mac['ESCAPE'] = 'cancel';
-options.keyMap.mac['UP'] = 'up';
-options.keyMap.mac['DOWN'] = 'down';
-
-options.styleTags = ['p', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote'];
-
-$.summernote.pluginEvents.insertTable = function (event, editor, layoutInfo, sDim) {
- var $editable = layoutInfo.editable();
- var dimension = sDim.split('x');
- var r = range.create();
- if (!r) return;
- r = r.deleteContents(true);
-
- var table = editor.table.createTable(dimension[0], dimension[1]);
- var parent = r.sc;
- while (dom.isText(parent.parentNode) || dom.isRemovableEmptyNode(parent.parentNode)) {
- parent = parent.parentNode;
- }
- var node = dom.splitTree(parent, {'node': r.sc, 'offset': r.so}) || r.sc;
- node.parentNode.insertBefore(table, node);
-
- if ($(node).text() === '' || node.textContent === '\u00A0') {
- node.parentNode.removeChild(node);
- }
-
- editor.afterCommand($editable);
- event.preventDefault();
- return false;
-};
-$.summernote.pluginEvents.tab = function (event, editor, layoutInfo, outdent) {
- var $editable = layoutInfo.editable();
- $editable.data('NoteHistory').recordUndo($editable, 'tab');
- var r = range.create();
- var outdent = outdent || false;
- event.preventDefault();
-
- if (r && (dom.ancestor(r.sc, dom.isCell) || dom.ancestor(r.ec, dom.isCell))) {
- if (r.isCollapsed() && r.isOnCell() && r.isOnCellFirst()) {
- var td = dom.ancestor(r.sc, dom.isCell);
- if (!outdent && !dom.nextElementSibling(td) && !dom.nextElementSibling(td.parentNode)) {
- var last = dom.lastChild(td);
- range.create(last, dom.nodeLength(last), last, dom.nodeLength(last)).select();
- $.summernote.pluginEvents.enter(event, editor, layoutInfo);
- } else if (outdent && !dom.previousElementSibling(td) && !$(td.parentNode).text().match(/\S/)) {
- $.summernote.pluginEvents.backspace(event, editor, layoutInfo);
- } else {
- editor.table.tab(r, outdent);
- }
- } else {
- $.summernote.pluginEvents.indent(event, editor, layoutInfo, outdent);
- }
- } else if (r && r.isCollapsed()) {
- if (!r.sc.textContent.slice(0,r.so).match(/\S/) && r.isOnList()) {
- if (outdent) {
- $.summernote.pluginEvents.outdent(event, editor, layoutInfo);
- } else {
- $.summernote.pluginEvents.indent(event, editor, layoutInfo);
- }
- } else {
- if (!outdent) {
- if(dom.isText(r.sc)) {
- var next = r.sc.splitText(r.so);
- } else {
- var next = document.createTextNode('')
- $(r.sc.childNodes[r.so]).before(next);
- }
- editor.typing.insertTab($editable, r, options.tabsize);
- r = range.create(next, 0, next, 0);
- r = dom.merge(r.sc.parentNode, r.sc, r.so, r.ec, r.eo, null, true);
- range.create(r.sc, r.so, r.ec, r.eo).select();
- } else {
- r = dom.merge(r.sc.parentNode, r.sc, r.so, r.ec, r.eo, null, true);
- r = range.create(r.sc, r.so, r.ec, r.eo);
- var next = r.sc.splitText(r.so);
- r.sc.textContent = r.sc.textContent.replace(/(\u00A0)+$/g, '');
- next.textContent = next.textContent.replace(/^(\u00A0)+/g, '');
- range.create(r.sc, r.sc.textContent.length, r.sc, r.sc.textContent.length).select();
- }
- }
- }
- return false;
-};
-$.summernote.pluginEvents.untab = function (event, editor, layoutInfo) {
- return $.summernote.pluginEvents.tab(event, editor, layoutInfo, true);
-};
-$.summernote.pluginEvents.up = function (event, editor, layoutInfo) {
- var r = range.create();
- var node = dom.firstChild(r.sc.childNodes[r.so] || r.sc);
- if (!r.isOnCell() || (!dom.isCell(node) && dom.hasContentBefore(node) && (!dom.isBR(dom.hasContentBefore(node)) || !dom.isText(node) || dom.isVisibleText(node) || dom.hasContentBefore(dom.hasContentBefore(node))))) {
- return;
- }
- event.preventDefault();
- var td = dom.ancestor(r.sc, dom.isCell);
- var tr = td.parentNode;
- var target = tr.previousElementSibling && tr.previousElementSibling.children[_.indexOf(tr.children, td)];
- if (!target) {
- target = (dom.ancestorHavePreviousSibling(tr) || tr).previousSibling;
- }
- if (target) {
- range.create(dom.lastChild(target), dom.lastChild(target).textContent.length).select();
- }
-};
-$.summernote.pluginEvents.down = function (event, editor, layoutInfo) {
- var r = range.create();
- var node = dom.firstChild(r.sc.childNodes[r.so] || r.sc);
- if (!r.isOnCell() || (!dom.isCell(node) && dom.hasContentAfter(node) && (!dom.isBR(dom.hasContentAfter(node)) || !dom.isText(node) || dom.isVisibleText(node) || dom.hasContentAfter(dom.hasContentAfter(node))))) {
- return;
- }
- event.preventDefault();
- var td = dom.ancestor(r.sc, dom.isCell);
- var tr = td.parentNode;
- var target = tr.nextElementSibling && tr.nextElementSibling.children[_.indexOf(tr.children, td)];
- if (!target) {
- target = (dom.ancestorHaveNextSibling(tr) || tr).nextSibling;
- }
- if (target) {
- range.create(dom.firstChild(target), 0).select();
- }
-};
-$.summernote.pluginEvents.enter = function (event, editor, layoutInfo) {
- var $editable = layoutInfo.editable();
- $editable.data('NoteHistory').recordUndo($editable, 'enter');
-
- var r = range.create();
- if (!r.isContentEditable()) {
- event.preventDefault();
- return false;
- }
- if (!r.isCollapsed()) {
- r = r.deleteContents();
- r.select();
- }
-
- var br = $(" ")[0];
-
- var contentBefore = r.sc.textContent.slice(0,r.so).match(/\S|\u00A0/);
- if (!contentBefore && dom.isText(r.sc)) {
- var node = r.sc.previousSibling;
- while (!contentBefore && node && dom.isText(node)) {
- contentBefore = dom.isVisibleText(node);
- node = node.previousSibling;
- }
- }
-
- var node = dom.node(r.sc);
- var exist = r.sc.childNodes[r.so] || r.sc;
- exist = dom.isVisibleText(exist) || dom.isBR(exist) ? exist : dom.hasContentAfter(exist) || (dom.hasContentBefore(exist) || exist);
-
- // table: add a tr
- var td = dom.ancestor(node, dom.isCell);
- if (td && !dom.nextElementSibling(node) && !dom.nextElementSibling(td) && !dom.nextElementSibling(td.parentNode) && (!dom.isText(r.sc) || !r.sc.textContent.slice(r.so).match(/\S|\u00A0/))) {
- var $node = $(td.parentNode);
- var $clone = $node.clone();
- $clone.children().html(dom.blank);
- $node.after($clone);
- var node = dom.firstElementChild($clone[0]) || $clone[0];
- range.create(node, 0, node, 0).select();
- dom.scrollIntoViewIfNeeded(br);
- event.preventDefault();
- return false;
- }
-
- var last = node;
- while (node && dom.isSplitable(node) && !dom.isList(node)) {
- last = node;
- node = node.parentNode;
- }
-
- if (last === node && !dom.isBR(node)) {
- node = r.insertNode(br, true);
- if (isFormatNode(last.firstChild) && $(last).closest(options.styleTags.join(',')).length) {
- dom.moveContent(last.firstChild, last);
- last.removeChild(last.firstChild);
- }
- do {
- node = dom.hasContentAfter(node);
- } while (node && dom.isBR(node));
-
- // create an other br because the user can't see the new line with only br in a block
- if (!node && (!br.nextElementSibling || !dom.isBR(br.nextElementSibling))) {
- $(br).before($(" ")[0]);
- }
- node = br.nextSibling || br;
- } else if (last === node && dom.isBR(node)) {
- $(node).after(br);
- node = br;
- } else if (!r.so && r.isOnList() && !r.sc.textContent.length && !dom.ancestor(r.sc, dom.isLi).nextElementSibling) {
- // double enter on the end of a list = new line out of the list
- $('
').append(br).insertAfter(dom.ancestor(r.sc, dom.isList));
- node = br;
- } else if (dom.isBR(exist) && $(r.sc).closest('blockquote, pre').length && !dom.hasContentAfter($(exist.parentNode).closest('blockquote *, pre *').length ? exist.parentNode : exist)) {
- // double enter on the end of a blockquote & pre = new line out of the list
- $('
').append(br).insertAfter($(r.sc).closest('blockquote, pre'));
- node = br;
- } else if (last === r.sc) {
- if (dom.isBR(last)) {
- last = last.parentNode;
- }
- var $node = $(last);
- var $clone = $node.clone().text("");
- $node.after($clone);
- node = dom.node(dom.firstElementChild($clone[0]) || $clone[0]);
- $(node).html(br);
- node = br;
- } else {
- node = dom.splitTree(last, {'node': r.sc, 'offset': r.so}) || r.sc;
- if (!contentBefore) {
- var cur = dom.node(dom.lastChild(node.previousSibling));
- if (!dom.isBR(cur)) {
- $(cur).html(br);
- }
- }
- if (!dom.isVisibleText(node)) {
- node = dom.firstChild(node);
- $(dom.node( dom.isBR(node) ? node.parentNode : node )).html(br);
- node = br;
- }
- }
-
- if (node) {
- node = dom.firstChild(node);
- if (dom.isBR(node)) {
- range.createFromNode(node).select();
- } else {
- range.create(node,0).select();
- }
- dom.scrollIntoViewIfNeeded(node);
- }
- event.preventDefault();
- return false;
-};
-$.summernote.pluginEvents.visible = function (event, editor, layoutInfo) {
- var $editable = layoutInfo.editable();
- $editable.data('NoteHistory').recordUndo($editable, "visible");
-
- var r = range.create();
- if (!r) return;
-
- if (!r.isCollapsed()) {
- if (dom.isCell(dom.node(r.sc)) || dom.isCell(dom.node(r.ec))) {
- remove_table_content(r);
- r = range.create(r.ec, 0).select();
- }
- r.select();
- }
-
- // don't write in forbidden tag (like span for font awsome)
- var node = dom.firstChild(r.sc.tagName && r.so ? r.sc.childNodes[r.so] || r.sc : r.sc);
- while (node.parentNode) {
- if (dom.isForbiddenNode(node)) {
- var text = node.previousSibling;
- if (text && dom.isText(text) && dom.isVisibleText(text)) {
- range.create(text, text.textContent.length, text, text.textContent.length).select();
- } else {
- text = node.parentNode.insertBefore(document.createTextNode( "." ), node);
- range.create(text, 1, text, 1).select();
- setTimeout(function () {
- var text = range.create().sc;
- text.textContent = text.textContent.replace(/^./, '');
- range.create(text, text.textContent.length, text, text.textContent.length).select();
- },0);
- }
- break;
- }
- node = node.parentNode;
- }
-
- return true;
-};
-
-function summernote_keydown_clean (field) {
- setTimeout(function () {
- var r = range.create();
- if (!r) return;
- var node = r[field];
- while (dom.isText(node)) {node = node.parentNode;}
- node = node.parentNode;
- var data = dom.merge(node, r.sc, r.so, r.ec, r.eo, null, true);
- data = dom.removeSpace(node.parentNode, data.sc, data.so, data.sc, data.so);
-
- range.create(data.sc, data.so, data.sc, data.so).select();
- },0);
-}
-
-function remove_table_content(r) {
- var ancestor = r.commonAncestor();
- var nodes = dom.listBetween(r.sc, r.ec);
- if (dom.isText(r.sc)) {
- r.sc.textContent = r.sc.textContent.slice(0, r.so);
- }
- if (dom.isText(r.ec)) {
- r.ec.textContent = r.ec.textContent.slice(r.eo);
- }
- for (var i in nodes) {
- var node = nodes[i];
- if (node === r.sc || node === r.ec || $.contains(node, r.sc) || $.contains(node, r.ec)) {
- continue;
- } else if (dom.isCell(node)) {
- $(node).html(" ");
- } else if(node.parentNode) {
- do {
- var parent = node.parentNode;
- parent.removeChild(node);
- node = parent;
- } while(!dom.isVisibleText(node) && !dom.firstElementChild(node) &&
- !dom.isCell(node) &&
- node.parentNode && !$(node.parentNode).hasClass('o_editable'));
- }
- }
- return false;
-}
-
-$.summernote.pluginEvents.delete = function (event, editor, layoutInfo) {
- var $editable = layoutInfo.editable();
- $editable.data('NoteHistory').recordUndo($editable, "delete");
-
- var r = range.create();
- if (!r) return;
- if (!r.isContentEditable()) {
- event.preventDefault();
- return false;
- }
- if (!r.isCollapsed()) {
- if (dom.isCell(dom.node(r.sc)) || dom.isCell(dom.node(r.ec))) {
- remove_table_content(r);
- range.create(r.ec, 0).select();
- } else {
- r = r.deleteContents();
- r.select();
- }
- event.preventDefault();
- return false;
- }
-
- var target = r.ec;
- var offset = r.eo;
- if (target.tagName && target.childNodes[offset]) {
- target = target.childNodes[offset];
- offset = 0;
- }
-
- var node = dom.node(target);
- var data = dom.merge(node, target, offset, target, offset, null, true);
- data = dom.removeSpace(node.parentNode, data.sc, data.so, data.ec, data.eo);
- r = range.create(data.sc, data.so);
- r.select();
- target = r.sc;
- offset = r.so;
-
- while (!dom.hasContentAfter(node) && !dom.hasContentBefore(node) && !dom.isImg(node)) {node = node.parentNode;}
-
- var contentAfter = target.textContent.slice(offset,Infinity).match(/\S|\u00A0/);
- var content = target.textContent.replace(/[ \t\r\n]+$/, '');
- var temp;
- var temp2;
-
- // media
- if (dom.isImg(node) || (!contentAfter && dom.isImg(dom.hasContentAfter(node)))) {
- var parent;
- var index;
- var r;
- if (!dom.isImg(node)) {
- node = dom.hasContentAfter(node);
- }
- while (dom.isImg(node)) {
- parent = node.parentNode;
- index = dom.position(node);
- if (index>0) {
- var next = node.previousSibling;
- r = range.create(next, next.textContent.length);
- } else {
- r = range.create(parent, 0);
- }
- if (!dom.hasContentAfter(node) && !dom.hasContentBefore(node)) {
- parent.appendChild($(' ')[0]);
- }
- parent.removeChild(node);
- node = parent;
- r.select();
- }
- }
- // empty tag
- else if (!content.length && target.tagName && dom.isRemovableEmptyNode(dom.isBR(target) ? target.parentNode : target)) {
- if (node === $editable[0] || $.contains(node, $editable[0])) {
- event.preventDefault();
- return false;
- }
- var before = false;
- var next = dom.hasContentAfter(dom.ancestorHaveNextSibling(node));
- if (!dom.isContentEditable(next)) {
- before = true;
- next = dom.hasContentBefore(dom.ancestorHavePreviousSibling(node));
- }
- dom.removeSpace(next.parentNode, next, 0, next, 0); // clean before jump for not select invisible space between 2 tag
- next = dom.firstChild(next);
- node.parentNode.removeChild(node);
- range.create(next, before ? next.textContent.length : 0).select();
- }
- // normal feature if same tag and not the end
- else if (contentAfter) {
- return true;
- }
- // merge with the next text node
- else if (dom.isText(target) && (temp = dom.hasContentAfter(target)) && dom.isText(temp)) {
- return true;
- }
- //merge with the next block
- else if ((temp = dom.ancestorHaveNextSibling(target)) &&
- !r.isOnCell() &&
- dom.isMergable(temp) &&
- dom.isMergable(temp2 = dom.hasContentAfter(temp)) &&
- temp.tagName === temp2.tagName &&
- (temp.tagName !== "LI" || !$('ul,ol', temp).length) && (temp2.tagName !== "LI" || !$('ul,ol', temp2).length) && // protect li
- !dom.isNotBreakable(temp) &&
- !dom.isNotBreakable(temp2)) {
- dom.autoMerge(target, false);
- var next = dom.firstChild(dom.hasContentAfter(dom.ancestorHaveNextSibling(target)));
- if (dom.isBR(next)) {
- if(dom.position(next) == 0) {
- range.create(next.parentNode, 0).select();
- }
- else {
- range.create(next.previousSibling, next.previousSibling.textContent.length).select();
- }
- next.parentNode.removeChild(next);
- } else {
- range.create(next, 0).select();
- }
- }
- // jump to next node for delete
- else if ((temp = dom.ancestorHaveNextSibling(target)) && (temp2 = dom.hasContentAfter(temp)) && dom.isContentEditable(temp2)) {
-
- dom.removeSpace(temp2.parentNode, temp2, 0, temp, 0); // clean before jump for not select invisible space between 2 tag
- temp2 = dom.firstChild(temp2);
-
- r = range.create(temp2, 0);
- r.select();
-
- if ((dom.isText(temp) || dom.getComputedStyle(temp).display === "inline") && (dom.isText(temp2) || dom.getComputedStyle(temp2).display === "inline")) {
- if (dom.isText(temp2)) {
- temp2.textContent = temp2.textContent.replace(/^\s*\S/, '');
- } else {
- $.summernote.pluginEvents.delete(event, editor, layoutInfo);
- }
- }
- }
-
- $(dom.node(r.sc)).trigger("click"); // trigger click to disable and reanable editor and image handler
- event.preventDefault();
- return false;
-};
-$.summernote.pluginEvents.backspace = function (event, editor, layoutInfo) {
- var $editable = layoutInfo.editable();
- $editable.data('NoteHistory').recordUndo($editable, "backspace");
-
- var r = range.create();
- if (!r) return;
- if (!r.isContentEditable()) {
- event.preventDefault();
- return false;
- }
- if (!r.isCollapsed()) {
- if (dom.isCell(dom.node(r.sc)) || dom.isCell(dom.node(r.ec))) {
- remove_table_content(r);
- range.create(r.sc, dom.nodeLength(r.sc)).select();
- } else {
- r = r.deleteContents();
- r.select();
- }
- event.preventDefault();
- return false;
- }
-
- var target = r.sc;
- var offset = r.so;
- if (target.tagName && target.childNodes[offset]) {
- target = target.childNodes[offset];
- offset = 0;
- }
-
- var node = dom.node(target);
- var data = dom.merge(node, target, offset, target, offset, null, true);
- data = dom.removeSpace(node.parentNode, data.sc, data.so, data.ec, data.eo);
- r = dom.isVoid(data.sc) ? range.createFromNode(data.sc) : range.create(data.sc, data.so);
- r.select();
- target = r.sc;
- offset = r.so;
- if (target.tagName && target.childNodes[offset]) {
- target = target.childNodes[offset];
- offset = 0;
- node = dom.node(target);
- }
-
- while (node.parentNode && !dom.hasContentAfter(node) && !dom.hasContentBefore(node) && !dom.isImg(node)) {node = node.parentNode;}
-
- var contentBefore = target.textContent.slice(0,offset).match(/\S|\u00A0/);
- var content = target.textContent.replace(/[ \t\r\n]+$/, '');
- var temp;
- var temp2;
-
- // delete media
- if (dom.isImg(node) || (!contentBefore && dom.isImg(dom.hasContentBefore(node)))) {
- if (!dom.isImg(node)) {
- node = dom.hasContentBefore(node);
- }
- range.createFromNode(node).select();
- $.summernote.pluginEvents.delete(event, editor, layoutInfo);
- }
- // table tr td
- else if (r.isOnCell() && !offset && (target === (temp = dom.ancestor(target, dom.isCell)) || target === temp.firstChild || (dom.isText(temp.firstChild) && !dom.isVisibleText(temp.firstChild) && target === temp.firstChild.nextSibling))) {
- if (dom.previousElementSibling(temp)) {
- var td = dom.previousElementSibling(temp);
- node = td.lastChild || td;
- } else {
- var tr = temp.parentNode;
- var prevTr = dom.previousElementSibling(tr);
- if (!$(temp.parentNode).text().match(/\S|\u00A0/)) {
- if (prevTr) {
- node = dom.lastChild(dom.lastElementChild(prevTr));
- } else {
- node = dom.lastChild(dom.hasContentBefore(dom.ancestorHavePreviousSibling(tr)) || $editable.get(0));
- }
- $(tr).empty();
- if(!$(tr).closest('table').has('td, th').length) {
- $(tr).closest('table').remove();
- }
- $(tr).remove();
- range.create(node, node.textContent.length, node, node.textContent.length).select();
- } else {
- node = dom.lastElementChild(prevTr).lastChild || dom.lastElementChild(prevTr);
- }
- }
- if (dom.isBR(node)) {
- range.createFromNode(node).select();
- } else {
- range.create(node, dom.nodeLength(node)).select();
- }
- }
- // empty tag
- else if (!content.length && target.tagName && dom.isRemovableEmptyNode(target)) {
- if (node === $editable[0] || $.contains(node, $editable[0])) {
- event.preventDefault();
- return false;
- }
- var before = true;
- var prev = dom.hasContentBefore(dom.ancestorHavePreviousSibling(node));
- if (!dom.isContentEditable(prev)) {
- before = false;
- prev = dom.hasContentAfter(dom.ancestorHaveNextSibling(node));
- }
- dom.removeSpace(prev.parentNode, prev, 0, prev, 0); // clean before jump for not select invisible space between 2 tag
- prev = dom.lastChild(prev);
- node.parentNode.removeChild(node);
- range.createFromNode(prev).select();
- range.create(prev, before ? prev.textContent.length : 0).select();
- }
- // normal feature if same tag and not the begin
- else if (contentBefore) {
- return true;
- }
- // merge with the previous text node
- else if (dom.isText(target) && (temp = dom.hasContentBefore(target)) && (dom.isText(temp) || dom.isBR(temp))) {
- return true;
- }
- //merge with the previous block
- else if ((temp = dom.ancestorHavePreviousSibling(target)) &&
- dom.isMergable(temp) &&
- dom.isMergable(temp2 = dom.hasContentBefore(temp)) &&
- temp.tagName === temp2.tagName &&
- (temp.tagName !== "LI" || !$('ul,ol', temp).length) && (temp2.tagName !== "LI" || !$('ul,ol', temp2).length) && // protect li
- !dom.isNotBreakable(temp) &&
- !dom.isNotBreakable(temp2)) {
- var prev = dom.firstChild(target);
- dom.autoMerge(target, true);
- range.create(prev, 0).select();
- }
- // jump to previous node for delete
- else if ((temp = dom.ancestorHavePreviousSibling(target)) && (temp2 = dom.hasContentBefore(temp)) && dom.isContentEditable(temp2)) {
-
- dom.removeSpace(temp2.parentNode, temp2, 0, temp, 0); // clean before jump for not select invisible space between 2 tag
- temp2 = dom.lastChild(temp2);
-
- r = range.create(temp2, temp2.textContent.length, temp2, temp2.textContent.length);
- r.select();
-
- if ((dom.isText(temp) || dom.getComputedStyle(temp).display === "inline") && (dom.isText(temp2) || dom.getComputedStyle(temp2).display === "inline")) {
- if (dom.isText(temp2)) {
- temp2.textContent = temp2.textContent.replace(/\S\s*$/, '');
- } else {
- $.summernote.pluginEvents.backspace(event, editor, layoutInfo);
- }
- }
- }
-
- var r = range.create();
- if (r) {
- $(dom.node(r.sc)).trigger("click"); // trigger click to disable and reanable editor and image handler
- dom.scrollIntoViewIfNeeded(r.sc.parentNode.previousElementSibling || r.sc);
- }
-
- event.preventDefault();
- return false;
-};
-
-//////////////////////////////////////////////////////////////////////////////////////////////////////////
-/* add list command (create a uggly dom for chrome) */
-
-function isFormatNode(node) {
- return node.tagName && options.styleTags.indexOf(node.tagName.toLowerCase()) !== -1;
-}
-
-$.summernote.pluginEvents.insertUnorderedList = function (event, editor, layoutInfo, sorted) {
- var $editable = layoutInfo.editable();
- $editable.data('NoteHistory').recordUndo($editable);
-
- var parent;
- var r = range.create();
- if (!r) return;
- var node = r.sc;
- while (node && node !== $editable[0]) {
-
- parent = node.parentNode;
- if (node.tagName === (sorted ? "UL" : "OL")) {
-
- var ul = document.createElement(sorted ? "ol" : "ul");
- ul.className = node.className;
- parent.insertBefore(ul, node);
- while (node.firstChild) {
- ul.appendChild(node.firstChild);
- }
- parent.removeChild(node);
- r.select();
- return;
-
- } else if (node.tagName === (sorted ? "OL" : "UL")) {
-
- var lis = [];
- for (var i=0; i' : sTagName;
- document.execCommand('FormatBlock', false, sTagName);
- return;
- }
-
- // fix by odoo because if you select a style in a li with no p tag all the ul is wrapped by the style tag
- var nodes = dom.listBetween(r.sc, r.ec);
- for (var i=0; i 0.05) {
- font.style.fontSize = size + "px";
- }
- }
- }
- }
-
- // remove empty values
- // we must remove the value in 2 steps (applay inherit then remove) because some
- // browser like chrome have some time an error for the rendering and/or keep inherit
- for (var i=0; i0 && (font = dom.ancestor(nodes[i-1], dom.isFont))) {
- className2 = font.getAttribute('class');
- style2 = font.getAttribute('style');
- if (node !== font && className == className2 && style == style2) {
- remove(node, font);
- nodes.splice(i,1);
- i--;
- continue;
- }
- }
- }
- }
-
- range.create(startPoint.node, startPoint.offset, endPoint.node, endPoint.offset).select();
-};
-$.summernote.pluginEvents.fontSize = function (event, editor, layoutInfo, value) {
- var $editable = layoutInfo.editable();
- event.preventDefault();
- $.summernote.pluginEvents.applyFont(event, editor, layoutInfo, null, null, value);
- editor.afterCommand($editable);
-};
-$.summernote.pluginEvents.color = function (event, editor, layoutInfo, sObjColor) {
- var $editable = layoutInfo.editable();
- var oColor = JSON.parse(sObjColor);
- var foreColor = oColor.foreColor, backColor = oColor.backColor;
-
- if (foreColor) { $.summernote.pluginEvents.foreColor(event, editor, layoutInfo, foreColor); }
- if (backColor) { $.summernote.pluginEvents.backColor(event, editor, layoutInfo, backColor); }
-};
-$.summernote.pluginEvents.foreColor = function (event, editor, layoutInfo, foreColor) {
- var $editable = layoutInfo.editable();
- $.summernote.pluginEvents.applyFont(event, editor, layoutInfo, foreColor, null, null);
- editor.afterCommand($editable);
-};
-$.summernote.pluginEvents.backColor = function (event, editor, layoutInfo, backColor) {
- var $editable = layoutInfo.editable();
- var r = range.create();
- if (!r) return;
- if (r.isCollapsed() && r.isOnCell()) {
- var cell = dom.ancestor(r.sc, dom.isCell);
- cell.className = cell.className.replace(new RegExp('(^|\\s+)bg-[^\\s]+(\\s+|$)', 'gi'), '');
- cell.style.backgroundColor = "";
- if (backColor.indexOf('bg-') !== -1) {
- cell.className += ' ' + backColor;
- } else if (backColor !== 'inherit') {
- cell.style.backgroundColor = backColor;
- }
- return;
- }
- $.summernote.pluginEvents.applyFont(event, editor, layoutInfo, null, backColor, null);
- editor.afterCommand($editable);
-};
-
-//////////////////////////////////////////////////////////////////////////////////////////////////////////
-
-options.onCreateLink = function (sLinkUrl) {
- if (sLinkUrl.indexOf('mailto:') === 0) {
- // pass
- } else if (sLinkUrl.indexOf('@') !== -1 && sLinkUrl.indexOf(':') === -1) {
- sLinkUrl = 'mailto:' + sLinkUrl;
- } else if (sLinkUrl.indexOf('://') === -1 && sLinkUrl.indexOf('/') !== 0 && sLinkUrl.indexOf('#') !== 0) {
- sLinkUrl = 'http://' + sLinkUrl;
- }
- return sLinkUrl;
-};
-
-//////////////////////////////////////////////////////////////////////////////////////////////////////////
-/* table */
-
-function summernote_table_scroll (event) {
- var r = range.create();
- if (r && r.isOnCell()) {
- $('.o_table_handler').remove();
- }
-}
-function summernote_table_update (oStyle) {
- var r = range.create();
- if (!oStyle.range || !r || !r.isOnCell() || !r.isOnCellFirst()) {
- $('.o_table_handler').remove();
- return;
- }
- var table = dom.ancestor(oStyle.range.sc, dom.isTable);
- if (!table) { // if the editable tag is inside the table
- return;
- }
- var $editable = $(table).closest('.o_editable');
-
- $('.o_table_handler').remove();
-
- var $dels = $();
- var $adds = $();
- var $tds = $('tr:first', table).children();
- $tds.each(function () {
- var $td = $(this);
- var pos = $td.offset();
-
- var $del = $(' ').appendTo('body');
- $del.data('td', this);
- $dels = $dels.add($del);
- $del.css({
- left: ((pos.left + $td.outerWidth()/2)-6) + "px",
- top: (pos.top-6) + "px"
- });
-
- var $add = $(' ').appendTo('body');
- $add.data('td', this);
- $adds = $adds.add($add);
- $add.css({
- left: (pos.left-6) + "px",
- top: (pos.top-6) + "px"
- });
- });
-
- var $last = $tds.last();
- var pos = $last.offset();
- var $add = $(' ').appendTo('body');
- $adds = $adds.add($add);
- $add.css({
- left: (pos.left+$last.outerWidth()-6) + "px",
- top: (pos.top-6) + "px"
- });
-
- var $table = $(table);
- $dels.data('table', table).on('mousedown', function (event) {
- var td = $(this).data('td');
- $editable.data('NoteHistory').recordUndo($editable);
-
- var newTd;
- if ($(td).siblings().length) {
- var eq = $(td).index();
- $table.find('tr').each(function () {
- $('> td:eq('+eq+')', this).remove();
- });
- newTd = $table.find('tr:first > td:eq('+eq+'), tr:first > td:last').first();
- } else {
- var prev = dom.lastChild(dom.hasContentBefore(dom.ancestorHavePreviousSibling($table[0])));
- $table.remove();
- $('.o_table_handler').remove();
- r = range.create(prev, prev.textContent.length);
- r.select();
- $(r.sc).trigger('mouseup');
- return;
- }
-
- $('.o_table_handler').remove();
- range.create(newTd[0], 0, newTd[0], 0).select();
- newTd.trigger('mouseup');
- });
- $adds.data('table', table).on('mousedown', function (event) {
- var td = $(this).data('td');
- $editable.data('NoteHistory').recordUndo($editable);
-
- var newTd;
- if (td) {
- var eq = $(td).index();
- $table.find('tr').each(function () {
- $('td:eq('+eq+')', this).before(''+dom.blank+' ');
- });
- newTd = $table.find('tr:first td:eq('+eq+')');
- } else {
- $table.find('tr').each(function () {
- $(this).append(''+dom.blank+' ');
- });
- newTd = $table.find('tr:first td:last');
- }
-
- $('.o_table_handler').remove();
- range.create(newTd[0], 0, newTd[0], 0).select();
- newTd.trigger('mouseup');
- });
-
- $dels.css({
- 'position': 'absolute',
- 'cursor': 'pointer',
- 'background-color': '#fff',
- 'color': '#ff0000'
- });
- $adds.css({
- 'position': 'absolute',
- 'cursor': 'pointer',
- 'background-color': '#fff',
- 'color': '#00ff00'
- });
-}
-var fn_popover_update = eventHandler.modules.popover.update;
-eventHandler.modules.popover.update = function ($popover, oStyle, isAirMode) {
- fn_popover_update.call(this, $popover, oStyle, isAirMode);
- if(!!(isAirMode ? $popover : $popover.parent()).find('.note-table').length) {
- summernote_table_update(oStyle);
- }
-};
-
-// override summernote clipboard functionality
-eventHandler.modules.clipboard.attach = function(layoutInfo) {
- var $editable = layoutInfo.editable();
- $editable.on('paste', function(e) {
- var clipboardData = ((e.originalEvent || e).clipboardData || window.clipboardData);
- // Change nothing if pasting html (copy from text editor / web / ...) or
- // if clipboardData is not available (IE / ...)
- if (clipboardData && clipboardData.types && clipboardData.types.length === 1 && clipboardData.types[0] === "text/plain") {
- e.preventDefault();
- $editable.data('NoteHistory').recordUndo($editable); // FIXME
- var pastedText = clipboardData.getData("text/plain");
- // Try removing linebreaks which are not really linebreaks (in a PDF,
- // when a sentence goes over the next line, copying it considers it
- // a linebreak for example).
- var formattedText = pastedText.replace(/([\w-])\r?\n([\w-])/g, "$1 $2").trim();
- document.execCommand("insertText", false, formattedText);
- }
- });
-};
-
-//////////////////////////////////////////////////////////////////////////////////////////////////////////
-
-var fn_attach = eventHandler.attach;
-eventHandler.attach = function (oLayoutInfo, options) {
- var $editable = oLayoutInfo.editor().hasClass('note-editable') ? oLayoutInfo.editor() : oLayoutInfo.editor().find('.note-editable');
- fn_attach.call(this, oLayoutInfo, options);
- $editable.on("scroll", summernote_table_scroll);
-};
-var fn_detach = eventHandler.detach;
-eventHandler.detach = function (oLayoutInfo, options) {
- var $editable = oLayoutInfo.editor().hasClass('note-editable') ? oLayoutInfo.editor() : oLayoutInfo.editor().find('.note-editable');
- fn_detach.call(this, oLayoutInfo, options);
- $editable.off("scroll", summernote_table_scroll);
- $('.o_table_handler').remove();
-};
-
-options.icons.image.image = "file-image-o";
-$.summernote.lang['en-US'].image.image = "File / Image";
-
-return $.summernote;
-
-});
diff --git a/addons/web_editor/static/src/js/tour.rte.js b/addons/web_editor/static/src/js/tour.rte.js
deleted file mode 100644
index da4780202d3..00000000000
--- a/addons/web_editor/static/src/js/tour.rte.js
+++ /dev/null
@@ -1,322 +0,0 @@
-odoo.define('web_editor.tour', function (require) {
- "use strict";
-
- var tour = require("web_tour.tour");
- var base = require("web_editor.base");
- var snippet_editor = require('web_editor.snippet.editor');
-
- var click_event = function (el, type) {
- var evt = document.createEvent("MouseEvents");
- evt.initMouseEvent(type, true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, el);
- el.dispatchEvent(evt);
- };
-
- tour.register("rte", {
- url: '/web_editor/field/html?callback=FieldTextHtml_0&enable_editor=1&datarecord=%7B%7D',
- test: true,
- wait_for: base.ready(),
- }, [{
- content: "Change html for this test",
- trigger: "#editable_area",
- run: function () {
- var html = '\n'+
- '\n'+
- ' \n'+
- '
\n'+
- '
\n'+
- '
Batnae municipium in Anthemusia \n'+
- '
Batnae municipium in Anthemusia conditum Macedonum manu priscorum ab Euphrate flumine brevi spatio disparatur, refertum mercatoribus opulentis, ubi annua sollemnitate prope Septembris initium mensis ad.
\n'+
- '
Quam quidem partem accusationis admiratus sum et moleste tuli potissimum esse Atratino datam. Neque enim decebat neque aetas.
\n'+
- '
Et hanc quidem praeter oppida multa duae civitates exornant Seleucia opus Seleuci regis, et Claudiopolis quam deduxit coloniam Claudius Caesar. Isaura enim antehac nimium potens, olim subversa ut rebellatrix.
'+
- '
Harum trium sententiarum nulli prorsus assentior.
\n'+
- '
\n'+
- '
\n'+
- '
\n'+
- '
\n'+
- '
\n'+
- '\n';
- this.$anchor.html(html);
- }
- }, {
- content: "simulate triple click and change text bg-color",
- trigger: '#editable_area > section .row > div:first',
- run: function () {
- var $h1 = $('h1', this.$anchor);
- $.summernote.core.range.create($h1[0].firstChild, 0, $('p', this.$anchor)[0], 0).select();
- click_event($h1[0], 'mouseup');
- }
- }, {
- content: "change text bg-color after triple click",
- trigger: '.note-popover button[data-event="color"]',
- extra_trigger: '#editable_area > section .row > div:first',
- }, {
- content: "change selection to change text color",
- trigger: '#editable_area > section .row > div:first:not(:has(p font)) h1 font',
- run: function () {
- $.summernote.core.range.create(this.$anchor[0].firstChild, 5, this.$anchor[0].firstChild, 10).select();
- click_event(this.$anchor[0], 'mouseup');
- }
- }, {
- content: "open color dropdown",
- trigger: ".note-color button.dropdown-toggle",
- }, {
- content: "change text color",
- trigger: ".btn-group.open button[data-event=foreColor]:first",
- }, {
- content: "change selection to change text bg-color again",
- trigger: '#editable_area > section .row > div:first h1 font:eq(2)',
- run: function () {
- $.summernote.core.range.create(this.$anchor.prev()[0].firstChild, 3, this.$anchor[0].firstChild, 10).select();
- click_event(this.$anchor.prev()[0], 'mouseup');
- }
- }, {
- content: "open color dropdown",
- trigger: ".note-color button.dropdown-toggle",
- }, {
- content: "change text backColor again",
- trigger: "button[data-event=backColor]:visible:first",
- }, {
- content: "change selection (h1 and p) to change text color with class",
- trigger: '#editable_area > section .row > div:first h1 font:eq(4)',
- run: function () {
- $.summernote.core.range.create(this.$anchor.prev()[0].firstChild, 3, this.$anchor.parent("h1").next("p")[0].firstChild, 30).select();
- click_event(this.$anchor.prev()[0], 'mouseup');
- }
- }, {
- content: "open color dropdown",
- trigger: ".note-color button.dropdown-toggle",
- }, {
- content: "change text color again",
- trigger: "button[data-event=foreColor]:visible:eq(3)",
- }, {
- content: "delete selection",
- trigger: '.o_editable.note-editable.o_dirty',
- extra_trigger: '#editable_area > section .row > div:first p font',
- run: "keydown 46", // delete
- }, {
- content: "create an other selection to delete",
- trigger: '#editable_area > section .row > div:first:not(:has(p font)) h1',
- extra_trigger: '.o_editable.note-editable.o_dirty',
- run: function () {
- $.summernote.core.range.createFromNode(this.$anchor.next("p")[0]).clean();
- $.summernote.core.range.create(this.$anchor.find('font:containsExact(ici)')[0].firstChild, 1, this.$anchor.next().next()[0].firstChild, 5).select();
- click_event(this.$anchor.find('font:last')[0], 'mouseup');
- },
- }, {
- content: "clean and delete (backspace) an other selection",
- trigger: '#editable_area > section .row > div:first:not(:has(p font)) h1',
- run: "keydown 8", // backspace
- }, {
- content: "an other selection",
- trigger: '#editable_area > section .row > div:first:has( font:last:containsExact(i) ):has( p:first:containsRegex(/^uam/) ) h1',
- extra_trigger: '.o_editable.note-editable.o_dirty',
- run: function () {
- $.summernote.core.range.create(this.$anchor.find('font:first')[0].firstChild, 3, this.$anchor.next("p")[0].childNodes[2], 8).select();
- click_event(this.$anchor.find('font:first')[0], 'mouseup');
- },
- }, {
- content: "delete an other selection",
- trigger: '#editable_area > section .row > div:first:has( font:last:containsExact(i) ):has( p:first:containsRegex(/^uam/) ) h1',
- run: "keydown 46",
- }, {
- content: "undo",
- trigger: '.note-image-popover button[data-event="undo"]',
- extra_trigger: '#editable_area > section .row > div:first:has( font:last:containsExact(Bat) )',
- }, {
- content: "undo again",
- trigger: '.note-air-popover button[data-event="undo"]',
- extra_trigger: '#editable_area > section .row > div:first:has( font:last:containsExact(i) )',
- }, {
- content: "delete (backspace) after undo",
- trigger: '.o_editable.note-editable.o_dirty',
- extra_trigger: '#editable_area > section .row > div:first:not(:has(p font)) h1',
- run: "keydown 8", // backspace
- }, {
- content: "click on image",
- trigger: '#editable_area > section .row > div:first img[style*="25%"]',
- extra_trigger: '#editable_area > section .row > div:first:has( font:last:containsExact(i) ):has( p:first:containsRegex(/^uam/) )',
- }, {
- content: "Click on resize half",
- trigger: '.note-image-popover:visible button[data-event="resize"][data-value="0.5"]',
- }, {
- content: "Click on edit picture",
- trigger: '.note-image-popover:visible button[data-event="showImageDialog"]',
- extra_trigger: '#editable_area > section .row > div:first img[style*="50%"]',
- }, {
- content: "Click on pictogram tab",
- trigger: 'a[data-toggle="tab"]:contains(Pictogram)',
- extra_trigger: '#editor-media-image',
- }, {
- content: "select a pictogram",
- trigger: '#editor-media-icon.active span.fa:first',
- }, {
- content: "save pictogram",
- trigger: 'button.o_save_button',
- extra_trigger: '#editor-media-icon.active span.o_selected',
- }, {
- content: "select a size for the pictogram",
- trigger: '.note-image-popover button[data-event="resizefa"][data-value="3"]',
- }, {
- content: "click on float right",
- trigger: '.note-image-popover:visible button[data-event="floatMe"][data-value="right"]',
- extra_trigger: '#wrapwrap span.fa-3x',
- }, {
- content: "click on create link",
- trigger: '.note-image-popover:visible button[data-event="showLinkDialog"]',
- extra_trigger: '#editable_area > section .row > div:first span.fa.pull-right',
- }, {
- content: "click on color style",
- trigger: '.o_link_dialog_color > .o_link_dialog_color_item.btn-success',
- extra_trigger: 'a#link-preview:containsRegex(/^
]+><\\/span>$/) > span.fa.fa-3x.pull-right',
- }, {
- content: "insert a link url",
- trigger: '#o_link_dialog_url_input',
- extra_trigger: 'a#link-preview.btn',
- run: "text http://www.odoo.com",
- }, {
- content: "change text label",
- trigger: '#o_link_dialog_label_input',
- run: "text ABC[IMG] DEF",
- }, {
- content: "save link",
- trigger: 'button.o_save_button',
- extra_trigger: 'a#link-preview.btn:containsRegex(/^ABC]+><\\/span> DEF$/)',
- }, {
- content: "click on other picture",
- trigger: '#editable_area > section .row > div:last img',
- extra_trigger: 'body:not(:has(#link-preview)) a.btn[href^="http://"]:has(span.fa.fa-3x.pull-right)',
- }, {
- content: "click on create link again",
- trigger: '.note-image-popover:visible button[data-event="showLinkDialog"]',
- extra_trigger: '#editable_area > section .row > div:first span.fa.pull-right',
- }, {
- content: "click on color style again",
- trigger: '.o_link_dialog_color > .o_link_dialog_color_item.btn-success',
- extra_trigger: 'a#link-preview:containsRegex(/^ ]+>$/) img',
- }, {
- content: "insert an email",
- trigger: '#o_link_dialog_url_input',
- extra_trigger: 'a#link-preview.btn',
- run: "text test@test.test",
- }, {
- content: "save link",
- trigger: 'button.o_save_button',
- extra_trigger: 'a#link-preview.btn[href="mailto:test@test.test"]',
- }, {
- content: "select for triple enter then double backspace",
- trigger: '#editable_area > section .row > div:first p:eq(2)',
- extra_trigger: 'body:not(:has(#link-preview)) #editable_area > section .row > div:eq(1) > a > img',
- run: function () {
- var p = this.$anchor[0].firstChild;
- $.summernote.core.range.create(p, p.textContent.length, p, p.textContent.length).select();
- click_event(p, 'mouseup');
- },
- }, {
- content: "triple enter then double backspace",
- trigger: '#editable_area > section .row > div:first p:eq(2)',
- run: "keydown 66,13,66,13,13,8,8", // B enter B enter enter backspace backspace
- }, {
- content: "add ul content",
- trigger: '#editable_area > section .row > div:first',
- extra_trigger: 'body:not(:has(#editable_area > section .row > div:first p:eq(5), #editable_area > section .row > div:eq(3))) #editable_area > section .row > div:first p:eq(3)',
- run: function () {
- var html = ' '+
- '\n Batnae municipium.
'+
- '\n Seleucia praeter. '+
- '\n Et hanc quidem.
'+
- '\n ';
- this.$anchor.append(html);
- var node = this.$anchor.find('ul li p:last')[0].firstChild;
- $.summernote.core.range.create(node, 6).select();
- click_event(node, 'mouseup');
- }
- }, {
- content: "click on style dropdown",
- trigger: '.note-air-popover .note-style button.dropdown-toggle',
- extra_trigger: '#editable_area > section .row > div:first ul li p:first',
- }, {
- content: "select h3",
- trigger: '.note-air-popover .note-style ul:visible a[data-value="h3"]',
- }, {
- content: "select h3",
- trigger: '#editable_area > section .row > div:first > ul > li > h3',
- run: function () {
- var node = this.$anchor[0].firstChild;
- $.summernote.core.range.create(node, 0).select();
- click_event(node, 'mouseup');
- }
- }, {
- content: "double tabulation",
- trigger: '#editable_area > section .row > div:first > ul > li > h3',
- run: "keydown 9,9", // tabulation
- }, {
- content: "click on order list",
- trigger: '.note-air-popover button[data-event="insertOrderedList"]',
- extra_trigger: '#editable_area > section .row > div:first ul > li > ul > li > ul > li > h3',
- }, {
- content: "select for enter in ul",
- trigger: '#editable_area > section .row > div:first ul li > p:last',
- run: function () {
- this.$anchor[0].firstChild.textContent += "";
- $.summernote.core.range.create(this.$anchor[0].firstChild, 7).select();
- click_event(this.$anchor[0], 'mouseup');
- }
- }, {
- content: "enter in ul",
- trigger: '#editable_area > section .row > div:first ul li > p:last',
- run: "keydown 66,13", // enter
- }, {
- trigger: '#editable_area > section .row > div:first ul li > p:eq(1):containsRegex(/^municipium./)',
- content: "backspace in list",
- run: "keydown 8",
- }, {
- content: "end",
- trigger: '#editable_area > section .row > div:first ul li p:eq(0):containsRegex(/^Batnae Bmunicipium.$/)',
- }]);
-
- tour.register('rte_inline', {
- url: '/web_editor/field/html/inline?callback=FieldTextHtml_0&enable_editor=1&datarecord=%7B%7D',
- test: true,
- wait_for: base.ready(),
- }, [{
- content: "Change html for this test",
- trigger: "#editable_area",
- run: function () {
- var html = '\n'+
- '\n'+
- '
\n'+
- ' \n'+
- ' \n'+
- ' \n'+
- ' \n'+
- ' \n'+
- ' \n'+
- ' \n'+
- ' \n'+
- ' \n'+
- ' \n'+
- ' \n'+
- '
\n'+
- '
';
- this.$anchor.html(html);
- }
- }, {
- content: "call clean for save",
- trigger: '#wrapwrap table',
- run: function () {
- snippet_editor.instance.clean_for_save();
- }
- }, {
- content: "check the image style",
- trigger: '#wrapwrap img:first[width][height][style*="-radius"][style*="1px"][style*="padding"]',
- }, {
- content: "check the font image src",
- trigger: '#wrapwrap img:eq(1)[src^="/web_editor/font_to_img/"][src$="/rgb(51,122,183)/28"]',
- }, {
- content: "check the font class to css",
- trigger: '#wrapwrap img:eq(1)[height]:not([class*="fa"])',
- }, {
- content: "check the second font class to css",
- trigger: '#wrapwrap img:eq(2)[style*="float: right"]',
- }]);
-});
diff --git a/addons/web_editor/static/src/js/tours/rte.js b/addons/web_editor/static/src/js/tours/rte.js
new file mode 100644
index 00000000000..da4780202d3
--- /dev/null
+++ b/addons/web_editor/static/src/js/tours/rte.js
@@ -0,0 +1,322 @@
+odoo.define('web_editor.tour', function (require) {
+ "use strict";
+
+ var tour = require("web_tour.tour");
+ var base = require("web_editor.base");
+ var snippet_editor = require('web_editor.snippet.editor');
+
+ var click_event = function (el, type) {
+ var evt = document.createEvent("MouseEvents");
+ evt.initMouseEvent(type, true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, el);
+ el.dispatchEvent(evt);
+ };
+
+ tour.register("rte", {
+ url: '/web_editor/field/html?callback=FieldTextHtml_0&enable_editor=1&datarecord=%7B%7D',
+ test: true,
+ wait_for: base.ready(),
+ }, [{
+ content: "Change html for this test",
+ trigger: "#editable_area",
+ run: function () {
+ var html = '\n'+
+ '\n'+
+ ' \n'+
+ '
\n'+
+ '
\n'+
+ '
Batnae municipium in Anthemusia \n'+
+ '
Batnae municipium in Anthemusia conditum Macedonum manu priscorum ab Euphrate flumine brevi spatio disparatur, refertum mercatoribus opulentis, ubi annua sollemnitate prope Septembris initium mensis ad.
\n'+
+ '
Quam quidem partem accusationis admiratus sum et moleste tuli potissimum esse Atratino datam. Neque enim decebat neque aetas.
\n'+
+ '
Et hanc quidem praeter oppida multa duae civitates exornant Seleucia opus Seleuci regis, et Claudiopolis quam deduxit coloniam Claudius Caesar. Isaura enim antehac nimium potens, olim subversa ut rebellatrix.
'+
+ '
Harum trium sententiarum nulli prorsus assentior.
\n'+
+ '
\n'+
+ '
\n'+
+ '
\n'+
+ '
\n'+
+ '
\n'+
+ '\n';
+ this.$anchor.html(html);
+ }
+ }, {
+ content: "simulate triple click and change text bg-color",
+ trigger: '#editable_area > section .row > div:first',
+ run: function () {
+ var $h1 = $('h1', this.$anchor);
+ $.summernote.core.range.create($h1[0].firstChild, 0, $('p', this.$anchor)[0], 0).select();
+ click_event($h1[0], 'mouseup');
+ }
+ }, {
+ content: "change text bg-color after triple click",
+ trigger: '.note-popover button[data-event="color"]',
+ extra_trigger: '#editable_area > section .row > div:first',
+ }, {
+ content: "change selection to change text color",
+ trigger: '#editable_area > section .row > div:first:not(:has(p font)) h1 font',
+ run: function () {
+ $.summernote.core.range.create(this.$anchor[0].firstChild, 5, this.$anchor[0].firstChild, 10).select();
+ click_event(this.$anchor[0], 'mouseup');
+ }
+ }, {
+ content: "open color dropdown",
+ trigger: ".note-color button.dropdown-toggle",
+ }, {
+ content: "change text color",
+ trigger: ".btn-group.open button[data-event=foreColor]:first",
+ }, {
+ content: "change selection to change text bg-color again",
+ trigger: '#editable_area > section .row > div:first h1 font:eq(2)',
+ run: function () {
+ $.summernote.core.range.create(this.$anchor.prev()[0].firstChild, 3, this.$anchor[0].firstChild, 10).select();
+ click_event(this.$anchor.prev()[0], 'mouseup');
+ }
+ }, {
+ content: "open color dropdown",
+ trigger: ".note-color button.dropdown-toggle",
+ }, {
+ content: "change text backColor again",
+ trigger: "button[data-event=backColor]:visible:first",
+ }, {
+ content: "change selection (h1 and p) to change text color with class",
+ trigger: '#editable_area > section .row > div:first h1 font:eq(4)',
+ run: function () {
+ $.summernote.core.range.create(this.$anchor.prev()[0].firstChild, 3, this.$anchor.parent("h1").next("p")[0].firstChild, 30).select();
+ click_event(this.$anchor.prev()[0], 'mouseup');
+ }
+ }, {
+ content: "open color dropdown",
+ trigger: ".note-color button.dropdown-toggle",
+ }, {
+ content: "change text color again",
+ trigger: "button[data-event=foreColor]:visible:eq(3)",
+ }, {
+ content: "delete selection",
+ trigger: '.o_editable.note-editable.o_dirty',
+ extra_trigger: '#editable_area > section .row > div:first p font',
+ run: "keydown 46", // delete
+ }, {
+ content: "create an other selection to delete",
+ trigger: '#editable_area > section .row > div:first:not(:has(p font)) h1',
+ extra_trigger: '.o_editable.note-editable.o_dirty',
+ run: function () {
+ $.summernote.core.range.createFromNode(this.$anchor.next("p")[0]).clean();
+ $.summernote.core.range.create(this.$anchor.find('font:containsExact(ici)')[0].firstChild, 1, this.$anchor.next().next()[0].firstChild, 5).select();
+ click_event(this.$anchor.find('font:last')[0], 'mouseup');
+ },
+ }, {
+ content: "clean and delete (backspace) an other selection",
+ trigger: '#editable_area > section .row > div:first:not(:has(p font)) h1',
+ run: "keydown 8", // backspace
+ }, {
+ content: "an other selection",
+ trigger: '#editable_area > section .row > div:first:has( font:last:containsExact(i) ):has( p:first:containsRegex(/^uam/) ) h1',
+ extra_trigger: '.o_editable.note-editable.o_dirty',
+ run: function () {
+ $.summernote.core.range.create(this.$anchor.find('font:first')[0].firstChild, 3, this.$anchor.next("p")[0].childNodes[2], 8).select();
+ click_event(this.$anchor.find('font:first')[0], 'mouseup');
+ },
+ }, {
+ content: "delete an other selection",
+ trigger: '#editable_area > section .row > div:first:has( font:last:containsExact(i) ):has( p:first:containsRegex(/^uam/) ) h1',
+ run: "keydown 46",
+ }, {
+ content: "undo",
+ trigger: '.note-image-popover button[data-event="undo"]',
+ extra_trigger: '#editable_area > section .row > div:first:has( font:last:containsExact(Bat) )',
+ }, {
+ content: "undo again",
+ trigger: '.note-air-popover button[data-event="undo"]',
+ extra_trigger: '#editable_area > section .row > div:first:has( font:last:containsExact(i) )',
+ }, {
+ content: "delete (backspace) after undo",
+ trigger: '.o_editable.note-editable.o_dirty',
+ extra_trigger: '#editable_area > section .row > div:first:not(:has(p font)) h1',
+ run: "keydown 8", // backspace
+ }, {
+ content: "click on image",
+ trigger: '#editable_area > section .row > div:first img[style*="25%"]',
+ extra_trigger: '#editable_area > section .row > div:first:has( font:last:containsExact(i) ):has( p:first:containsRegex(/^uam/) )',
+ }, {
+ content: "Click on resize half",
+ trigger: '.note-image-popover:visible button[data-event="resize"][data-value="0.5"]',
+ }, {
+ content: "Click on edit picture",
+ trigger: '.note-image-popover:visible button[data-event="showImageDialog"]',
+ extra_trigger: '#editable_area > section .row > div:first img[style*="50%"]',
+ }, {
+ content: "Click on pictogram tab",
+ trigger: 'a[data-toggle="tab"]:contains(Pictogram)',
+ extra_trigger: '#editor-media-image',
+ }, {
+ content: "select a pictogram",
+ trigger: '#editor-media-icon.active span.fa:first',
+ }, {
+ content: "save pictogram",
+ trigger: 'button.o_save_button',
+ extra_trigger: '#editor-media-icon.active span.o_selected',
+ }, {
+ content: "select a size for the pictogram",
+ trigger: '.note-image-popover button[data-event="resizefa"][data-value="3"]',
+ }, {
+ content: "click on float right",
+ trigger: '.note-image-popover:visible button[data-event="floatMe"][data-value="right"]',
+ extra_trigger: '#wrapwrap span.fa-3x',
+ }, {
+ content: "click on create link",
+ trigger: '.note-image-popover:visible button[data-event="showLinkDialog"]',
+ extra_trigger: '#editable_area > section .row > div:first span.fa.pull-right',
+ }, {
+ content: "click on color style",
+ trigger: '.o_link_dialog_color > .o_link_dialog_color_item.btn-success',
+ extra_trigger: 'a#link-preview:containsRegex(/^
]+><\\/span>$/) > span.fa.fa-3x.pull-right',
+ }, {
+ content: "insert a link url",
+ trigger: '#o_link_dialog_url_input',
+ extra_trigger: 'a#link-preview.btn',
+ run: "text http://www.odoo.com",
+ }, {
+ content: "change text label",
+ trigger: '#o_link_dialog_label_input',
+ run: "text ABC[IMG] DEF",
+ }, {
+ content: "save link",
+ trigger: 'button.o_save_button',
+ extra_trigger: 'a#link-preview.btn:containsRegex(/^ABC]+><\\/span> DEF$/)',
+ }, {
+ content: "click on other picture",
+ trigger: '#editable_area > section .row > div:last img',
+ extra_trigger: 'body:not(:has(#link-preview)) a.btn[href^="http://"]:has(span.fa.fa-3x.pull-right)',
+ }, {
+ content: "click on create link again",
+ trigger: '.note-image-popover:visible button[data-event="showLinkDialog"]',
+ extra_trigger: '#editable_area > section .row > div:first span.fa.pull-right',
+ }, {
+ content: "click on color style again",
+ trigger: '.o_link_dialog_color > .o_link_dialog_color_item.btn-success',
+ extra_trigger: 'a#link-preview:containsRegex(/^ ]+>$/) img',
+ }, {
+ content: "insert an email",
+ trigger: '#o_link_dialog_url_input',
+ extra_trigger: 'a#link-preview.btn',
+ run: "text test@test.test",
+ }, {
+ content: "save link",
+ trigger: 'button.o_save_button',
+ extra_trigger: 'a#link-preview.btn[href="mailto:test@test.test"]',
+ }, {
+ content: "select for triple enter then double backspace",
+ trigger: '#editable_area > section .row > div:first p:eq(2)',
+ extra_trigger: 'body:not(:has(#link-preview)) #editable_area > section .row > div:eq(1) > a > img',
+ run: function () {
+ var p = this.$anchor[0].firstChild;
+ $.summernote.core.range.create(p, p.textContent.length, p, p.textContent.length).select();
+ click_event(p, 'mouseup');
+ },
+ }, {
+ content: "triple enter then double backspace",
+ trigger: '#editable_area > section .row > div:first p:eq(2)',
+ run: "keydown 66,13,66,13,13,8,8", // B enter B enter enter backspace backspace
+ }, {
+ content: "add ul content",
+ trigger: '#editable_area > section .row > div:first',
+ extra_trigger: 'body:not(:has(#editable_area > section .row > div:first p:eq(5), #editable_area > section .row > div:eq(3))) #editable_area > section .row > div:first p:eq(3)',
+ run: function () {
+ var html = ' '+
+ '\n Batnae municipium.
'+
+ '\n Seleucia praeter. '+
+ '\n Et hanc quidem.
'+
+ '\n ';
+ this.$anchor.append(html);
+ var node = this.$anchor.find('ul li p:last')[0].firstChild;
+ $.summernote.core.range.create(node, 6).select();
+ click_event(node, 'mouseup');
+ }
+ }, {
+ content: "click on style dropdown",
+ trigger: '.note-air-popover .note-style button.dropdown-toggle',
+ extra_trigger: '#editable_area > section .row > div:first ul li p:first',
+ }, {
+ content: "select h3",
+ trigger: '.note-air-popover .note-style ul:visible a[data-value="h3"]',
+ }, {
+ content: "select h3",
+ trigger: '#editable_area > section .row > div:first > ul > li > h3',
+ run: function () {
+ var node = this.$anchor[0].firstChild;
+ $.summernote.core.range.create(node, 0).select();
+ click_event(node, 'mouseup');
+ }
+ }, {
+ content: "double tabulation",
+ trigger: '#editable_area > section .row > div:first > ul > li > h3',
+ run: "keydown 9,9", // tabulation
+ }, {
+ content: "click on order list",
+ trigger: '.note-air-popover button[data-event="insertOrderedList"]',
+ extra_trigger: '#editable_area > section .row > div:first ul > li > ul > li > ul > li > h3',
+ }, {
+ content: "select for enter in ul",
+ trigger: '#editable_area > section .row > div:first ul li > p:last',
+ run: function () {
+ this.$anchor[0].firstChild.textContent += "";
+ $.summernote.core.range.create(this.$anchor[0].firstChild, 7).select();
+ click_event(this.$anchor[0], 'mouseup');
+ }
+ }, {
+ content: "enter in ul",
+ trigger: '#editable_area > section .row > div:first ul li > p:last',
+ run: "keydown 66,13", // enter
+ }, {
+ trigger: '#editable_area > section .row > div:first ul li > p:eq(1):containsRegex(/^municipium./)',
+ content: "backspace in list",
+ run: "keydown 8",
+ }, {
+ content: "end",
+ trigger: '#editable_area > section .row > div:first ul li p:eq(0):containsRegex(/^Batnae Bmunicipium.$/)',
+ }]);
+
+ tour.register('rte_inline', {
+ url: '/web_editor/field/html/inline?callback=FieldTextHtml_0&enable_editor=1&datarecord=%7B%7D',
+ test: true,
+ wait_for: base.ready(),
+ }, [{
+ content: "Change html for this test",
+ trigger: "#editable_area",
+ run: function () {
+ var html = '\n'+
+ '\n'+
+ '
\n'+
+ ' \n'+
+ ' \n'+
+ ' \n'+
+ ' \n'+
+ ' \n'+
+ ' \n'+
+ ' \n'+
+ ' \n'+
+ ' \n'+
+ ' \n'+
+ ' \n'+
+ '
\n'+
+ '
';
+ this.$anchor.html(html);
+ }
+ }, {
+ content: "call clean for save",
+ trigger: '#wrapwrap table',
+ run: function () {
+ snippet_editor.instance.clean_for_save();
+ }
+ }, {
+ content: "check the image style",
+ trigger: '#wrapwrap img:first[width][height][style*="-radius"][style*="1px"][style*="padding"]',
+ }, {
+ content: "check the font image src",
+ trigger: '#wrapwrap img:eq(1)[src^="/web_editor/font_to_img/"][src$="/rgb(51,122,183)/28"]',
+ }, {
+ content: "check the font class to css",
+ trigger: '#wrapwrap img:eq(1)[height]:not([class*="fa"])',
+ }, {
+ content: "check the second font class to css",
+ trigger: '#wrapwrap img:eq(2)[style*="float: right"]',
+ }]);
+});
diff --git a/addons/web_editor/static/src/js/transcoder.js b/addons/web_editor/static/src/js/transcoder.js
deleted file mode 100644
index 6b42e028b3a..00000000000
--- a/addons/web_editor/static/src/js/transcoder.js
+++ /dev/null
@@ -1,234 +0,0 @@
-odoo.define('web_editor.transcoder', function (require) {
-'use strict';
-
-var widget = require('web_editor.widget');
-
-var rulesCache = [];
-var getMatchedCSSRules = function (a) {
- if (!rulesCache.length) {
- var sheets = document.styleSheets;
- for(var i = sheets.length-1; i >= 0 ; i--) {
- var rules;
- if (sheets[i].rules) {
- rules = sheets[i].rules;
- } else {
- //try...catch because Firefox not able to enumerate document.styleSheets[].cssRules[] for cross-domain stylesheets.
- try {
- rules = sheets[i].cssRules;
- } catch(e) {
- console.warn("Can't read the css rules of: " + sheets[i].href, e);
- continue;
- }
- rules = sheets[i].cssRules;
- }
- if (rules) {
- for(var r = rules.length-1; r >= 0; r--) {
- var selectorText = rules[r].selectorText;
- if (selectorText &&
- rules[r].cssText &&
- selectorText !== "*" &&
- selectorText.indexOf(":hover") === -1 &&
- selectorText.indexOf(":before") === -1 &&
- selectorText.indexOf(":after") === -1 &&
- selectorText.indexOf(":active") === -1 &&
- selectorText.indexOf(":link") === -1 &&
- selectorText.indexOf("::") === -1 &&
- selectorText.indexOf("\"") === -1 &&
- selectorText.indexOf("'") === -1) {
- var st = selectorText.split(/\s*,\s*/);
- for (var k=0; k property: [top/right/bottom/left value];
- style[p] = style[p+'-top'];
- }
- else {
- // keep => property: [top value] [right value] [bottom value] [left value];
- style[p] = (style[p+'-top'] || 0) + ' ' + (style[p+'-right'] || 0) + ' ' + (style[p+'-bottom'] || 0) + ' ' + (style[p+'-left'] || 0);
- if (style[p].indexOf('inherit') !== -1 || style[p].indexOf('initial') !== -1) {
- // keep => property-top: [top value]; property-right: [right value]; property-bottom: [bottom value]; property-left: [left value];
- delete style[p];
- return;
- }
- }
- delete style[p+'-top'];
- delete style[p+'-right'];
- delete style[p+'-bottom'];
- delete style[p+'-left'];
- }
- });
-
- // text-decoration rule is decomposed in -line, -color and -style. This is
- // however not supported by many browser/mail clients and the editor does
- // not allow to change -color and -style rule anyway
- if (style['text-decoration-line']) {
- style['text-decoration'] = style['text-decoration-line'];
- delete style['text-decoration-line'];
- delete style['text-decoration-color'];
- delete style['text-decoration-style'];
- }
-
- return style;
-};
-
-// convert font awsome into image
-var font_to_img = function ($editable) {
- $editable.find(".fa").each(function () {
- var $font = $(this);
- var icon, content;
- _.find(widget.fontIcons, function (font) {
- return _.find(widget.getCssSelectors(font.parser), function (css) {
- if ($font.is(css[0].replace(/::?before/g, ''))) {
- icon = css[2].split("-").shift();
- content = css[1].match(/content:\s*['"]?(.)['"]?/)[1];
- return true;
- }
- });
- });
- if (content) {
- var color = $font.css("color").replace(/\s/g, '');
- $font.replaceWith($(" ", {
- "src": _.str.sprintf('/web_editor/font_to_img/%s/%s/%s', content.charCodeAt(0), window.encodeURI(color), Math.max(1, $font.height())),
- "data-class": $font.attr("class"),
- "data-style": $font.attr("style"),
- "class": $font.attr("class").replace(new RegExp("(^|\\s+)" + icon + "(-[^\\s]+)?", "gi"), ''), // remove inline font-awsome style
- "style": $font.attr("style")
- }).css({height: "auto", width: "auto"}));
- } else {
- $font.remove();
- }
- });
-};
-// convert image into font awsome
-var img_to_font = function ($editable) {
- $editable.find("img[src*='/web_editor/font_to_img/']").each(function () {
- var $img = $(this);
- $img.replaceWith($(" ", {
- "class": $img.data("class"),
- "style": $img.data("style")
- }));
- });
-};
-
-// convert class into inline style to send by mail
-var class_to_style = function ($editable) {
- if (!rulesCache.length) {
- getMatchedCSSRules($editable[0]);
- }
- $editable.find('*').each(function () {
- var $target = $(this);
- var css = getMatchedCSSRules(this);
- var style = $target.attr("style") || "";
- _.each(css, function (v,k) {
- if (!(new RegExp('(^|;)\s*' + k).test(style))) {
- style = k+":"+v+";"+style;
- }
- });
- if (_.isEmpty(style)) {
- $target.removeAttr("style");
- } else {
- $target.attr("style", style);
- }
- });
-};
-// convert style into inline class from mail
-var style_to_class = function ($editable) {
- getMatchedCSSRules($editable[0]);
-
- var $c = $(' ').appendTo("body");
-
- $editable.find('*').each(function () {
- var $target = $(this);
- var css = getMatchedCSSRules(this);
- var style = "";
- _.each(css, function (v,k) {
- if (!(new RegExp('(^|;)\s*' + k).test(style))) {
- style = k+":"+v+";"+style;
- }
- });
- css = ($c.attr("style", style).attr("style") || "").split(/\s*;\s*/);
- style = $target.attr("style") || "";
- _.each(css, function (v) {
- style = style.replace(v, '');
- });
- style = style.replace(/;+(\s;)*/g, ';').replace(/^;/g, '');
- if (style !== '') {
- $target.attr("style", style);
- } else {
- $target.removeAttr("style");
- }
- });
- $c.remove();
-};
-
-return {
- 'font_to_img': font_to_img,
- 'img_to_font': img_to_font,
- 'class_to_style': class_to_style,
- 'style_to_class': style_to_class,
-};
-
-});
diff --git a/addons/web_editor/static/src/js/translator.js b/addons/web_editor/static/src/js/translator.js
deleted file mode 100644
index e82ebbb5b81..00000000000
--- a/addons/web_editor/static/src/js/translator.js
+++ /dev/null
@@ -1,319 +0,0 @@
-odoo.define('web_editor.translate', function (require) {
-'use strict';
-
-var core = require('web.core');
-var ajax = require('web.ajax');
-var Widget = require('web.Widget');
-var base = require('web_editor.base');
-var rte = require('web_editor.rte');
-var editor_widget = require('web_editor.widget');
-
-var _t = core._t;
-
-var translatable = !!$('html').data('translatable');
-var edit_translations = !!$('html').data('edit_translations');
-
-var RTE_Translate = rte.Class.extend({
- saveElement: function ($el, context) {
- // remove multi edition
- var key = $el.data('oe-translation-id') ? 'translation:'+$el.data('oe-translation-id') :
- ($el.data('oe-model') ? $el.data('oe-model')+":"+$el.data('oe-id')+":"+$el.data('oe-field')+":"+$el.data('oe-type')+":"+$el.data('oe-expression') : false);
- if (!key || this.__saved[key]) return true;
- this.__saved[key] = true;
-
- if ($el.data('oe-translation-id')) {
- var translation_content = this.getEscapedElement($el).html();
-
- return ajax.jsonRpc('/web/dataset/call', 'call', {
- model: 'ir.translation',
- method: 'save_html',
- args: [
- [+$el.data('oe-translation-id')],
- translation_content,
- context || base.get_context()
- ],
- });
- } else {
- var markup = this.getEscapedElement($el).prop('outerHTML');
-
- return ajax.jsonRpc('/web/dataset/call', 'call', {
- model: 'ir.ui.view',
- method: 'save',
- args: [
- $el.data('oe-id'),
- markup,
- $el.data('oe-xpath') || null,
- context || base.get_context()
- ],
- });
- }
- },
-});
-
-var Translate_Modal = editor_widget.Dialog.extend({
- init: function (p, options, parent, node) {
- this._super(p, _.extend({}, {
- title: _t("Translate Attribute"),
- buttons: [
- {text: _t("Close"), classes: "btn-primary o_save_button", close: true, click: this.save}
- ]
- }, options || {}));
- this.parent = parent;
- this.$target = $(node);
- this.translation = $(node).data('translation');
- },
- start: function () {
- var self = this;
- var def = this._super.apply(this, arguments);
- var $group = $("
", {"class": "form-group"}).appendTo(this.$el);
- _.each(this.translation, function (node, attr) {
- var $node = $(node);
- var $label = $(' ').text(attr);
- var $input = $(' ').val($node.html());
- $input.on('change keyup', function () {
- var value = $input.val();
- $node.html(value).trigger('change', node);
- $node.data('$node').attr($node.data('attribute'), value).trigger('translate');
- self.parent.rte_changed(node);
- });
- $group.append($label).append($input);
- });
- return def;
- }
-});
-
-var Translate = Widget.extend({
- events: {
- 'click [data-action="save"]': 'save_and_reload',
- 'click [data-action="cancel"]': 'cancel',
- },
- template: 'web_editor.editorbar',
- init: function (parent, $target, lang) {
- this.parent = parent;
- this.lang = lang || base.get_context().lang;
- this.setTarget($target);
- this._super.apply(this, arguments);
-
- this.rte = new RTE_Translate(this, this.config);
- this.rte.on('change', this, this.rte_changed);
- },
- start: function () {
- this._super.apply(this, arguments);
- this.$('#web_editor-toolbars').remove();
- return this.edit();
- },
- setTarget: function ($target) {
- var $edit = $target.find('[data-oe-translation-id], [data-oe-model][data-oe-id][data-oe-field]');
- $edit.filter(':has([data-oe-translation-id], [data-oe-model][data-oe-id][data-oe-field])').attr('data-oe-readonly', true);
-
- this.$target = $edit.not('[data-oe-readonly]');
-
- // attributes
-
- var attrs = ['placeholder', 'title', 'alt'];
- _.each(attrs, function (attr) {
- $target.find('['+attr+'*="data-oe-translation-id="]').filter(':empty, input, select, textarea, img').each(function () {
- var $node = $(this);
- var translation = $node.data('translation') || {};
- var trans = $node.attr(attr);
- var match = trans.match(/]*data-oe-translation-id="([0-9]+)"[^>]*>(.*)<\/span>/);
- var $trans = $(trans).addClass('hidden o_editable o_editable_translatable_attribute').appendTo('body');
- $trans.data('$node', $node).data('attribute', attr);
- translation[attr] = $trans[0];
- $node.attr(attr, match[2]);
-
- var select2 = $node.data('select2');
- if (select2) {
- select2.blur();
- $node.on('translate', function () {
- select2.blur();
- });
- $node = select2.container.find('input');
- }
- $node.addClass('o_translatable_attribute').data('translation', translation);
- });
- });
- this.$target_attr = $target.find('.o_translatable_attribute');
- this.$target_attribute = $('.o_editable_translatable_attribute');
- },
- find: function (selector) {
- return selector ? this.$target.find(selector).addBack().filter(selector) : this.$target;
- },
- edit: function () {
- var flag = false;
- window.onbeforeunload = function (event) {
- if ($('.o_editable.o_dirty').length && !flag) {
- flag = true;
- setTimeout(function () {flag=false;},0);
- return _t('This document is not saved!');
- }
- };
- this.$target.addClass("o_editable");
- this.rte.start();
- this.translations = [];
- this.markTranslatableNodes();
- this.onTranslateReady();
- },
- onTranslateReady: function () {
- this.$el.show();
- this.trigger("edit");
- },
- rte_changed: function (node) {
- var $node = $(node);
- $node.find("p").each(function () { // remove
element which might have been inserted because of copy-paste
- var $p = $(this);
- $p.after($p.html()).remove();
- });
- var trans = this.getTranlationObject($node[0]);
- $node.toggleClass('o_dirty', trans.value !== $node.html().replace(/[ \t\n\r]+/, ' '));
- },
- getTranlationObject: function (node) {
- var $node = $(node);
- var id = +$node.data('oe-translation-id');
- if (!id) {
- id = $node.data('oe-model')+','+$node.data('oe-id')+','+$node.data('oe-field');
- }
- var trans = _.find(this.translations, function (trans) {
- return trans.id === id;
- });
- if (!trans) {
- this.translations.push(trans = {'id': id});
- }
- return trans;
- },
- __unbind_click: function (event) {
- if (event.ctrlKey || !$(event.target).is(':o_editable')) {
- return;
- }
- event.preventDefault();
- event.stopPropagation();
- },
- __translate_attribute: function (event) {
- if (event.ctrlKey) {
- return;
- }
- event.preventDefault();
- event.stopPropagation();
- if (event.type !== 'mousedown') {
- return;
- }
-
- new Translate_Modal(null, {}, event.data, event.target).open();
- },
- markTranslatableNodes: function (node) {
- var self = this;
- this.$target.add(this.$target_attribute).each(function () {
- var $node = $(this);
- var trans = self.getTranlationObject(this);
- trans.value = (trans.value ? trans.value : $node.html() ).replace(/[ \t\n\r]+/, ' ');
- });
- this.$target.parent().prependEvent('click.translator', this, this.__unbind_click);
-
- // attributes
-
- this.$target_attr.each(function () {
- var $node = $(this);
- var translation = $node.data('translation');
- _.each(translation, function (node, attr) {
- var trans = self.getTranlationObject(node);
- trans.value = (trans.value ? trans.value : $node.html() ).replace(/[ \t\n\r]+/, ' ');
- $node.attr('data-oe-translation-state', (trans.state || 'to_translate'));
- });
- });
-
- this.$target_attr.prependEvent('mousedown.translator click.translator mouseup.translator', this, this.__translate_attribute);
-
- console.info('Click on CTRL when you click in an translatable area to have the default behavior');
- },
- unarkTranslatableNode: function () {
- this.$target.removeClass('o_editable').removeAttr('contentEditable');
- this.$target.parent().off('.translator');
- this.$target_attr.off('.translator');
- },
- save_and_reload: function () {
- return this.save().then(function () {
- window.location.href = window.location.href.replace(/&?edit_translations(=[^&]*)?/g, '');
- });
- },
- save: function () {
- var context = base.get_context();
- context.lang = this.lang;
- return this.rte.save(context);
- },
- cancel: function () {
- var self = this;
- this.rte.cancel();
- this.$target.each(function () {
- $(this).html(self.getTranlationObject(this).value);
- });
- this.unarkTranslatableNode();
- this.trigger("cancel");
- this.$el.hide();
- window.onbeforeunload = null;
- },
- destroy: function () {
- this.cancel();
- this.$el.remove();
- this._super.apply(this, arguments);
- },
-
- config: function ($editable) {
- if ($editable.data('oe-model')) {
- return {
- 'airMode' : true,
- 'focus': false,
- 'airPopover': [
- ['history', ['undo', 'redo']],
- ],
- 'styleWithSpan': false,
- 'inlinemedia' : ['p'],
- 'lang': "odoo",
- 'onChange': function (html, $editable) {
- $editable.trigger("content_changed");
- }
- };
- }
- return {
- 'airMode' : true,
- 'focus': false,
- 'airPopover': [
- ['font', ['bold', 'italic', 'underline', 'clear']],
- ['fontsize', ['fontsize']],
- ['color', ['color']],
- ['history', ['undo', 'redo']],
- ],
- 'styleWithSpan': false,
- 'inlinemedia' : ['p'],
- 'lang': "odoo",
- 'onChange': function (html, $editable) {
- $editable.trigger("content_changed");
- }
- };
- }
-});
-
-
-if (edit_translations) {
- base.ready().then(function () {
- data.instance = new Translate(this, $('#wrapwrap'));
- data.instance.prependTo(document.body);
-
- $('a[href*=edit_translations]').each(function () {
- this.href = this.href.replace(/[$?]edit_translations[^&?]+/, '');
- });
- $('form[action*=edit_translations]').each(function () {
- this.action = this.action.replace(/[$?]edit_translations[^&?]+/, '');
- });
-
- $('title').html($('title').html().replace(/<span data-oe-model.+?>(.+?)<\/span>/, '\$1'));
- });
-}
-
-var data = {
- 'translatable': translatable,
- 'edit_translations': edit_translations,
- 'Class': Translate,
-};
-return data;
-
-});
diff --git a/addons/web_editor/static/src/js/widgets.js b/addons/web_editor/static/src/js/widgets.js
deleted file mode 100644
index 3bfae6ce240..00000000000
--- a/addons/web_editor/static/src/js/widgets.js
+++ /dev/null
@@ -1,1177 +0,0 @@
-odoo.define('web_editor.widget', function (require) {
-'use strict';
-
-var core = require('web.core');
-var ajax = require('web.ajax');
-var Dialog = require('web.Dialog');
-var Widget = require('web.Widget');
-var base = require('web_editor.base');
-var rte = require('web_editor.rte');
-
-var QWeb = core.qweb;
-var range = $.summernote.core.range;
-var dom = $.summernote.core.dom;
-
-var _t = core._t;
-
-/**
- * Extend Dialog class to handle save/cancel of edition components.
- */
-Dialog = Dialog.extend({
- init: function (parent, options) {
- options = options || {};
- this._super(parent, _.extend({}, {
- buttons: [
- {text: options.save_text || _t("Save"), classes: "btn-primary o_save_button", click: this.save},
- {text: _t("Discard"), close: true}
- ]
- }, options));
-
- this.destroyAction = "cancel";
-
- var self = this;
- this.opened().then(function () {
- self.$('input:first').focus();
- });
- this.on("closed", this, function () {
- this.trigger(this.destroyAction, this.final_data || null);
- });
- },
- save: function () {
- this.destroyAction = "save";
- this.close();
- },
-});
-
-/**
- * alt widget. Lets users change a alt & title on a media
- */
-var alt = Dialog.extend({
- template: 'web_editor.dialog.alt',
- init: function (parent, options, $editable, media) {
- this._super(parent, _.extend({}, {
- title: _t("Change media description and tooltip")
- }, options));
- this.$editable = $editable;
- this.media = media;
- this.alt = ($(this.media).attr('alt') || "").replace(/"/g, '"');
- this.title = ($(this.media).attr('title') || "").replace(/"/g, '"');
- },
- save: function () {
- range.createFromNode(this.media).select();
- this.$editable.data('NoteHistory').recordUndo();
- var alt = this.$('#alt').val();
- var title = this.$('#title').val();
- $(this.media).attr('alt', alt ? alt.replace(/"/g, """) : null).attr('title', title ? title.replace(/"/g, """) : null);
- _.defer((function () {
- click_event(this.media, "mouseup");
- }).bind(this));
- return this._super.apply(this, arguments);
- },
-});
-
-var click_event = function (el, type) {
- var evt = document.createEvent("MouseEvents");
- evt.initMouseEvent(type, true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, el);
- el.dispatchEvent(evt);
-};
-
-/**
- * MediaDialog widget. Lets users change a media, including uploading a
- * new image, font awsome or video and can change a media into an other
- * media
- *
- * options: select_images: allow the selection of more of one image
- */
-var MediaDialog = Dialog.extend({
- template: 'web_editor.dialog.media',
- events : _.extend({}, Dialog.prototype.events, {
- 'input input#icon-search': 'search',
- }),
- init: function (parent, options, $editable, media) {
- this._super(parent, _.extend({}, {
- title: _t("Select a Media"),
- }, options));
- if ($editable) {
- this.$editable = $editable;
- this.rte = this.$editable.rte || this.$editable.data('rte');
- }
- this.options = options || {};
- this.old_media = media;
- this.media = media;
- this.isNewMedia = !media;
- this.range = range.create();
- },
- start: function () {
- var self = this;
-
- this.$modal.addClass('note-image-dialog');
- this.$modal.find('.modal-dialog').addClass('o_select_media_dialog');
-
- this.only_images = this.options.only_images || this.options.select_images || (this.media && ($(this.media).parent().data("oe-field") === "image" || $(this.media).parent().data("oe-type") === "image"));
- if (this.only_images) {
- this.$('[href="#editor-media-document"], [href="#editor-media-video"], [href="#editor-media-icon"]').addClass('hidden');
- }
-
- this.opened((function () {
- if (this.media) {
- if (this.media.nodeName === "IMG") {
- this.$('[href="#editor-media-image"]').tab('show');
- } else if ($(this.media).is('a.o_image')) {
- this.$('[href="#editor-media-document"]').tab('show');
- } else if (this.media.className.match(/(^|\s)media_iframe_video($|\s)/)) {
- this.$('[href="#editor-media-video"]').tab('show');
- } else if (this.media.parentNode.className.match(/(^|\s)media_iframe_video($|\s)/)) {
- this.media = this.media.parentNode;
- this.$('[href="#editor-media-video"]').tab('show');
- } else if (this.media.className.match(/(^|\s)fa($|\s)/)) {
- this.$('[href="#editor-media-icon"]').tab('show');
- }
- }
- }).bind(this));
-
- this.imageDialog = new ImageDialog(this, this.media, this.options);
- this.imageDialog.appendTo(this.$("#editor-media-image"));
- this.documentDialog = new ImageDialog(this, this.media, _.extend({'document': true}, this.options));
- this.documentDialog.appendTo(this.$("#editor-media-document"));
- if (!this.only_images) {
- this.iconDialog = new fontIconsDialog(this, this.media, this.options);
- this.iconDialog.appendTo(this.$("#editor-media-icon"));
- this.videoDialog = new VideoDialog(this, this.media, this.options);
- this.videoDialog.appendTo(this.$("#editor-media-video"));
- }
-
- this.active = this.imageDialog;
-
- this.$('a[data-toggle="tab"]').on('shown.bs.tab', function (event) {
- if ($(event.target).is('[href="#editor-media-image"]')) {
- self.active = self.imageDialog;
- self.$('li.search, li.previous, li.next').removeClass("hidden");
- } else if ($(event.target).is('[href="#editor-media-document"]')) {
- self.active = self.documentDialog;
- self.$('li.search, li.previous, li.next').removeClass("hidden");
- } else if ($(event.target).is('[href="#editor-media-icon"]')) {
- self.active = self.iconDialog;
- self.$('li.search, li.previous, li.next').removeClass("hidden");
- self.$('.nav-tabs li.previous, .nav-tabs li.next').addClass("hidden");
- } else if ($(event.target).is('[href="#editor-media-video"]')) {
- self.active = self.videoDialog;
- self.$('.nav-tabs li.search').addClass("hidden");
- }
- });
-
- return this._super.apply(this, arguments);
- },
- save: function () {
- if (this.options.select_images) {
- this.final_data = this.active.save();
- this._super.apply(this, arguments);
- return;
- }
- if(this.rte) {
- this.range.select();
- this.rte.historyRecordUndo(this.media);
- }
-
- var self = this;
- if (self.media) {
- this.media.innerHTML = "";
- if (this.active !== this.imageDialog && this.active !== this.documentDialog) {
- this.imageDialog.clear();
- }
- // if not mode only_images
- if (this.iconDialog && this.active !== this.iconDialog) {
- this.iconDialog.clear();
- }
- if (this.videoDialog && this.active !== this.videoDialog) {
- this.videoDialog.clear();
- }
- } else {
- this.media = document.createElement("img");
- this.range.insertNode(this.media, true);
- this.active.media = this.media;
- }
- this.active.save();
-
- if (this.active.add_class) {
- $(this.active.media).addClass(this.active.add_class);
- }
- var media = this.active.media;
-
- this.final_data = [media, self.old_media];
- $(document.body).trigger("media-saved", this.final_data);
- $(self.old_media).trigger("save", this.final_data);
-
- // Update editor bar after image edition (in case the image change to icon or other)
- _.defer(function () {
- if (!media.parentNode) return;
- range.createFromNode(media).select();
- click_event(media, "mousedown");
- click_event(media, "mouseup");
- });
-
- this._super.apply(this, arguments);
- },
- searchTimer: null,
- search: function () {
- var self = this;
- var needle = this.$("input#icon-search").val();
- clearTimeout(this.searchTimer);
- this.searchTimer = setTimeout(function () {
- self.active.search(needle || "");
- },250);
- }
-});
-
-/**
- * ImageDialog widget. Let users change an image, including uploading a
- * new image in odoo or selecting the image style (if supported by
- * the caller).
- */
-var ImageDialog = Widget.extend({
- template: 'web_editor.dialog.image',
- IMAGES_PER_ROW: 6,
- IMAGES_ROWS: 2,
- events: _.extend({}, Dialog.prototype.events, {
- 'change .url-source': function (e) {
- this.changed($(e.target));
- },
- 'click button.filepicker': function () {
- var filepicker = this.$('input[type=file]');
- if (!_.isEmpty(filepicker)) {
- filepicker[0].click();
- }
- },
- 'click .js_disable_optimization': function () {
- this.$('input[name="disable_optimization"]').val('1');
- var filepicker = this.$('button.filepicker');
- if (!_.isEmpty(filepicker)) {
- filepicker[0].click();
- }
- },
- 'change input[type=file]': 'file_selection',
- 'submit form': 'form_submit',
- 'change input.url': "change_input",
- 'keyup input.url': "change_input",
- 'click .existing-attachments [data-src]': 'select_existing',
- 'dblclick .existing-attachments [data-src]': function () {
- this.getParent().save();
- },
- 'click .o_existing_attachment_remove': 'try_remove',
- 'keydown.dismiss.bs.modal': function () {},
- }),
- init: function (parent, media, options) {
- this._super.apply(this, arguments);
- this.options = options || {};
- this.accept = this.options.accept || this.options.document ? "*/*" : "image/*";
- this.domain = this.options.domain || ['|', ['mimetype', '=', false], ['mimetype', this.options.document ? 'not in' : 'in', ['image/gif', 'image/jpe', 'image/jpeg', 'image/jpg', 'image/gif', 'image/png']]];
- this.parent = parent;
- this.old_media = media;
- this.media = media;
- this.images = [];
- this.page = 0;
- },
- start: function () {
- this.$preview = this.$('.preview-container').detach();
- var self = this;
- var res = this._super.apply(this, arguments);
- var o = {url: null, alt: null};
-
- if ($(this.media).is("img")) {
- o.url = this.media.getAttribute('src');
- } else if ($(this.media).is("a.o_image")) {
- o.url = this.media.getAttribute('href').replace(/[?].*/, '');
- o.id = +o.url.match(/\/web\/content\/([0-9]*)/, '')[1];
- }
- this.parent.$(".pager > li").click(function (e) {
- if(!self.$el.is(':visible')) {
- return;
- }
- e.preventDefault();
- var $target = $(e.currentTarget);
- if ($target.hasClass('disabled')) {
- return;
- }
- self.page += $target.hasClass('previous') ? -1 : 1;
- self.display_attachments();
- });
- this.fetch_existing().then(function () {
- if (o.url) {
- self.set_image(_.find(self.records, function (record) { return record.url === o.url;}) || o);
- }
- });
- return res;
- },
- push: function (attachment) {
- if (this.options.select_images) {
- var img = _.select(this.images, function (v) { return v.id === attachment.id; });
- if (img.length) {
- this.images.splice(this.images.indexOf(img[0]),1);
- } else {
- this.images.push(attachment);
- }
- } else {
- this.images = [attachment];
- }
- },
- save: function () {
- if (this.options.select_images) {
- return this.images;
- }
-
- var img = this.images[0];
- if (!img) {
- var id = this.$(".existing-attachments [data-src]:first").data('id');
- img = _.find(this.images, function (img) { return img.id === id;});
- }
-
- var media;
- if (!img.is_document) {
- if (this.media.tagName !== "IMG" || !this.old_media) {
- this.add_class = "pull-left";
- this.style = {"width": "100%"};
- }
- if(this.media.tagName !== "IMG") {
- media = document.createElement('img');
- $(this.media).replaceWith(media);
- this.media = media;
- }
- this.media.setAttribute('src', img.src);
- } else {
- if (this.media.tagName !== "A") {
- $('.note-control-selection').hide();
- media = document.createElement('a');
- $(this.media).replaceWith(media);
- this.media = media;
- }
- this.media.setAttribute('href', '/web/content/' + img.id + '?unique=' + img.checksum + '&download=true');
- $(this.media).addClass('o_image').attr('title', img.name).attr('data-mimetype', img.mimetype);
- }
-
- $(this.media).attr('alt', img.alt);
- var style = this.style;
- if (style) { $(this.media).css(style); }
-
- return this.media;
- },
- clear: function () {
- this.media.className = this.media.className.replace(/(^|\s+)((img(\s|$)|img-(?!circle|rounded|thumbnail))[^\s]*)/g, ' ');
- },
- change_input: function (e) {
- var $input = $(e.target);
- var $button = $input.parent().find("button");
- var emptyValue = ($input.val() === "");
- $button.toggleClass("btn-default", emptyValue).toggleClass("btn-primary", !emptyValue);
- },
- search: function (needle) {
- var self = this;
- this.fetch_existing(needle).then(function () {
- self.selected_existing();
- });
- },
- set_image: function (attachment) {
- this.push(attachment);
- this.$('input.url').val('');
- this.search();
- this.images = [];
- },
- form_submit: function (event) {
- var self = this;
- var $form = this.$('form[action="/web_editor/attachment/add"]');
- if (!$form.find('input[name="upload"]').val().length) {
- if (this.selected_existing().size()) {
- event.preventDefault();
- return false;
- }
- }
- $form.find('.well > div').hide().last().after(' ');
-
- var callback = _.uniqueId('func_');
- this.$('input[name=func]').val(callback);
- window[callback] = function (attachments, error) {
- delete window[callback];
- $form.find('.well > span').remove();
- $form.find('.well > div').show();
- _.each(attachments, function (record) {
- record.src = record.url || '/web/image/' + record.id;
- record.is_document = !(/gif|jpe|jpg|png/.test(record.mimetype));
- });
- if (error || !attachments.length) {
- self.file_selected(null, error || !attachments.length);
- }
- self.images = attachments;
- for (var i=0; i 0);
- }), function (r) {
- return (r.url || r.id);
- });
- _.each(this.records, function (record) {
- record.src = record.url || '/web/image/' + record.id;
- record.is_document = !(/gif|jpe|jpg|png/.test(record.mimetype));
- });
- this.display_attachments();
- },
- display_attachments: function () {
- var self = this;
- var per_screen = this.IMAGES_PER_ROW * this.IMAGES_ROWS;
- var from = this.page * per_screen;
- var records = this.records;
-
- // Create rows of 3 records
- var rows = _(records).chain()
- .slice(from, from + per_screen)
- .groupBy(function (_, index) { return Math.floor(index / self.IMAGES_PER_ROW); })
- .values()
- .value();
-
- this.$('.help-block').empty();
-
- this.$('.existing-attachments').replaceWith(QWeb.render('web_editor.dialog.image.existing.content', {rows: rows}));
- this.parent.$('.pager')
- .find('li.previous a').toggleClass('disabled', (from === 0)).end()
- .find('li.next a').toggleClass('disabled', (from + per_screen >= records.length));
-
- this.$el.find('.o_image').each(function () {
- var $div = $(this);
- if (/gif|jpe|jpg|png/.test($div.data('mimetype'))) {
- var $img = $(' ').addClass('img img-responsive').attr('src', $div.data('url') || $div.data('src'));
- $div.addClass('o_webimage').append($img);
- }
- });
- this.selected_existing();
- },
- select_existing: function (e) {
- var $img = $(e.currentTarget);
- if (!this.options.select_images) {
- this.$('.o_existing_attachment_cell.o_selected').removeClass("o_selected");
- $img.closest('.o_existing_attachment_cell').addClass('o_selected');
- } else {
- $img.closest('.o_existing_attachment_cell').toggleClass('o_selected');
- }
- var attachment = _.find(this.records, function (record) { return record.id === $img.data('id'); });
- this.push(attachment);
- this.selected_existing();
- },
- selected_existing: function () {
- var self = this;
- var $select = this.$('.o_existing_attachment_cell [data-src]').filter(function () {
- var $img = $(this);
- return !!_.find(self.images, function (v) {
- return (v.url === $img.data("src") || ($img.data("url") && v.url === $img.data("url")) || v.id === $img.data("id"));
- });
- });
- return $select;
- },
- try_remove: function (e) {
- var $help_block = this.$('.help-block').empty();
- var self = this;
- var $a = $(e.target);
- var id = parseInt($a.data('id'), 10);
- var attachment = _.findWhere(this.records, {id: id});
- var $both = $a.parent().children();
-
- $both.css({borderWidth: "5px", borderColor: "#f00"});
-
- return ajax.jsonRpc('/web_editor/attachment/remove', 'call', {'ids': [id]}).then(function (prevented) {
- if (_.isEmpty(prevented)) {
- self.records = _.without(self.records, attachment);
- self.display_attachments();
- return;
- }
- $both.css({borderWidth: "", borderColor: ""});
- $help_block.replaceWith(QWeb.render('web_editor.dialog.image.existing.error', {
- views: prevented[id]
- }));
- });
- },
-});
-
-/* list of font icons to load by editor. The icons are displayed in the media editor and
- * identified like font and image (can be colored, spinned, resized with fa classes).
- * To add font, push a new object {base, parser}
- * - base: class who appear on all fonts (eg: fa fa-refresh)
- * - parser: regular expression used to select all font in css style sheets
- */
-var fontIcons = [{'base': 'fa', 'parser': /(?=^|\s)(\.fa-[0-9a-z_-]+::?before)/i}];
-
-var cacheCssSelectors = {};
-var getCssSelectors = function (filter) {
- var css = [];
- if (cacheCssSelectors[filter]) {
- return cacheCssSelectors[filter];
- }
- var sheets = document.styleSheets;
- for(var i = 0; i < sheets.length; i++) {
- var rules;
- if (sheets[i].rules) {
- rules = sheets[i].rules;
- } else {
- //try...catch because Firefox not able to enumerate document.styleSheets[].cssRules[] for cross-domain stylesheets.
- try {
- rules = sheets[i].cssRules;
- } catch(e) {
- console.warn("Can't read the css rules of: " + sheets[i].href, e);
- continue;
- }
- }
- if (rules) {
- for(var r = 0; r < rules.length; r++) {
- var selectorText = rules[r].selectorText;
- if (selectorText) {
- selectorText = selectorText.split(/\s*,\s*/);
- var data = null;
- for(var s = 0; s < selectorText.length; s++) {
- var match = selectorText[s].match(filter);
- if (match) {
- var clean = match[1].slice(1).replace(/::?before$/, '');
- if (!data) {
- data = [match[1], rules[r].cssText.replace(/(^.*\{\s*)|(\s*\}\s*$)/g, ''), clean, [clean]];
- } else {
- data[0] += (", " + match[1]);
- data[3].push(clean);
- }
- }
- }
- if (data) {
- css.push(data);
- }
- }
- }
- }
- }
- return cacheCssSelectors[filter] = css;
-};
-var computeFonts = _.once(function () {
- _.each(fontIcons, function (data) {
- data.cssData = getCssSelectors(data.parser);
- data.alias = [];
- data.icons = _.map(data.cssData, function (css) {
- data.alias.push.apply(data.alias, css[3]);
- return css[2];
- });
- });
-});
-
-rte.Class.include({
- init: function () {
- this._super.apply(this, arguments);
- computeFonts();
- },
- onEnableEditableArea: function ($editable) {
- if ($editable.data('oe-type') === "monetary") {
- $editable.attr('contenteditable', false);
- $editable.find('.oe_currency_value').attr('contenteditable', true);
- }
- if ($editable.is('[data-oe-model]') && !$editable.is('[data-oe-model="ir.ui.view"]') && !$editable.is('[data-oe-type="html"]')) {
- $editable.data('layoutInfo').popover().find('.btn-group:not(.note-history)').remove();
- }
- },
-});
-
-/**
- * FontIconsDialog widget. Lets users change a font awesome, support all
- * font awesome loaded in the css files.
- */
-var fontIconsDialog = Widget.extend({
- template: 'web_editor.dialog.font-icons',
- events : _.extend({}, Dialog.prototype.events, {
- 'click .font-icons-icon': function (e) {
- e.preventDefault();
- e.stopPropagation();
-
- this.$('#fa-icon').val(e.target.getAttribute('data-id'));
- $(".font-icons-icon").removeClass("o_selected");
- $(e.target).addClass("o_selected");
- },
- 'dblclick .font-icons-icon': function () {
- this.getParent().save();
- },
- 'keydown.dismiss.bs.modal': function () {},
- }),
-
- init: function (parent, media) {
- this._super.apply(this, arguments);
- this.parent = parent;
- this.media = media;
- computeFonts();
- },
- start: function () {
- return this._super.apply(this, arguments).then(this.proxy('load_data'));
- },
- renderElement: function () { // extract list of font (like awesome) from the cheatsheet.
- this.iconsParser = fontIcons;
- this.icons = _.flatten(_.map(fontIcons, function (data) { // TODO maybe useless now
- return data.icons;
- }));
- this.alias = _.flatten(_.map(fontIcons, function (data) {
- return data.alias;
- }));
- this._super.apply(this, arguments);
- },
- search: function (needle) {
- var iconsParser = this.iconsParser;
- if (needle) {
- iconsParser = [];
- _.filter(this.iconsParser, function (data) {
- var cssData = _.filter(data.cssData, function (cssData) {
- return _.find(cssData[3], function (alias) {
- return alias.indexOf(needle) !== -1;
- });
- });
- if (cssData.length) {
- iconsParser.push({
- base: data.base,
- cssData: cssData
- });
- }
- });
- }
- this.$('div.font-icons-icons').html(
- QWeb.render('web_editor.dialog.font-icons.icons', {'iconsParser': iconsParser}));
- },
- /**
- * Removes existing FontAwesome classes on the bound element, and sets
- * all the new ones if necessary.
- */
- save: function () {
- var self = this;
- var style = this.media.attributes.style ? this.media.attributes.style.value : '';
- var classes = (this.media.className||"").split(/\s+/);
- var custom_classes = /^fa(-[1-5]x|spin|rotate-(9|18|27)0|flip-(horizont|vertic)al|fw|border)?$/;
- var non_fa_classes = _.reject(classes, function (cls) {
- return self.getFont(cls) || custom_classes.test(cls);
- });
- var final_classes = non_fa_classes.concat(this.get_fa_classes());
- if (this.media.tagName !== "SPAN") {
- var media = document.createElement('span');
- $(media).data($(this.media).data());
- $(this.media).replaceWith(media);
- this.media = media;
- style = style.replace(/\s*width:[^;]+/, '');
- }
- $(this.media).attr("class", _.compact(final_classes).join(' ')).attr("style", style);
-
- return this.media;
- },
- /**
- * return the data font object (with base, parser and icons) or null
- */
- getFont: function (classNames) {
- if (!(classNames instanceof Array)) {
- classNames = (classNames||"").split(/\s+/);
- }
- var fontIcon, cssData;
- for (var k=0; k');
- if (ytMatch && ytMatch[1].length === 11) {
- var youtubeId = ytMatch[1];
- $video = $('