[FIX] web_editor: video selector component adjustments for Knowledge
This commit will apply the following changes: 1. Introduce a new `VideoIframe` component for the `VideoSelector` component. With that change, one can easily mock the `VideoIframe` component in a tour and replace the iframe of the video with alternative content. It will then be possible to test the whole video integration flow with a tour without relying on Third-Party services. 2. Enhance the Python video parser to include the video id and the video parameters of the url being parsed. These new information will be used for the new video behavior of Knowledge. 3. Call the callback function notifying that the video url has changed when the textarea is emptied (see: `selectMedia`). 4. Authorize non-internal users to call the /web_editor/video_url/data route. This will ensure that portal users will be able to integrate videos in their Knowledge articles. 5. Apply some minor UI and UX adjustments: - The label next to the toggle buttons will now be properly aligned. - The textarea to input the video url will be automatically focused. task-3297215 closes odoo/odoo#127452 Related: odoo/enterprise#40683 Signed-off-by: Damien Abeloos (abd) <abd@odoo.com>
This commit is contained in:
@@ -230,8 +230,6 @@ class Web_Editor(http.Controller):
|
||||
def video_url_data(self, video_url, autoplay=False, loop=False,
|
||||
hide_controls=False, hide_fullscreen=False, hide_yt_logo=False,
|
||||
hide_dm_logo=False, hide_dm_share=False):
|
||||
if not request.env.user._is_internal():
|
||||
raise werkzeug.exceptions.Forbidden()
|
||||
return get_video_url_data(
|
||||
video_url, autoplay=autoplay, loop=loop,
|
||||
hide_controls=hide_controls, hide_fullscreen=hide_fullscreen,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { useService } from '@web/core/utils/hooks';
|
||||
import { useAutofocus, useService } from '@web/core/utils/hooks';
|
||||
import { debounce } from '@web/core/utils/timing';
|
||||
|
||||
import { Component, useState, useRef, onMounted, onWillStart } from "@odoo/owl";
|
||||
@@ -9,6 +9,13 @@ import { Component, useState, useRef, onMounted, onWillStart } from "@odoo/owl";
|
||||
class VideoOption extends Component {}
|
||||
VideoOption.template = 'web_editor.VideoOption';
|
||||
|
||||
class VideoIframe extends Component {
|
||||
static template = 'web_editor.VideoIframe';
|
||||
static props = {
|
||||
src: { type: String },
|
||||
};
|
||||
}
|
||||
|
||||
export class VideoSelector extends Component {
|
||||
setup() {
|
||||
this.rpc = useService('rpc');
|
||||
@@ -98,6 +105,8 @@ export class VideoSelector extends Component {
|
||||
}));
|
||||
});
|
||||
|
||||
useAutofocus();
|
||||
|
||||
this.onChangeUrl = debounce((ev) => this.updateVideo(ev.target.value), 500);
|
||||
}
|
||||
|
||||
@@ -130,6 +139,12 @@ export class VideoSelector extends Component {
|
||||
this.state.options = [];
|
||||
this.state.platform = null;
|
||||
this.state.errorMessage = '';
|
||||
/**
|
||||
* When the url input is emptied, we need to call the `selectMedia`
|
||||
* callback function to notify the other components that the media
|
||||
* has changed.
|
||||
*/
|
||||
this.props.selectMedia({});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -150,7 +165,13 @@ export class VideoSelector extends Component {
|
||||
options[option.id] = option.value;
|
||||
}
|
||||
}
|
||||
const { embed_url: src, platform } = await this._getVideoURLData(url, options);
|
||||
|
||||
const {
|
||||
embed_url: src,
|
||||
video_id: videoId,
|
||||
params,
|
||||
platform
|
||||
} = await this._getVideoURLData(url, options);
|
||||
|
||||
if (!src) {
|
||||
this.state.errorMessage = _t("The provided url is not valid");
|
||||
@@ -173,7 +194,13 @@ export class VideoSelector extends Component {
|
||||
}
|
||||
|
||||
this.state.src = src;
|
||||
this.props.selectMedia({ id: src, src });
|
||||
this.props.selectMedia({
|
||||
id: src,
|
||||
src,
|
||||
platform,
|
||||
videoId,
|
||||
params
|
||||
});
|
||||
if (platform !== this.state.platform) {
|
||||
this.state.platform = platform;
|
||||
this.state.options = newOptions;
|
||||
@@ -213,6 +240,7 @@ VideoSelector.mediaExtraClasses = [];
|
||||
VideoSelector.tagNames = ['IFRAME', 'DIV'];
|
||||
VideoSelector.template = 'web_editor.VideoSelector';
|
||||
VideoSelector.components = {
|
||||
VideoIframe,
|
||||
VideoOption,
|
||||
};
|
||||
VideoSelector.defaultProps = {
|
||||
|
||||
@@ -3,27 +3,37 @@
|
||||
|
||||
<t t-name="web_editor.VideoOption">
|
||||
<div class="mb-1">
|
||||
<label class="o_switch" t-on-change="props.onChangeOption">
|
||||
<input type="checkbox" t-att-checked="props.value"/><span/>
|
||||
<label class="d-flex align-items-start gap-2 cursor-pointer" t-on-change="props.onChangeOption">
|
||||
<div class="o_switch flex-shrink-0">
|
||||
<input type="checkbox" t-att-checked="props.value"/>
|
||||
<span/>
|
||||
</div>
|
||||
<span t-esc="props.label"/>
|
||||
<span t-if="props.description" class="small text-muted ms-auto" t-esc="props.description"/>
|
||||
<span t-if="props.description" class="text-muted" t-esc="props.description"/>
|
||||
</label>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="web_editor.VideoIframe">
|
||||
<iframe t-att-src="this.props.src"
|
||||
class="o_video_dialog_iframe mw-100 mh-100 overflow-hidden shadow"
|
||||
width="1280" height="720"
|
||||
allowfullscreen="allowfullscreen" frameborder="0"/>
|
||||
</t>
|
||||
|
||||
<t t-name="web_editor.VideoSelector">
|
||||
<div class="row">
|
||||
<div class="col mt-4 o_video_dialog_form">
|
||||
<div class="mb-2">
|
||||
<label class="col-form-label" for="o_video_text">
|
||||
<label for="o_video_text">
|
||||
<b>Video code </b>(URL or Embed)
|
||||
</label>
|
||||
<div class="text-start">
|
||||
<small class="text-muted">Accepts <b><i>Youtube</i></b>, <b><i>Vimeo</i></b>, <b><i>Dailymotion</i></b> and <b><i>Youku</i></b> videos</small>
|
||||
</div>
|
||||
<textarea t-model="state.urlInput" class="form-control" id="o_video_text" placeholder="Copy-paste your URL or embed code here" t-on-input="onChangeUrl" t-att-class="{ 'is-valid': state.urlInput and !this.state.errorMessage, 'is-invalid': state.urlInput and this.state.errorMessage }"/>
|
||||
<textarea t-ref="autofocus" t-model="state.urlInput" class="form-control" id="o_video_text" placeholder="Copy-paste your URL or embed code here" t-on-input="onChangeUrl" t-att-class="{ 'is-valid': state.urlInput and !this.state.errorMessage, 'is-invalid': state.urlInput and this.state.errorMessage }"/>
|
||||
</div>
|
||||
<div t-if="shownOptions.length" class="o_video_dialog_options mt-4">
|
||||
<div t-if="shownOptions.length" class="o_video_dialog_options">
|
||||
<VideoOption t-foreach="shownOptions" t-as="option" t-key="option.id"
|
||||
value="option.value"
|
||||
onChangeOption="() => this.onChangeOption(option.id)"
|
||||
@@ -46,7 +56,9 @@
|
||||
<div t-if="this.state.src and !this.state.errorMessage" class="o_video_dialog_preview_text mb-2">Preview</div>
|
||||
<div class="media_iframe_video">
|
||||
<div class="media_iframe_video_size"/>
|
||||
<iframe t-if="this.state.src and !this.state.errorMessage" height="720" width="1280" class="o_video_dialog_iframe mw-100 mh-100 overflow-hidden shadow" allowfullscreen="allowfullscreen" frameborder="0" t-att-src="this.state.src"/>
|
||||
<VideoIframe
|
||||
t-if="this.state.src and !this.state.errorMessage"
|
||||
src="this.state.src"/>
|
||||
<div t-if="this.state.errorMessage" class="alert alert-warning o_video_dialog_iframe mw-100 mh-100 mb-2 mt-2" t-esc="this.state.errorMessage"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -68,7 +68,7 @@ class TestVideoUtils(common.BaseCase):
|
||||
self.assertEqual('B6dXGTxggTG', tools.get_video_source_data(TestVideoUtils.urls['instagram'])[1])
|
||||
|
||||
def test_get_video_url_data(self):
|
||||
self.assertEqual(2, len(tools.get_video_url_data(TestVideoUtils.urls['youtube'])))
|
||||
self.assertEqual(4, len(tools.get_video_url_data(TestVideoUtils.urls['youtube'])))
|
||||
#youtube
|
||||
self.assertEqual('youtube', tools.get_video_url_data(TestVideoUtils.urls['youtube'])['platform'])
|
||||
#vimeo
|
||||
|
||||
@@ -52,7 +52,7 @@ def get_video_source_data(video_url):
|
||||
|
||||
|
||||
def get_video_url_data(video_url, autoplay=False, loop=False, hide_controls=False, hide_fullscreen=False, hide_yt_logo=False, hide_dm_logo=False, hide_dm_share=False):
|
||||
""" Computes the platform name and embed_url from given URL
|
||||
""" Computes the platform name, the embed_url, the video id and the video params of the given URL
|
||||
(or error message in case of invalid URL).
|
||||
"""
|
||||
source = get_video_source_data(video_url)
|
||||
@@ -115,7 +115,12 @@ def get_video_url_data(video_url, autoplay=False, loop=False, hide_controls=Fals
|
||||
if params:
|
||||
embed_url = f'{embed_url}?{url_encode(params)}'
|
||||
|
||||
return {'platform': platform, 'embed_url': embed_url}
|
||||
return {
|
||||
'platform': platform,
|
||||
'embed_url': embed_url,
|
||||
'video_id': video_id,
|
||||
'params': params
|
||||
}
|
||||
|
||||
|
||||
def get_video_embed_code(video_url):
|
||||
|
||||
Reference in New Issue
Block a user