Skip to content

Commit 2fb21cf

Browse files
sebgeelendmo-odoo
authored andcommitted
[IMP] allow youtube and images url to be directly embeded
1 parent 1021d47 commit 2fb21cf

File tree

3 files changed

+162
-98
lines changed

3 files changed

+162
-98
lines changed

src/commandbar.js

Lines changed: 94 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -129,106 +129,110 @@ export class CommandBar {
129129
if (!selection.isCollapsed || !selection.rangeCount) return;
130130

131131
if (event.key === '/' && !this._active) {
132-
this.options.onActivate && this.options.onActivate();
132+
this.open({ commands: this.options.commands, openOnKeyupTarget: event.target });
133+
}
134+
}
135+
136+
open(options) {
137+
this.options.onActivate && this.options.onActivate();
133138

139+
if (options.openOnKeyupTarget) {
134140
const showOnceOnKeyup = () => {
135141
this.show();
136-
event.target.removeEventListener('keyup', showOnceOnKeyup, true);
137-
initialTarget = event.target;
138-
oldValue = event.target.innerText;
142+
options.openOnKeyupTarget.removeEventListener('keyup', showOnceOnKeyup, true);
143+
initialTarget = options.openOnKeyupTarget;
144+
oldValue = options.openOnKeyupTarget.innerText;
139145
};
140-
event.target.addEventListener('keyup', showOnceOnKeyup, true);
141-
this._active = true;
142-
this.render(this.options.commands);
143-
this._resetPosition();
146+
options.openOnKeyupTarget.addEventListener('keyup', showOnceOnKeyup, true);
147+
}
148+
this._active = true;
149+
this._currentFilteredCommands = options.commands;
150+
this.render(options.commands);
151+
this._resetPosition();
144152

145-
let initialTarget;
146-
let oldValue;
153+
let initialTarget;
154+
let oldValue;
147155

148-
const keyup = event => {
149-
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
150-
event.preventDefault();
151-
return;
152-
}
153-
if (!initialTarget) return;
154-
const diff = patienceDiff(
155-
oldValue.split(''),
156-
initialTarget.innerText.split(''),
157-
true,
158-
);
159-
this._lastText = diff.bMove.join('').trim();
156+
const keyup = event => {
157+
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
158+
event.preventDefault();
159+
return;
160+
}
161+
if (!initialTarget) return;
162+
const diff = patienceDiff(oldValue.split(''), initialTarget.innerText.split(''), true);
163+
this._lastText = diff.bMove.join('').trim();
160164

161-
if (this._lastText.match(/\s/)) {
162-
this._stop();
163-
return;
164-
}
165-
const term = this._lastText;
165+
if (this._lastText.match(/\s/)) {
166+
this._stop();
167+
return;
168+
}
169+
const term = this._lastText;
166170

167-
this._currentFilteredCommands = this._filter(term);
168-
this.render(this._currentFilteredCommands);
169-
this._resetPosition();
170-
};
171-
const keydown = e => {
172-
if (e.key === 'Enter') {
173-
e.stopImmediatePropagation();
174-
this._currentValidate();
175-
e.preventDefault();
176-
} else if (e.key === 'Escape') {
177-
e.stopImmediatePropagation();
178-
this._stop();
179-
e.preventDefault();
180-
} else if (e.key === 'Backspace' && !this._lastText) {
181-
this._stop();
182-
} else if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
183-
e.preventDefault();
184-
e.stopImmediatePropagation();
171+
this._currentFilteredCommands = this._filter(term, options.commands);
172+
this.render(this._currentFilteredCommands);
173+
this._resetPosition();
174+
};
175+
const keydown = e => {
176+
if (e.key === 'Enter') {
177+
e.stopImmediatePropagation();
178+
this._currentValidate();
179+
e.preventDefault();
180+
} else if (e.key === 'Escape') {
181+
e.stopImmediatePropagation();
182+
this._stop();
183+
e.preventDefault();
184+
} else if (e.key === 'Backspace' && !this._lastText) {
185+
this._stop();
186+
} else if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
187+
e.preventDefault();
188+
e.stopImmediatePropagation();
185189

186-
const index = this._currentFilteredCommands.findIndex(
187-
c => c === this._currentSelectedCommand,
188-
);
189-
if (!this._currentFilteredCommands.length || index === -1) {
190-
this._currentSelectedCommand = undefined;
191-
} else {
192-
const n = e.key === 'ArrowDown' ? 1 : -1;
193-
const newIndex = cycle(index + n, this._currentFilteredCommands.length - 1);
194-
this._currentSelectedCommand = this._currentFilteredCommands[newIndex];
195-
}
196-
e.preventDefault();
197-
this.render(this._currentFilteredCommands);
190+
const index = this._currentFilteredCommands.findIndex(
191+
c => c === this._currentSelectedCommand,
192+
);
193+
if (!this._currentFilteredCommands.length || index === -1) {
194+
this._currentSelectedCommand = undefined;
195+
} else {
196+
const n = e.key === 'ArrowDown' ? 1 : -1;
197+
const newIndex = cycle(index + n, this._currentFilteredCommands.length - 1);
198+
this._currentSelectedCommand = this._currentFilteredCommands[newIndex];
198199
}
199-
};
200-
const mousemove = () => {
201-
this._hoverActive = true;
202-
};
200+
e.preventDefault();
201+
this.render(this._currentFilteredCommands);
202+
}
203+
};
204+
const mousemove = () => {
205+
this._hoverActive = true;
206+
};
203207

204-
this._stop = () => {
205-
this._active = false;
206-
this.hide();
207-
this._currentSelectedCommand = undefined;
208+
this._stop = () => {
209+
this._active = false;
210+
this.hide();
211+
this._currentSelectedCommand = undefined;
208212

209-
document.removeEventListener('mousedown', this._stop);
210-
document.removeEventListener('keyup', keyup);
211-
document.removeEventListener('keydown', keydown, true);
212-
document.removeEventListener('mousemove', mousemove);
213+
document.removeEventListener('mousedown', this._stop);
214+
document.removeEventListener('keyup', keyup);
215+
document.removeEventListener('keydown', keydown, true);
216+
document.removeEventListener('mousemove', mousemove);
213217

214-
this.options.onStop && this.options.onStop();
215-
};
216-
this._currentValidate = () => {
217-
const command = this._currentFilteredCommands.find(
218-
c => c === this._currentSelectedCommand,
219-
);
220-
if (command) {
221-
this.options.preValidate && this.options.preValidate();
222-
command.callback();
223-
this.options.postValidate && this.options.postValidate();
224-
}
225-
this._stop();
226-
};
227-
document.addEventListener('mousedown', this._stop);
228-
document.addEventListener('keyup', keyup);
229-
document.addEventListener('keydown', keydown, true);
230-
document.addEventListener('mousemove', mousemove);
231-
}
218+
this.options.onStop && this.options.onStop();
219+
};
220+
this._currentValidate = () => {
221+
const command = this._currentFilteredCommands.find(
222+
c => c === this._currentSelectedCommand,
223+
);
224+
if (command) {
225+
this.options.preValidate && this.options.preValidate();
226+
command.callback();
227+
this.options.postValidate && this.options.postValidate();
228+
}
229+
this._stop();
230+
};
231+
document.addEventListener('mousedown', this._stop);
232+
document.addEventListener('keyup', keyup);
233+
document.addEventListener('keydown', keydown, true);
234+
document.addEventListener('mousemove', mousemove);
235+
if (!options.openOnKeyupTarget) this.show();
232236
}
233237

234238
show() {
@@ -246,8 +250,8 @@ export class CommandBar {
246250
// private
247251
// -------------------------------------------------------------------------
248252

249-
_filter(term) {
250-
let commands = this.options.commands;
253+
_filter(term, commands) {
254+
const initalCommands = commands;
251255
term = term.toLowerCase();
252256
term = term.replaceAll(/\s/g, '\\s');
253257
const regex = new RegExp(
@@ -257,7 +261,7 @@ export class CommandBar {
257261
.join('.*'),
258262
);
259263
if (term.length) {
260-
commands = this.options.commands.filter(command => {
264+
commands = initalCommands.filter(command => {
261265
const commandText = (command.groupName + ' ' + command.title).toLowerCase();
262266
return commandText.match(regex);
263267
});

src/editor.js

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import {
4141
getUrlsInfosInString,
4242
URL_REGEX,
4343
isBold,
44+
YOUTUBE_URL_GET_VIDEO_ID,
4445
} from './utils/utils.js';
4546
import { editorCommands } from './commands.js';
4647
import { CommandBar } from './commandbar.js';
@@ -1819,10 +1820,71 @@ export class OdooEditor extends EventTarget {
18191820
const url = /^https?:\/\//gi.test(splitAroundUrl[i])
18201821
? splitAroundUrl[i]
18211822
: 'https://' + splitAroundUrl[i];
1822-
this.execCommand(
1823-
'insertHTML',
1824-
`<a href="${url}" ${linkAttrs}>${splitAroundUrl[i]}</a>`,
1825-
);
1823+
const youtubeUrl = YOUTUBE_URL_GET_VIDEO_ID.exec(url);
1824+
const urlFileExtention = url.split('.').pop();
1825+
const baseEmbedCommand = [
1826+
{
1827+
groupName: 'paste',
1828+
title: 'Paste as URL',
1829+
description: 'Create an URL.',
1830+
fontawesome: 'fa-link',
1831+
callback: () => {
1832+
this.historyUndo();
1833+
this.execCommand(
1834+
'insertHTML',
1835+
`<a href="${url}" ${linkAttrs}>${splitAroundUrl[i]}</a>`,
1836+
);
1837+
},
1838+
},
1839+
{
1840+
groupName: 'paste',
1841+
title: 'Paste as text',
1842+
description: 'Simple text paste.',
1843+
fontawesome: 'fa-font',
1844+
callback: () => {},
1845+
},
1846+
];
1847+
1848+
this.execCommand('insertText', splitAroundUrl[i]);
1849+
if (['jpg', 'jpeg', 'png', 'gif'].includes(urlFileExtention)) {
1850+
this.commandBar.open({
1851+
commands: [
1852+
{
1853+
groupName: 'Embed',
1854+
title: 'Embed Image',
1855+
description: 'Embed the image in the document.',
1856+
fontawesome: 'fa-image',
1857+
callback: () => {
1858+
this.historyUndo();
1859+
this.execCommand('insertHTML', `<img src="${url}" />`);
1860+
},
1861+
},
1862+
].concat(baseEmbedCommand),
1863+
});
1864+
} else if (youtubeUrl) {
1865+
this.commandBar.open({
1866+
commands: [
1867+
{
1868+
groupName: 'Embed',
1869+
title: 'Embed Youtube Video',
1870+
description: 'Embed the youtube video in the document.',
1871+
fontawesome: 'fa-youtube-play',
1872+
callback: () => {
1873+
this.historyUndo();
1874+
this.execCommand(
1875+
'insertHTML',
1876+
`<iframe width="560" height="315" src="https://www.youtube.com/embed/${youtubeUrl[1]}" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>`,
1877+
);
1878+
},
1879+
},
1880+
].concat(baseEmbedCommand),
1881+
});
1882+
} else {
1883+
this.execCommand(
1884+
'insertHTML',
1885+
`<a href="${url}" ${linkAttrs}>${splitAroundUrl[i]}</a>`,
1886+
);
1887+
}
18261888
} else if (splitAroundUrl[i] !== '') {
18271889
this.execCommand('insertText', splitAroundUrl[i]);
18281890
}

src/utils/utils.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export const CTGROUPS = {
2626

2727
export const URL_REGEX = /((?:https?:\/\/)?(?:[a-z0-9-]{1,63}\.){1,2}[a-z]{2,15}(?:\/[^\s]*)?)/gi;
2828
export const URL_REGEX_WITH_INFOS = /((https?:\/\/)?([a-z0-9-]{1,63}\.){1,2}[a-z]{2,15}(\/[^\s]*)?)/gi;
29+
export const YOUTUBE_URL_GET_VIDEO_ID = /^(?:(?:https?:)?\/\/)?(?:(?:www|m)\.)?(?:youtube\.com|youtu\.be)(?:\/(?:[\w-]+\?v=|embed\/|v\/)?)([^\s?&#]+)(?:\S+)?$/i;
2930

3031
//------------------------------------------------------------------------------
3132
// Position and sizes
@@ -846,10 +847,7 @@ export function isBlock(node) {
846847
*/
847848
export function isBold(node) {
848849
const fontWeight = +getComputedStyle(closestElement(node)).fontWeight;
849-
return (
850-
fontWeight > 500 ||
851-
fontWeight > +getComputedStyle(closestBlock(node)).fontWeight
852-
);
850+
return fontWeight > 500 || fontWeight > +getComputedStyle(closestBlock(node)).fontWeight;
853851
}
854852

855853
export function isUnbreakable(node) {

0 commit comments

Comments
 (0)