Передача параметров из команды в конвертер

Я определил новый тип элемента модели как плагин; давайте назовем это как 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-интерфейсы могут измениться, но я все же хотел бы, чтобы все работало с настоящей версией.

Модель → просмотр конверсии

Если я правильно понимаю, можно пройти string или function к toElement вызов. Вопрос об использовании последнего: какие именно параметры передаются в функцию? Я предполагал, что это будет модельный элемент (узел?) Для преобразования. Это тот случай? Если так, то почему атрибут установлен на этом узле через 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 заставить вид обновить?

Извините за очень длинный пост, но я пытаюсь передать как можно больше деталей. Мы надеемся, что кто-то заметит мою ошибку или непонимание того, как на самом деле работает API CKEditor 5.

Просмотр не обновляется?

Я повернулся к Document События и эксперименты с changesDone событие. Он успешно решает проблему "синхронизации", поскольку он последовательно срабатывает только после обработки всех изменений. Тем не менее, проблема представления не обновляется в ответ на изменение модели остается. Чтобы было понятно, модель меняется, но представление не отражает этого. Вот звонок:

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

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()), в этом случае вы могли бы действительно использовать обратные вызовы. Но это не имеет смысла в этом случае.

Атрибут может измениться независимо!

Другая проблема заключается в том, что вы написали конвертер элементов, который одновременно устанавливает атрибут. Тада, ты грузишь <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-бета.1, потому что 1.0.0-бета.1 принесла разницу.

До 1.0.0-бета.1 все изменения были преобразованы сразу же после их применения. Так, rename() приведет к немедленному remove а также insert:section События. На данный момент элемент, который вы получили в последнем, не будет иметь class атрибут установлен.

Благодаря различиям мы можем начать преобразование, как только все изменения будут применены (после 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-бета.2). Закомментированный кусок кода - это то, как вы должны его определить, если все работало нормально, и оно будет работать в 1.0.0-бета.2.

Печально, что мы пропустили такой простой случай, но это в основном из-за того, что все наши функции... намного сложнее, чем это.

Другие вопросы по тегам