0

我定义了一种新型的模型元素作为插件;让我们将其称为Foo. 模型中的Foo节点应转换为section视图中的元素。到目前为止,一切都很好。我设法通过定义简单的转换规则来做到这一点。我还设法定义了一个FooCommand将选定块转换(重命名)为Foo.

我试图将这些Foo模型节点上的属性转换为视图元素上的属性(反之亦然)时遇到了困难。假设 Foos 有一个名为的属性fooClass,它应该映射到视图元素的class属性。

<Foo fooClass="green-foo"> should map to/from <section class="green-foo">

我可以在 中成功接收参数FooCommand,但我似乎无法在命令正在处理的块上设置它们:

execute(options = {}) {
    const document = this.editor.document;
    const fooClass = options.fooClass;

    document.enqueueChanges(() => {
        const batch = options.batch || document.batch();
        const blocks = (options.selection || document.selection).getSelectedBlocks();

        for (const block of blocks) {
            if (!block.is('foo')) {
                batch.rename(block, 'foo');
                batch.setAttribute(block, 'fooClass', fooClass);
            }
        }
    });
}

下面是插件中init函数的代码Foo,包括模型→视图和视图→模型转换:

init() {
    const editor = this.editor;
    const doc = editor.document;
    const data = editor.data;
    const editing = editor.editing;

    editor.commands.add('foo', new FooCommand(editor));
    doc.schema.registerItem('foo', '$block');

    buildModelConverter().for(data.modelToView, editing.modelToView)
        .fromElement('foo')
        .toElement(modelElement => {
            const fooClass = modelElement.item.getAttribute('fooClass'));
            return new ContainerElement('section', {'class': fooClass});
        });

    buildViewConverter().for(data.viewToModel)
        .fromElement('section')
        .toElement(viewElement => {
            let classes = Array.from(viewElement.getClassNames());
            let modelElement = new ModelElement('foo', {'fooClass': classes[0]});
            return modelElement;
        });

}

当我尝试通过

editor.execute('foo', { fooClass: 'green-foo' })

我可以看到该green-foo值可用于FooCommand,但modelElement在模型→视图转换中,另一方面,没有fooClass属性。

我确定我在这里遗漏了重点并滥用了 API。如果有人能对这个问题有所了解,我将非常感激。我可以根据需要提供更多详细信息。

初步建议后的跟进

感谢@Reinmar 和@jodator 关于配置文档模式以允许自定义属性的建议。我真的以为这会解决它,但没有。无论如何这可能是一个必要的步骤,但是在模型→视图转换期间我仍然无法从模型元素中获取属性值。

首先,让我添加一条我遗漏的重要信息:我正在使用的 CKEditor5 的版本是1.0.0-alpha2。我知道一些 API 必然会发生变化,但我仍然希望使用当前版本。

模型→视图转换

如果我理解正确,可以将 astring或 a传递functiontoElement调用。关于使用后者的一个问题:传递给函数的参数到底是什么?我假设它将是要转换的模型元素(节点?)。是这样吗?如果是这样,为什么在请求时通过batch.setAttribute(内部)在该节点上设置的属性不可用?document.enqueueChanges应该是吗?

排序问题?

额外的测试似乎表明发生了某种执行顺序问题。我观察到,即使当我第一次尝试从modelElement参数中读取该属性时该属性不可用,但如果我稍后再次读取它就会如此。让我试着说明下面的情况。首先,我将修改转换代码,使其使用一些虚拟值,以防读取时属性值不可用:

buildModelConverter().for(data.modelToView, editing.modelToView)
    .fromElement('foo')
    .toElement(modelElement => {
        let fooClass = modelElement.item.getAttribute('fooClass') || 'naught';
        let viewElement = new ContainerElement('section');
        viewElement.setAttribute('class', fooClass);
        return viewElement;
    });

现在我重新加载页面并在控制台上执行以下指令:

c = Array.from(editor.document.getRoot().getChildren());

c[1].is('paragraph'); // true

// Changing the node from 'paragraph' to 'foo' and adding an attribute
// 'fooClass' with value 'green-foo' to it.
editor.document.enqueueChanges(() => {
    const batch = editor.document.batch();
    batch.rename(c[1], 'foo');
    batch.setAttribute(c[1], 'fooClass', 'green-foo');
    return batch;
});

c[1].is('paragraph'); // false
c[1].is('foo'); // true

c[1].hasAttribute('fooClass'); // true
c[1].getAttribute('fooClass'); // 'green-foo'

尽管看起来正在生成预期的输出,但看一眼生成的视图元素就会发现问题:

<section class="naught"/>

最后,即使我尝试重置fooClass模型元素的属性,更改也不会反映在视图元素上。这是为什么?不应该通过更改enqueueChanges导致视图更新吗?

对不起,很长的帖子,但我试图传达尽可能多的细节。希望有人能发现我对 CKEditor 5 的 API 实际工作方式的错误或误解。

视图不更新?

我转向Document's events并尝试了该changesDone事件。它成功地解决了“时间”问题,因为它只有在处理完所有更改后才会始终触发。尽管如此,视图没有响应模型变化而更新的问题仍然存在。为了清楚起见,模型确实发生了变化,但视图并未反映这一点。这是电话:

editor.document.enqueueChanges(() => editor.document.batch().setAttribute(c[1], 'fooClass', 'red-foo'));
4

1 回答 1

1

100% 确定我自己编写了整个功能。我使用的 1.0.0-beta.1 API 与您所拥有的完全不同。

基本上——它有效。它还不是 100% 正确,但我会做到的。

如何转换元素+属性对?

实现需要转换元素+属性的功能时,需要分别处理元素和属性转换,因为它们由CKEditor 5单独处理。

因此,在下面的代码中,您会发现我使用了elementToElement()

editor.conversion.elementToElement( {
    model: 'foo',
    view: 'section'
} );

所以模型<foo>元素和视图<section>元素之间的转换器。这是一个双向转换器,因此它处理向上转换(视图 -> 模型)和向下转换(模型 -> 视图)转换。

注意:它不处理该属性。

从理论上讲,作为view属性,您可以编写一个回调来读取模型元素的属性并创建具有该属性集的视图元素。但这不起作用,因为这样的配置只有在向下转换的情况下才有意义(模型 -> 视图)。我们如何使用该回调来向下转换视图结构?

注意:您可以分别为向下转换和向上转换管道编写转换器(通过使用editor.conversion.for()),在这种情况下,您可以真正使用回调。但在这种情况下,它真的没有意义。

属性可以独立改变!

另一个问题是,假设您编写了一个同时设置属性的元素转换器。Tada,您在模型中加载<section class=ohmy>并获取<foo class=ohmy>。

但是……如果模型中的属性会发生变化怎么办?

在向下转换管道中,CKEditor 5 将元素更改与属性更改分开处理。它将它们作为单独的事件触发。因此,当您FooCommand在标题上执行时,它会调用writer.rename(),我们会在以下事件中得到以下事件DowncastDispatcher

  1. remove<heading>
  2. insert:section

但是随后属性也发生了变化(writer.setAttribute()),所以我们也得到:

  1. setAttibute:class:section

elementToElement()转换助手监听事件insert:section。所以它是盲目的setAttribute:class:selection

因此,当您更改属性的值时,您需要进行attributeToAttribute()转换。

测序

在我们发布 1.0.0-beta.1 之前我不想回答你的问题,因为 1.0.0-beta.1 带来了Differ

在 1.0.0-beta.1 之前,所有更改在应用时都会立即转换。所以,rename()会引起即时removeinsert:section事件。此时,您在后一个中获得的元素还没有class设置属性。

多亏了 Differ,一旦应用了所有更改(在change()执行块之后),我们就能够开始转换。这意味着insert:section一旦模型<foo>元素已经class设置了属性,就会触发该事件。这就是为什么你可以编写一个基于回调的转换器......但你不应该:D

编码

import { downcastAttributeToAttribute } from '@ckeditor/ckeditor5-engine/src/conversion/downcast-converters';
import { upcastAttributeToAttribute } from '@ckeditor/ckeditor5-engine/src/conversion/upcast-converters';

class FooCommand extends Command {
    execute( options = {} ) {
        const model = this.editor.model;
        const fooClass = options.class;

        model.change( writer => {
            const blocks = model.document.selection.getSelectedBlocks();

            for ( const block of blocks ) {
                if ( !block.is( 'foo' ) ) {
                    writer.rename( block, 'foo' );
                    writer.setAttribute( 'class', fooClass, block );
                }
            }
        } );
    }
}

class FooPlugin extends Plugin {
    init() {
        const editor = this.editor;

        editor.commands.add( 'foo', new FooCommand( editor ) );

        editor.model.schema.register( 'foo', {
            allowAttributes: 'class',
            inheritAllFrom: '$block'
        } );

        editor.conversion.elementToElement( {
            model: 'foo',
            view: 'section'
        } );

        editor.conversion.for( 'upcast' ).add(
            upcastAttributeToAttribute( {
                model: 'class',
                view: 'class'
            } )
        );

        editor.conversion.for( 'downcast' ).add(
            downcastAttributeToAttribute( {
                model: 'class',
                view: 'class'
            } )
        );

        // This should work but it does not due to https://github.com/ckeditor/ckeditor5-engine/issues/1379 :(((    
        // EDIT: The above issue is fixed and will be released in 1.0.0-beta.2.
        // editor.conversion.attributeToAttribute( {
        //  model: {
        //      name: 'foo',
        //      key: 'class'
        //  },
        //  view: {
        //      name: 'section',
        //      key: 'class'
        //  }
        // } );
    }
}

这段代码工作得很好,除了它转换class任何可能拥有它的元素上的属性。那是因为我不得不使用非常通用downcastAttributeToAttribute()upcastAttributeToAttribute()转换器,因为我发现了一个错误编辑:它已修复,将在 1.0.0-beta.2 中可用)。注释掉的代码是你应该如何定义它,如果一切正常并且它将在 1.0.0-beta.2 中工作。

很遗憾我们错过了这样一个简单的案例,但这主要是因为我们所有的功能……都比这复杂得多。

于 2018-03-23T18:38:29.080 回答