[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:
Benjamin Vray
2021-09-16 10:01:06 +00:00
committed by qsm-odoo
parent c0dcc8265b
commit 360abc0570
8 changed files with 232 additions and 8 deletions
@@ -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>
+3 -3
View File
@@ -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>