[FIX] web_editor: fix issues when pasting media URL
Issue # 1: pasting image/video URL as text
This commit removes this command, because it was considered to be useless.
The category names "Embed" and "Paste" for the other two commands
("Paste as URL" and "Embed Image/Video") were ommited as it is no longer
relevant to group commands, as this would result into two categories with
one command each (the powerbox does not display the category name when all
the commands are under the same one - undefined in this case).
Issue # 2: pasting image/video URL + other text fragments
The powerbox commands are not compatible with an image/video URL + text
or multiple URLs. Choosing one of its commands will lead to loss of all
text fragments but the last valid URL.
This commit ensures the powerbox options are only offered when a single
image/video URL is pasted.
Issue # 3: pasting image URL inside an exisiting link
Even though the Editor's specs do not allow a URL to be transformed into
a link when pasting it inside an exisiting link, before this commit,
pasting an image URL inside a link would open the powerbox with 3 commands,
one of them allowing to transform the URL into a link.
This commit ensures the powerbox with the "Paste as URL" command is not
opened in such case. When pasting an image URL inside an existing link, the
image is directly embedded, and other URLs are pasted as text.
Issue # 4: unnecessary regex matching
Before this commit, every text fragment in "text URL text" was being
prepended with "https://" and a match with the URL regex was attempted.
Every fragment (text or URL) was also probed for a match with the
youtube video URL regex, but such matches would be discarded anyway in
modules like note and mass_mailing where the allowCommandVideo option
is set to false.
This commit avoids unnecessary searching with regular expressions by treating
pasting of a single URL (possibly an image or video URL that would lead
to powerbox commands) differently than URLs pasted among other text fragments
or other URLs (see issue # 2 above).
Issue # 5: `historyPauseSteps` with argument
This function takes no arguments. Yet, it was called with the string 'onPaste'
as argument. This commit removes it.
task-3099012
X-original-commit: 3fb93d7e9047a73a8e84f165e2ff52b9887d34a4
Part-of: odoo/odoo#118204
This commit is contained in:
@@ -1034,14 +1034,6 @@ msgid ""
|
||||
"prevent it from being updated during future App upgrades."
|
||||
msgstr ""
|
||||
|
||||
#. module: web_editor
|
||||
#. odoo-javascript
|
||||
#: code:addons/web_editor/static/src/js/editor/odoo-editor/src/OdooEditor.js:0
|
||||
#: code:addons/web_editor/static/src/js/editor/odoo-editor/src/OdooEditor.js:0
|
||||
#, python-format
|
||||
msgid "Embed"
|
||||
msgstr ""
|
||||
|
||||
#. module: web_editor
|
||||
#. odoo-javascript
|
||||
#: code:addons/web_editor/static/src/js/editor/odoo-editor/src/OdooEditor.js:0
|
||||
@@ -2036,14 +2028,6 @@ msgstr ""
|
||||
msgid "Paragraph block"
|
||||
msgstr ""
|
||||
|
||||
#. module: web_editor
|
||||
#. odoo-javascript
|
||||
#: code:addons/web_editor/static/src/js/editor/odoo-editor/src/OdooEditor.js:0
|
||||
#: code:addons/web_editor/static/src/js/editor/odoo-editor/src/OdooEditor.js:0
|
||||
#, python-format
|
||||
msgid "Paste"
|
||||
msgstr ""
|
||||
|
||||
#. module: web_editor
|
||||
#. odoo-javascript
|
||||
#: code:addons/web_editor/static/src/js/editor/odoo-editor/src/OdooEditor.js:0
|
||||
@@ -2051,13 +2035,6 @@ msgstr ""
|
||||
msgid "Paste as URL"
|
||||
msgstr ""
|
||||
|
||||
#. module: web_editor
|
||||
#. odoo-javascript
|
||||
#: code:addons/web_editor/static/src/js/editor/odoo-editor/src/OdooEditor.js:0
|
||||
#, python-format
|
||||
msgid "Paste as text"
|
||||
msgstr ""
|
||||
|
||||
#. module: web_editor
|
||||
#: model_terms:ir.ui.view,arch_db:web_editor.snippet_options
|
||||
msgid "Patterns"
|
||||
@@ -2593,13 +2570,6 @@ msgstr ""
|
||||
msgid "Signature"
|
||||
msgstr ""
|
||||
|
||||
#. module: web_editor
|
||||
#. odoo-javascript
|
||||
#: code:addons/web_editor/static/src/js/editor/odoo-editor/src/OdooEditor.js:0
|
||||
#, python-format
|
||||
msgid "Simple text paste"
|
||||
msgstr ""
|
||||
|
||||
#. module: web_editor
|
||||
#. odoo-javascript
|
||||
#: code:addons/web_editor/static/src/js/editor/snippets.options.js:0
|
||||
|
||||
@@ -4308,135 +4308,116 @@ export class OdooEditor extends EventTarget {
|
||||
if(!text.match(/\${.*}/gi)) {
|
||||
splitAroundUrl = text.split(URL_REGEX);
|
||||
}
|
||||
this.historyPauseSteps("_onPaste");
|
||||
for (let i = 0; i < splitAroundUrl.length; i++) {
|
||||
const url = /^https?:\/\//gi.test(splitAroundUrl[i])
|
||||
? splitAroundUrl[i]
|
||||
: 'https://' + splitAroundUrl[i];
|
||||
const youtubeUrl = YOUTUBE_URL_GET_VIDEO_ID.exec(url);
|
||||
if (splitAroundUrl.length === 3 && !splitAroundUrl[0] && !splitAroundUrl[2]) {
|
||||
// Pasted content is a single URL.
|
||||
const url = /^https?:\/\//i.test(text) ? text : 'https://' + text;
|
||||
const youtubeUrl = this.options.allowCommandVideo &&YOUTUBE_URL_GET_VIDEO_ID.exec(url);
|
||||
const urlFileExtention = url.split('.').pop();
|
||||
const isImageUrl = ['jpg', 'jpeg', 'png', 'gif'].includes(urlFileExtention.toLowerCase());
|
||||
// Even indexes will always be plain text, and odd indexes will always be URL.
|
||||
// only allow images emebed inside an existing link. No other url or video embed.
|
||||
if (i % 2 && (isImageUrl || !selectionIsInsideALink)) {
|
||||
const baseEmbedCommand = [
|
||||
{
|
||||
category: this.options._t('Paste'),
|
||||
name: this.options._t('Paste as URL'),
|
||||
description: this.options._t('Create an URL'),
|
||||
fontawesome: 'fa-link',
|
||||
callback: () => {
|
||||
this.historyUndo();
|
||||
this._applyRawCommand('insert', this._createLink(splitAroundUrl[i], url));
|
||||
},
|
||||
},
|
||||
{
|
||||
category: this.options._t('Paste'),
|
||||
name: this.options._t('Paste as text'),
|
||||
description: this.options._t('Simple text paste'),
|
||||
fontawesome: 'fa-font',
|
||||
callback: () => {},
|
||||
},
|
||||
];
|
||||
|
||||
const execCommandAtStepIndex = (index, callback) => {
|
||||
this.historyRevertUntil(index);
|
||||
const isImageUrl = ['jpg', 'jpeg', 'png', 'gif', 'svg'].includes(urlFileExtention.toLowerCase());
|
||||
// A url cannot be transformed inside an existing link.
|
||||
// An image can be embedded inside an existing link, a video cannot.
|
||||
if (selectionIsInsideALink) {
|
||||
if (isImageUrl) {
|
||||
const img = document.createElement('IMG');
|
||||
img.setAttribute('src', url);
|
||||
this._applyCommand('insert', img);
|
||||
} else {
|
||||
this._applyCommand('insert', text);
|
||||
}
|
||||
} else if (isImageUrl || youtubeUrl) {
|
||||
// Open powerbox with commands to embed media or paste as link.
|
||||
// Insert URL as text, store history step index to revert it later.
|
||||
const stepIndexBeforeInsert = this._historySteps.length - 1;
|
||||
this._applyCommand('insert', text);
|
||||
const revertTextInsertion = () => {
|
||||
this.historyRevertUntil(stepIndexBeforeInsert);
|
||||
this.historyStep(true);
|
||||
this._historyStepsStates.set(peek(this._historySteps).id, 'consumed');
|
||||
|
||||
callback();
|
||||
|
||||
this.historyStep(true);
|
||||
};
|
||||
|
||||
let commands;
|
||||
const pasteAsURLCommand = {
|
||||
name: this.options._t('Paste as URL'),
|
||||
description: this.options._t('Create an URL.'),
|
||||
fontawesome: 'fa-link',
|
||||
callback: () => {
|
||||
revertTextInsertion();
|
||||
this._applyRawCommand('insert', this._createLink(text, url))
|
||||
},
|
||||
};
|
||||
if (isImageUrl) {
|
||||
const stepIndexBeforeInsert = this._historySteps.length - 1;
|
||||
this.execCommand('insert', splitAroundUrl[i]);
|
||||
this.powerbox.open([
|
||||
{
|
||||
category: this.options._t('Embed'),
|
||||
name: this.options._t('Embed Image'),
|
||||
description: this.options._t('Embed the image in the document'),
|
||||
fontawesome: 'fa-image',
|
||||
callback: () => {
|
||||
execCommandAtStepIndex(stepIndexBeforeInsert, () => {
|
||||
const img = document.createElement('IMG');
|
||||
img.setAttribute('src', url);
|
||||
const sel = this.document.getSelection();
|
||||
if (!sel.isCollapsed) {
|
||||
this.deleteRange(sel);
|
||||
}
|
||||
if (sel.rangeCount) {
|
||||
sel.getRangeAt(0).insertNode(img);
|
||||
sel.collapseToEnd();
|
||||
}
|
||||
});
|
||||
},
|
||||
const embedImageCommand = {
|
||||
name: this.options._t('Embed Image'),
|
||||
description: this.options._t('Embed the image in the document.'),
|
||||
fontawesome: 'fa-image',
|
||||
callback: () => {
|
||||
revertTextInsertion();
|
||||
const img = document.createElement('IMG');
|
||||
img.setAttribute('src', url);
|
||||
this._applyRawCommand('insert', img);
|
||||
},
|
||||
...baseEmbedCommand,
|
||||
]);
|
||||
} else if (this.options.allowCommandVideo && youtubeUrl) {
|
||||
const stepIndexBeforeInsert = this._historySteps.length - 1;
|
||||
this.execCommand('insert', splitAroundUrl[i]);
|
||||
this.powerbox.open([
|
||||
{
|
||||
category: this.options._t('Embed'),
|
||||
name: this.options._t('Embed Youtube Video'),
|
||||
description: this.options._t('Embed the youtube video in the document'),
|
||||
fontawesome: 'fa-youtube-play',
|
||||
callback: async () => {
|
||||
let videoElement;
|
||||
if (this.options.getYoutubeVideoElement) {
|
||||
videoElement = await this.options.getYoutubeVideoElement(youtubeUrl[0]);
|
||||
} else {
|
||||
videoElement = document.createElement('iframe');
|
||||
videoElement.setAttribute('width', '560');
|
||||
videoElement.setAttribute('height', '315');
|
||||
videoElement.setAttribute(
|
||||
'src',
|
||||
`https://www.youtube.com/embed/${youtubeUrl[1]}`,
|
||||
);
|
||||
videoElement.setAttribute('title', 'YouTube video player');
|
||||
videoElement.setAttribute('frameborder', '0');
|
||||
videoElement.setAttribute(
|
||||
'allow',
|
||||
'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture',
|
||||
);
|
||||
videoElement.setAttribute('allowfullscreen', '1');
|
||||
}
|
||||
|
||||
execCommandAtStepIndex(stepIndexBeforeInsert, () => {
|
||||
|
||||
const sel = this.document.getSelection();
|
||||
if (!sel.isCollapsed) {
|
||||
this.deleteRange(sel);
|
||||
}
|
||||
if (sel.rangeCount) {
|
||||
sel.getRangeAt(0).insertNode(videoElement);
|
||||
sel.collapseToEnd();
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
...baseEmbedCommand,
|
||||
]);
|
||||
};
|
||||
commands = [embedImageCommand, pasteAsURLCommand];
|
||||
} else {
|
||||
this._applyCommand('insert', this._createLink(splitAroundUrl[i], url));
|
||||
// URL is a YouTube video.
|
||||
const embedVideoCommand = {
|
||||
name: this.options._t('Embed Youtube Video'),
|
||||
description: this.options._t('Embed the youtube video in the document.'),
|
||||
fontawesome: 'fa-youtube-play',
|
||||
callback: () => {
|
||||
revertTextInsertion();
|
||||
let videoElement;
|
||||
if (this.options.getYoutubeVideoElement) {
|
||||
videoElement = this.options.getYoutubeVideoElement(youtubeUrl[0]);
|
||||
} else {
|
||||
videoElement = document.createElement('iframe');
|
||||
videoElement.setAttribute('width', '560');
|
||||
videoElement.setAttribute('height', '315');
|
||||
videoElement.setAttribute(
|
||||
'src',
|
||||
`https://www.youtube.com/embed/${youtubeUrl[1]}`,
|
||||
);
|
||||
videoElement.setAttribute('title', 'YouTube video player');
|
||||
videoElement.setAttribute('frameborder', '0');
|
||||
videoElement.setAttribute(
|
||||
'allow',
|
||||
'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture',
|
||||
);
|
||||
videoElement.setAttribute('allowfullscreen', '1');
|
||||
}
|
||||
this._applyRawCommand('insert', videoElement);
|
||||
},
|
||||
};
|
||||
commands = [embedVideoCommand, pasteAsURLCommand];
|
||||
}
|
||||
} else if (splitAroundUrl[i] !== '') {
|
||||
const textFragments = splitAroundUrl[i].split(/\r?\n/);
|
||||
let textIndex = 1;
|
||||
for (const textFragment of textFragments) {
|
||||
this._applyCommand('insert', textFragment);
|
||||
if (textIndex < textFragments.length) {
|
||||
this._applyCommand('oShiftEnter');
|
||||
this.powerbox.open(commands);
|
||||
} else {
|
||||
this._applyCommand('insert', this._createLink(text, url));
|
||||
}
|
||||
} else {
|
||||
this.historyPauseSteps();
|
||||
for (let i = 0; i < splitAroundUrl.length; i++) {
|
||||
const url = /^https?:\/\//gi.test(splitAroundUrl[i])
|
||||
? splitAroundUrl[i]
|
||||
: 'https://' + splitAroundUrl[i];
|
||||
// Even indexes will always be plain text, and odd indexes will always be URL.
|
||||
// only allow images emebed inside an existing link. No other url or video embed.
|
||||
if (i % 2 && !selectionIsInsideALink) {
|
||||
this._applyCommand('insert', this._createLink(splitAroundUrl[i], url));
|
||||
} else if (splitAroundUrl[i] !== '') {
|
||||
const textFragments = splitAroundUrl[i].split(/\r?\n/);
|
||||
let textIndex = 1;
|
||||
for (const textFragment of textFragments) {
|
||||
this._applyCommand('insert', textFragment);
|
||||
if (textIndex < textFragments.length) {
|
||||
this._applyCommand('oShiftEnter');
|
||||
}
|
||||
textIndex++;
|
||||
}
|
||||
textIndex++;
|
||||
}
|
||||
}
|
||||
this.historyUnpauseSteps();
|
||||
this.historyStep();
|
||||
}
|
||||
this.historyUnpauseSteps("_onPaste");
|
||||
this.historyStep();
|
||||
}
|
||||
}
|
||||
_onDragStart(ev) {
|
||||
|
||||
Reference in New Issue
Block a user