@ckeditor/ckeditor5-html-support 41.1.0 → 41.3.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,72 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
+ */
5
+ /**
6
+ * @module html-support/utils
7
+ */
8
+ import type { DocumentSelection, DowncastWriter, Item, ViewElement, Writer } from 'ckeditor5/src/engine.js';
9
+ export interface GHSViewAttributes {
10
+ attributes?: Record<string, unknown>;
11
+ classes?: Array<string>;
12
+ styles?: Record<string, string>;
13
+ }
14
+ /**
15
+ * Helper function for the downcast converter. Updates attributes on the given view element.
16
+ *
17
+ * @param writer The view writer.
18
+ * @param oldViewAttributes The previous GHS attribute value.
19
+ * @param newViewAttributes The current GHS attribute value.
20
+ * @param viewElement The view element to update.
21
+ */
22
+ export declare function updateViewAttributes(writer: DowncastWriter, oldViewAttributes: GHSViewAttributes, newViewAttributes: GHSViewAttributes, viewElement: ViewElement): void;
23
+ /**
24
+ * Helper function for the downcast converter. Sets attributes on the given view element.
25
+ *
26
+ * @param writer The view writer.
27
+ * @param viewAttributes The GHS attribute value.
28
+ * @param viewElement The view element to update.
29
+ */
30
+ export declare function setViewAttributes(writer: DowncastWriter, viewAttributes: GHSViewAttributes, viewElement: ViewElement): void;
31
+ /**
32
+ * Helper function for the downcast converter. Removes attributes on the given view element.
33
+ *
34
+ * @param writer The view writer.
35
+ * @param viewAttributes The GHS attribute value.
36
+ * @param viewElement The view element to update.
37
+ */
38
+ export declare function removeViewAttributes(writer: DowncastWriter, viewAttributes: GHSViewAttributes, viewElement: ViewElement): void;
39
+ /**
40
+ * Merges view element attribute objects.
41
+ */
42
+ export declare function mergeViewElementAttributes(target: GHSViewAttributes, source: GHSViewAttributes): GHSViewAttributes;
43
+ type ModifyGhsAttributesCallback = (t: Map<string, unknown>) => void;
44
+ type ModifyGhsClassesCallback = (t: Set<string>) => void;
45
+ type ModifyGhsStylesCallback = (t: Map<string, string>) => void;
46
+ /**
47
+ * Updates a GHS attribute on a specified item.
48
+ * @param callback That receives a map as an argument and should modify it (add or remove entries).
49
+ */
50
+ export declare function modifyGhsAttribute(writer: Writer, item: Item | DocumentSelection, ghsAttributeName: string, subject: 'attributes', callback: ModifyGhsAttributesCallback): void;
51
+ /**
52
+ * Updates a GHS attribute on a specified item.
53
+ * @param callback That receives a set as an argument and should modify it (add or remove entries).
54
+ */
55
+ export declare function modifyGhsAttribute(writer: Writer, item: Item | DocumentSelection, ghsAttributeName: string, subject: 'classes', callback: ModifyGhsClassesCallback): void;
56
+ /**
57
+ * Updates a GHS attribute on a specified item.
58
+ * @param callback That receives a map as an argument and should modify it (add or remove entries).
59
+ */
60
+ export declare function modifyGhsAttribute(writer: Writer, item: Item | DocumentSelection, ghsAttributeName: string, subject: 'styles', callback: ModifyGhsStylesCallback): void;
61
+ /**
62
+ * Transforms passed string to PascalCase format. Examples:
63
+ * * `div` => `Div`
64
+ * * `h1` => `H1`
65
+ * * `table` => `Table`
66
+ */
67
+ export declare function toPascalCase(data: string): string;
68
+ /**
69
+ * Returns the attribute name of the model element that holds raw HTML attributes.
70
+ */
71
+ export declare function getHtmlAttributeName(viewElementName: string): string;
72
+ export {};
@@ -14,7 +14,7 @@ msgid ""
14
14
  msgstr ""
15
15
  "Language-Team: Hebrew (https://app.transifex.com/ckeditor/teams/11143/he/)\n"
16
16
  "Language: he\n"
17
- "Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n == 2 && n % 1 == 0) ? 1: (n % 10 == 0 && n % 1 == 0 && n > 10) ? 2 : 3;\n"
17
+ "Plural-Forms: nplurals=3; plural=(n == 1 && n % 1 == 0) ? 0 : (n == 2 && n % 1 == 0) ? 1: 2;\n"
18
18
  "Content-Type: text/plain; charset=UTF-8\n"
19
19
 
20
20
  msgctxt "A label describing an HTML object widget."
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ckeditor/ckeditor5-html-support",
3
- "version": "41.1.0",
3
+ "version": "41.3.0-alpha.0",
4
4
  "description": "HTML Support feature for CKEditor 5.",
5
5
  "keywords": [
6
6
  "ckeditor",
@@ -17,7 +17,7 @@
17
17
  "type": "module",
18
18
  "main": "src/index.js",
19
19
  "dependencies": {
20
- "ckeditor5": "41.1.0",
20
+ "ckeditor5": "41.3.0-alpha.0",
21
21
  "lodash-es": "4.17.21"
22
22
  },
23
23
  "author": "CKSource (http://cksource.com/)",
@@ -30,6 +30,7 @@
30
30
  "directory": "packages/ckeditor5-html-support"
31
31
  },
32
32
  "files": [
33
+ "dist",
33
34
  "lang",
34
35
  "src/**/*.js",
35
36
  "src/**/*.d.ts",
package/src/datafilter.js CHANGED
@@ -6,13 +6,13 @@
6
6
  * @module html-support/datafilter
7
7
  */
8
8
  import { Plugin } from 'ckeditor5/src/core.js';
9
- import { Matcher } from 'ckeditor5/src/engine.js';
9
+ import { Matcher, StylesMap } from 'ckeditor5/src/engine.js';
10
10
  import { CKEditorError, priorities, isValidAttributeName } from 'ckeditor5/src/utils.js';
11
11
  import { Widget } from 'ckeditor5/src/widget.js';
12
12
  import { viewToModelObjectConverter, toObjectWidgetConverter, createObjectView, viewToAttributeInlineConverter, attributeToViewInlineConverter, emptyInlineModelElementToViewConverter, viewToModelBlockAttributeConverter, modelToViewBlockAttributeConverter } from './converters.js';
13
13
  import { default as DataSchema } from './dataschema.js';
14
14
  import { getHtmlAttributeName } from './utils.js';
15
- import { isPlainObject, pull as removeItemFromArray } from 'lodash-es';
15
+ import { isPlainObject } from 'lodash-es';
16
16
  import '../theme/datafilter.css';
17
17
  /**
18
18
  * Allows to validate elements and element attributes registered by {@link module:html-support/dataschema~DataSchema}.
@@ -213,10 +213,11 @@ export default class DataFilter extends Plugin {
213
213
  * - classes Set with matched class names.
214
214
  */
215
215
  processViewAttributes(viewElement, conversionApi) {
216
+ const { consumable } = conversionApi;
216
217
  // Make sure that the disabled attributes are handled before the allowed attributes are called.
217
218
  // For example, for block images the <figure> converter triggers conversion for <img> first and then for other elements, i.e. <a>.
218
- consumeAttributes(viewElement, conversionApi, this._disallowedAttributes);
219
- return consumeAttributes(viewElement, conversionApi, this._allowedAttributes);
219
+ matchAndConsumeAttributes(viewElement, this._disallowedAttributes, consumable);
220
+ return prepareGHSAttribute(viewElement, matchAndConsumeAttributes(viewElement, this._allowedAttributes, consumable));
220
221
  }
221
222
  /**
222
223
  * Adds allowed element definition and fires registration event.
@@ -605,104 +606,101 @@ export default class DataFilter extends Plugin {
605
606
  }
606
607
  }
607
608
  /**
608
- * Matches and consumes the given view attributes.
609
- */
610
- function consumeAttributes(viewElement, conversionApi, matcher) {
611
- const matches = consumeAttributeMatches(viewElement, conversionApi, matcher);
612
- const { attributes, styles, classes } = mergeMatchResults(matches);
613
- const viewAttributes = {};
614
- // Remove invalid DOM element attributes.
615
- if (attributes.size) {
616
- for (const key of attributes) {
617
- if (!isValidAttributeName(key)) {
618
- attributes.delete(key);
619
- }
620
- }
621
- }
622
- if (attributes.size) {
623
- viewAttributes.attributes = iterableToObject(attributes, key => viewElement.getAttribute(key));
624
- }
625
- if (styles.size) {
626
- viewAttributes.styles = iterableToObject(styles, key => viewElement.getStyle(key));
627
- }
628
- if (classes.size) {
629
- viewAttributes.classes = Array.from(classes);
630
- }
631
- if (!Object.keys(viewAttributes).length) {
632
- return null;
633
- }
634
- return viewAttributes;
635
- }
636
- /**
637
- * Consumes matched attributes.
609
+ * Matches and consumes matched attributes.
638
610
  *
639
- * @returns Array with match information about found attributes.
611
+ * @returns Object with following properties:
612
+ * - attributes Array with matched attribute names.
613
+ * - classes Array with matched class names.
614
+ * - styles Array with matched style names.
640
615
  */
641
- function consumeAttributeMatches(viewElement, { consumable }, matcher) {
616
+ function matchAndConsumeAttributes(viewElement, matcher, consumable) {
642
617
  const matches = matcher.matchAll(viewElement) || [];
643
- const consumedMatches = [];
644
- for (const match of matches) {
645
- removeConsumedAttributes(consumable, viewElement, match);
646
- // We only want to consume attributes, so element can be still processed by other converters.
647
- delete match.match.name;
648
- consumable.consume(viewElement, match.match);
649
- consumedMatches.push(match);
650
- }
651
- return consumedMatches;
652
- }
653
- /**
654
- * Removes attributes from the given match that were already consumed by other converters.
655
- */
656
- function removeConsumedAttributes(consumable, viewElement, match) {
657
- for (const key of ['attributes', 'classes', 'styles']) {
658
- const attributes = match.match[key];
659
- if (!attributes) {
660
- continue;
618
+ const stylesProcessor = viewElement.document.stylesProcessor;
619
+ return matches.reduce((result, { match }) => {
620
+ // Verify and consume styles.
621
+ for (const style of match.styles || []) {
622
+ // Check longer forms of the same style as those could be matched
623
+ // but not present in the element directly.
624
+ // Consider only longhand (or longer than current notation) so that
625
+ // we do not include all sides of the box if only one side is allowed.
626
+ const sortedRelatedStyles = stylesProcessor.getRelatedStyles(style)
627
+ .filter(relatedStyle => relatedStyle.split('-').length > style.split('-').length)
628
+ .sort((a, b) => b.split('-').length - a.split('-').length);
629
+ for (const relatedStyle of sortedRelatedStyles) {
630
+ if (consumable.consume(viewElement, { styles: [relatedStyle] })) {
631
+ result.styles.push(relatedStyle);
632
+ }
633
+ }
634
+ // Verify and consume style as specified in the matcher.
635
+ if (consumable.consume(viewElement, { styles: [style] })) {
636
+ result.styles.push(style);
637
+ }
661
638
  }
662
- // Iterating over a copy of an array so removing items doesn't influence iteration.
663
- for (const value of Array.from(attributes)) {
664
- if (!consumable.test(viewElement, ({ [key]: [value] }))) {
665
- removeItemFromArray(attributes, value);
639
+ // Verify and consume class names.
640
+ for (const className of match.classes || []) {
641
+ if (consumable.consume(viewElement, { classes: [className] })) {
642
+ result.classes.push(className);
666
643
  }
667
644
  }
668
- }
645
+ // Verify and consume other attributes.
646
+ for (const attributeName of match.attributes || []) {
647
+ if (consumable.consume(viewElement, { attributes: [attributeName] })) {
648
+ result.attributes.push(attributeName);
649
+ }
650
+ }
651
+ return result;
652
+ }, {
653
+ attributes: [],
654
+ classes: [],
655
+ styles: []
656
+ });
669
657
  }
670
658
  /**
671
- * Merges the result of {@link module:engine/view/matcher~Matcher#matchAll} method.
672
- *
673
- * @param matches
674
- * @returns Object with following properties:
675
- * - attributes Set with matched attribute names.
676
- * - styles Set with matched style names.
677
- * - classes Set with matched class names.
659
+ * Prepares the GHS attribute value as an object with element attributes' values.
678
660
  */
679
- function mergeMatchResults(matches) {
680
- const matchResult = {
681
- attributes: new Set(),
682
- classes: new Set(),
683
- styles: new Set()
684
- };
685
- for (const match of matches) {
686
- for (const key in matchResult) {
687
- const values = match.match[key] || [];
688
- values.forEach(value => (matchResult[key]).add(value));
689
- }
661
+ function prepareGHSAttribute(viewElement, { attributes, classes, styles }) {
662
+ if (!attributes.length && !classes.length && !styles.length) {
663
+ return null;
690
664
  }
691
- return matchResult;
665
+ return {
666
+ ...(attributes.length && {
667
+ attributes: getAttributes(viewElement, attributes)
668
+ }),
669
+ ...(styles.length && {
670
+ styles: getReducedStyles(viewElement, styles)
671
+ }),
672
+ ...(classes.length && {
673
+ classes
674
+ })
675
+ };
692
676
  }
693
677
  /**
694
- * Converts the given iterable object into an object.
678
+ * Returns attributes as an object with names and values.
695
679
  */
696
- function iterableToObject(iterable, getValue) {
680
+ function getAttributes(viewElement, attributes) {
697
681
  const attributesObject = {};
698
- for (const prop of iterable) {
699
- const value = getValue(prop);
700
- if (value !== undefined) {
701
- attributesObject[prop] = getValue(prop);
682
+ for (const key of attributes) {
683
+ const value = viewElement.getAttribute(key);
684
+ if (value !== undefined && isValidAttributeName(key)) {
685
+ attributesObject[key] = value;
702
686
  }
703
687
  }
704
688
  return attributesObject;
705
689
  }
690
+ /**
691
+ * Returns styles as an object reduced to shorthand notation without redundant entries.
692
+ */
693
+ function getReducedStyles(viewElement, styles) {
694
+ // Use StyleMap to reduce style value to the minimal form (without shorthand and long-hand notation and duplication).
695
+ const stylesMap = new StylesMap(viewElement.document.stylesProcessor);
696
+ for (const key of styles) {
697
+ const styleValue = viewElement.getStyle(key);
698
+ if (styleValue !== undefined) {
699
+ stylesMap.set(key, styleValue);
700
+ }
701
+ }
702
+ return Object.fromEntries(stylesMap.getStylesEntries());
703
+ }
706
704
  /**
707
705
  * Matcher by default has to match **all** patterns to count it as an actual match. Splitting the pattern
708
706
  * into separate patterns means that any matched pattern will be count as a match.
@@ -714,7 +712,8 @@ function splitPattern(pattern, attributeName) {
714
712
  const { name } = pattern;
715
713
  const attributeValue = pattern[attributeName];
716
714
  if (isPlainObject(attributeValue)) {
717
- return Object.entries(attributeValue).map(([key, value]) => ({
715
+ return Object.entries(attributeValue)
716
+ .map(([key, value]) => ({
718
717
  name,
719
718
  [attributeName]: {
720
719
  [key]: value
@@ -722,7 +721,8 @@ function splitPattern(pattern, attributeName) {
722
721
  }));
723
722
  }
724
723
  if (Array.isArray(attributeValue)) {
725
- return attributeValue.map(value => ({
724
+ return attributeValue
725
+ .map(value => ({
726
726
  name,
727
727
  [attributeName]: [value]
728
728
  }));