mirror of
https://github.com/OCA/web.git
synced 2025-02-22 13:21:25 +02:00
[ADD] web_editor_class_selector: new module to add custom CSS in HTML editor.
This module allows users to create custom CSS classes, which can then be selected and applied directly in the HTML editor.
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
/** @odoo-module **/
|
||||
import {HtmlField} from "@web_editor/js/backend/html_field";
|
||||
import {patch} from "@web/core/utils/patch";
|
||||
import {useService} from "@web/core/utils/hooks";
|
||||
|
||||
const {onWillStart} = owl;
|
||||
|
||||
patch(HtmlField.prototype, "web_editor_class_selector.HtmlField", {
|
||||
setup() {
|
||||
this._super(...arguments);
|
||||
this.orm = useService("orm");
|
||||
this.custom_class_css = [];
|
||||
onWillStart(async () => {
|
||||
this.custom_class_css = await this.orm.searchRead(
|
||||
"web.editor.class",
|
||||
[],
|
||||
["name", "class_name"]
|
||||
);
|
||||
});
|
||||
},
|
||||
async startWysiwyg(wysiwyg) {
|
||||
// Provide the custom class css to the wysiwyg editor
|
||||
// to render the custom class css in the toolbar
|
||||
wysiwyg.options.custom_class_css = this.custom_class_css;
|
||||
return this._super(wysiwyg);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,68 @@
|
||||
/** @odoo-module **/
|
||||
import {_t} from "web.core";
|
||||
import {patch} from "web.utils";
|
||||
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";
|
||||
|
||||
patch(OdooEditor.prototype, "web_editor_class_selector.OdooEditor", {
|
||||
_updateToolbar(show) {
|
||||
const res = this._super(show);
|
||||
if (!this.toolbar) {
|
||||
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;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
/** @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);
|
||||
@@ -0,0 +1,37 @@
|
||||
/** @odoo-module **/
|
||||
import {
|
||||
closestElement,
|
||||
formatsSpecs,
|
||||
} from "@web_editor/js/editor/odoo-editor/src/utils/utils";
|
||||
|
||||
// This function is called in the _configureToolbar method of the Wysiwyg class
|
||||
// It generates the new formatsSpecs object with the custom CSS class
|
||||
export function createCustomCssFormats(custom_class_css) {
|
||||
const newformatsSpecs = {};
|
||||
const class_names = custom_class_css.map((customCss) => customCss.class_name);
|
||||
const removeCustomClass = (node) => {
|
||||
for (const class_name of class_names) {
|
||||
node.classList.remove(class_name);
|
||||
if (node.parentElement) {
|
||||
node.parentElement.classList.remove(class_name);
|
||||
}
|
||||
}
|
||||
};
|
||||
for (const customCss of custom_class_css) {
|
||||
const className = customCss.class_name;
|
||||
newformatsSpecs[className] = {
|
||||
tagName: "span",
|
||||
isFormatted: (node) => closestElement(node).classList.contains(className),
|
||||
isTag: (node) =>
|
||||
["SPAN"].includes(node.tagName) && node.classList.contains(className),
|
||||
hasStyle: (node) => closestElement(node).classList.contains(className),
|
||||
addStyle: (node) => {
|
||||
removeCustomClass(node);
|
||||
node.classList.add(className);
|
||||
},
|
||||
addNeutralStyle: (node) => removeCustomClass(node),
|
||||
removeStyle: (node) => removeCustomClass(node),
|
||||
};
|
||||
}
|
||||
Object.assign(formatsSpecs, newformatsSpecs);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/** @odoo-module **/
|
||||
import Wysiwyg from "web_editor.wysiwyg";
|
||||
import core from "web.core";
|
||||
import {createCustomCssFormats} from "../odoo-editor/utils.esm";
|
||||
|
||||
const Qweb = core.qweb;
|
||||
|
||||
Wysiwyg.include({
|
||||
_configureToolbar: function (options) {
|
||||
this._super(options);
|
||||
if (options.custom_class_css && options.custom_class_css.length > 0) {
|
||||
const $dialogContent = $(
|
||||
Qweb.render("web_editor_class_selector.custom_class_css", {
|
||||
custom_class_css: options.custom_class_css,
|
||||
})
|
||||
);
|
||||
$dialogContent.appendTo(this.toolbar.$el);
|
||||
// Binding the new commands to the editor
|
||||
// to react to the click on the new options
|
||||
this.odooEditor.bindExecCommand($dialogContent[0]);
|
||||
this.odooEditor.custom_class_css = options.custom_class_css;
|
||||
createCustomCssFormats(options.custom_class_css);
|
||||
}
|
||||
},
|
||||
});
|
||||
21
web_editor_class_selector/static/src/scss/demo_styles.scss
Normal file
21
web_editor_class_selector/static/src/scss/demo_styles.scss
Normal file
@@ -0,0 +1,21 @@
|
||||
.demo_menu {
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
color: #714b67;
|
||||
}
|
||||
|
||||
.demo_button {
|
||||
border: 1px solid #71639e;
|
||||
border-radius: 0.25rem;
|
||||
padding: 0.25rem 0.7rem;
|
||||
font-weight: bold;
|
||||
color: #343a40;
|
||||
background-color: #dee2e6;
|
||||
border-color: #dee2e6 !important;
|
||||
}
|
||||
|
||||
.demo_field {
|
||||
border-top: 1px solid grey;
|
||||
border-bottom: 1px solid grey;
|
||||
font-weight: bold;
|
||||
}
|
||||
33
web_editor_class_selector/static/src/xml/web_editor.xml
Normal file
33
web_editor_class_selector/static/src/xml/web_editor.xml
Normal file
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
<t t-name="web_editor_class_selector.custom_class_css">
|
||||
<t t-jquery="#decoration" t-operation="before">
|
||||
|
||||
<div id="custom_class" class="btn-group dropdown">
|
||||
<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="custom_class_css" t-as="line">
|
||||
<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>
|
||||
</t>
|
||||
|
||||
</t>
|
||||
</templates>
|
||||
Reference in New Issue
Block a user