如何在ckeditor5中为`a`标签添加"target"属性?

Gau*_*rav 5 ckeditor5

我已经为链接创建了自己的插件.现在我想为a插件生成的标签添加一些其他属性,比如target,rel.

但我无法完成它.这是我的转换器的插件代码.我应该添加哪些转换器,以便a标记可以支持其他属性?

/**
 * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
 * For licensing, see LICENSE.md.
 */

/**
 * @module link/linkediting
 */

import LinkEditing from '@ckeditor/ckeditor5-link/src/linkediting';
import {
    downcastAttributeToElement
} from '@ckeditor/ckeditor5-engine/src/conversion/downcast-converters';
import { upcastElementToAttribute } from '@ckeditor/ckeditor5-engine/src/conversion/upcast-converters';
import LinkCommand from './uclinkcommand';
import UnlinkCommand from './ucunlinkcommand';
import { createLinkElement } from '@ckeditor/ckeditor5-link/src/utils';
import { ensureSafeUrl } from './utils';
import bindTwoStepCaretToAttribute from '@ckeditor/ckeditor5-engine/src/utils/bindtwostepcarettoattribute';

/**
 * The link engine feature.
 *
 * It introduces the `linkHref="url"` attribute in the model which renders to the view as a `<a href="url">` element.
 *
 * @extends module:core/plugin~Plugin
 */
export default class UcLinkEditing extends LinkEditing {
    /**
     * @inheritDoc
     */
    init() {
        const editor = this.editor;

        // Allow link attribute on all inline nodes.
        editor.model.schema.extend( '$text', { allowAttributes: 'linkHref' } );

        editor.conversion.for( 'dataDowncast' )
            .add( downcastAttributeToElement( { model: 'linkHref', view: createLinkElement } ) );

        editor.conversion.for( 'editingDowncast' )
            .add( downcastAttributeToElement( { model: 'linkHref', view: ( href, writer ) => {
                return createLinkElement( ensureSafeUrl( href ), writer );
            } } ) );

        editor.conversion.for( 'upcast' )
            .add( upcastElementToAttribute( {
                view: {
                    name: 'a',
                    attribute: {
                        href: true
                    }
                },
                model: {
                    key: 'linkHref',
                    value: viewElement => viewElement.getAttribute( 'href' )
                }
            } ) );

        // Create linking commands.
        editor.commands.add( 'ucLink', new LinkCommand( editor ) );
        editor.commands.add( 'ucUnlink', new UnlinkCommand( editor ) );

        // Enable two-step caret movement for `linkHref` attribute.
        bindTwoStepCaretToAttribute( editor.editing.view, editor.model, this, 'linkHref' );

        // Setup highlight over selected link.
        this._setupLinkHighlight();
    }
}
Run Code Online (Sandbox Code Playgroud)

Szy*_*lik 15

介绍

在我开始编写代码之前,我想借此机会解释内联元素(如<a>)的CKEditor 5方法,以便更容易理解解决方案.有了这些知识,未来类似的问题不应该令人不安.以下是一个全面的教程,所以期待长时间阅读.

即使您可能知道理论部分中的大部分内容,我建议您阅读它以全面了解CKEditor 5中的工作原理.

此外,请注意我将为原始CKEditor 5插件提供解决方案,因为它对于寻求有关此问题的教程的其他社区成员更有价值.不过,我希望通过本教程的见解,您可以将代码示例调整为自定义插件.

另外,请记住,本教程不讨论此插件的UI部分,而是仅讨论如何为转换目的配置内容.添加和删​​除属性是UI或代码的其他部分的工作.在这里我只讨论引擎的东西.

CKEditor 5中的内联元素

首先,让我们确定哪些元素是内联的.通过内联元素我明白了相似的元素<strong>,<a><span>.不同于<p>,<blockquote><div>,内联元件不构造数据.相反,它们以特定(视觉和语义)方式标记一些文本.因此,在某种程度上,这些元素是文本给定部分的特征.因此,我们说文本的给定部分是粗体,或者给定部分文本是/有链接.

同样,在模型中,我们不代表<a><strong>直接作为元素.相反,我们允许将属性添加到文本的一部分.这就是文本特征(粗体,斜体或链接)的表示方式.

例如,在模型中,我们可能有一个<paragraph>与元素Foo bar文本,其中barbold属性设置为true.我们会这样注意:<paragraph>Foo <$text bold="true">bar</$text></paragraph>.请注意,那里没有<strong>或任何其他元素.它只是一些带有属性的文本.稍后,该bold属性将转换为<strong>元素.

顺便说一下:来自模型属性的视图元素有自己的类:view.AttributeElement而不是内联元素也可以称为属性元素.遗憾的是,名称与"属性"作为视图元素的属性冲突(更糟糕的是,属性元素被允许具有属性).

当然,文本可能具有多个属性,并且所有属性都将转换为各自的视图内联元素.请记住,在模型中,属性没有任何设置顺序.这与视图或HTML相反,其中内联元素彼此嵌套.嵌套在从模型转换到视图期间发生.这使得在模型中工作更简单,因为功能不需要处理模型中的元素断开或重新排列.

考虑这个模型字符串:

<paragraph>
    <$text bold="true">Foo </$text>
    <$text bold="true" linkHref="bar.html">bar</$text>
    <$text bold="true"> baz</$text>
</paragraph>
Run Code Online (Sandbox Code Playgroud)

这是一个Foo bar baz带有链接的粗体文本bar.转换期间,它将转换为:

<p>
    <strong>Foo </strong><a href="bar.html"><strong>bar</strong></a><strong> baz</strong>
</p>
Run Code Online (Sandbox Code Playgroud)

请注意,<a>元素的转换方式始终是最顶层的元素.这是故意的,因此任何元素都不会破坏<a>元素.看到这个,不正确的视图/ HTML字符串:

<p>
    <a href="bar.html">Foo </a><strong><a href="bar.html">bar</a></strong>
</p>
Run Code Online (Sandbox Code Playgroud)

生成的视图/ HTML有两个彼此相邻的链接元素,这是错误的.

我们使用priorityproperty view.AttributeElement来定义哪个元素应该在其他元素之上.大多数元素,如<strong>不关心它并保持默认优先级.但是,<a>元素已更改优先级以保证视图/ HTML中的正确顺序.

复杂的内联元素和合并

到目前为止,我们主要讨论了更简单的内联元素,即没有属性的元素.例子是<strong>,<em>.相反,<a>有其他属性.

很容易提出需要标记/样式化文本的一部分但是足够自定义的功能,因此仅使用标记是不够的.一个例子是字体系列功能.使用时,它会fontFamily向文本添加属性,该文本稍后会转换为<span>具有适当style属性的元素.

此时,您需要询问如果在文本的同一部分设置了多个此类属性会发生什么?以此模型为例:

<paragraph>
    <$text fontFamily="Tahoma" fontSize="big">Foo</$text>
</paragraph>
Run Code Online (Sandbox Code Playgroud)

以上属性转换如下:

  • fontFamily="value"转换为<span style="font-family: value;">,
  • fontSize="value"转换为<span class="text-value">.

那么,我们可以期待什么样的视图/ HTML?

<p>
    <span style="font-family: Tahoma;">
        <span class="text-big">Foo</span>
    </span>
</p>
Run Code Online (Sandbox Code Playgroud)

然而,这似乎是错误的.为什么不只有一个<span>元素?这样不是更好吗?

<p>
    <span style="font-family: Tahoma;" class="text-big">Foo</span>
</p>
Run Code Online (Sandbox Code Playgroud)

为了解决这些情况,在CKEditor 5转换机制中我们实际上引入了一种合并机制.

在上面的场景中,我们有两个转换为的属性<span>.当第一个属性(比如说,fontFamily被转换,<span>在视图中没有.所以<span>添加了style属性.但是,当fontSize转换时,已经存在<span>于视图中.view.Writer识别它并检查这些元素是否可以合并.规则是三:

  • 元素必须相同view.Element#name,
  • 元素必须相同view.AttributeElement#priority,
  • 这两个元素都没有view.AttributeElement#id设定.

我们还没有讨论过id房产,但为了简单起见,我现在不讨论它.可以说,某些属性元素阻止合并它们很重要.

将另一个属性添加到链接

此时,应该非常清楚如何向<a>元素添加另一个属性.

所有需要做的就是定义一个新的模型属性(linkTargetlinkRel),并使其转换为<a>具有所需(target="..."rel="...")属性的元素.然后,它将与原始<a href="...">元素合并.

请记住,<a>原始CKEditor 5链接插件中的元素已自定义priority.这意味着新插件生成的元素需要具有指定的相同优先级才能正确合并.

向上转换合并的属性元素

目前,我们只讨论了向下转换(即从模型转换到视图).现在让我们谈谈向上转换(即从视图转换为模型).幸运的是,它比前一部分更容易.

有两个"事物"可以升级 - 元素和属性.没有魔法在这里-元素元素(<p>,<a>,<strong>等),属性是属性(class="",href="",等).

元素可以向上转换为元素(<p>- > <paragraph>)或属性(<strong>- > bold,<a>- > linkHref).属性可以向上转换为属性.

我们的示例显然需要从元素到属性的向上转换.实际上,<a>元素被转换为linkHref属性,linkHref属性值取自元素的href=""属性<a>.

当然,人们会为他们的新linkTargetlinkRel属性定义相同的转换.但是,这里有一个陷阱.视图的每个部分只能转换("消耗")一次(对于向下转换的模型也是如此).

这是什么意思?简单地说,如果一个特征已经转换了给定元素名称或给定元素属性,则两个特征都不能转换它.这种方式功能可以正确地相互覆盖.这也意味着可以引入通用转换器(例如,如果没有其他功能被识别为可以由该功能转换的内容,<div>则可以转换为通用转换器).这也有助于发现冲突的转换器.<paragraph><div>

回到我们的例子.我们无法定义两个转换相同元素(<a>)并希望它们同时一起工作的元素到属性转换器.一个人会覆盖另一个人.

由于我们不想更改原始链接插件,因此我们需要按原样保留该转换器.但是,新插件的upcast转换器将是属性到属性转换器.由于该转换器不会转换元素(或更确切地说,元素名称),因此它将与原始转换器一起使用.

代码示例

这是链接目标插件的代码示例.下面我将解释它的一些部分.

import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
import { downcastAttributeToElement } from '@ckeditor/ckeditor5-engine/src/conversion/downcast-converters';
import { upcastAttributeToAttribute } from '@ckeditor/ckeditor5-engine/src/conversion/upcast-converters';

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

        editor.model.schema.extend( '$text', { allowAttributes: 'linkTarget' } );

        editor.conversion.for( 'downcast' ).add( downcastAttributeToElement( {
            model: 'linkTarget',
            view: ( attributeValue, writer ) => {
                return writer.createAttributeElement( 'a', { target: attributeValue }, { priority: 5 } );
            },
            converterPriority: 'low'
        } ) );

        editor.conversion.for( 'upcast' ).add( upcastAttributeToAttribute( {
            view: {
                name: 'a',
                key: 'target'
            },
            model: 'linkTarget',
            converterPriority: 'low'
        } ) );
    }
}
Run Code Online (Sandbox Code Playgroud)

对于这么长的教程,它肯定是一个小片段.希望大部分都是不言自明的.

首先,我们Schema通过定义linkTarget文本允许的新属性来扩展.

然后,我们定义向下转换.downcastAttributeToElement因为我们想要创建<a target="...">将与原始<a>元素合并的元素.请记住,<a>此处创建的元素具有定义的优先级5,就像在原始链接插件中一样.

最后一步是向上转换.upcastAttributeToAttribute正如前面所讨论的那样使用了助手.在view配置中,指定只应转换元素的target属性<a>(name: 'a').这并不意味着<a>元素将被转换!这只是转换器的过滤配置,因此它不会转换target其他元素的属性.

最后,两个转换器的优先级都低于原始转换器,以防止出现任何假设问题.

以上示例适用于我当前的主人ckeditor5-engineckeditor5-link.

  • 这是一个内容极其详尽的解释。从 CKEditor 文档中并不清楚它是如何工作的。 (3认同)
  • 这个漂亮的答案确实只能说明 ckeditor 5 有多糟糕...... (2认同)