@ckeditor/ckeditor5-html-support 44.2.1 → 44.3.0-alpha.1
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.
- package/build/html-support.js +1 -1
- package/dist/index.js +277 -40
- package/dist/index.js.map +1 -1
- package/package.json +11 -11
- package/src/augmentation.d.ts +3 -1
- package/src/datafilter.js +38 -27
- package/src/dataschema.d.ts +4 -0
- package/src/emptyblock.d.ts +62 -0
- package/src/emptyblock.js +148 -0
- package/src/generalhtmlsupport.d.ts +2 -1
- package/src/generalhtmlsupport.js +2 -0
- package/src/generalhtmlsupportconfig.d.ts +12 -0
- package/src/index.d.ts +2 -0
- package/src/index.js +1 -0
- package/src/integrations/horizontalline.d.ts +30 -0
- package/src/integrations/horizontalline.js +80 -0
- package/src/schemadefinitions.js +12 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ckeditor/ckeditor5-html-support",
|
|
3
|
-
"version": "44.
|
|
3
|
+
"version": "44.3.0-alpha.1",
|
|
4
4
|
"description": "HTML Support feature for CKEditor 5.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ckeditor",
|
|
@@ -17,16 +17,16 @@
|
|
|
17
17
|
"type": "module",
|
|
18
18
|
"main": "src/index.js",
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@ckeditor/ckeditor5-core": "44.
|
|
21
|
-
"@ckeditor/ckeditor5-engine": "44.
|
|
22
|
-
"@ckeditor/ckeditor5-enter": "44.
|
|
23
|
-
"@ckeditor/ckeditor5-heading": "44.
|
|
24
|
-
"@ckeditor/ckeditor5-image": "44.
|
|
25
|
-
"@ckeditor/ckeditor5-list": "44.
|
|
26
|
-
"@ckeditor/ckeditor5-table": "44.
|
|
27
|
-
"@ckeditor/ckeditor5-utils": "44.
|
|
28
|
-
"@ckeditor/ckeditor5-widget": "44.
|
|
29
|
-
"ckeditor5": "44.
|
|
20
|
+
"@ckeditor/ckeditor5-core": "44.3.0-alpha.1",
|
|
21
|
+
"@ckeditor/ckeditor5-engine": "44.3.0-alpha.1",
|
|
22
|
+
"@ckeditor/ckeditor5-enter": "44.3.0-alpha.1",
|
|
23
|
+
"@ckeditor/ckeditor5-heading": "44.3.0-alpha.1",
|
|
24
|
+
"@ckeditor/ckeditor5-image": "44.3.0-alpha.1",
|
|
25
|
+
"@ckeditor/ckeditor5-list": "44.3.0-alpha.1",
|
|
26
|
+
"@ckeditor/ckeditor5-table": "44.3.0-alpha.1",
|
|
27
|
+
"@ckeditor/ckeditor5-utils": "44.3.0-alpha.1",
|
|
28
|
+
"@ckeditor/ckeditor5-widget": "44.3.0-alpha.1",
|
|
29
|
+
"ckeditor5": "44.3.0-alpha.1",
|
|
30
30
|
"lodash-es": "4.17.21"
|
|
31
31
|
},
|
|
32
32
|
"author": "CKSource (http://cksource.com/)",
|
package/src/augmentation.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
3
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
|
|
4
4
|
*/
|
|
5
|
-
import type { GeneralHtmlSupport, DataFilter, DataSchema, GeneralHtmlSupportConfig, CodeBlockElementSupport, CustomElementSupport, ListElementSupport, DualContentModelElementSupport, HeadingElementSupport, ImageElementSupport, MediaEmbedElementSupport, ScriptElementSupport, StyleElementSupport, TableElementSupport, HtmlComment, FullPage } from './index.js';
|
|
5
|
+
import type { GeneralHtmlSupport, DataFilter, DataSchema, GeneralHtmlSupportConfig, CodeBlockElementSupport, CustomElementSupport, ListElementSupport, DualContentModelElementSupport, HeadingElementSupport, ImageElementSupport, MediaEmbedElementSupport, ScriptElementSupport, StyleElementSupport, TableElementSupport, HorizontalLineElementSupport, HtmlComment, FullPage, EmptyBlock } from './index.js';
|
|
6
6
|
declare module '@ckeditor/ckeditor5-core' {
|
|
7
7
|
interface EditorConfig {
|
|
8
8
|
/**
|
|
@@ -27,7 +27,9 @@ declare module '@ckeditor/ckeditor5-core' {
|
|
|
27
27
|
[ScriptElementSupport.pluginName]: ScriptElementSupport;
|
|
28
28
|
[StyleElementSupport.pluginName]: StyleElementSupport;
|
|
29
29
|
[TableElementSupport.pluginName]: TableElementSupport;
|
|
30
|
+
[HorizontalLineElementSupport.pluginName]: HorizontalLineElementSupport;
|
|
30
31
|
[HtmlComment.pluginName]: HtmlComment;
|
|
31
32
|
[FullPage.pluginName]: FullPage;
|
|
33
|
+
[EmptyBlock.pluginName]: EmptyBlock;
|
|
32
34
|
}
|
|
33
35
|
}
|
package/src/datafilter.js
CHANGED
|
@@ -528,6 +528,11 @@ export default class DataFilter extends Plugin {
|
|
|
528
528
|
const conversion = editor.conversion;
|
|
529
529
|
const { view: viewName, model: modelName } = definition;
|
|
530
530
|
if (!schema.isRegistered(definition.model)) {
|
|
531
|
+
// Do not register converters and empty schema for editor existing feature
|
|
532
|
+
// as empty schema won't allow element anywhere in the model.
|
|
533
|
+
if (!definition.modelSchema) {
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
531
536
|
schema.register(definition.model, definition.modelSchema);
|
|
532
537
|
if (!viewName) {
|
|
533
538
|
return;
|
|
@@ -542,7 +547,9 @@ export default class DataFilter extends Plugin {
|
|
|
542
547
|
});
|
|
543
548
|
conversion.for('downcast').elementToElement({
|
|
544
549
|
model: modelName,
|
|
545
|
-
view:
|
|
550
|
+
view: (modelElement, { writer }) => definition.isEmpty ?
|
|
551
|
+
writer.createEmptyElement(viewName) :
|
|
552
|
+
writer.createContainerElement(viewName)
|
|
546
553
|
});
|
|
547
554
|
}
|
|
548
555
|
if (!viewName) {
|
|
@@ -623,35 +630,39 @@ function matchAndConsumeAttributes(viewElement, matcher, consumable) {
|
|
|
623
630
|
const matches = matcher.matchAll(viewElement) || [];
|
|
624
631
|
const stylesProcessor = viewElement.document.stylesProcessor;
|
|
625
632
|
return matches.reduce((result, { match }) => {
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
633
|
+
for (const [key, token] of match.attributes || []) {
|
|
634
|
+
// Verify and consume styles.
|
|
635
|
+
if (key == 'style') {
|
|
636
|
+
const style = token;
|
|
637
|
+
// Check longer forms of the same style as those could be matched
|
|
638
|
+
// but not present in the element directly.
|
|
639
|
+
// Consider only longhand (or longer than current notation) so that
|
|
640
|
+
// we do not include all sides of the box if only one side is allowed.
|
|
641
|
+
const sortedRelatedStyles = stylesProcessor.getRelatedStyles(style)
|
|
642
|
+
.filter(relatedStyle => relatedStyle.split('-').length > style.split('-').length)
|
|
643
|
+
.sort((a, b) => b.split('-').length - a.split('-').length);
|
|
644
|
+
for (const relatedStyle of sortedRelatedStyles) {
|
|
645
|
+
if (consumable.consume(viewElement, { styles: [relatedStyle] })) {
|
|
646
|
+
result.styles.push(relatedStyle);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
// Verify and consume style as specified in the matcher.
|
|
650
|
+
if (consumable.consume(viewElement, { styles: [style] })) {
|
|
651
|
+
result.styles.push(style);
|
|
638
652
|
}
|
|
639
653
|
}
|
|
640
|
-
// Verify and consume
|
|
641
|
-
if (
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
for (const className of match.classes || []) {
|
|
647
|
-
if (consumable.consume(viewElement, { classes: [className] })) {
|
|
648
|
-
result.classes.push(className);
|
|
654
|
+
// Verify and consume class names.
|
|
655
|
+
else if (key == 'class') {
|
|
656
|
+
const className = token;
|
|
657
|
+
if (consumable.consume(viewElement, { classes: [className] })) {
|
|
658
|
+
result.classes.push(className);
|
|
659
|
+
}
|
|
649
660
|
}
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
661
|
+
else {
|
|
662
|
+
// Verify and consume other attributes.
|
|
663
|
+
if (consumable.consume(viewElement, { attributes: [key] })) {
|
|
664
|
+
result.attributes.push(key);
|
|
665
|
+
}
|
|
655
666
|
}
|
|
656
667
|
}
|
|
657
668
|
return result;
|
package/src/dataschema.d.ts
CHANGED
|
@@ -141,6 +141,10 @@ export interface DataSchemaDefinition {
|
|
|
141
141
|
* Indicates that the definition describes inline element.
|
|
142
142
|
*/
|
|
143
143
|
isInline?: boolean;
|
|
144
|
+
/**
|
|
145
|
+
* Indicates that the definition describes an empty HTML element like `<hr>`.
|
|
146
|
+
*/
|
|
147
|
+
isEmpty?: boolean;
|
|
144
148
|
}
|
|
145
149
|
/**
|
|
146
150
|
* A definition of {@link module:html-support/dataschema~DataSchema data schema} for block elements.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
|
+
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
|
|
4
|
+
*/
|
|
5
|
+
import { Plugin } from 'ckeditor5/src/core.js';
|
|
6
|
+
/**
|
|
7
|
+
* This is experimental plugin that allows for preserving empty block elements
|
|
8
|
+
* in the editor content instead of automatically filling them with block fillers (` `).
|
|
9
|
+
*
|
|
10
|
+
* This is useful when you want to:
|
|
11
|
+
*
|
|
12
|
+
* * Preserve empty block elements exactly as they were in the source HTML.
|
|
13
|
+
* * Allow for styling empty blocks with CSS (block fillers can interfere with height/margin).
|
|
14
|
+
* * Maintain compatibility with external systems that expect empty blocks to remain empty.
|
|
15
|
+
*
|
|
16
|
+
* Known limitations:
|
|
17
|
+
*
|
|
18
|
+
* * Empty blocks may not work correctly with revision history features.
|
|
19
|
+
* * Keyboard navigation through the document might behave unexpectedly, especially when
|
|
20
|
+
* navigating through structures like lists and tables.
|
|
21
|
+
*
|
|
22
|
+
* For example, this allows for HTML like:
|
|
23
|
+
*
|
|
24
|
+
* ```html
|
|
25
|
+
* <p></p>
|
|
26
|
+
* <p class="spacer"></p>
|
|
27
|
+
* <td></td>
|
|
28
|
+
* ```
|
|
29
|
+
* to remain empty instead of being converted to:
|
|
30
|
+
*
|
|
31
|
+
* ```html
|
|
32
|
+
* <p> </p>
|
|
33
|
+
* <p class="spacer"> </p>
|
|
34
|
+
* <td> </td>
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* **Warning**: This is an experimental plugin. It may have bugs and breaking changes may be introduced without prior notice.
|
|
38
|
+
*/
|
|
39
|
+
export default class EmptyBlock extends Plugin {
|
|
40
|
+
/**
|
|
41
|
+
* @inheritDoc
|
|
42
|
+
*/
|
|
43
|
+
static get pluginName(): "EmptyBlock";
|
|
44
|
+
/**
|
|
45
|
+
* @inheritDoc
|
|
46
|
+
*/
|
|
47
|
+
static get isOfficialPlugin(): true;
|
|
48
|
+
/**
|
|
49
|
+
* @inheritDoc
|
|
50
|
+
*/
|
|
51
|
+
afterInit(): void;
|
|
52
|
+
/**
|
|
53
|
+
* Handle clipboard paste events:
|
|
54
|
+
*
|
|
55
|
+
* * It does not affect *copying* content from the editor, only *pasting*.
|
|
56
|
+
* * When content is pasted from another editor instance with `<p></p>`,
|
|
57
|
+
* the ` ` filler is added, so the getData result is `<p> </p>`.
|
|
58
|
+
* * When content is pasted from the same editor instance with `<p></p>`,
|
|
59
|
+
* the ` ` filler is not added, so the getData result is `<p></p>`.
|
|
60
|
+
*/
|
|
61
|
+
private _registerClipboardPastingHandler;
|
|
62
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
|
+
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
|
|
4
|
+
*/
|
|
5
|
+
import { Plugin } from 'ckeditor5/src/core.js';
|
|
6
|
+
const EMPTY_BLOCK_MODEL_ATTRIBUTE = 'htmlEmptyBlock';
|
|
7
|
+
/**
|
|
8
|
+
* This is experimental plugin that allows for preserving empty block elements
|
|
9
|
+
* in the editor content instead of automatically filling them with block fillers (` `).
|
|
10
|
+
*
|
|
11
|
+
* This is useful when you want to:
|
|
12
|
+
*
|
|
13
|
+
* * Preserve empty block elements exactly as they were in the source HTML.
|
|
14
|
+
* * Allow for styling empty blocks with CSS (block fillers can interfere with height/margin).
|
|
15
|
+
* * Maintain compatibility with external systems that expect empty blocks to remain empty.
|
|
16
|
+
*
|
|
17
|
+
* Known limitations:
|
|
18
|
+
*
|
|
19
|
+
* * Empty blocks may not work correctly with revision history features.
|
|
20
|
+
* * Keyboard navigation through the document might behave unexpectedly, especially when
|
|
21
|
+
* navigating through structures like lists and tables.
|
|
22
|
+
*
|
|
23
|
+
* For example, this allows for HTML like:
|
|
24
|
+
*
|
|
25
|
+
* ```html
|
|
26
|
+
* <p></p>
|
|
27
|
+
* <p class="spacer"></p>
|
|
28
|
+
* <td></td>
|
|
29
|
+
* ```
|
|
30
|
+
* to remain empty instead of being converted to:
|
|
31
|
+
*
|
|
32
|
+
* ```html
|
|
33
|
+
* <p> </p>
|
|
34
|
+
* <p class="spacer"> </p>
|
|
35
|
+
* <td> </td>
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* **Warning**: This is an experimental plugin. It may have bugs and breaking changes may be introduced without prior notice.
|
|
39
|
+
*/
|
|
40
|
+
export default class EmptyBlock extends Plugin {
|
|
41
|
+
/**
|
|
42
|
+
* @inheritDoc
|
|
43
|
+
*/
|
|
44
|
+
static get pluginName() {
|
|
45
|
+
return 'EmptyBlock';
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* @inheritDoc
|
|
49
|
+
*/
|
|
50
|
+
static get isOfficialPlugin() {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* @inheritDoc
|
|
55
|
+
*/
|
|
56
|
+
afterInit() {
|
|
57
|
+
const { model, conversion, plugins, config } = this.editor;
|
|
58
|
+
const schema = model.schema;
|
|
59
|
+
const preserveEmptyBlocksInEditingView = config.get('htmlSupport.preserveEmptyBlocksInEditingView');
|
|
60
|
+
schema.extend('$block', { allowAttributes: [EMPTY_BLOCK_MODEL_ATTRIBUTE] });
|
|
61
|
+
schema.extend('$container', { allowAttributes: [EMPTY_BLOCK_MODEL_ATTRIBUTE] });
|
|
62
|
+
if (schema.isRegistered('tableCell')) {
|
|
63
|
+
schema.extend('tableCell', { allowAttributes: [EMPTY_BLOCK_MODEL_ATTRIBUTE] });
|
|
64
|
+
}
|
|
65
|
+
if (preserveEmptyBlocksInEditingView) {
|
|
66
|
+
conversion.for('downcast').add(createEmptyBlockDowncastConverter());
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
conversion.for('dataDowncast').add(createEmptyBlockDowncastConverter());
|
|
70
|
+
}
|
|
71
|
+
conversion.for('upcast').add(createEmptyBlockUpcastConverter(schema));
|
|
72
|
+
if (plugins.has('ClipboardPipeline')) {
|
|
73
|
+
this._registerClipboardPastingHandler();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Handle clipboard paste events:
|
|
78
|
+
*
|
|
79
|
+
* * It does not affect *copying* content from the editor, only *pasting*.
|
|
80
|
+
* * When content is pasted from another editor instance with `<p></p>`,
|
|
81
|
+
* the ` ` filler is added, so the getData result is `<p> </p>`.
|
|
82
|
+
* * When content is pasted from the same editor instance with `<p></p>`,
|
|
83
|
+
* the ` ` filler is not added, so the getData result is `<p></p>`.
|
|
84
|
+
*/
|
|
85
|
+
_registerClipboardPastingHandler() {
|
|
86
|
+
const clipboardPipeline = this.editor.plugins.get('ClipboardPipeline');
|
|
87
|
+
this.listenTo(clipboardPipeline, 'contentInsertion', (evt, data) => {
|
|
88
|
+
if (data.sourceEditorId === this.editor.id) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
this.editor.model.change(writer => {
|
|
92
|
+
for (const { item } of writer.createRangeIn(data.content)) {
|
|
93
|
+
if (item.is('element') && item.hasAttribute(EMPTY_BLOCK_MODEL_ATTRIBUTE)) {
|
|
94
|
+
writer.removeAttribute(EMPTY_BLOCK_MODEL_ATTRIBUTE, item);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Creates a downcast converter for handling empty blocks.
|
|
103
|
+
* This converter prevents filler elements from being added to elements marked as empty blocks.
|
|
104
|
+
*/
|
|
105
|
+
function createEmptyBlockDowncastConverter() {
|
|
106
|
+
return (dispatcher) => {
|
|
107
|
+
dispatcher.on(`attribute:${EMPTY_BLOCK_MODEL_ATTRIBUTE}`, (evt, data, conversionApi) => {
|
|
108
|
+
const { mapper, consumable } = conversionApi;
|
|
109
|
+
const { item } = data;
|
|
110
|
+
if (!consumable.consume(item, evt.name)) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const viewElement = mapper.toViewElement(item);
|
|
114
|
+
if (viewElement && data.attributeNewValue) {
|
|
115
|
+
viewElement.getFillerOffset = () => null;
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Creates an upcast converter for handling empty blocks.
|
|
122
|
+
* The converter detects empty elements and marks them with the empty block attribute.
|
|
123
|
+
*/
|
|
124
|
+
function createEmptyBlockUpcastConverter(schema) {
|
|
125
|
+
return (dispatcher) => {
|
|
126
|
+
dispatcher.on('element', (evt, data, conversionApi) => {
|
|
127
|
+
const { viewItem, modelRange } = data;
|
|
128
|
+
if (!viewItem.is('element') || !viewItem.isEmpty || viewItem.getCustomProperty('$hasBlockFiller')) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
// Handle element itself.
|
|
132
|
+
const modelElement = modelRange && modelRange.start.nodeAfter;
|
|
133
|
+
if (!modelElement || !schema.checkAttribute(modelElement, EMPTY_BLOCK_MODEL_ATTRIBUTE)) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
conversionApi.writer.setAttribute(EMPTY_BLOCK_MODEL_ATTRIBUTE, true, modelElement);
|
|
137
|
+
// Handle an auto-paragraphed bogus paragraph inside empty element.
|
|
138
|
+
if (modelElement.childCount != 1) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const firstModelChild = modelElement.getChild(0);
|
|
142
|
+
if (firstModelChild.is('element', 'paragraph') &&
|
|
143
|
+
schema.checkAttribute(firstModelChild, EMPTY_BLOCK_MODEL_ATTRIBUTE)) {
|
|
144
|
+
conversionApi.writer.setAttribute(EMPTY_BLOCK_MODEL_ATTRIBUTE, true, firstModelChild);
|
|
145
|
+
}
|
|
146
|
+
}, { priority: 'lowest' });
|
|
147
|
+
};
|
|
148
|
+
}
|
|
@@ -17,6 +17,7 @@ import ScriptElementSupport from './integrations/script.js';
|
|
|
17
17
|
import TableElementSupport from './integrations/table.js';
|
|
18
18
|
import StyleElementSupport from './integrations/style.js';
|
|
19
19
|
import ListElementSupport from './integrations/list.js';
|
|
20
|
+
import HorizontalLineElementSupport from './integrations/horizontalline.js';
|
|
20
21
|
import CustomElementSupport from './integrations/customelement.js';
|
|
21
22
|
import type { Selectable } from 'ckeditor5/src/engine.js';
|
|
22
23
|
/**
|
|
@@ -37,7 +38,7 @@ export default class GeneralHtmlSupport extends Plugin {
|
|
|
37
38
|
/**
|
|
38
39
|
* @inheritDoc
|
|
39
40
|
*/
|
|
40
|
-
static get requires(): readonly [typeof DataFilter, typeof CodeBlockElementSupport, typeof DualContentModelElementSupport, typeof HeadingElementSupport, typeof ImageElementSupport, typeof MediaEmbedElementSupport, typeof ScriptElementSupport, typeof TableElementSupport, typeof StyleElementSupport, typeof ListElementSupport, typeof CustomElementSupport];
|
|
41
|
+
static get requires(): readonly [typeof DataFilter, typeof CodeBlockElementSupport, typeof DualContentModelElementSupport, typeof HeadingElementSupport, typeof ImageElementSupport, typeof MediaEmbedElementSupport, typeof ScriptElementSupport, typeof TableElementSupport, typeof StyleElementSupport, typeof ListElementSupport, typeof HorizontalLineElementSupport, typeof CustomElementSupport];
|
|
41
42
|
/**
|
|
42
43
|
* @inheritDoc
|
|
43
44
|
*/
|
|
@@ -17,6 +17,7 @@ import ScriptElementSupport from './integrations/script.js';
|
|
|
17
17
|
import TableElementSupport from './integrations/table.js';
|
|
18
18
|
import StyleElementSupport from './integrations/style.js';
|
|
19
19
|
import ListElementSupport from './integrations/list.js';
|
|
20
|
+
import HorizontalLineElementSupport from './integrations/horizontalline.js';
|
|
20
21
|
import CustomElementSupport from './integrations/customelement.js';
|
|
21
22
|
import { getHtmlAttributeName, modifyGhsAttribute } from './utils.js';
|
|
22
23
|
/**
|
|
@@ -53,6 +54,7 @@ export default class GeneralHtmlSupport extends Plugin {
|
|
|
53
54
|
TableElementSupport,
|
|
54
55
|
StyleElementSupport,
|
|
55
56
|
ListElementSupport,
|
|
57
|
+
HorizontalLineElementSupport,
|
|
56
58
|
CustomElementSupport
|
|
57
59
|
];
|
|
58
60
|
}
|
|
@@ -74,4 +74,16 @@ export interface GeneralHtmlSupportConfig {
|
|
|
74
74
|
* ```
|
|
75
75
|
*/
|
|
76
76
|
allowEmpty?: Array<string>;
|
|
77
|
+
/**
|
|
78
|
+
* Whether a filler text (non-breaking space entity — ` `) will be inserted into empty block elements in HTML output.
|
|
79
|
+
* This is used to render block elements properly with line-height.
|
|
80
|
+
*
|
|
81
|
+
* When set to `true`, empty blocks will be preserved in the editing view.
|
|
82
|
+
* When `false` (default), empty blocks are only preserved in the data output.
|
|
83
|
+
*
|
|
84
|
+
* The option is used by the {@link module:html-support/emptyblock~EmptyBlock} feature.
|
|
85
|
+
*
|
|
86
|
+
* @default false
|
|
87
|
+
*/
|
|
88
|
+
preserveEmptyBlocksInEditingView?: boolean;
|
|
77
89
|
}
|
package/src/index.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export { default as DataSchema, type DataSchemaBlockElementDefinition } from './
|
|
|
11
11
|
export { default as HtmlComment } from './htmlcomment.js';
|
|
12
12
|
export { default as FullPage } from './fullpage.js';
|
|
13
13
|
export { default as HtmlPageDataProcessor } from './htmlpagedataprocessor.js';
|
|
14
|
+
export { default as EmptyBlock } from './emptyblock.js';
|
|
14
15
|
export type { GeneralHtmlSupportConfig } from './generalhtmlsupportconfig.js';
|
|
15
16
|
export type { default as CodeBlockElementSupport } from './integrations/codeblock.js';
|
|
16
17
|
export type { default as CustomElementSupport } from './integrations/customelement.js';
|
|
@@ -22,4 +23,5 @@ export type { default as MediaEmbedElementSupport } from './integrations/mediaem
|
|
|
22
23
|
export type { default as ScriptElementSupport } from './integrations/script.js';
|
|
23
24
|
export type { default as StyleElementSupport } from './integrations/style.js';
|
|
24
25
|
export type { default as TableElementSupport } from './integrations/table.js';
|
|
26
|
+
export type { default as HorizontalLineElementSupport } from './integrations/horizontalline.js';
|
|
25
27
|
import './augmentation.js';
|
package/src/index.js
CHANGED
|
@@ -11,4 +11,5 @@ export { default as DataSchema } from './dataschema.js';
|
|
|
11
11
|
export { default as HtmlComment } from './htmlcomment.js';
|
|
12
12
|
export { default as FullPage } from './fullpage.js';
|
|
13
13
|
export { default as HtmlPageDataProcessor } from './htmlpagedataprocessor.js';
|
|
14
|
+
export { default as EmptyBlock } from './emptyblock.js';
|
|
14
15
|
import './augmentation.js';
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
|
+
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* @module html-support/integrations/horizontalline
|
|
7
|
+
*/
|
|
8
|
+
import { Plugin } from 'ckeditor5/src/core.js';
|
|
9
|
+
import DataFilter from '../datafilter.js';
|
|
10
|
+
/**
|
|
11
|
+
* Provides the General HTML Support integration with the {@link module:horizontal-line/horizontalline~HorizontalLine} feature.
|
|
12
|
+
*/
|
|
13
|
+
export default class HorizontalLineElementSupport extends Plugin {
|
|
14
|
+
/**
|
|
15
|
+
* @inheritDoc
|
|
16
|
+
*/
|
|
17
|
+
static get requires(): readonly [typeof DataFilter];
|
|
18
|
+
/**
|
|
19
|
+
* @inheritDoc
|
|
20
|
+
*/
|
|
21
|
+
static get pluginName(): "HorizontalLineElementSupport";
|
|
22
|
+
/**
|
|
23
|
+
* @inheritDoc
|
|
24
|
+
*/
|
|
25
|
+
static get isOfficialPlugin(): true;
|
|
26
|
+
/**
|
|
27
|
+
* @inheritDoc
|
|
28
|
+
*/
|
|
29
|
+
init(): void;
|
|
30
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
|
|
3
|
+
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* @module html-support/integrations/horizontalline
|
|
7
|
+
*/
|
|
8
|
+
import { Plugin } from 'ckeditor5/src/core.js';
|
|
9
|
+
import DataFilter from '../datafilter.js';
|
|
10
|
+
import { updateViewAttributes } from '../utils.js';
|
|
11
|
+
import { getDescendantElement } from './integrationutils.js';
|
|
12
|
+
import { viewToModelBlockAttributeConverter } from '../converters.js';
|
|
13
|
+
/**
|
|
14
|
+
* Provides the General HTML Support integration with the {@link module:horizontal-line/horizontalline~HorizontalLine} feature.
|
|
15
|
+
*/
|
|
16
|
+
export default class HorizontalLineElementSupport extends Plugin {
|
|
17
|
+
/**
|
|
18
|
+
* @inheritDoc
|
|
19
|
+
*/
|
|
20
|
+
static get requires() {
|
|
21
|
+
return [DataFilter];
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* @inheritDoc
|
|
25
|
+
*/
|
|
26
|
+
static get pluginName() {
|
|
27
|
+
return 'HorizontalLineElementSupport';
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* @inheritDoc
|
|
31
|
+
*/
|
|
32
|
+
static get isOfficialPlugin() {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* @inheritDoc
|
|
37
|
+
*/
|
|
38
|
+
init() {
|
|
39
|
+
const editor = this.editor;
|
|
40
|
+
if (!editor.plugins.has('HorizontalLineEditing')) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const schema = editor.model.schema;
|
|
44
|
+
const conversion = editor.conversion;
|
|
45
|
+
const dataFilter = editor.plugins.get(DataFilter);
|
|
46
|
+
dataFilter.on('register:hr', (evt, definition) => {
|
|
47
|
+
if (definition.model !== 'horizontalLine') {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
schema.extend('horizontalLine', {
|
|
51
|
+
allowAttributes: ['htmlHrAttributes']
|
|
52
|
+
});
|
|
53
|
+
conversion.for('upcast').add(viewToModelBlockAttributeConverter(definition, dataFilter));
|
|
54
|
+
conversion.for('downcast').add(modelToViewHorizontalLineAttributeConverter());
|
|
55
|
+
evt.stop();
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* A model-to-view conversion helper applying attributes from the
|
|
61
|
+
* {@link module:horizontal-line/horizontalline~HorizontalLine HorizontalLine} feature.
|
|
62
|
+
*
|
|
63
|
+
* @returns Returns a conversion callback.
|
|
64
|
+
*/
|
|
65
|
+
function modelToViewHorizontalLineAttributeConverter() {
|
|
66
|
+
return (dispatcher) => {
|
|
67
|
+
dispatcher.on('attribute:htmlHrAttributes:horizontalLine', (evt, data, conversionApi) => {
|
|
68
|
+
if (!conversionApi.consumable.test(data.item, evt.name)) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const { attributeOldValue, attributeNewValue } = data;
|
|
72
|
+
const containerElement = conversionApi.mapper.toViewElement(data.item);
|
|
73
|
+
const viewElement = getDescendantElement(conversionApi.writer, containerElement, 'hr');
|
|
74
|
+
if (viewElement) {
|
|
75
|
+
updateViewAttributes(conversionApi.writer, attributeOldValue, attributeNewValue, viewElement);
|
|
76
|
+
conversionApi.consumable.consume(data.item, evt.name);
|
|
77
|
+
}
|
|
78
|
+
}, { priority: 'low' });
|
|
79
|
+
};
|
|
80
|
+
}
|
package/src/schemadefinitions.js
CHANGED
|
@@ -111,6 +111,10 @@ export default {
|
|
|
111
111
|
model: 'imageInline',
|
|
112
112
|
view: 'img'
|
|
113
113
|
},
|
|
114
|
+
{
|
|
115
|
+
model: 'horizontalLine',
|
|
116
|
+
view: 'hr'
|
|
117
|
+
},
|
|
114
118
|
// Compatibility features.
|
|
115
119
|
{
|
|
116
120
|
model: 'htmlP',
|
|
@@ -517,6 +521,14 @@ export default {
|
|
|
517
521
|
inheritAllFrom: '$container',
|
|
518
522
|
isBlock: false
|
|
519
523
|
}
|
|
524
|
+
},
|
|
525
|
+
{
|
|
526
|
+
model: 'htmlHr',
|
|
527
|
+
view: 'hr',
|
|
528
|
+
isEmpty: true,
|
|
529
|
+
modelSchema: {
|
|
530
|
+
inheritAllFrom: '$blockObject'
|
|
531
|
+
}
|
|
520
532
|
}
|
|
521
533
|
],
|
|
522
534
|
inline: [
|