无法输入到 CKEditor5 的 EditableElement
Can not input to EditableElement of CKEditor5
在 CKEditor5 中,我尝试实现自定义元素以将模型转换为视图以进行编辑。然后,容器元素(@ckeditor/ckeditor5-engine/src/view/containerelement)中的可编辑元素(@ckeditor/ckeditor5-engine/src/view/editableelement)聚焦在parent容器元素上,无法编辑。
例如如果实现如下:
buildModelConverter().for(editing.modelToView)
.fromElement('myElement')
.toElement(new ContainerElement('div', {}, [new EditableElement('h4')]));
插入 'myElement' 和按键 "abc" 后实际编辑 dom 的结果。 (我希望将 "abc" 的文本输入到 h4 标签,但是...)
<div>
abc
<h4>
<br data-cke-filler="true">
</h4>
</div>
我还尝试使用小部件来应用 contenteditable 属性。
但是,无法在 h4 中输入文本。
<div class="ck-widget" contenteditable="false">
<h4 class="ck-editable" contenteditable="true">
<br data-cke-filler="true">
</h4>
</div>
这是错误,还是我对容器元素的理解有误?
[其他详细信息]
我假设为排名列表制作一个小部件插件。
首先,排名列表由<ol>
和<li>
标签构成,因为有多个项目。
我通过定义两个模式解决了这个问题,例如 "rankingList" 和 "rankingListItem",
所以我使用嵌套模型元素实现了动态元素。
const item1 = new ModelElement('rankingListItem');
const item2 = new ModelElement('rankingListItem');
const model = new ModelElement('rankingList', {}, [item1, item2]);
// and insert
接下来,排行榜的项目有link、图片、标题和备注。
因此,排名列表项具有以下DOM结构:
<ol><!-- apply toWidget -->
<li>
<a href="link[editable]">
<img src="image[editable]">
<h3>title[editable]</h3>
<p>notes[editable]</p>
</a>
</li>
...
</ol>
我希望视图元素如下:
const {ref, src, title, notes} = data; // how to get data?
const view = new ContainerElement('a', {ref}, [
new EmptyElement('img', {src}),
new EditableElement('h3', {}, new Text(title)),
new EditableElement('p', {}, new Text(title)),
]);
// maybe incorrect ...
总而言之,我想使用可编辑视图而不破坏已定义的 DOM 树。
怎么实现?
感谢您描述您的案例。目前,了解开发人员如何使用编辑器以及您的期望对我们来说意义重大。
不幸的是,这看起来是一个非常复杂的功能。它看起来也需要自定义 UI(编辑 link url 和图像 src——除非它们在添加到编辑器后没有改变)。看来您遇到了两个问题:
- 模型和视图之间的位置映射,
- 嵌套的可编辑项。
首先,回答你关于 EditableElement
的问题 - 将它们用于 h3
和 p
元素似乎是正确的。
但是,如此复杂的功能需要自定义转换器。转换器构建器(您使用的)专用于简单情况,例如元素到元素的转换或属性到属性的转换。
漂亮的API背后,构建转换器是一个函数工厂,它创建一个或多个函数。然后将这些作为回调添加到 ModelConversionDispatcher
(editor.editing.modelToView
是其实例)。 ModelConversionDispatcher
在转换期间触发一系列事件,这是将模型中的更改转换为视图的过程。
如前所述,您必须自己编写这些转换函数。
由于这个主题太大,无法提供详细而全面的答案,我将简要介绍您应该感兴趣的内容。不幸的是,目前还没有关于从头开始创建自定义转换器的指南。这是一个非常广泛的主题。
首先,让我向您解释一下您的大部分问题是从哪里来的。如您所知,编辑器具有三层:模型(数据)、视图(DOM 类结构)和 DOM。模型转换为视图,视图渲染为 DOM。此外,另一种方式是,当您加载数据时,DOM 会转换为视图,而视图会转换为模型。这就是为什么您需要为您的功能提供模型到视图转换器和视图到模型转换器的原因。
这个拼图的重要部分是 engine.conversion.Mapper
。它的作用是将元素和位置从模型映射到视图。正如您可能已经看到的那样,模型可能与视图完全不同。它们之间正确的位置映射是关键。当您在插入符号位置(在 DOM 中)键入一个字母时,该位置将映射到模型,并且该字母将插入到模型中,然后才转换回视图和 DOM。如果视图到模型的位置转换错误,您将无法在该位置打字或做任何事情。
Mapper
本身非常简单。它所需要的只是您指定哪些视图元素绑定到哪些模型元素。例如,在模型中您可能有:
<listItem type="bulleted" indent="0">Foo</listItem>
<listItem type="bulleted" indent="1">Bar</listItem>
在视图中您有:
<ul>
<li>
Foo
<ul>
<li>Bar</li>
</ul>
</li>
</ul>
如果映射器知道第一个 listItem
与第一个 <li>
绑定,第二个 listItem
与第二个 <li>
绑定,则它能够转换位置正确。
回到你的案例。每个转换器必须为 Mapper
提供数据。由于您使用了转换器构建器,因此由它构建的转换器已经这样做了。但是它们很简单,所以当你提供:
buildModelConverter().for(editing.modelToView)
.fromElement('myElement')
.toElement(new ContainerElement('div', {}, [new EditableElement('h4')]));
假定 myElement
与 <div>
绑定。所以在 <div>
里面写的任何东西都会直接进入 myElement
然后在 myElement
:
的开头呈现
<div>
<h4></h4>
</div>
假设你刚刚在<h4>
处写了x
,那个位置会映射到模型中的myElement
偏移量0
,然后渲染到最开始的视图<div>
.
型号:
<myElement>x</myElement>
查看:
<div>x<h4></h4></div>
如您所见,在您的情况下,<h4>
应该与 myElement
绑定。
目前,我们正处于重构阶段。目标之一是为转换器构建器提供更多实用功能。其中一个实用程序是具有包装元素的元素的转换器,如上面的 "div + h4" 案例。这也是图像特征的情况。图像在模型中由 <image>
表示,但在视图中为 <figure><img /></figure>
。您可以查看 ckeditor5-image
以了解这些转换器现在的样子。我们想简化它们。
不幸的是,你的实际情况更加复杂,因为你里面有多个元素。 CKE5 体系结构应该能够处理您的情况,但您必须了解,如果没有适当的指导,这几乎是不可能编写的。
如果你想修修补补,你应该研究 ckeditor5-image
回购。这并不容易,但这是最好的方法。 Image
插件和 ImageCaption
与您的情况非常相似。
型号:
<image alt="x" src="y">
<caption>Foo</caption>
</image>
查看:
<figure class="image">
<img alt="x" src="y" />
<figcaption>Foo</caption>
</figure>
在你的情况下,我会在这些线之间的某处看到模型:
<rankItem imageSrc="x" linkUrl="y">
<rankTitle>Foo</rankTitle>
<rankNotes>Bar</rankNotes>
</rankItem>
我会让视图更重一些,但编写转换器会更容易:
<li contenteditable="false">
<a href="y">
<img src="x" />
<span class="data">
<span class="title" contenteditable="true">Foo</span>
<span class="notes" contenteditable="true">Bar</span>
</span>
</a>
</li>
对于 rankTitle
和 rankNotes
- 基于 caption
元素 (ckeditor5-image/src/imagecaption/imagecaptionengine.js
).
对于 rankItem
- 基于 image
元素 (ckeditor5-image/src/image/
).
再一次 - 请记住,我们正在简化所有这些。我们希望人们编写自己的功能,即使是像您这样的复杂功能。但是,我们知道现在它有多复杂。这就是目前没有文档的原因 - 我们正在寻求改变和简化事情。
最后 - 您可以更简单地创建排名列表,使用 Link
插件和使用转换器构建器构建的元素:
rankList
-> <ol class="rank">
,
rankItem
-> <li>
,
rankImage
-> <img />
,
rankNotes
-> <span class="notes">
,
rankTitle
-> <span class="title">
.
但是,由于整个结构都是可编辑的,因此可能会弄乱它。
型号:
<rankList>
<rankItem>
<rankImage linkHref="" />
<rankTitle>Foo</rankTitle>
<rankNotes>Bar</rankNotes>
</rankItem>
...
</rankList>
其中 "Foo" 和 "Bar" 也设置了 linkHref
属性。
查看:
<ol class="rank">
<li>
<a href=""><img src="" /></a>
<span class="title"><a href="">Title</a></span>
<span class="notes"><a href="">Foo</a></span>
</li>
...
</ol>
像这样的东西,虽然远非完美,但只要我们在重构之前和编写指南之前,应该更容易编写。
当然,您还必须提供视图到模型转换器。您可能想自己编写它们(看看 ckeditor5-list/src/converters.js
viewModelConverter()
—— 尽管您的会更容易,因为它是扁平的,而不是嵌套列表)。或者您可以通过转换器生成器生成它们。
也许可以使用上面的方法(更简单)但是使用contentEditable
属性来控制结构。 rankList
必须用 contentEditable="false"
转换为 <ol>
。例如,也许您可以以某种方式使用 toWidget
来更好地处理选择。 rankNotes
和 rankTitle
必须转换为带有 contentEditable="true"
的元素(可能使用 toWidgetEditable()
)。
在 CKEditor5 中,我尝试实现自定义元素以将模型转换为视图以进行编辑。然后,容器元素(@ckeditor/ckeditor5-engine/src/view/containerelement)中的可编辑元素(@ckeditor/ckeditor5-engine/src/view/editableelement)聚焦在parent容器元素上,无法编辑。
例如如果实现如下:
buildModelConverter().for(editing.modelToView)
.fromElement('myElement')
.toElement(new ContainerElement('div', {}, [new EditableElement('h4')]));
插入 'myElement' 和按键 "abc" 后实际编辑 dom 的结果。 (我希望将 "abc" 的文本输入到 h4 标签,但是...)
<div>
abc
<h4>
<br data-cke-filler="true">
</h4>
</div>
我还尝试使用小部件来应用 contenteditable 属性。 但是,无法在 h4 中输入文本。
<div class="ck-widget" contenteditable="false">
<h4 class="ck-editable" contenteditable="true">
<br data-cke-filler="true">
</h4>
</div>
这是错误,还是我对容器元素的理解有误?
[其他详细信息]
我假设为排名列表制作一个小部件插件。
首先,排名列表由<ol>
和<li>
标签构成,因为有多个项目。
我通过定义两个模式解决了这个问题,例如 "rankingList" 和 "rankingListItem",
所以我使用嵌套模型元素实现了动态元素。
const item1 = new ModelElement('rankingListItem');
const item2 = new ModelElement('rankingListItem');
const model = new ModelElement('rankingList', {}, [item1, item2]);
// and insert
接下来,排行榜的项目有link、图片、标题和备注。 因此,排名列表项具有以下DOM结构:
<ol><!-- apply toWidget -->
<li>
<a href="link[editable]">
<img src="image[editable]">
<h3>title[editable]</h3>
<p>notes[editable]</p>
</a>
</li>
...
</ol>
我希望视图元素如下:
const {ref, src, title, notes} = data; // how to get data?
const view = new ContainerElement('a', {ref}, [
new EmptyElement('img', {src}),
new EditableElement('h3', {}, new Text(title)),
new EditableElement('p', {}, new Text(title)),
]);
// maybe incorrect ...
总而言之,我想使用可编辑视图而不破坏已定义的 DOM 树。 怎么实现?
感谢您描述您的案例。目前,了解开发人员如何使用编辑器以及您的期望对我们来说意义重大。
不幸的是,这看起来是一个非常复杂的功能。它看起来也需要自定义 UI(编辑 link url 和图像 src——除非它们在添加到编辑器后没有改变)。看来您遇到了两个问题:
- 模型和视图之间的位置映射,
- 嵌套的可编辑项。
首先,回答你关于 EditableElement
的问题 - 将它们用于 h3
和 p
元素似乎是正确的。
但是,如此复杂的功能需要自定义转换器。转换器构建器(您使用的)专用于简单情况,例如元素到元素的转换或属性到属性的转换。
漂亮的API背后,构建转换器是一个函数工厂,它创建一个或多个函数。然后将这些作为回调添加到 ModelConversionDispatcher
(editor.editing.modelToView
是其实例)。 ModelConversionDispatcher
在转换期间触发一系列事件,这是将模型中的更改转换为视图的过程。
如前所述,您必须自己编写这些转换函数。
由于这个主题太大,无法提供详细而全面的答案,我将简要介绍您应该感兴趣的内容。不幸的是,目前还没有关于从头开始创建自定义转换器的指南。这是一个非常广泛的主题。
首先,让我向您解释一下您的大部分问题是从哪里来的。如您所知,编辑器具有三层:模型(数据)、视图(DOM 类结构)和 DOM。模型转换为视图,视图渲染为 DOM。此外,另一种方式是,当您加载数据时,DOM 会转换为视图,而视图会转换为模型。这就是为什么您需要为您的功能提供模型到视图转换器和视图到模型转换器的原因。
这个拼图的重要部分是 engine.conversion.Mapper
。它的作用是将元素和位置从模型映射到视图。正如您可能已经看到的那样,模型可能与视图完全不同。它们之间正确的位置映射是关键。当您在插入符号位置(在 DOM 中)键入一个字母时,该位置将映射到模型,并且该字母将插入到模型中,然后才转换回视图和 DOM。如果视图到模型的位置转换错误,您将无法在该位置打字或做任何事情。
Mapper
本身非常简单。它所需要的只是您指定哪些视图元素绑定到哪些模型元素。例如,在模型中您可能有:
<listItem type="bulleted" indent="0">Foo</listItem>
<listItem type="bulleted" indent="1">Bar</listItem>
在视图中您有:
<ul>
<li>
Foo
<ul>
<li>Bar</li>
</ul>
</li>
</ul>
如果映射器知道第一个 listItem
与第一个 <li>
绑定,第二个 listItem
与第二个 <li>
绑定,则它能够转换位置正确。
回到你的案例。每个转换器必须为 Mapper
提供数据。由于您使用了转换器构建器,因此由它构建的转换器已经这样做了。但是它们很简单,所以当你提供:
buildModelConverter().for(editing.modelToView)
.fromElement('myElement')
.toElement(new ContainerElement('div', {}, [new EditableElement('h4')]));
假定 myElement
与 <div>
绑定。所以在 <div>
里面写的任何东西都会直接进入 myElement
然后在 myElement
:
<div>
<h4></h4>
</div>
假设你刚刚在<h4>
处写了x
,那个位置会映射到模型中的myElement
偏移量0
,然后渲染到最开始的视图<div>
.
型号:
<myElement>x</myElement>
查看:
<div>x<h4></h4></div>
如您所见,在您的情况下,<h4>
应该与 myElement
绑定。
目前,我们正处于重构阶段。目标之一是为转换器构建器提供更多实用功能。其中一个实用程序是具有包装元素的元素的转换器,如上面的 "div + h4" 案例。这也是图像特征的情况。图像在模型中由 <image>
表示,但在视图中为 <figure><img /></figure>
。您可以查看 ckeditor5-image
以了解这些转换器现在的样子。我们想简化它们。
不幸的是,你的实际情况更加复杂,因为你里面有多个元素。 CKE5 体系结构应该能够处理您的情况,但您必须了解,如果没有适当的指导,这几乎是不可能编写的。
如果你想修修补补,你应该研究 ckeditor5-image
回购。这并不容易,但这是最好的方法。 Image
插件和 ImageCaption
与您的情况非常相似。
型号:
<image alt="x" src="y">
<caption>Foo</caption>
</image>
查看:
<figure class="image">
<img alt="x" src="y" />
<figcaption>Foo</caption>
</figure>
在你的情况下,我会在这些线之间的某处看到模型:
<rankItem imageSrc="x" linkUrl="y">
<rankTitle>Foo</rankTitle>
<rankNotes>Bar</rankNotes>
</rankItem>
我会让视图更重一些,但编写转换器会更容易:
<li contenteditable="false">
<a href="y">
<img src="x" />
<span class="data">
<span class="title" contenteditable="true">Foo</span>
<span class="notes" contenteditable="true">Bar</span>
</span>
</a>
</li>
对于 rankTitle
和 rankNotes
- 基于 caption
元素 (ckeditor5-image/src/imagecaption/imagecaptionengine.js
).
对于 rankItem
- 基于 image
元素 (ckeditor5-image/src/image/
).
再一次 - 请记住,我们正在简化所有这些。我们希望人们编写自己的功能,即使是像您这样的复杂功能。但是,我们知道现在它有多复杂。这就是目前没有文档的原因 - 我们正在寻求改变和简化事情。
最后 - 您可以更简单地创建排名列表,使用 Link
插件和使用转换器构建器构建的元素:
rankList
-><ol class="rank">
,rankItem
-><li>
,rankImage
-><img />
,rankNotes
-><span class="notes">
,rankTitle
-><span class="title">
.
但是,由于整个结构都是可编辑的,因此可能会弄乱它。
型号:
<rankList>
<rankItem>
<rankImage linkHref="" />
<rankTitle>Foo</rankTitle>
<rankNotes>Bar</rankNotes>
</rankItem>
...
</rankList>
其中 "Foo" 和 "Bar" 也设置了 linkHref
属性。
查看:
<ol class="rank">
<li>
<a href=""><img src="" /></a>
<span class="title"><a href="">Title</a></span>
<span class="notes"><a href="">Foo</a></span>
</li>
...
</ol>
像这样的东西,虽然远非完美,但只要我们在重构之前和编写指南之前,应该更容易编写。
当然,您还必须提供视图到模型转换器。您可能想自己编写它们(看看 ckeditor5-list/src/converters.js
viewModelConverter()
—— 尽管您的会更容易,因为它是扁平的,而不是嵌套列表)。或者您可以通过转换器生成器生成它们。
也许可以使用上面的方法(更简单)但是使用contentEditable
属性来控制结构。 rankList
必须用 contentEditable="false"
转换为 <ol>
。例如,也许您可以以某种方式使用 toWidget
来更好地处理选择。 rankNotes
和 rankTitle
必须转换为带有 contentEditable="true"
的元素(可能使用 toWidgetEditable()
)。