操纵CKEditor视图的innertext

发布于 2025-02-06 20:25:22 字数 4570 浏览 2 评论 0原文

我正在为#Neoscms创建一个为CKEditor5创建一个自定义插件。 NEOS使用#ckeditor5,但具有自定义视图。

该插件或多或少是占位符插件。用户可以使用项目(标识符和标签)的键值存储配置数据源。 ckeditor中的下拉列表中充满了项目,当用户从下拉列表中选择一个项目时,它会创建一个占位符元素,该元素应以某些数据属性结尾。

主要想法是拥有一个空的元素,只是数据属性以识别元素并能够分配实时数据。但是事实证明,实时数据很棘手。当我在网站上使用额外的JS片段来操纵跨度时,CKEditor无法处理。

是否可以操纵DOM中的视图元素并仍然有一个工作编辑器? 如果我只是在降低的降低中添加内部文本并且不替换某些内容,则该插件正常工作。但是实时数据会很好。

带有元素的neos后端

也许该代码可以给出包装的想法。 它尚未准备就绪,因为这或多或少是主要功能;)

import {Plugin, toWidget, viewToModelPositionOutsideModelElement, Widget,} from "ckeditor5-exports";

import PlaceholderCommand from "./placeHolderCommand";

export default class PlaceholderEditing extends Plugin {
    static get requires() {
        return [Widget];
    }

    init() {
        this._defineSchema();
        this._defineConverters();

        this.editor.commands.add(
            "placeholder",
            new PlaceholderCommand(this.editor)
        );

        this.editor.editing.mapper.on(
            "viewToModelPosition",
            viewToModelPositionOutsideModelElement(this.editor.model, (viewElement) =>
                viewElement.hasClass("internezzo-placeholder")
            )
        );

        this.editor.config.define("placeholderProps", {
            types: ["name", "node", "nodePath"],
        });

        this.editor.config.define("placeholderBrackets", {
            open: "[",
            close: "]",
        });
    }

    _defineSchema() {
        const schema = this.editor.model.schema;

        schema.register("placeholder", {
            allowWhere: "$text",
            isInline: true,
            isObject: true,
            allowAttributes: [
                "name",
                "node",
                "nodePath",
                "data-placeholder-identifier",
                "data-node-identifier",
                "data-node-path",
            ],
        });
    }

    _defineConverters() {
        const conversion = this.editor.conversion;
        const config = this.editor.config;

        conversion.for("upcast").elementToElement({
            view: {
                name: "span",
                classes: ["foobar-placeholder"],
            },
            model: (viewElement, writer) => {
                const name = viewElement.getAttribute('data-placeholder-identifier');
                const node = viewElement.getAttribute('data-node-identifier');
                const nodePath = viewElement.getAttribute('data-node-path');
                const modelWriter = writer.writer || writer;
                return modelWriter.createElement("placeholder", {name, node, nodePath, editable: false});
            },
        });

        conversion.for("editingDowncast").elementToElement({
            model: "placeholder",
            view: (modelItem, writer) => {
                const viewWriter = writer.writer || writer;
                const widgetElement = createPlaceholderView(modelItem, viewWriter);
                return toWidget(widgetElement, viewWriter);
            },
        });

        conversion.for("dataDowncast").elementToElement({
            model: "placeholder",
            view: (modelItem, writer) => {
                const viewWriter = writer.writer || writer;

                return createPlaceholderView(modelItem, viewWriter);
            },
        });

        // Helper method for downcast converters.
        function createPlaceholderView(modelItem, viewWriter) {
            const name = modelItem.getAttribute("name");
            const node = modelItem.getAttribute("node");
            const nodePath = modelItem.getAttribute("nodePath");

            const placeholderView = viewWriter.createContainerElement("span", {
                class: "foobar-placeholder",
                "data-placeholder-identifier": name,
                "data-node-identifier": node,
                "data-node-path": nodePath,
            });

            // Would be nice to remove that and have just empty spans that get dynamic data
            let innerText = config.get("placeholderBrackets.open") + name;
            innerText += config.get("placeholderBrackets.close");
            viewWriter.insert(
                viewWriter.createPositionAt(placeholderView, 0),
                viewWriter.createText(innerText)
            );

            return placeholderView;
        }
    }
}

因此,网站使用的额外JS摘要正在搜索使用类foobar-codeholder 的跨度,并使用实时数据进入跨度。当然,这在前端中起作用,但是使用CKEditor的CMS的后端在不断变化的数据上存在问题。

I am creating a little custom plugin for the CKEditor5 for the #neoscms.
Neos is using the #ckeditor5 but with a custom view.

The plugin is more or less a placeholder plugin. The user can configure a data-source with a key value store for items (identifier and labels). The dropdown in the CKEditor is filled with the items and when the user selects an item from the dropdown, it creates a placeholder element that should end in a span element with some data-attributes.

The main idea was to have an empty element and just data-attributes to identify the element and being able to assign live data. But it turns out that the live data thing is tricky. When I manipulate the span with an extra JS snippet on the Website, the CKEditor cannot handle this.

Is it possible to manipulate a view element in the DOM and still have a working Editor?
The Plugin works fine if I just add inner Text in the downCasting and don't replace something. But the live data would be nice.

Neos Backend with a element

Maybe that code gives an idea of the package.
It is not ready yet as this is more or less the main feature ;)

import {Plugin, toWidget, viewToModelPositionOutsideModelElement, Widget,} from "ckeditor5-exports";

import PlaceholderCommand from "./placeHolderCommand";

export default class PlaceholderEditing extends Plugin {
    static get requires() {
        return [Widget];
    }

    init() {
        this._defineSchema();
        this._defineConverters();

        this.editor.commands.add(
            "placeholder",
            new PlaceholderCommand(this.editor)
        );

        this.editor.editing.mapper.on(
            "viewToModelPosition",
            viewToModelPositionOutsideModelElement(this.editor.model, (viewElement) =>
                viewElement.hasClass("internezzo-placeholder")
            )
        );

        this.editor.config.define("placeholderProps", {
            types: ["name", "node", "nodePath"],
        });

        this.editor.config.define("placeholderBrackets", {
            open: "[",
            close: "]",
        });
    }

    _defineSchema() {
        const schema = this.editor.model.schema;

        schema.register("placeholder", {
            allowWhere: "$text",
            isInline: true,
            isObject: true,
            allowAttributes: [
                "name",
                "node",
                "nodePath",
                "data-placeholder-identifier",
                "data-node-identifier",
                "data-node-path",
            ],
        });
    }

    _defineConverters() {
        const conversion = this.editor.conversion;
        const config = this.editor.config;

        conversion.for("upcast").elementToElement({
            view: {
                name: "span",
                classes: ["foobar-placeholder"],
            },
            model: (viewElement, writer) => {
                const name = viewElement.getAttribute('data-placeholder-identifier');
                const node = viewElement.getAttribute('data-node-identifier');
                const nodePath = viewElement.getAttribute('data-node-path');
                const modelWriter = writer.writer || writer;
                return modelWriter.createElement("placeholder", {name, node, nodePath, editable: false});
            },
        });

        conversion.for("editingDowncast").elementToElement({
            model: "placeholder",
            view: (modelItem, writer) => {
                const viewWriter = writer.writer || writer;
                const widgetElement = createPlaceholderView(modelItem, viewWriter);
                return toWidget(widgetElement, viewWriter);
            },
        });

        conversion.for("dataDowncast").elementToElement({
            model: "placeholder",
            view: (modelItem, writer) => {
                const viewWriter = writer.writer || writer;

                return createPlaceholderView(modelItem, viewWriter);
            },
        });

        // Helper method for downcast converters.
        function createPlaceholderView(modelItem, viewWriter) {
            const name = modelItem.getAttribute("name");
            const node = modelItem.getAttribute("node");
            const nodePath = modelItem.getAttribute("nodePath");

            const placeholderView = viewWriter.createContainerElement("span", {
                class: "foobar-placeholder",
                "data-placeholder-identifier": name,
                "data-node-identifier": node,
                "data-node-path": nodePath,
            });

            // Would be nice to remove that and have just empty spans that get dynamic data
            let innerText = config.get("placeholderBrackets.open") + name;
            innerText += config.get("placeholderBrackets.close");
            viewWriter.insert(
                viewWriter.createPositionAt(placeholderView, 0),
                viewWriter.createText(innerText)
            );

            return placeholderView;
        }
    }
}

So, the extra JS snippet that is used by the website is searching for spans with the class foobar-placeholder and writes a value with live data into the span. That works in the frontend, of course, but the backend of the CMS that uses CKEditor has issues with the changing data.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

萌化 2025-02-13 20:25:22

我找不到使用Ckeditor文档的解决方案,也许我以某种方式滥用了API,但是我现在为我找到了一个工作解决方案。

我的网站片段现在通过广播消息与插件进行通信。然后我搜索占位符元素,并检查是否需要更改属性。

const broadcastChannel = new BroadcastChannel('placeholder:changeData');
broadcastChannel.postMessage({identifier: name, value});

现在仅在插件中

// Receive new values for placeholder via broadcast
        const broadcastChannel = new BroadcastChannel('placeholder:changeData');
        broadcastChannel.onmessage = (message) => {
            const identifier = get('data.identifier', message);
            const newValue = get('data.value', message);
            this.editor.model.change( writer => {
                if (identifier) {
                    this._replaceAttribute(writer, identifier, newValue);
                }
            });
        };

,我需要重新加载页面,但是已经读到,这可能是由于我的元素下降而导致的,并且我更改了属性。

I could not find a solution with docs of CKEditor, and maybe I misuse the API somehow, but I now found a working solution for me.

My website snippet is now communicating with the Plugin via Broadcast messages. And then I search for placeholder elements and check if I need to change an attribute.

const broadcastChannel = new BroadcastChannel('placeholder:changeData');
broadcastChannel.postMessage({identifier: name, value});

And in the plugin

// Receive new values for placeholder via broadcast
        const broadcastChannel = new BroadcastChannel('placeholder:changeData');
        broadcastChannel.onmessage = (message) => {
            const identifier = get('data.identifier', message);
            const newValue = get('data.value', message);
            this.editor.model.change( writer => {
                if (identifier) {
                    this._replaceAttribute(writer, identifier, newValue);
                }
            });
        };

Only downside now is that I need to reload the page, but already read that this is maybe cause by my element down casting and I change attributes.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文