首页 > 解决方案 > 如何将“target”属性添加到ckeditor5中的`a`标签?

问题描述

我已经创建了自己的链接插件。现在我想为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();
    }
}

标签: ckeditor5

解决方案


介绍

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

尽管您可能了解理论部分的大部分内容,但我建议您阅读它以全面了解 CKEditor 5 中的工作原理。

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

另外,请记住,本教程不讨论这个插件的 UI 部分,只讨论应该如何配置以进行转换。添加和删​​除属性是 UI 或其他代码部分的工作。这里我只讨论引擎的东西。

CKEditor 5 中的内联元素

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

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

例如,在模型中,我们可能有一个带有文本的<paragraph>元素,其中的属性设置为。我们会这样记:. 看,那里没有或任何其他附加元素。它只是一些带有属性的文本。稍后,属性被转换为元素。Foo barbarboldtrue<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>

Foo bar baz这是一个带有链接的粗体文本bar。在转换过程中,它将转换为:

<p>
    <strong>Foo </strong><a href="bar.html"><strong>bar</strong></a><strong> baz</strong>
</p>

请注意,该<a>元素的转换方式始终是最顶部的元素。这是故意的,因此没有一个元素会破坏一个<a>元素。看到这个,不正确的视图/HTML 字符串:

<p>
    <a href="bar.html">Foo </a><strong><a href="bar.html">bar</a></strong>
</p>

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

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

复杂的内联元素和合并

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

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

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

<paragraph>
    <$text fontFamily="Tahoma" fontSize="big">Foo</$text>
</paragraph>

上述属性转换如下:

  • 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>

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

<p>
    <span style="font-family: Tahoma;" class="text-big">Foo</span>
</p>

为了解决这样的情况,在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'
        } ) );
    }
}

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

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

Then, we define downcast conversion. downcastAttributeToElement is used as we want to create <a target="..."> element that will be merged with the original <a> element. Keep in mind that the <a> element that is created here has the priority defined to 5, just as in the original link plugin.

The last step is upcast conversion. upcastAttributeToAttribute helper is used, as discussed earlier. In view configuration, it is specified that only target attribute of <a> element should be converted (name: 'a'). This does not mean that <a> element will be converted! This is only filtering configuration for the converter, so it won't convert target attribute of some other element.

Lastly, both converters are added with priority lower than the original converters to prevent any hypothetical problems.

The above sample works for me on the current master of ckeditor5-engine and ckeditor5-link.


推荐阅读