[IMP] web_editor, website: make animation options available on text
After this commit, it is possible to animate text in website pages. task-2545252 closes odoo/odoo#76610 X-original-commit: 187acb938f70a2130d25fa76079221339c742f1e Signed-off-by: Quentin Smetz (qsm) <qsm@odoo.com>
This commit is contained in:
@@ -805,4 +805,14 @@ section, .oe_img_bg, [data-oe-shape-data] {
|
||||
.text-gradient {
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
|
||||
// FIXME (or wait for a fix in Chrome): the code below is needed to make
|
||||
// animated text work with gradient background on Chrome. But the side
|
||||
// effect is that text node wrapping by a span (e.g. bold, italic) no longer
|
||||
// have the "gradient crossing all the text" on it but its own gradient.
|
||||
* {
|
||||
background-image: inherit;
|
||||
-webkit-background-clip: inherit;
|
||||
-webkit-text-fill-color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -487,7 +487,7 @@ body.editor_enable.editor_has_snippets {
|
||||
@extend %we-generic-button;
|
||||
}
|
||||
|
||||
#removeFormat, #oe-table-delete-table {
|
||||
#removeFormat, #oe-table-delete-table, .o_we_highlight_animated_text {
|
||||
@extend %we-generic-link;
|
||||
}
|
||||
|
||||
@@ -496,9 +496,9 @@ body.editor_enable.editor_has_snippets {
|
||||
background: transparent;
|
||||
margin-top: $o-we-sidebar-content-field-spacing;
|
||||
padding: 0 $o-we-sidebar-content-padding-base 0 $o-we-sidebar-content-indent;
|
||||
grid-template-areas: "typo typo style colors" "size align list link" "options options options options";
|
||||
grid-template-areas: "typo typo style colors" "size align list link" "options options options options" "options2 options2 options2 options2";
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr;
|
||||
grid-template-rows: minmax($o-we-sidebar-content-field-height, auto) minmax($o-we-sidebar-content-field-height, auto) auto ;
|
||||
grid-template-rows: minmax($o-we-sidebar-content-field-height, auto) minmax($o-we-sidebar-content-field-height, auto) auto auto ;
|
||||
row-gap: $o-we-sidebar-content-field-spacing;
|
||||
column-gap: $o-we-sidebar-content-field-multi-spacing;
|
||||
width: 100%;
|
||||
@@ -565,9 +565,14 @@ body.editor_enable.editor_has_snippets {
|
||||
input::placeholder {
|
||||
font-style: italic;
|
||||
}
|
||||
we-input:not(.o_we_small_input) {
|
||||
&, div, input {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
we-input, we-input div, we-input input {
|
||||
width: 100% !important;
|
||||
& + we-customizeblock-option {
|
||||
grid-area: options2;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,9 @@ const {qweb, _t, _lt} = require('web.core');
|
||||
const Dialog = require('web.Dialog');
|
||||
const weSnippetEditor = require('web_editor.snippet.editor');
|
||||
const wSnippetOptions = require('website.editor.snippets.options');
|
||||
const OdooEditorLib = require('@web_editor/../lib/odoo-editor/src/utils/utils');
|
||||
const getDeepRange = OdooEditorLib.getDeepRange;
|
||||
const getTraversedNodes = OdooEditorLib.getTraversedNodes;
|
||||
|
||||
const FontFamilyPickerUserValueWidget = wSnippetOptions.FontFamilyPickerUserValueWidget;
|
||||
|
||||
@@ -13,6 +16,8 @@ weSnippetEditor.SnippetsMenu.include({
|
||||
.concat(['/website/static/src/xml/website.editor.xml']),
|
||||
events: _.extend({}, weSnippetEditor.SnippetsMenu.prototype.events, {
|
||||
'click .o_we_customize_theme_btn': '_onThemeTabClick',
|
||||
'click .o_we_animate_text': '_onAnimateTextClick',
|
||||
'click .o_we_highlight_animated_text': '_onHighlightAnimatedTextClick',
|
||||
}),
|
||||
custom_events: Object.assign({}, weSnippetEditor.SnippetsMenu.prototype.custom_events, {
|
||||
'gmap_api_request': '_onGMapAPIRequest',
|
||||
@@ -27,6 +32,27 @@ weSnippetEditor.SnippetsMenu.include({
|
||||
['website-settings', _lt("Website Settings")],
|
||||
],
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
start() {
|
||||
this._super(...arguments);
|
||||
this.$currentAnimatedText = $();
|
||||
|
||||
this.__onSelectionChange = ev => {
|
||||
this._toggleAnimatedTextButton();
|
||||
};
|
||||
this.ownerDocument.addEventListener('selectionchange', this.__onSelectionChange);
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
destroy() {
|
||||
this._super(...arguments);
|
||||
this.ownerDocument.removeEventListener('selectionchange', this.__onSelectionChange);
|
||||
document.body.classList.remove('o_animated_text_highlighted');
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -158,6 +184,63 @@ weSnippetEditor.SnippetsMenu.include({
|
||||
this._super(...arguments);
|
||||
this.$('.o_we_customize_theme_btn').toggleClass('active', tab === this.tabs.THEME);
|
||||
},
|
||||
/**
|
||||
* Returns the animated text element wrapping the selection if it exists.
|
||||
*
|
||||
* @private
|
||||
* @return {Element|false}
|
||||
*/
|
||||
_getAnimatedTextElement() {
|
||||
const editable = this.options.wysiwyg.$editable[0];
|
||||
const animatedTextNode = getTraversedNodes(editable).find(n => n.parentElement.closest(".o_animated_text"));
|
||||
return animatedTextNode ? animatedTextNode.parentElement.closest('.o_animated_text') : false;
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_addToolbar() {
|
||||
this._super(...arguments);
|
||||
this.$('#o_we_editor_toolbar_container > we-title > span').after($(`
|
||||
<div class="btn fa fa-fw fa-2x o_we_highlight_animated_text d-none
|
||||
${$('body').hasClass('o_animated_text_highlighted') ? 'fa-eye text-success' : 'fa-eye-slash'}"
|
||||
title="${_t('Highlight Animated Text')}"
|
||||
aria-label="Highlight Animated Text"/>
|
||||
`));
|
||||
this._toggleAnimatedTextButton();
|
||||
this._toggleHighlightAnimatedTextButton();
|
||||
},
|
||||
/**
|
||||
* Activates the button to animate text if the selection is in an
|
||||
* animated text element or deactivates the button if not.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_toggleAnimatedTextButton() {
|
||||
if (!this._isValidSelection(window.getSelection())) {
|
||||
return;
|
||||
}
|
||||
const animatedText = this._getAnimatedTextElement();
|
||||
this.$('.o_we_animate_text').toggleClass('active', !!animatedText);
|
||||
this.$currentAnimatedText = animatedText ? $(animatedText) : $();
|
||||
},
|
||||
/**
|
||||
* Displays the button that allows to highlight the animated text if there
|
||||
* is animated text in the page.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_toggleHighlightAnimatedTextButton() {
|
||||
const $animatedText = this.getEditableArea().find('.o_animated_text');
|
||||
this.$('#o_we_editor_toolbar_container .o_we_highlight_animated_text').toggleClass('d-none', !$animatedText.length);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Node} node
|
||||
* @return {Boolean}
|
||||
*/
|
||||
_isValidSelection(sel) {
|
||||
return sel.rangeCount && [...this.getEditableArea()].some(el => el.contains(sel.anchorNode));
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
@@ -233,6 +316,64 @@ weSnippetEditor.SnippetsMenu.include({
|
||||
throw e;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onAnimateTextClick(ev) {
|
||||
const sel = window.getSelection();
|
||||
if (!this._isValidSelection(sel)) {
|
||||
return;
|
||||
}
|
||||
const editable = this.options.wysiwyg.$editable[0];
|
||||
const range = getDeepRange(editable, { splitText: true, select: true, correctTripleClick: true });
|
||||
if (this.$currentAnimatedText.length) {
|
||||
this.$currentAnimatedText.contents().unwrap();
|
||||
this.options.wysiwyg.odooEditor.historyResetLatestComputedSelection();
|
||||
this._toggleHighlightAnimatedTextButton();
|
||||
ev.target.classList.remove('active');
|
||||
this.options.wysiwyg.odooEditor.historyStep();
|
||||
} else {
|
||||
if (sel.getRangeAt(0).collapsed) {
|
||||
return;
|
||||
}
|
||||
const animatedTextEl = document.createElement('span');
|
||||
animatedTextEl.classList.add('o_animated_text', 'o_animate', 'o_animate_preview', 'o_anim_fade_in');
|
||||
let $snippet = null;
|
||||
try {
|
||||
range.surroundContents(animatedTextEl);
|
||||
$snippet = $(animatedTextEl);
|
||||
} catch (e) {
|
||||
// This try catch is needed because 'surroundContents' may
|
||||
// fail when the range has partially selected a non-Text node.
|
||||
if (range.commonAncestorContainer.textContent === range.toString()) {
|
||||
const $commonAncestor = $(range.commonAncestorContainer);
|
||||
$commonAncestor.wrapInner(animatedTextEl);
|
||||
$snippet = $commonAncestor.find('.o_animated_text');
|
||||
}
|
||||
}
|
||||
if ($snippet) {
|
||||
$snippet[0].normalize();
|
||||
this.trigger_up('activate_snippet', {
|
||||
$snippet: $snippet,
|
||||
previewMode: false,
|
||||
});
|
||||
this.options.wysiwyg.odooEditor.historyStep();
|
||||
} else {
|
||||
this.displayNotification({
|
||||
message: _t("The current text selection cannot be animated. Try clearing the format and try again."),
|
||||
type: 'danger',
|
||||
sticky: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onHighlightAnimatedTextClick(ev) {
|
||||
$('body').toggleClass('o_animated_text_highlighted');
|
||||
$(ev.target).toggleClass('fa-eye fa-eye-slash').toggleClass('text-success');
|
||||
},
|
||||
});
|
||||
|
||||
weSnippetEditor.SnippetEditor.include({
|
||||
|
||||
@@ -3171,6 +3171,36 @@ options.registry.ConditionalVisibility = options.Class.extend({
|
||||
});
|
||||
|
||||
options.registry.WebsiteAnimate = options.Class.extend({
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
async start() {
|
||||
await this._super(...arguments);
|
||||
this.isAnimatedText = this.$target.hasClass('o_animated_text');
|
||||
this.$optionsSection = this.$overlay.data('$optionsSection');
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
onFocus() {
|
||||
if (this.isAnimatedText) {
|
||||
// For animated text, the animation options must be in the editor
|
||||
// toolbar.
|
||||
const $toolbar = this.options.wysiwyg.toolbar.$el;
|
||||
$toolbar.append(this.$el);
|
||||
this.$optionsSection.addClass('d-none');
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
onBlur() {
|
||||
if (this.isAnimatedText) {
|
||||
// For animated text, the options must be returned to their
|
||||
// original location as they were moved in the toolbar.
|
||||
this.$optionsSection.append(this.$el);
|
||||
}
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Options
|
||||
@@ -3216,6 +3246,15 @@ options.registry.WebsiteAnimate = options.Class.extend({
|
||||
this.$target.removeClass('o_animating');
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_computeWidgetVisibility(widgetName, params) {
|
||||
if (widgetName === 'no_animation_opt') {
|
||||
return !this.isAnimatedText;
|
||||
}
|
||||
return this._super(...arguments);
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@@ -226,3 +226,21 @@ body.editor_enable {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Website Animate
|
||||
.editor_enable.o_animated_text_highlighted {
|
||||
.o_animated_text {
|
||||
position: relative;
|
||||
|
||||
&:after {
|
||||
content: "";
|
||||
pointer-events: none;
|
||||
@include o-position-absolute(0, 0, 0, 0);
|
||||
z-index: 1;
|
||||
// This border is useful when there is a green background behind
|
||||
// the text.
|
||||
border: 1px dotted white;
|
||||
background-color: rgba(greenyellow, .2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1497,6 +1497,9 @@ ul.o_checklist > li.o_checked::after {
|
||||
.o_wanim_overflow_x_hidden {
|
||||
overflow-x: hidden !important;
|
||||
}
|
||||
.o_animated_text {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
@keyframes o_anim_bounce_in {
|
||||
0%, 20%, 40%, 60%, 80%, 100% {
|
||||
|
||||
@@ -158,4 +158,12 @@
|
||||
<div class="o_new_content_loader"/>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<!-- Editor Toolbar -->
|
||||
<t t-extend="web_editor.toolbar">
|
||||
<t t-jquery="#list .oe-toggle-checklist" t-operation="replace"/>
|
||||
<t t-jquery="#list" t-operation="append">
|
||||
<div title="Animate text" class="btn fa fa-play fa-fw o_we_animate_text"/>
|
||||
</t>
|
||||
</t>
|
||||
</templates>
|
||||
|
||||
@@ -1210,7 +1210,7 @@
|
||||
|
||||
<!-- Website Animate -->
|
||||
<div data-js="WebsiteAnimate"
|
||||
data-selector=".o_animable, section .row > div, img, .fa, .btn"
|
||||
data-selector=".o_animable, section .row > div, img, .fa, .btn, .o_animated_text"
|
||||
data-exclude=".o_not-animable">
|
||||
<we-select string="Animate" data-is-animation-type-selection="true">
|
||||
<we-button data-select-class="" data-name="no_animation_opt">No Animation</we-button>
|
||||
@@ -1248,9 +1248,9 @@
|
||||
<we-button data-select-class="">First Time Only</we-button>
|
||||
<we-button data-select-class="o_animate_both_scroll">Every Time</we-button>
|
||||
</we-select>
|
||||
<we-input string="Animation Duration" data-dependencies="!no_animation_opt"
|
||||
<we-input string="Animation Duration" data-dependencies="!no_animation_opt" class="o_we_small_input"
|
||||
data-select-style="0.4s" data-css-property="animation-duration" data-unit="s"/>
|
||||
<we-input string="Animation Delay" data-dependencies="!no_animation_opt"
|
||||
<we-input string="Animation Delay" data-dependencies="!no_animation_opt" class="o_we_small_input"
|
||||
data-select-style="0s" data-css-property="animation-delay" data-unit="s"/>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user