ckeditor5 列表项起始编号

ckeditor5 list item start number

正在尝试构建插件以在 ckEditor5 中为 ol 设置启动属性。

据我所知,model 包含 listItems 的集合。

需要在 ol 上设置 start 属性,但是,列表项的父项,而不是列表项本身。有没有办法从 ol 访问model?

我可以用

得到当前的li
first(editor.model.document.selection.getSelectedBlocks())

有没有办法在 ol 元素上设置属性?

编辑——获取 LI 属性的代码:

editor.model.schema.extend('listItem', { allowAttributes: 'listStart' });

editor.conversion.attributeToAttribute({ 
  model: 'listStart', 
  view: 'start'
});

在 model 中启用:<listItem type="numbered" listStart="4">,这将在视图中转换为:

<ol>
  <li start="4">words</li>
</ol>

我想要实现的是

<ol start="4">
  <li>words</li>
</ol>

当我检查源代码时,它看起来好像 ol(或 ul)是在此处自动创建的:

function generateLiInUl( modelItem, conversionApi ) {

    const mapper = conversionApi.mapper;
    const viewWriter = conversionApi.writer;
    const listType = modelItem.getAttribute( 'listType' ) == 'numbered' ? 'ol' : 'ul';
    const viewItem = createViewListItemElement( viewWriter );
  // ** OL or UL created here -->
    const viewList = viewWriter.createContainerElement( listType, null );
        viewWriter.insert( ViewPosition.createAt( viewList ), viewItem );

    mapper.bindElements( modelItem, viewItem );

    return viewItem;
}

link to source

有没有我可以观察的事件?或者在转换定义中是否有一种方法可以将属性定位到父级?

更新 2

如果我们要 mod 来源,我们可以通过将此添加到 generateLiInUl 函数来拦截向下转换(感谢 MTilsted):

    const listStart = modelItem.getAttribute('listStart');
    if (listStart) {
        viewWriter.setAttribute('start', listStart, viewList);
    }

为了便于向上转换,将其添加到 viewModelConverter 函数

    const listStart = data.viewItem.parent.getAttribute('start');
    if (listStart) {
        writer.setAttribute( 'listStart', listStart, listItem );
    }

这有点难看,因为我们正在 mod 化源代码,这是维护的皮塔饼,并且在向上转换时,我们将 listStart 属性添加到每个 listItem 元素model.. 但这是一个开始。

我简要地查看了添加调度程序.. 例如:

data.upcastDispatcher.on( 'element:li', myCustomUpcastFunction );

但无法弄清楚如何获取对添加到上述 viewModelConverter 函数中的 model 的 listItem 元素的引用。

更新: 好的,生成列表的工作方式很奇怪。这可能与列表合并和更改类型的方式有关。我必须承认我真的不明白那部分是如何工作的。

我找不到根据需要向下转换属性的方法,所以除非来自 Cksource 的人出现,否则我能找到的唯一解决方案是修补列表代码。而且我不确定它与 upcast 的配合情况如何。 (仅进行了轻微测试)。

但是在文件中node_modules/@ckeditor/ckeditor5-list/src/converters.js

只需尝试添加这一行:

viewWriter.setAttribute('start',model.getAttribute('listStart'),viewList);

到方法

function generateLiInUl( modelItem, conversionApi ) {

在我的ckeditor版本中(最新的)它应该被添加到第810行。

原始答案(有用但不适用于列表)。

哦,是的。你对模型是正确的。但 ckeditor 5 中的整个设计要点是您不必修改现有插件即可添加属性。您可以使用架构注册额外的属性,然后在您自己的插件中添加匹配的 DowncastElementToElement。

这是我用来向图像添加额外属性的代码 class。更改为在 listItems 上工作应该是微不足道的。 (Cut/Pasted 来自我代码中的不同位置,所以我可能忘记了一些东西,但请尝试一下。如果你不能让它工作,我将在星期一制作一个完整的示例 :)

        model.schema.extend('image', {
            allowAttributes: ['displaywidth','ignorecolumns','fullpagepicture']
        } );

    editor.conversion.for('upcast')
                .add(upcastAttributeToAttribute( { model: 'isvisible', view: 'isvisible' }))
                .add(upcastAttributeToAttribute( { model: 'displaywidth', view: 'displaywidth' }))
                .add(upcastAttributeToAttribute( { model: 'fullpagepicture', view: 'fullpagepicture' }))

editor.conversion.for('downcast')
        .add(downcastAttributeToAttribute( { model: 'isvisible', view: 'isvisible' }))
        .add(downcastAttributeToAttribute( { model: 'displaywidth', view: 'displaywidth' }))
        .add(downcastAttributeToAttribute( { model: 'fullpagepicture', view: 'fullpagepicture' }))

对我有用的答案非常简单,我觉得没有早点看到它有点傻:使用 LI value 属性而不是 OL start 属性。

<ol>
  <li value="4">words</li>
</ol>

而不是:

<ol start="4">
  <li>words</li>
</ol>

这将属性保留在 listItem 上并避免了所有的复杂化(我在最初的尝试中完全错过了 LI 的值属性的存在):

editor.model.schema.extend('listItem', { allowAttributes: 'value' });

editor.conversion.attributeToAttribute({ 
  model: 'value', 
  view: 'value'
});

应用值的命令:

execute(arg) {

  let val = arg.value;
  const model = this.editor.model;
  const block = first(model.document.selection.getSelectedBlocks());

  model.change(writer => {
    if (+val) {
      writer.setAttribute('value', val, block);
    } else {
      writer.removeAttribute('value', block);
    }
  });

}

并防止在输入时将该值复制到下一个 LI:

editor.commands.get('enter').on('afterExecute', () => {
  const block = first(editor.model.document.selection.getSelectedBlocks());
  if ( block.name == 'listItem' && block.hasAttribute('value')) {
    editor.model.change( writer => {
      writer.removeAttribute('value', block);
    });
  }
});

我想@Steve 已经解决了他的问题,但他的问题帮助我理解了我自己的问题。因此,我想帮助像我一样需要帮助的人。

首先,我注意到按照之前的建议更改内置插件是不正常的。 CKEditor 的创建者鼓励我们使用我们的自定义插件来覆盖内置逻辑。所以,使用 CKEditor API 来改变编辑器的行为。

我的解决方案是

// @ckeditor/ckeditor5-core@25.0.0
import Plugin from '@ckeditor/ckeditor5-core/src/plugin' 

export class ListStartAttribute extends Plugin {
    init() {
        console.log('ListStartAttribute is init')
        const editor = this.editor

        // 1.extend schema
        editor.model.schema.extend('listItem', { allowAttributes: 'start' })

        // 2.set conversion up/down
        editor.conversion.for('downcast').add(dispatcher => {
            dispatcher.on('attribute', (evt, data, conversionApi) => {

                if (data.item.name != 'listItem') {
                    return
                }

                const viewWriter = conversionApi.writer
                const viewElement = conversionApi.mapper.toViewElement(
                    data.item
                )
                const containerElement = viewElement.parent

                if (
                    data.attributeNewValue &&
                    !containerElement.getAttribute('start')
                ) {
                    viewWriter.setAttribute(
                        data.attributeKey,
                        data.attributeNewValue,
                        containerElement
                    )
                }
            })
        })

        editor.conversion.for('upcast').attributeToAttribute({
            model: {
                name: 'listItem',
                key: 'start',
            },
            view: {
                name: 'ol',
                key: 'start',
            },
            converterPriority: 'low',
        })
    }
}