[IMP] web_editor: allow to use link popover everywhere
Before this commit, the link popover was only available in the website builder. Now it is available anywhere the wysiwyg is used. Task-2678412 closes odoo/odoo#79542 X-original-commit: 161c5fc8e742294e8d85c889b4bf8d3d8cd484f5 Signed-off-by: David Monjoie (dmo) <dmo@odoo.com>
This commit is contained in:
@@ -57,7 +57,6 @@ Odoo Web Editor widget.
|
||||
'web_editor/static/lib/odoo-editor/src/commands/toggleList.js',
|
||||
|
||||
# utils
|
||||
'web_editor/static/src/js/wysiwyg/wysiwyg_utils.js',
|
||||
'web_editor/static/src/js/wysiwyg/PeerToPeer.js',
|
||||
|
||||
# odoo utils
|
||||
|
||||
@@ -1,16 +1,6 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
/**
|
||||
* Returns a list of all the ancestors nodes of the provided node.
|
||||
*
|
||||
* @param {Node} node
|
||||
* @param {Node} [stopElement] include to prevent bubbling up further than the stopElement.
|
||||
* @returns {HTMLElement[]}
|
||||
*/
|
||||
function ancestors(node, stopElement) {
|
||||
if (!node || !node.parentElement || node === stopElement) return [];
|
||||
return [node.parentElement, ...ancestors(node.parentElement, stopElement)];
|
||||
}
|
||||
import { ancestors } from '@web_editor/js/common/wysiwyg_utils';
|
||||
|
||||
export class QWebPlugin {
|
||||
constructor(options = {}) {
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
export function isImg(node) {
|
||||
return (node && (node.nodeName === "IMG" || (node.className && node.className.match(/(^|\s)(media_iframe_video|o_image|fa)(\s|$)/i))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all the ancestors nodes of the provided node.
|
||||
*
|
||||
* @param {Node} node
|
||||
* @param {Node} [stopElement] include to prevent bubbling up further than the stopElement.
|
||||
* @returns {HTMLElement[]}
|
||||
*/
|
||||
export function ancestors(node, stopElement) {
|
||||
if (!node || !node.parentElement || node === stopElement) return [];
|
||||
return [node.parentElement, ...ancestors(node.parentElement, stopElement)];
|
||||
}
|
||||
@@ -3,7 +3,6 @@ odoo.define('wysiwyg.widgets.Link', function (require) {
|
||||
|
||||
const core = require('web.core');
|
||||
const OdooEditorLib = require('@web_editor/../lib/odoo-editor/src/OdooEditor');
|
||||
const wysiwygUtils = require('@web_editor/js/wysiwyg/wysiwyg_utils');
|
||||
const Widget = require('web.Widget');
|
||||
const {isColorGradient} = require('web_editor.utils');
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import Widget from 'web.Widget';
|
||||
import {_t} from 'web.core';
|
||||
import {DropPrevious} from 'web.concurrency';
|
||||
import { ancestors } from '@web_editor/js/common/wysiwyg_utils';
|
||||
|
||||
const LinkPopoverWidget = Widget.extend({
|
||||
template: 'wysiwyg.widgets.link.edit.tooltip',
|
||||
@@ -15,9 +16,11 @@ const LinkPopoverWidget = Widget.extend({
|
||||
/**
|
||||
* @constructor
|
||||
* @param {Element} target: target Element for which we display a popover
|
||||
* @param {Wysiwyg} [option.wysiwyg]: The wysiwyg editor
|
||||
*/
|
||||
init(parent, target) {
|
||||
init(parent, target, options) {
|
||||
this._super(...arguments);
|
||||
this.options = options;
|
||||
this.target = target;
|
||||
this.$target = $(target);
|
||||
this.href = this.$target.attr('href'); // for template
|
||||
@@ -48,7 +51,12 @@ const LinkPopoverWidget = Widget.extend({
|
||||
});
|
||||
|
||||
// init tooltips & popovers
|
||||
this.$('[data-toggle="tooltip"]').tooltip({delay: 0, placement: 'bottom'});
|
||||
this.$('[data-toggle="tooltip"]').tooltip({
|
||||
delay: 0,
|
||||
placement: 'bottom',
|
||||
container: this.options.wysiwyg.odooEditor.document.body,
|
||||
});
|
||||
let popoverShown = true;
|
||||
this.$target.popover({
|
||||
html: true,
|
||||
content: this.$el,
|
||||
@@ -59,15 +67,45 @@ const LinkPopoverWidget = Widget.extend({
|
||||
// 3. Remain open when the popover content is clicked..
|
||||
// 4. ..except if it the click was on a button of the popover content
|
||||
// 5. Close when the user click somewhere on the page (not being the link or the popover content)
|
||||
trigger: 'focus',
|
||||
trigger: 'manual',
|
||||
boundary: 'viewport',
|
||||
container: this.options.wysiwyg.odooEditor.document.body,
|
||||
})
|
||||
.on('show.bs.popover.link_popover', () => {
|
||||
this._loadAsyncLinkPreview();
|
||||
popoverShown = true;
|
||||
})
|
||||
.on('hide.bs.popover.link_popover', () => {
|
||||
popoverShown = false;
|
||||
})
|
||||
.popover('show')
|
||||
.data('bs.popover').tip.classList.add('o_edit_menu_popover');
|
||||
|
||||
|
||||
this.$target.on('mousedown.link_popover', (e) => {
|
||||
if (!popoverShown) {
|
||||
this.$target.popover('show');
|
||||
}
|
||||
});
|
||||
const onClickDocument = (e) => {
|
||||
if (popoverShown) {
|
||||
const hierarchy = [e.target, ...ancestors(e.target)];
|
||||
if (
|
||||
!(
|
||||
hierarchy.includes(this.$target[0]) ||
|
||||
(hierarchy.includes(this.$el[0]) &&
|
||||
!hierarchy.some(x => x.tagName && x.tagName === 'A'))
|
||||
)
|
||||
) {
|
||||
this.$target.popover('hide');
|
||||
}
|
||||
}
|
||||
}
|
||||
$(document).on('mouseup.link_popover', onClickDocument);
|
||||
if (document !== this.options.wysiwyg.odooEditor.document) {
|
||||
$(this.options.wysiwyg.odooEditor.document).on('mouseup.link_popover', onClickDocument);
|
||||
}
|
||||
|
||||
return this._super(...arguments);
|
||||
},
|
||||
/**
|
||||
@@ -79,10 +117,19 @@ const LinkPopoverWidget = Widget.extend({
|
||||
// leak. However, it is only one leak per click on a link during edit
|
||||
// mode so this should not be a huge problem.
|
||||
this.$target.off('.link_popover');
|
||||
$(document).off('.link_popover');
|
||||
$(this.options.wysiwyg.odooEditor.document).off('.link_popover');
|
||||
this.$target.popover('dispose');
|
||||
return this._super(...arguments);
|
||||
},
|
||||
|
||||
/**
|
||||
* Hide the popover.
|
||||
*/
|
||||
hide() {
|
||||
this.$target.popover('hide');
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -174,7 +221,8 @@ const LinkPopoverWidget = Widget.extend({
|
||||
* @param {Event} ev
|
||||
*/
|
||||
_onEditLinkClick(ev) {
|
||||
$('#wrapwrap').data('wysiwyg').toggleLinkTools({
|
||||
ev.preventDefault();
|
||||
this.options.wysiwyg.toggleLinkTools({
|
||||
forceOpen: true,
|
||||
link: this.$target[0],
|
||||
});
|
||||
@@ -187,20 +235,20 @@ const LinkPopoverWidget = Widget.extend({
|
||||
* @param {Event} ev
|
||||
*/
|
||||
_onRemoveLinkClick(ev) {
|
||||
// TODO surely there is better to do than finding the editor instance in the DOM?
|
||||
$('#wrapwrap').data('wysiwyg').odooEditor.execCommand('unlink');
|
||||
ev.preventDefault();
|
||||
this.options.wysiwyg.odooEditor.execCommand('unlink');
|
||||
ev.stopImmediatePropagation();
|
||||
},
|
||||
});
|
||||
|
||||
LinkPopoverWidget.createFor = async function (parent, targetEl) {
|
||||
LinkPopoverWidget.createFor = async function (parent, targetEl, options) {
|
||||
const noLinkPopoverClass = ".o_no_link_popover, .carousel-control-prev, .carousel-control-next, .dropdown-toggle";
|
||||
// Target might already have a popover, eg cart icon in navbar
|
||||
const alreadyPopover = $(targetEl).data('bs.popover');
|
||||
if (alreadyPopover || $(targetEl).is(noLinkPopoverClass) || !!$(targetEl).parents(noLinkPopoverClass).length) {
|
||||
return null;
|
||||
}
|
||||
const popoverWidget = new this(parent, targetEl);
|
||||
const popoverWidget = new this(parent, targetEl, options);
|
||||
return popoverWidget.appendTo(targetEl).then(() => popoverWidget);
|
||||
};
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ const OdooEditorLib = require('@web_editor/../lib/odoo-editor/src/OdooEditor');
|
||||
const snippetsEditor = require('web_editor.snippet.editor');
|
||||
const Toolbar = require('web_editor.toolbar');
|
||||
const weWidgets = require('wysiwyg.widgets');
|
||||
const wysiwygUtils = require('@web_editor/js/wysiwyg/wysiwyg_utils');
|
||||
const wysiwygUtils = require('@web_editor/js/common/wysiwyg_utils');
|
||||
const weUtils = require('web_editor.utils');
|
||||
const { PeerToPeer } = require('@web_editor/js/wysiwyg/PeerToPeer');
|
||||
const { Mutex } = require('web.concurrency');
|
||||
@@ -41,6 +41,7 @@ const Wysiwyg = Widget.extend({
|
||||
lang: 'odoo',
|
||||
colors: customColors,
|
||||
recordInfo: {context: {}},
|
||||
document: document,
|
||||
},
|
||||
init: function (parent, options) {
|
||||
this._super.apply(this, arguments);
|
||||
@@ -122,6 +123,7 @@ const Wysiwyg = Widget.extend({
|
||||
const $wrapwrap = $('#wrapwrap');
|
||||
if ($wrapwrap.length) {
|
||||
$wrapwrap[0].addEventListener('scroll', this.odooEditor.multiselectionRefresh, { passive: true });
|
||||
this.$root = $wrapwrap;
|
||||
}
|
||||
|
||||
if (this._peerToPeerLoading) {
|
||||
@@ -226,6 +228,44 @@ const Wysiwyg = Widget.extend({
|
||||
// Ensure the Toolbar always have the correct layout in note.
|
||||
this._updateEditorUI();
|
||||
|
||||
this.$root.on('mousedown', (ev) => {
|
||||
const $target = $(ev.target);
|
||||
|
||||
// Keep popover open if clicked inside it, but not on a button
|
||||
if ($target.parents('.o_edit_menu_popover').length && !$target.parent('a').addBack('a').length) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
if ($target.is(this.customizableLinksSelector) && $target.is('a') && !$target.attr('data-oe-model') && !$target.find('> [data-oe-model]').length) {
|
||||
if (!$target.data('popover-widget-initialized')) {
|
||||
// TODO this code is ugly maybe the mutex should be in the
|
||||
// editor root widget / the popover should not depend on
|
||||
// editor panel (like originally intended but...) / ...
|
||||
(async () => {
|
||||
if (this.snippetsMenu) {
|
||||
// Await for the editor panel to be fully updated
|
||||
// as some buttons of the link popover we create
|
||||
// here relies on clicking in that editor panel...
|
||||
await this.snippetsMenu._mutex.exec(() => null);
|
||||
}
|
||||
this.linkPopover = await weWidgets.LinkPopoverWidget.createFor(this, ev.target, { wysiwyg: this });
|
||||
$target.data('popover-widget-initialized', true);
|
||||
})();
|
||||
}
|
||||
$target.focus();
|
||||
if ($target.closest('#wrapwrap').length) {
|
||||
this.toggleLinkTools({
|
||||
forceOpen: true,
|
||||
link: $target[0],
|
||||
noFocusUrl: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this._onSelectionChange = this._onSelectionChange.bind(this);
|
||||
this.odooEditor.document.addEventListener('selectionchange', this._onSelectionChange);
|
||||
|
||||
return _super.apply(this, arguments).then(() => {
|
||||
if (this.options.autohideToolbar) {
|
||||
if (this.odooEditor.isMobile) {
|
||||
@@ -236,7 +276,6 @@ const Wysiwyg = Widget.extend({
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
setupCollaboration(collaborationChannel) {
|
||||
const modelName = collaborationChannel.collaborationModelName;
|
||||
const fieldName = collaborationChannel.collaborationFieldName;
|
||||
@@ -463,6 +502,7 @@ const Wysiwyg = Widget.extend({
|
||||
}
|
||||
|
||||
if (this.odooEditor) {
|
||||
this.odooEditor.document.removeEventListener('selectionchange', this._onSelectionChange);
|
||||
this.odooEditor.destroy();
|
||||
}
|
||||
// If peer to peer is initializing, wait for properly closing it.
|
||||
@@ -481,6 +521,11 @@ const Wysiwyg = Widget.extend({
|
||||
if ($wrapwrap.length) {
|
||||
$('#wrapwrap')[0].removeEventListener('scroll', this.odooEditor.multiselectionRefresh, { passive: true });
|
||||
}
|
||||
$(this.$root).off('mousedown');
|
||||
if (this.linkPopover) {
|
||||
this.linkPopover.hide();
|
||||
}
|
||||
|
||||
this._super();
|
||||
},
|
||||
/**
|
||||
@@ -488,9 +533,11 @@ const Wysiwyg = Widget.extend({
|
||||
*/
|
||||
renderElement: function () {
|
||||
this.$editable = this.options.editable || $('<div class="note-editable">');
|
||||
this.$root = this.$editable;
|
||||
|
||||
if (this.options.resizable && !device.isMobile) {
|
||||
const $wrapper = $('<div class="o_wysiwyg_wrapper odoo-editor">');
|
||||
this.$root = $wrapper;
|
||||
$wrapper.append(this.$editable);
|
||||
this.$resizer = $(`<div class="o_wysiwyg_resizer">
|
||||
<div class="o_wysiwyg_resizer_hook"></div>
|
||||
@@ -1826,6 +1873,14 @@ const Wysiwyg = Widget.extend({
|
||||
}
|
||||
return new Promise(function () {});
|
||||
},
|
||||
_onSelectionChange() {
|
||||
if (this.options.autohideToolbar) {
|
||||
const isVisible = this.linkPopover && this.linkPopover.el.offsetParent;
|
||||
if (isVisible && !this.odooEditor.document.getSelection().isCollapsed) {
|
||||
this.linkPopover.hide();
|
||||
}
|
||||
}
|
||||
},
|
||||
_onDocumentMousedown: function (e) {
|
||||
if (!e.target.classList.contains('o_editable_date_field_linked')) {
|
||||
this.$editable.find('.o_editable_date_field_linked').removeClass('o_editable_date_field_linked');
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
export function isImg(node) {
|
||||
return (node && (node.nodeName === "IMG" || (node.className && node.className.match(/(^|\s)(media_iframe_video|o_image|fa)(\s|$)/i))));
|
||||
}
|
||||
@@ -779,3 +779,33 @@ img::selection {
|
||||
.o_transform_removal {
|
||||
transform: none !important;
|
||||
}
|
||||
|
||||
.o_edit_menu_popover {
|
||||
max-width: $popover-max-width * 1.2;
|
||||
// Prevent UI glitch after fetching page preview (size might change)
|
||||
width: $popover-max-width * 1.2;
|
||||
// Prevent the edited link from being deselected when clicking between
|
||||
// buttons in the popover
|
||||
user-select: none;
|
||||
|
||||
.o_we_url_link {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.o_we_full_url {
|
||||
word-break: break-all;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
// clamp (`-webkit-box` display toggle in js)
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
|
||||
&.o_we_webkit_box {
|
||||
display: -webkit-box;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
-webkit-line-clamp: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1295,7 +1295,7 @@ options.registry.menu_data = options.Class.extend({
|
||||
* @override
|
||||
*/
|
||||
start: function () {
|
||||
wLinkPopoverWidget.createFor(this, this.$target[0]);
|
||||
wLinkPopoverWidget.createFor(this, this.$target[0], { wysiwyg: $('#wrapwrap').data('wysiwyg') });
|
||||
return this._super(...arguments);
|
||||
},
|
||||
/**
|
||||
|
||||
@@ -51,39 +51,6 @@ Wysiwyg.include({
|
||||
start: function () {
|
||||
this.options.toolbarHandler = $('#web_editor-top-edit');
|
||||
|
||||
$(document.body).on('mousedown', (ev) => {
|
||||
const $target = $(ev.target);
|
||||
|
||||
// Keep popover open if clicked inside it, but not on a button
|
||||
if ($target.parents('.o_edit_menu_popover').length && !$target.parent('a').addBack('a').length) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
if ($target.is(this.customizableLinksSelector) && !$target.attr('data-oe-model') && !$target.find('> [data-oe-model]').length && $target.closest('#wrapwrap').length) {
|
||||
if (!$target.data('popover-widget-initialized')) {
|
||||
// TODO this code is ugly maybe the mutex should be in the
|
||||
// editor root widget / the popover should not depend on
|
||||
// editor panel (like originally intended but...) / ...
|
||||
(async () => {
|
||||
if (this.snippetsMenu) {
|
||||
// Await for the editor panel to be fully updated
|
||||
// as some buttons of the link popover we create
|
||||
// here relies on clicking in that editor panel...
|
||||
await this.snippetsMenu._mutex.exec(() => null);
|
||||
}
|
||||
weWidgets.LinkPopoverWidget.createFor(this, ev.target);
|
||||
$target.data('popover-widget-initialized', true);
|
||||
})();
|
||||
}
|
||||
$target.focus();
|
||||
$('#wrapwrap').data('wysiwyg').toggleLinkTools({
|
||||
forceOpen: true,
|
||||
link: $target[0],
|
||||
noFocusUrl: true,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Dropdown menu initialization: handle dropdown openings by hand
|
||||
var $dropdownMenuToggles = this.$('.o_mega_menu_toggle, #top_menu_container .dropdown-toggle');
|
||||
$dropdownMenuToggles.removeAttr('data-toggle').dropdown('dispose');
|
||||
|
||||
@@ -112,36 +112,6 @@ $o-we-switch-inactive-color: #F7F7F7 !default;
|
||||
}
|
||||
}
|
||||
|
||||
.o_edit_menu_popover {
|
||||
max-width: $popover-max-width * 1.2;
|
||||
// Prevent UI glitch after fetching page preview (size might change)
|
||||
width: $popover-max-width * 1.2;
|
||||
// Prevent the edited link from being deselected when clicking between
|
||||
// buttons in the popover
|
||||
user-select: none;
|
||||
|
||||
.o_we_url_link {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.o_we_full_url {
|
||||
word-break: break-all;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
// clamp (`-webkit-box` display toggle in js)
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
|
||||
&.o_we_webkit_box {
|
||||
display: -webkit-box;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
-webkit-line-clamp: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_new_content_loader_container {
|
||||
background-color: rgba($o-shadow-color, .9);
|
||||
pointer-events: all;
|
||||
|
||||
Reference in New Issue
Block a user