@ckeditor/ckeditor5-html-support 44.2.1 → 44.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.
- 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/dist/index.js
CHANGED
|
@@ -433,6 +433,10 @@ var defaultConfig = {
|
|
|
433
433
|
model: 'imageInline',
|
|
434
434
|
view: 'img'
|
|
435
435
|
},
|
|
436
|
+
{
|
|
437
|
+
model: 'horizontalLine',
|
|
438
|
+
view: 'hr'
|
|
439
|
+
},
|
|
436
440
|
// Compatibility features.
|
|
437
441
|
{
|
|
438
442
|
model: 'htmlP',
|
|
@@ -856,6 +860,14 @@ var defaultConfig = {
|
|
|
856
860
|
inheritAllFrom: '$container',
|
|
857
861
|
isBlock: false
|
|
858
862
|
}
|
|
863
|
+
},
|
|
864
|
+
{
|
|
865
|
+
model: 'htmlHr',
|
|
866
|
+
view: 'hr',
|
|
867
|
+
isEmpty: true,
|
|
868
|
+
modelSchema: {
|
|
869
|
+
inheritAllFrom: '$blockObject'
|
|
870
|
+
}
|
|
859
871
|
}
|
|
860
872
|
],
|
|
861
873
|
inline: [
|
|
@@ -2021,6 +2033,11 @@ var defaultConfig = {
|
|
|
2021
2033
|
const conversion = editor.conversion;
|
|
2022
2034
|
const { view: viewName, model: modelName } = definition;
|
|
2023
2035
|
if (!schema.isRegistered(definition.model)) {
|
|
2036
|
+
// Do not register converters and empty schema for editor existing feature
|
|
2037
|
+
// as empty schema won't allow element anywhere in the model.
|
|
2038
|
+
if (!definition.modelSchema) {
|
|
2039
|
+
return;
|
|
2040
|
+
}
|
|
2024
2041
|
schema.register(definition.model, definition.modelSchema);
|
|
2025
2042
|
if (!viewName) {
|
|
2026
2043
|
return;
|
|
@@ -2035,7 +2052,7 @@ var defaultConfig = {
|
|
|
2035
2052
|
});
|
|
2036
2053
|
conversion.for('downcast').elementToElement({
|
|
2037
2054
|
model: modelName,
|
|
2038
|
-
view: viewName
|
|
2055
|
+
view: (modelElement, { writer })=>definition.isEmpty ? writer.createEmptyElement(viewName) : writer.createContainerElement(viewName)
|
|
2039
2056
|
});
|
|
2040
2057
|
}
|
|
2041
2058
|
if (!viewName) {
|
|
@@ -2112,49 +2129,50 @@ var defaultConfig = {
|
|
|
2112
2129
|
const matches = matcher.matchAll(viewElement) || [];
|
|
2113
2130
|
const stylesProcessor = viewElement.document.stylesProcessor;
|
|
2114
2131
|
return matches.reduce((result, { match })=>{
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2132
|
+
for (const [key, token] of match.attributes || []){
|
|
2133
|
+
// Verify and consume styles.
|
|
2134
|
+
if (key == 'style') {
|
|
2135
|
+
const style = token;
|
|
2136
|
+
// Check longer forms of the same style as those could be matched
|
|
2137
|
+
// but not present in the element directly.
|
|
2138
|
+
// Consider only longhand (or longer than current notation) so that
|
|
2139
|
+
// we do not include all sides of the box if only one side is allowed.
|
|
2140
|
+
const sortedRelatedStyles = stylesProcessor.getRelatedStyles(style).filter((relatedStyle)=>relatedStyle.split('-').length > style.split('-').length).sort((a, b)=>b.split('-').length - a.split('-').length);
|
|
2141
|
+
for (const relatedStyle of sortedRelatedStyles){
|
|
2142
|
+
if (consumable.consume(viewElement, {
|
|
2143
|
+
styles: [
|
|
2144
|
+
relatedStyle
|
|
2145
|
+
]
|
|
2146
|
+
})) {
|
|
2147
|
+
result.styles.push(relatedStyle);
|
|
2148
|
+
}
|
|
2149
|
+
}
|
|
2150
|
+
// Verify and consume style as specified in the matcher.
|
|
2123
2151
|
if (consumable.consume(viewElement, {
|
|
2124
2152
|
styles: [
|
|
2125
|
-
|
|
2153
|
+
style
|
|
2126
2154
|
]
|
|
2127
2155
|
})) {
|
|
2128
|
-
result.styles.push(
|
|
2156
|
+
result.styles.push(style);
|
|
2157
|
+
}
|
|
2158
|
+
} else if (key == 'class') {
|
|
2159
|
+
const className = token;
|
|
2160
|
+
if (consumable.consume(viewElement, {
|
|
2161
|
+
classes: [
|
|
2162
|
+
className
|
|
2163
|
+
]
|
|
2164
|
+
})) {
|
|
2165
|
+
result.classes.push(className);
|
|
2166
|
+
}
|
|
2167
|
+
} else {
|
|
2168
|
+
// Verify and consume other attributes.
|
|
2169
|
+
if (consumable.consume(viewElement, {
|
|
2170
|
+
attributes: [
|
|
2171
|
+
key
|
|
2172
|
+
]
|
|
2173
|
+
})) {
|
|
2174
|
+
result.attributes.push(key);
|
|
2129
2175
|
}
|
|
2130
|
-
}
|
|
2131
|
-
// Verify and consume style as specified in the matcher.
|
|
2132
|
-
if (consumable.consume(viewElement, {
|
|
2133
|
-
styles: [
|
|
2134
|
-
style
|
|
2135
|
-
]
|
|
2136
|
-
})) {
|
|
2137
|
-
result.styles.push(style);
|
|
2138
|
-
}
|
|
2139
|
-
}
|
|
2140
|
-
// Verify and consume class names.
|
|
2141
|
-
for (const className of match.classes || []){
|
|
2142
|
-
if (consumable.consume(viewElement, {
|
|
2143
|
-
classes: [
|
|
2144
|
-
className
|
|
2145
|
-
]
|
|
2146
|
-
})) {
|
|
2147
|
-
result.classes.push(className);
|
|
2148
|
-
}
|
|
2149
|
-
}
|
|
2150
|
-
// Verify and consume other attributes.
|
|
2151
|
-
for (const attributeName of match.attributes || []){
|
|
2152
|
-
if (consumable.consume(viewElement, {
|
|
2153
|
-
attributes: [
|
|
2154
|
-
attributeName
|
|
2155
|
-
]
|
|
2156
|
-
})) {
|
|
2157
|
-
result.attributes.push(attributeName);
|
|
2158
2176
|
}
|
|
2159
2177
|
}
|
|
2160
2178
|
return result;
|
|
@@ -3310,6 +3328,75 @@ function modelToViewMediaAttributeConverter(mediaElementName) {
|
|
|
3310
3328
|
return listType === 'numbered' || listType == 'customNumbered' ? 'htmlOlAttributes' : 'htmlUlAttributes';
|
|
3311
3329
|
}
|
|
3312
3330
|
|
|
3331
|
+
/**
|
|
3332
|
+
* Provides the General HTML Support integration with the {@link module:horizontal-line/horizontalline~HorizontalLine} feature.
|
|
3333
|
+
*/ class HorizontalLineElementSupport extends Plugin {
|
|
3334
|
+
/**
|
|
3335
|
+
* @inheritDoc
|
|
3336
|
+
*/ static get requires() {
|
|
3337
|
+
return [
|
|
3338
|
+
DataFilter
|
|
3339
|
+
];
|
|
3340
|
+
}
|
|
3341
|
+
/**
|
|
3342
|
+
* @inheritDoc
|
|
3343
|
+
*/ static get pluginName() {
|
|
3344
|
+
return 'HorizontalLineElementSupport';
|
|
3345
|
+
}
|
|
3346
|
+
/**
|
|
3347
|
+
* @inheritDoc
|
|
3348
|
+
*/ static get isOfficialPlugin() {
|
|
3349
|
+
return true;
|
|
3350
|
+
}
|
|
3351
|
+
/**
|
|
3352
|
+
* @inheritDoc
|
|
3353
|
+
*/ init() {
|
|
3354
|
+
const editor = this.editor;
|
|
3355
|
+
if (!editor.plugins.has('HorizontalLineEditing')) {
|
|
3356
|
+
return;
|
|
3357
|
+
}
|
|
3358
|
+
const schema = editor.model.schema;
|
|
3359
|
+
const conversion = editor.conversion;
|
|
3360
|
+
const dataFilter = editor.plugins.get(DataFilter);
|
|
3361
|
+
dataFilter.on('register:hr', (evt, definition)=>{
|
|
3362
|
+
if (definition.model !== 'horizontalLine') {
|
|
3363
|
+
return;
|
|
3364
|
+
}
|
|
3365
|
+
schema.extend('horizontalLine', {
|
|
3366
|
+
allowAttributes: [
|
|
3367
|
+
'htmlHrAttributes'
|
|
3368
|
+
]
|
|
3369
|
+
});
|
|
3370
|
+
conversion.for('upcast').add(viewToModelBlockAttributeConverter(definition, dataFilter));
|
|
3371
|
+
conversion.for('downcast').add(modelToViewHorizontalLineAttributeConverter());
|
|
3372
|
+
evt.stop();
|
|
3373
|
+
});
|
|
3374
|
+
}
|
|
3375
|
+
}
|
|
3376
|
+
/**
|
|
3377
|
+
* A model-to-view conversion helper applying attributes from the
|
|
3378
|
+
* {@link module:horizontal-line/horizontalline~HorizontalLine HorizontalLine} feature.
|
|
3379
|
+
*
|
|
3380
|
+
* @returns Returns a conversion callback.
|
|
3381
|
+
*/ function modelToViewHorizontalLineAttributeConverter() {
|
|
3382
|
+
return (dispatcher)=>{
|
|
3383
|
+
dispatcher.on('attribute:htmlHrAttributes:horizontalLine', (evt, data, conversionApi)=>{
|
|
3384
|
+
if (!conversionApi.consumable.test(data.item, evt.name)) {
|
|
3385
|
+
return;
|
|
3386
|
+
}
|
|
3387
|
+
const { attributeOldValue, attributeNewValue } = data;
|
|
3388
|
+
const containerElement = conversionApi.mapper.toViewElement(data.item);
|
|
3389
|
+
const viewElement = getDescendantElement(conversionApi.writer, containerElement, 'hr');
|
|
3390
|
+
if (viewElement) {
|
|
3391
|
+
updateViewAttributes(conversionApi.writer, attributeOldValue, attributeNewValue, viewElement);
|
|
3392
|
+
conversionApi.consumable.consume(data.item, evt.name);
|
|
3393
|
+
}
|
|
3394
|
+
}, {
|
|
3395
|
+
priority: 'low'
|
|
3396
|
+
});
|
|
3397
|
+
};
|
|
3398
|
+
}
|
|
3399
|
+
|
|
3313
3400
|
/**
|
|
3314
3401
|
* Provides the General HTML Support for custom elements (not registered in the {@link module:html-support/dataschema~DataSchema}).
|
|
3315
3402
|
*/ class CustomElementSupport extends Plugin {
|
|
@@ -3501,6 +3588,7 @@ function modelToViewMediaAttributeConverter(mediaElementName) {
|
|
|
3501
3588
|
TableElementSupport,
|
|
3502
3589
|
StyleElementSupport,
|
|
3503
3590
|
ListElementSupport,
|
|
3591
|
+
HorizontalLineElementSupport,
|
|
3504
3592
|
CustomElementSupport
|
|
3505
3593
|
];
|
|
3506
3594
|
}
|
|
@@ -4036,5 +4124,154 @@ function modelToViewMediaAttributeConverter(mediaElementName) {
|
|
|
4036
4124
|
}
|
|
4037
4125
|
}
|
|
4038
4126
|
|
|
4039
|
-
|
|
4127
|
+
const EMPTY_BLOCK_MODEL_ATTRIBUTE = 'htmlEmptyBlock';
|
|
4128
|
+
/**
|
|
4129
|
+
* This is experimental plugin that allows for preserving empty block elements
|
|
4130
|
+
* in the editor content instead of automatically filling them with block fillers (` `).
|
|
4131
|
+
*
|
|
4132
|
+
* This is useful when you want to:
|
|
4133
|
+
*
|
|
4134
|
+
* * Preserve empty block elements exactly as they were in the source HTML.
|
|
4135
|
+
* * Allow for styling empty blocks with CSS (block fillers can interfere with height/margin).
|
|
4136
|
+
* * Maintain compatibility with external systems that expect empty blocks to remain empty.
|
|
4137
|
+
*
|
|
4138
|
+
* Known limitations:
|
|
4139
|
+
*
|
|
4140
|
+
* * Empty blocks may not work correctly with revision history features.
|
|
4141
|
+
* * Keyboard navigation through the document might behave unexpectedly, especially when
|
|
4142
|
+
* navigating through structures like lists and tables.
|
|
4143
|
+
*
|
|
4144
|
+
* For example, this allows for HTML like:
|
|
4145
|
+
*
|
|
4146
|
+
* ```html
|
|
4147
|
+
* <p></p>
|
|
4148
|
+
* <p class="spacer"></p>
|
|
4149
|
+
* <td></td>
|
|
4150
|
+
* ```
|
|
4151
|
+
* to remain empty instead of being converted to:
|
|
4152
|
+
*
|
|
4153
|
+
* ```html
|
|
4154
|
+
* <p> </p>
|
|
4155
|
+
* <p class="spacer"> </p>
|
|
4156
|
+
* <td> </td>
|
|
4157
|
+
* ```
|
|
4158
|
+
*
|
|
4159
|
+
* **Warning**: This is an experimental plugin. It may have bugs and breaking changes may be introduced without prior notice.
|
|
4160
|
+
*/ class EmptyBlock extends Plugin {
|
|
4161
|
+
/**
|
|
4162
|
+
* @inheritDoc
|
|
4163
|
+
*/ static get pluginName() {
|
|
4164
|
+
return 'EmptyBlock';
|
|
4165
|
+
}
|
|
4166
|
+
/**
|
|
4167
|
+
* @inheritDoc
|
|
4168
|
+
*/ static get isOfficialPlugin() {
|
|
4169
|
+
return true;
|
|
4170
|
+
}
|
|
4171
|
+
/**
|
|
4172
|
+
* @inheritDoc
|
|
4173
|
+
*/ afterInit() {
|
|
4174
|
+
const { model, conversion, plugins, config } = this.editor;
|
|
4175
|
+
const schema = model.schema;
|
|
4176
|
+
const preserveEmptyBlocksInEditingView = config.get('htmlSupport.preserveEmptyBlocksInEditingView');
|
|
4177
|
+
schema.extend('$block', {
|
|
4178
|
+
allowAttributes: [
|
|
4179
|
+
EMPTY_BLOCK_MODEL_ATTRIBUTE
|
|
4180
|
+
]
|
|
4181
|
+
});
|
|
4182
|
+
schema.extend('$container', {
|
|
4183
|
+
allowAttributes: [
|
|
4184
|
+
EMPTY_BLOCK_MODEL_ATTRIBUTE
|
|
4185
|
+
]
|
|
4186
|
+
});
|
|
4187
|
+
if (schema.isRegistered('tableCell')) {
|
|
4188
|
+
schema.extend('tableCell', {
|
|
4189
|
+
allowAttributes: [
|
|
4190
|
+
EMPTY_BLOCK_MODEL_ATTRIBUTE
|
|
4191
|
+
]
|
|
4192
|
+
});
|
|
4193
|
+
}
|
|
4194
|
+
if (preserveEmptyBlocksInEditingView) {
|
|
4195
|
+
conversion.for('downcast').add(createEmptyBlockDowncastConverter());
|
|
4196
|
+
} else {
|
|
4197
|
+
conversion.for('dataDowncast').add(createEmptyBlockDowncastConverter());
|
|
4198
|
+
}
|
|
4199
|
+
conversion.for('upcast').add(createEmptyBlockUpcastConverter(schema));
|
|
4200
|
+
if (plugins.has('ClipboardPipeline')) {
|
|
4201
|
+
this._registerClipboardPastingHandler();
|
|
4202
|
+
}
|
|
4203
|
+
}
|
|
4204
|
+
/**
|
|
4205
|
+
* Handle clipboard paste events:
|
|
4206
|
+
*
|
|
4207
|
+
* * It does not affect *copying* content from the editor, only *pasting*.
|
|
4208
|
+
* * When content is pasted from another editor instance with `<p></p>`,
|
|
4209
|
+
* the ` ` filler is added, so the getData result is `<p> </p>`.
|
|
4210
|
+
* * When content is pasted from the same editor instance with `<p></p>`,
|
|
4211
|
+
* the ` ` filler is not added, so the getData result is `<p></p>`.
|
|
4212
|
+
*/ _registerClipboardPastingHandler() {
|
|
4213
|
+
const clipboardPipeline = this.editor.plugins.get('ClipboardPipeline');
|
|
4214
|
+
this.listenTo(clipboardPipeline, 'contentInsertion', (evt, data)=>{
|
|
4215
|
+
if (data.sourceEditorId === this.editor.id) {
|
|
4216
|
+
return;
|
|
4217
|
+
}
|
|
4218
|
+
this.editor.model.change((writer)=>{
|
|
4219
|
+
for (const { item } of writer.createRangeIn(data.content)){
|
|
4220
|
+
if (item.is('element') && item.hasAttribute(EMPTY_BLOCK_MODEL_ATTRIBUTE)) {
|
|
4221
|
+
writer.removeAttribute(EMPTY_BLOCK_MODEL_ATTRIBUTE, item);
|
|
4222
|
+
}
|
|
4223
|
+
}
|
|
4224
|
+
});
|
|
4225
|
+
});
|
|
4226
|
+
}
|
|
4227
|
+
}
|
|
4228
|
+
/**
|
|
4229
|
+
* Creates a downcast converter for handling empty blocks.
|
|
4230
|
+
* This converter prevents filler elements from being added to elements marked as empty blocks.
|
|
4231
|
+
*/ function createEmptyBlockDowncastConverter() {
|
|
4232
|
+
return (dispatcher)=>{
|
|
4233
|
+
dispatcher.on(`attribute:${EMPTY_BLOCK_MODEL_ATTRIBUTE}`, (evt, data, conversionApi)=>{
|
|
4234
|
+
const { mapper, consumable } = conversionApi;
|
|
4235
|
+
const { item } = data;
|
|
4236
|
+
if (!consumable.consume(item, evt.name)) {
|
|
4237
|
+
return;
|
|
4238
|
+
}
|
|
4239
|
+
const viewElement = mapper.toViewElement(item);
|
|
4240
|
+
if (viewElement && data.attributeNewValue) {
|
|
4241
|
+
viewElement.getFillerOffset = ()=>null;
|
|
4242
|
+
}
|
|
4243
|
+
});
|
|
4244
|
+
};
|
|
4245
|
+
}
|
|
4246
|
+
/**
|
|
4247
|
+
* Creates an upcast converter for handling empty blocks.
|
|
4248
|
+
* The converter detects empty elements and marks them with the empty block attribute.
|
|
4249
|
+
*/ function createEmptyBlockUpcastConverter(schema) {
|
|
4250
|
+
return (dispatcher)=>{
|
|
4251
|
+
dispatcher.on('element', (evt, data, conversionApi)=>{
|
|
4252
|
+
const { viewItem, modelRange } = data;
|
|
4253
|
+
if (!viewItem.is('element') || !viewItem.isEmpty || viewItem.getCustomProperty('$hasBlockFiller')) {
|
|
4254
|
+
return;
|
|
4255
|
+
}
|
|
4256
|
+
// Handle element itself.
|
|
4257
|
+
const modelElement = modelRange && modelRange.start.nodeAfter;
|
|
4258
|
+
if (!modelElement || !schema.checkAttribute(modelElement, EMPTY_BLOCK_MODEL_ATTRIBUTE)) {
|
|
4259
|
+
return;
|
|
4260
|
+
}
|
|
4261
|
+
conversionApi.writer.setAttribute(EMPTY_BLOCK_MODEL_ATTRIBUTE, true, modelElement);
|
|
4262
|
+
// Handle an auto-paragraphed bogus paragraph inside empty element.
|
|
4263
|
+
if (modelElement.childCount != 1) {
|
|
4264
|
+
return;
|
|
4265
|
+
}
|
|
4266
|
+
const firstModelChild = modelElement.getChild(0);
|
|
4267
|
+
if (firstModelChild.is('element', 'paragraph') && schema.checkAttribute(firstModelChild, EMPTY_BLOCK_MODEL_ATTRIBUTE)) {
|
|
4268
|
+
conversionApi.writer.setAttribute(EMPTY_BLOCK_MODEL_ATTRIBUTE, true, firstModelChild);
|
|
4269
|
+
}
|
|
4270
|
+
}, {
|
|
4271
|
+
priority: 'lowest'
|
|
4272
|
+
});
|
|
4273
|
+
};
|
|
4274
|
+
}
|
|
4275
|
+
|
|
4276
|
+
export { DataFilter, DataSchema, EmptyBlock, FullPage, GeneralHtmlSupport, HtmlComment, HtmlPageDataProcessor };
|
|
4040
4277
|
//# sourceMappingURL=index.js.map
|