【问题标题】:How to add "target" attribute to `a` tag in ckeditor5?如何将“target”属性添加到ckeditor5中的`a`标签?
【发布时间】:2018-12-20 13:57:37
【问题描述】:

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

但我无法完成它。这是我的转换器插件代码。 我应该添加哪些转换器,以便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


    【解决方案1】:

    简介

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

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

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

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

    CKEditor 5 中的内联元素

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

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

    例如,在模型中,我们可能有一个带有Foo bar 文本的&lt;paragraph&gt; 元素,其中barbold 属性设置为true。我们会这样记:&lt;paragraph&gt;Foo &lt;$text bold="true"&gt;bar&lt;/$text&gt;&lt;/paragraph&gt;。看,那里没有&lt;strong&gt; 或任何其他附加元素。它只是一些带有属性的文本。稍后,bold 属性被转换为&lt;strong&gt; 元素。

    顺便说一句:来自模型属性的视图元素有自己的类: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>
    

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

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

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

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

    复杂的内联元素和合并

    到目前为止,我们主要讨论了更简单的内联元素,即没有属性的元素。例如&lt;strong&gt;&lt;em&gt;。相反,&lt;a&gt; 有额外的属性。

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

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

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

    以上属性转换如下:

    • fontFamily="value" 转换为 &lt;span style="font-family: value;"&gt;
    • fontSize="value" 转换为 &lt;span class="text-value"&gt;

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

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

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

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

    为了解决这样的情况,在CKEditor 5的转换机制中,实际上我们引入了合并机制。

    在上述场景中,我们有两个属性转换为&lt;span&gt;。当第一个属性(比如fontFamily被转换时,视图中还没有&lt;span&gt;。所以&lt;span&gt;加上style属性。但是,当fontSize被转换时,已经有&lt;span&gt; 在视图中。view.Writer 识别这一点并检查这些元素是否可以合并。规则是三个:

    • 元素必须具有相同的view.Element#name
    • 元素必须具有相同的view.AttributeElement#priority
    • 两个元素都不能设置view.AttributeElement#id

    我们还没有讨论过id 属性,但是为了简单起见,我现在不讨论它。对于某些属性元素来说,防止合并它们就足够了。

    向链接添加另一个属性

    此时,应该很清楚如何给&lt;a&gt;元素添加另一个属性了。

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

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

    向上转换合并的属性元素

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

    有两种“事物”可以向上转换——元素和属性。这里没有魔法 - 元素就是元素(&lt;p&gt;&lt;a&gt;&lt;strong&gt; 等),属性就是属性(class=""href="" 等)。

    元素可以向上转换为元素 (&lt;p&gt; -> &lt;paragraph&gt;) 或属性 (&lt;strong&gt; -> bold, &lt;a&gt; -> linkHref)。属性可以向上转换为属性。

    我们的示例显然需要从元素向上转换为属性。实际上,&lt;a&gt; 元素被转换为linkHref 属性,linkHref 属性值取自&lt;a&gt; 元素的href="" 属性。

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

    这是什么意思?简单地说,如果一个特征已经转换给定 元素名称 或给定元素属性,那么这两个特征也不能转换它。这样功能可以正确地相互覆盖。这也意味着可以引入通用转换器(例如,如果没有其他功能将&lt;div&gt; 识别为可以由该功能转换的东西,则&lt;div&gt; 可以转换为&lt;paragraph&gt;)。这也有助于发现有冲突的转换器。

    回到我们的例子。我们不能定义两个元素到属性的转换器来转换相同的元素 (&lt;a&gt;) 并期望它们同时一起工作。一个会覆盖另一个。

    由于我们不想更改原始链接插件,因此我们需要保持该转换器不变。但是,新插件的 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'
            } ) );
        }
    }
    

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

    首先,我们通过定义一个允许在文本上使用的新属性 linkTarget 来扩展 Schema

    然后,我们定义向下转换。 downcastAttributeToElement 用于创建 &lt;a target="..."&gt; 元素,该元素将与原始 &lt;a&gt; 元素合并。请记住,此处创建的 &lt;a&gt; 元素的优先级定义为 5,就像在原始链接插件中一样。

    最后一步是向上转换。如前所述,使用upcastAttributeToAttribute 助手。在view 配置中,指定只转换&lt;a&gt; 元素的target 属性(name: 'a')。这并不意味着&lt;a&gt; 元素将被转换!这只是转换器的过滤配置,因此它不会转换某些其他元素的target 属性。

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

    上面的示例适用于ckeditor5-engineckeditor5-link 的当前master。

    【讨论】:

    • 这是一个非常详尽的解释。从 CKEditor 文档中并不清楚它是如何工作的。
    • 你使用了这个包的哪个版本@ckeditor/ckeditor5-engine?当前版本的 24.0.0 似乎没有这个路径 '@ckeditor/ckeditor5-engine/src/conversion/downcast-converters'
    • 这个漂亮的答案真的只是说明了 ckeditor 5 是多么糟糕......
    • @UtkristAdhikari src 路径显然已更改为: import { downcastAttributeToElement } from '@ckeditor/ckeditor5-engine/src/conversion/downcasthelpers';从“@ckeditor/ckeditor5-engine/src/conversion/upcasthelpers”导入{ upcastAttributeToAttribute};
    猜你喜欢
    • 2011-12-29
    • 2019-07-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-17
    • 1970-01-01
    • 2022-06-24
    相关资源
    最近更新 更多