[MIG] web_editor_class_selector: Migration to version 18.0

pull/3161/head
Carlos Lopez 2025-04-22 09:48:48 -05:00
parent fa26af9fa2
commit e871d8a88e
14 changed files with 163 additions and 175 deletions

View File

@ -17,13 +17,13 @@ Web editor class selector
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3 :alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fweb-lightgray.png?logo=github .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fweb-lightgray.png?logo=github
:target: https://github.com/OCA/web/tree/17.0/web_editor_class_selector :target: https://github.com/OCA/web/tree/18.0/web_editor_class_selector
:alt: OCA/web :alt: OCA/web
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/web-17-0/web-17-0-web_editor_class_selector :target: https://translation.odoo-community.org/projects/web-18-0/web-18-0-web_editor_class_selector
:alt: Translate me on Weblate :alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png .. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/web&target_branch=17.0 :target: https://runboat.odoo-community.org/builds?repo=OCA/web&target_branch=18.0
:alt: Try me on Runboat :alt: Try me on Runboat
|badge1| |badge2| |badge3| |badge4| |badge5| |badge1| |badge2| |badge3| |badge4| |badge5|
@ -60,7 +60,7 @@ Bug Tracker
Bugs are tracked on `GitHub Issues <https://github.com/OCA/web/issues>`_. Bugs are tracked on `GitHub Issues <https://github.com/OCA/web/issues>`_.
In case of trouble, please check there if your issue has already been reported. In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/web/issues/new?body=module:%20web_editor_class_selector%0Aversion:%2017.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_. `feedback <https://github.com/OCA/web/issues/new?body=module:%20web_editor_class_selector%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues. Do not contact contributors directly about support or help with technical issues.
@ -85,6 +85,14 @@ OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and mission is to support the collaborative development of Odoo features and
promote its widespread use. promote its widespread use.
This module is part of the `OCA/web <https://github.com/OCA/web/tree/17.0/web_editor_class_selector>`_ project on GitHub. .. |maintainer-carlos-lopez-tecnativa| image:: https://github.com/carlos-lopez-tecnativa.png?size=40px
:target: https://github.com/carlos-lopez-tecnativa
:alt: carlos-lopez-tecnativa
Current `maintainer <https://odoo-community.org/page/maintainer-role>`__:
|maintainer-carlos-lopez-tecnativa|
This module is part of the `OCA/web <https://github.com/OCA/web/tree/18.0/web_editor_class_selector>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View File

@ -1,6 +1,6 @@
{ {
"name": "Web editor class selector", "name": "Web editor class selector",
"version": "17.0.1.1.0", "version": "18.0.1.0.0",
"summary": "", "summary": "",
"author": "Tecnativa, Odoo Community Association (OCA)", "author": "Tecnativa, Odoo Community Association (OCA)",
"website": "https://github.com/OCA/web", "website": "https://github.com/OCA/web",
@ -17,15 +17,14 @@
], ],
"assets": { "assets": {
"web.assets_backend": [ "web.assets_backend": [
"web_editor_class_selector/static/src/js/backend/**/*", "web_editor_class_selector/static/src/js/css_selector/**/*",
],
"web_editor.backend_assets_wysiwyg": [
"web_editor_class_selector/static/src/js/odoo-editor/**/*",
"web_editor_class_selector/static/src/js/wysiwyg/**/*",
"web_editor_class_selector/static/src/scss/demo_styles.scss", "web_editor_class_selector/static/src/scss/demo_styles.scss",
"web_editor_class_selector/static/src/xml/**/", "web_editor_class_selector/static/src/js/fields/**/*",
"web_editor_class_selector/static/src/js/utils/**/*",
"web_editor_class_selector/static/src/js/wysiwyg/**/*",
], ],
}, },
"maintainers": ["carlos-lopez-tecnativa"],
"installable": True, "installable": True,
"auto_install": False, "auto_install": False,
"license": "AGPL-3", "license": "AGPL-3",

View File

@ -369,7 +369,7 @@ ul.auto-toc {
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:53a338e09db97f68da45d08f963625f60a4069424d8d2f75bcca9887a201824b !! source digest: sha256:53a338e09db97f68da45d08f963625f60a4069424d8d2f75bcca9887a201824b
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/web/tree/17.0/web_editor_class_selector"><img alt="OCA/web" src="https://img.shields.io/badge/github-OCA%2Fweb-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/web-17-0/web-17-0-web_editor_class_selector"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/web&amp;target_branch=17.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p> <p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/web/tree/18.0/web_editor_class_selector"><img alt="OCA/web" src="https://img.shields.io/badge/github-OCA%2Fweb-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/web-18-0/web-18-0-web_editor_class_selector"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/web&amp;target_branch=18.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>This module allows users to create custom CSS class records, which can <p>This module allows users to create custom CSS class records, which can
then be selected and applied directly in the HTML editor. Note: The then be selected and applied directly in the HTML editor. Note: The
actual CSS file containing the class definitions is not provided by this actual CSS file containing the class definitions is not provided by this
@ -408,7 +408,7 @@ supported)</p>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/web/issues">GitHub Issues</a>. <p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/web/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported. In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed If you spotted it first, help us to smash it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/web/issues/new?body=module:%20web_editor_class_selector%0Aversion:%2017.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p> <a class="reference external" href="https://github.com/OCA/web/issues/new?body=module:%20web_editor_class_selector%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p> <p>Do not contact contributors directly about support or help with technical issues.</p>
</div> </div>
<div class="section" id="credits"> <div class="section" id="credits">
@ -428,7 +428,9 @@ If you spotted it first, help us to smash it by providing a detailed and welcome
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose <p>OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and mission is to support the collaborative development of Odoo features and
promote its widespread use.</p> promote its widespread use.</p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/web/tree/17.0/web_editor_class_selector">OCA/web</a> project on GitHub.</p> <p>Current <a class="reference external" href="https://odoo-community.org/page/maintainer-role">maintainer</a>:</p>
<p><a class="reference external image-reference" href="https://github.com/carlos-lopez-tecnativa"><img alt="carlos-lopez-tecnativa" src="https://github.com/carlos-lopez-tecnativa.png?size=40px" /></a></p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/web/tree/18.0/web_editor_class_selector">OCA/web</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p> <p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div> </div>
</div> </div>

View File

@ -0,0 +1,24 @@
import {Component, useState} from "@odoo/owl";
import {Dropdown} from "@web/core/dropdown/dropdown";
import {DropdownItem} from "@web/core/dropdown/dropdown_item";
import {toolbarButtonProps} from "@html_editor/main/toolbar/toolbar";
export class CssSelector extends Component {
static template = "web_editor_class_selector.CssSelector";
static props = {
getItems: Function,
getDisplay: Function,
onSelected: Function,
...toolbarButtonProps,
};
static components = {Dropdown, DropdownItem};
setup() {
this.items = this.props.getItems();
this.state = useState(this.props.getDisplay());
}
onSelected(item) {
this.props.onSelected(item);
}
}

View File

@ -0,0 +1,20 @@
<templates xml:space="preserve">
<t t-name="web_editor_class_selector.CssSelector">
<Dropdown>
<button class="btn btn-light" t-att-title="props.title">
<span class="px-1" t-esc="state.displayName" />
</button>
<t t-set-slot="content">
<t t-foreach="items" t-as="item" t-key="item_index">
<DropdownItem
class="item.class_name"
onSelected="() => this.onSelected(item)"
t-on-pointerdown.prevent="() => {}"
>
<t t-esc="item.name" />
</DropdownItem>
</t>
</t>
</Dropdown>
</t>
</templates>

View File

@ -0,0 +1,71 @@
import {CssSelector} from "./css_selector.esm";
import {Plugin} from "@html_editor/plugin";
import {_t} from "@web/core/l10n/translation";
import {reactive} from "@odoo/owl";
import {closestElement} from "@html_editor/utils/dom_traversal";
import {isVisibleTextNode} from "@html_editor/utils/dom_info";
import {withSequence} from "@html_editor/utils/resource";
export class CssSelectorPlugin extends Plugin {
static id = "css_selector_plugin";
static dependencies = ["selection", "format"];
resources = {
toolbar_groups: [withSequence(60, {id: "css-selector"})],
toolbar_items: [
{
id: "css-selector",
groupId: "css-selector",
title: _t("Custom CSS"),
Component: CssSelector,
props: {
getItems: () => this.custom_class_css,
getDisplay: () => this.custom_css,
onSelected: (item) => {
this.dependencies.format.formatSelection(item.class_name, {
formatProps: {className: item.class_name},
applyStyle: true,
});
this.updateCustomCssSelectorParams();
},
},
},
],
/** Handlers */
selectionchange_handlers: [this.updateCustomCssSelectorParams.bind(this)],
post_undo_handlers: [this.updateCustomCssSelectorParams.bind(this)],
post_redo_handlers: [this.updateCustomCssSelectorParams.bind(this)],
};
setup() {
this.custom_css = reactive({displayName: this.defaultCustomCssName});
this.custom_class_css = this.config.custom_class_css;
}
updateCustomCssSelectorParams() {
this.custom_css.displayName = this.customCssName;
}
get defaultCustomCssName() {
return _t("Custom CSS");
}
get customCssName() {
const selectedNodes = this.dependencies.selection
.getSelectedNodes()
.filter(
(n) =>
n.nodeType === Node.TEXT_NODE &&
closestElement(n).isContentEditable &&
isVisibleTextNode(n)
);
let activeLabel = this.defaultCustomCssName;
for (const selectedTextNode of selectedNodes) {
const parentNode = selectedTextNode.parentElement;
for (const customCss of this.custom_class_css) {
const isActive = parentNode.classList.contains(customCss.class_name);
if (isActive) {
activeLabel = customCss.name;
break;
}
}
}
return activeLabel;
}
}

View File

@ -1,5 +1,5 @@
/** @odoo-module **/ import {CssSelectorPlugin} from "../css_selector/css_selector_plugin.esm";
import {HtmlField} from "@web_editor/js/backend/html_field"; import {HtmlField} from "@html_editor/fields/html_field";
import {patch} from "@web/core/utils/patch"; import {patch} from "@web/core/utils/patch";
import {useService} from "@web/core/utils/hooks"; import {useService} from "@web/core/utils/hooks";
@ -18,14 +18,12 @@ patch(HtmlField.prototype, {
); );
}); });
}, },
get wysiwygOptions() { getConfig() {
// Provide the custom_class_css to the toolbar through the toolbarOptions. // Add the new Plugin to the list of plugins.
return { // Provide the custom_class_css to the toolbar.
...super.wysiwygOptions, const config = super.getConfig(...arguments);
toolbarOptions: { config.Plugins.push(CssSelectorPlugin);
...super.wysiwygOptions.toolbarOptions, config.custom_class_css = this.custom_class_css;
custom_class_css: this.custom_class_css, return config;
},
};
}, },
}); });

View File

@ -1,68 +0,0 @@
/** @odoo-module **/
import {
closestElement,
getSelectedNodes,
isVisibleTextNode,
} from "@web_editor/js/editor/odoo-editor/src/utils/utils";
import {OdooEditor} from "@web_editor/js/editor/odoo-editor/src/OdooEditor";
import {_t} from "@web/core/l10n/translation";
import {patch} from "@web/core/utils/patch";
patch(OdooEditor.prototype, {
_updateToolbar(show) {
const res = super._updateToolbar(show);
if (!this.toolbar || !this.custom_class_css) {
return res;
}
const sel = this.document.getSelection();
if (!this.isSelectionInEditable(sel)) {
return res;
}
// Get selected nodes within td to handle non-p elements like h1, h2...
// Targeting <br> to ensure span stays inside its corresponding block node.
const selectedNodesInTds = [
...this.editable.querySelectorAll(".o_selected_td"),
].map((node) => closestElement(node).querySelector("br"));
const selectedNodes = getSelectedNodes(this.editable).filter(
(n) =>
n.nodeType === Node.TEXT_NODE &&
closestElement(n).isContentEditable &&
isVisibleTextNode(n)
);
const selectedTextNodes = selectedNodes.length
? selectedNodes
: selectedNodesInTds;
let activeLabel = "";
for (const selectedTextNode of selectedTextNodes) {
const parentNode = selectedTextNode.parentElement;
for (const customCss of this.custom_class_css) {
const button = this.toolbar.querySelector("#" + customCss.class_name);
if (button) {
const isActive = parentNode.classList.contains(
customCss.class_name
);
button.classList.toggle("active", isActive);
if (isActive) {
activeLabel = button.textContent;
}
}
}
}
// Show current class active in the toolbar
// or remove active class if nothing is selected
const styleSection = this.toolbar.querySelector("#custom_class");
if (styleSection) {
if (!activeLabel) {
const css_selectors = this.toolbar.querySelectorAll(".css_selector");
for (const node of css_selectors) {
node.classList.toggle("active", false);
}
}
styleSection.querySelector("button span").textContent = activeLabel
? activeLabel
: _t("Custom CSS");
}
return res;
},
});

View File

@ -1,12 +0,0 @@
/** @odoo-module **/
import {editorCommands} from "@web_editor/js/editor/odoo-editor/src/commands/commands";
import {formatSelection} from "@web_editor/js/editor/odoo-editor/src/utils/utils";
const newCommands = {
setCustomCss: (editor, ...args) => {
const selectedId = parseInt(args[0], 10);
const record = editor.custom_class_css.find((item) => item.id === selectedId);
formatSelection(editor, record.class_name);
},
};
Object.assign(editorCommands, newCommands);

View File

@ -1,13 +0,0 @@
/** @odoo-module */
import {Toolbar} from "@web_editor/js/editor/toolbar";
import {patch} from "@web/core/utils/patch";
patch(Toolbar.props, {
...Toolbar.props,
custom_class_css: {type: Array, optional: true},
});
patch(Toolbar.defaultProps, {
...Toolbar.defaultProps,
custom_class_css: [],
});

View File

@ -1,10 +1,7 @@
/** @odoo-module **/ import {closestElement} from "@html_editor/utils/dom_traversal";
import { import {formatsSpecs} from "@html_editor/utils/formatting";
closestElement,
formatsSpecs,
} from "@web_editor/js/editor/odoo-editor/src/utils/utils";
// This function is called in the _configureToolbar method of the Wysiwyg class // This function is called in the getEditorConfig method of the Wysiwyg class
// It generates the new formatsSpecs object with the custom CSS class // It generates the new formatsSpecs object with the custom CSS class
export function createCustomCssFormats(custom_class_css) { export function createCustomCssFormats(custom_class_css) {
const newformatsSpecs = {}; const newformatsSpecs = {};

View File

@ -1,17 +1,16 @@
/** @odoo-module **/ import {Wysiwyg} from "@html_editor/wysiwyg";
import {Wysiwyg} from "@web_editor/js/wysiwyg/wysiwyg"; import {createCustomCssFormats} from "../utils/utils.esm";
import {createCustomCssFormats} from "../odoo-editor/utils.esm";
import {patch} from "@web/core/utils/patch"; import {patch} from "@web/core/utils/patch";
patch(Wysiwyg.prototype, { patch(Wysiwyg.prototype, {
_configureToolbar(options) { getEditorConfig() {
super._configureToolbar(options); const res = super.getEditorConfig(...arguments);
if ( if (
options.toolbarOptions.custom_class_css && this.props.config.custom_class_css &&
options.toolbarOptions.custom_class_css.length > 0 this.props.config.custom_class_css.length > 0
) { ) {
this.odooEditor.custom_class_css = options.toolbarOptions.custom_class_css; createCustomCssFormats(this.props.config.custom_class_css);
createCustomCssFormats(options.toolbarOptions.custom_class_css);
} }
return res;
}, },
}); });

View File

@ -1,37 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates id="template" xml:space="preserve">
<t t-inherit="web_editor.toolbar" t-inherit-mode="extension">
<xpath expr="//div[@id='chatgpt']" position="after">
<div
id="custom_class"
class="btn-group dropdown"
t-if="props.custom_class_css and props.custom_class_css.length"
>
<button
type="button"
class="btn dropdown-toggle"
data-bs-toggle="dropdown"
title="Custom CSS"
tabindex="-1"
data-bs-original-title="Custom CSS"
aria-expanded="false"
>
<span>Custom CSS</span>
</button>
<ul class="dropdown-menu">
<li t-foreach="props.custom_class_css" t-as="line" t-key="line.id">
<a
class="dropdown-item css_selector"
t-att-id="line.class_name"
href="#"
data-call="setCustomCss"
t-att-data-arg1="line.id"
>
<span t-att-class="line.class_name" t-out="line.name" />
</a>
</li>
</ul>
</div>
</xpath>
</t>
</templates>

View File

@ -1,14 +1,14 @@
<?xml version="1.0" encoding="utf-8" ?> <?xml version="1.0" encoding="utf-8" ?>
<odoo> <odoo>
<record id="view_web_editor_class_tree" model="ir.ui.view"> <record id="view_web_editor_class_tree" model="ir.ui.view">
<field name="name">view.web.editor.class.tree</field> <field name="name">view.web.editor.class.list</field>
<field name="model">web.editor.class</field> <field name="model">web.editor.class</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree> <list>
<field name="sequence" widget="handle" /> <field name="sequence" widget="handle" />
<field name="name" /> <field name="name" />
<field name="class_name" /> <field name="class_name" />
</tree> </list>
</field> </field>
</record> </record>
@ -50,7 +50,7 @@
<field name="name">Web Editor Class</field> <field name="name">Web Editor Class</field>
<field name="type">ir.actions.act_window</field> <field name="type">ir.actions.act_window</field>
<field name="res_model">web.editor.class</field> <field name="res_model">web.editor.class</field>
<field name="view_mode">tree,form</field> <field name="view_mode">list,form</field>
<field name="help" type="html"> <field name="help" type="html">
<p class="o_view_nocontent_smiling_face"> <p class="o_view_nocontent_smiling_face">
Click here to add new Web Editor Class. Click here to add new Web Editor Class.