UNPKG

@ckeditor/ckeditor5-html-support

Version:

HTML Support feature for CKEditor 5.

309 lines (308 loc) • 12.2 kB
/** * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options */ /** * @module html-support/datafilter */ import { Plugin, type Editor } from 'ckeditor5/src/core.js'; import { type MatcherPattern, type UpcastConversionApi, type ViewElement, type MatcherObjectPattern } from 'ckeditor5/src/engine.js'; import { Widget } from 'ckeditor5/src/widget.js'; import { DataSchema, type HtmlSupportDataSchemaDefinition } from './dataschema.js'; import { type GHSViewAttributes } from './utils.js'; import '../theme/datafilter.css'; /** * Allows to validate elements and element attributes registered by {@link module:html-support/dataschema~DataSchema}. * * To enable registered element in the editor, use {@link module:html-support/datafilter~DataFilter#allowElement} method: * * ```ts * dataFilter.allowElement( 'section' ); * ``` * * You can also allow or disallow specific element attributes: * * ```ts * // Allow `data-foo` attribute on `section` element. * dataFilter.allowAttributes( { * name: 'section', * attributes: { * 'data-foo': true * } * } ); * * // Disallow `color` style attribute on 'section' element. * dataFilter.disallowAttributes( { * name: 'section', * styles: { * color: /[\s\S]+/ * } * } ); * ``` * * To apply the information about allowed and disallowed attributes in custom integration plugin, * use the {@link module:html-support/datafilter~DataFilter#processViewAttributes `processViewAttributes()`} method. */ export declare class DataFilter extends Plugin { /** * An instance of the {@link module:html-support/dataschema~DataSchema}. */ private readonly _dataSchema; /** * {@link module:engine/view/matcher~Matcher Matcher} instance describing rules upon which * content attributes should be allowed. */ private readonly _allowedAttributes; /** * {@link module:engine/view/matcher~Matcher Matcher} instance describing rules upon which * content attributes should be disallowed. */ private readonly _disallowedAttributes; /** * Allowed element definitions by {@link module:html-support/datafilter~DataFilter#allowElement} method. */ private readonly _allowedElements; /** * Disallowed element names by {@link module:html-support/datafilter~DataFilter#disallowElement} method. */ private readonly _disallowedElements; /** * Indicates if {@link module:engine/controller/datacontroller~DataController editor's data controller} * data has been already initialized. */ private _dataInitialized; /** * Cached map of coupled attributes. Keys are the feature attributes names * and values are arrays with coupled GHS attributes names. */ private _coupledAttributes; constructor(editor: Editor); /** * @inheritDoc */ static get pluginName(): "DataFilter"; /** * @inheritDoc */ static get isOfficialPlugin(): true; /** * @inheritDoc */ static get requires(): readonly [typeof DataSchema, typeof Widget]; /** * Load a configuration of one or many elements, where their attributes should be allowed. * * **Note**: Rules will be applied just before next data pipeline data init or set. * * @param config Configuration of elements that should have their attributes accepted in the editor. */ loadAllowedConfig(config: Array<MatcherObjectPattern>): void; /** * Load a configuration of one or many elements, where their attributes should be disallowed. * * **Note**: Rules will be applied just before next data pipeline data init or set. * * @param config Configuration of elements that should have their attributes rejected from the editor. */ loadDisallowedConfig(config: Array<MatcherObjectPattern>): void; /** * Load a configuration of one or many elements, where when empty should be allowed. * * **Note**: It modifies DataSchema so must be loaded before registering filtering rules. * * @param config Configuration of elements that should be preserved even if empty. */ loadAllowedEmptyElementsConfig(config: Array<string>): void; /** * Allow the given element in the editor context. * * This method will only allow elements described by the {@link module:html-support/dataschema~DataSchema} used * to create data filter. * * **Note**: Rules will be applied just before next data pipeline data init or set. * * @param viewName String or regular expression matching view name. */ allowElement(viewName: string | RegExp): void; /** * Disallow the given element in the editor context. * * This method will only disallow elements described by the {@link module:html-support/dataschema~DataSchema} used * to create data filter. * * @param viewName String or regular expression matching view name. */ disallowElement(viewName: string | RegExp): void; /** * Allow the given empty element in the editor context. * * This method will only allow elements described by the {@link module:html-support/dataschema~DataSchema} used * to create data filter. * * **Note**: It modifies DataSchema so must be called before registering filtering rules. * * @param viewName String or regular expression matching view name. */ allowEmptyElement(viewName: string): void; /** * Allow the given attributes for view element allowed by {@link #allowElement} method. * * @param config Pattern matching all attributes which should be allowed. */ allowAttributes(config: MatcherPattern): void; /** * Disallow the given attributes for view element allowed by {@link #allowElement} method. * * @param config Pattern matching all attributes which should be disallowed. */ disallowAttributes(config: MatcherPattern): void; /** * Processes all allowed and disallowed attributes on the view element by consuming them and returning the allowed ones. * * This method applies the configuration set up by {@link #allowAttributes `allowAttributes()`} * and {@link #disallowAttributes `disallowAttributes()`} over the given view element by consuming relevant attributes. * It returns the allowed attributes that were found on the given view element for further processing by integration code. * * ```ts * dispatcher.on( 'element:myElement', ( evt, data, conversionApi ) => { * // Get rid of disallowed and extract all allowed attributes from a viewElement. * const viewAttributes = dataFilter.processViewAttributes( data.viewItem, conversionApi ); * // Do something with them, i.e. store inside a model as a dictionary. * if ( viewAttributes ) { * conversionApi.writer.setAttribute( 'htmlAttributesOfMyElement', viewAttributes, data.modelRange ); * } * } ); * ``` * * @see module:engine/conversion/viewconsumable~ViewConsumable#consume * * @returns Object with following properties: * - attributes Set with matched attribute names. * - styles Set with matched style names. * - classes Set with matched class names. */ processViewAttributes(viewElement: ViewElement, conversionApi: UpcastConversionApi): GHSViewAttributes | null; /** * Adds allowed element definition and fires registration event. */ private _addAllowedElement; /** * Registers elements allowed by {@link module:html-support/datafilter~DataFilter#allowElement} method * once {@link module:engine/controller/datacontroller~DataController editor's data controller} is initialized. */ private _registerElementsAfterInit; /** * Registers default element handlers. */ private _registerElementHandlers; /** * Registers a model post-fixer that is removing coupled GHS attributes of inline elements. Those attributes * are removed if a coupled feature attribute is removed. * * For example, consider following HTML: * * ```html * <a href="foo.html" id="myId">bar</a> * ``` * * Which would be upcasted to following text node in the model: * * ```html * <$text linkHref="foo.html" htmlA="{ attributes: { id: 'myId' } }">bar</$text> * ``` * * When the user removes the link from that text (using UI), only `linkHref` attribute would be removed: * * ```html * <$text htmlA="{ attributes: { id: 'myId' } }">bar</$text> * ``` * * The `htmlA` attribute would stay in the model and would cause GHS to generate an `<a>` element. * This is incorrect from UX point of view, as the user wanted to remove the whole link (not only `href`). */ private _registerCoupledAttributesPostFixer; /** * Removes `html*Attributes` attributes from incompatible elements. * * For example, consider the following HTML: * * ```html * <heading2 htmlH2Attributes="...">foobar[]</heading2> * ``` * * Pressing `enter` creates a new `paragraph` element that inherits * the `htmlH2Attributes` attribute from `heading2`. * * ```html * <heading2 htmlH2Attributes="...">foobar</heading2> * <paragraph htmlH2Attributes="...">[]</paragraph> * ``` * * This postfixer ensures that this doesn't happen, and that elements can * only have `html*Attributes` associated with them, * e.g.: `htmlPAttributes` for `<p>`, `htmlDivAttributes` for `<div>`, etc. * * With it enabled, pressing `enter` at the end of `<heading2>` will create * a new paragraph without the `htmlH2Attributes` attribute. * * ```html * <heading2 htmlH2Attributes="...">foobar</heading2> * <paragraph>[]</paragraph> * ``` */ private _registerAssociatedHtmlAttributesPostFixer; /** * Collects the map of coupled attributes. The returned map is keyed by the feature attribute name * and coupled GHS attribute names are stored in the value array. */ private _getCoupledAttributesMap; /** * Fires `register` event for the given element definition. */ private _fireRegisterEvent; /** * Registers object element and attribute converters for the given data schema definition. */ private _registerObjectElement; /** * Registers block element and attribute converters for the given data schema definition. */ private _registerBlockElement; /** * Registers inline element and attribute converters for the given data schema definition. * * Extends `$text` model schema to allow the given definition model attribute and its properties. */ private _registerInlineElement; } /** * Fired when {@link module:html-support/datafilter~DataFilter} is registering element and attribute * converters for the {@link module:html-support/dataschema~HtmlSupportDataSchemaDefinition element definition}. * * The event also accepts {@link module:html-support/dataschema~HtmlSupportDataSchemaDefinition#view} value * as an event namespace, e.g. `register:span`. * * ```ts * dataFilter.on( 'register', ( evt, definition ) => { * editor.model.schema.register( definition.model, definition.modelSchema ); * editor.conversion.elementToElement( { model: definition.model, view: definition.view } ); * * evt.stop(); * } ); * * dataFilter.on( 'register:span', ( evt, definition ) => { * editor.model.schema.extend( '$text', { allowAttributes: 'htmlSpan' } ); * * editor.conversion.for( 'upcast' ).elementToAttribute( { view: 'span', model: 'htmlSpan' } ); * editor.conversion.for( 'downcast' ).attributeToElement( { view: 'span', model: 'htmlSpan' } ); * * evt.stop(); * }, { priority: 'high' } ) * ``` * * @eventName ~DataFilter#register */ export interface HtmlSupportDataFilterRegisterEvent { name: 'register' | `register:${string}`; args: [data: HtmlSupportDataSchemaDefinition]; }