@ckeditor/ckeditor5-html-support 34.0.0 → 34.1.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/LICENSE.md +2 -2
- package/build/html-support.js +1 -1
- package/build/translations/lv.js +1 -0
- package/build/translations/ur.js +1 -0
- package/lang/translations/lv.po +21 -0
- package/lang/translations/ur.po +21 -0
- package/package.json +30 -30
- package/src/converters.js +16 -5
- package/src/datafilter.js +125 -11
- package/src/dataschema.js +3 -0
- package/src/generalhtmlsupport.js +4 -4
- package/src/integrations/codeblock.js +1 -1
- package/src/integrations/documentlist.js +3 -3
- package/src/integrations/dualcontent.js +12 -3
- package/src/integrations/image.js +29 -10
- package/src/integrations/mediaembed.js +29 -6
- package/src/integrations/table.js +29 -7
- package/src/schemadefinitions.js +11 -0
- package/build/html-support.js.map +0 -1
package/src/datafilter.js
CHANGED
|
@@ -53,6 +53,9 @@ import '../theme/datafilter.css';
|
|
|
53
53
|
* }
|
|
54
54
|
* } );
|
|
55
55
|
*
|
|
56
|
+
* To apply the information about allowed and disallowed attributes in custom integration plugin,
|
|
57
|
+
* use the {@link module:html-support/datafilter~DataFilter#processViewAttributes `processViewAttributes()`} method.
|
|
58
|
+
*
|
|
56
59
|
* @extends module:core/plugin~Plugin
|
|
57
60
|
*/
|
|
58
61
|
export default class DataFilter extends Plugin {
|
|
@@ -106,8 +109,18 @@ export default class DataFilter extends Plugin {
|
|
|
106
109
|
*/
|
|
107
110
|
this._dataInitialized = false;
|
|
108
111
|
|
|
112
|
+
/**
|
|
113
|
+
* Cached map of coupled attributes. Keys are the feature attributes names
|
|
114
|
+
* and values are arrays with coupled GHS attributes names.
|
|
115
|
+
*
|
|
116
|
+
* @private
|
|
117
|
+
* @member {Map.<String,Array>}
|
|
118
|
+
*/
|
|
119
|
+
this._coupledAttributes = null;
|
|
120
|
+
|
|
109
121
|
this._registerElementsAfterInit();
|
|
110
122
|
this._registerElementHandlers();
|
|
123
|
+
this._registerModelPostFixer();
|
|
111
124
|
}
|
|
112
125
|
|
|
113
126
|
/**
|
|
@@ -167,6 +180,9 @@ export default class DataFilter extends Plugin {
|
|
|
167
180
|
if ( this._dataInitialized ) {
|
|
168
181
|
this._fireRegisterEvent( definition );
|
|
169
182
|
}
|
|
183
|
+
|
|
184
|
+
// Reset cached map to recalculate it on the next usage.
|
|
185
|
+
this._coupledAttributes = null;
|
|
170
186
|
}
|
|
171
187
|
}
|
|
172
188
|
|
|
@@ -208,17 +224,30 @@ export default class DataFilter extends Plugin {
|
|
|
208
224
|
}
|
|
209
225
|
|
|
210
226
|
/**
|
|
211
|
-
*
|
|
227
|
+
* Processes all allowed and disallowed attributes on the view element by consuming them and returning the allowed ones.
|
|
228
|
+
*
|
|
229
|
+
* This method applies the configuration set up by {@link #allowAttributes `allowAttributes()`}
|
|
230
|
+
* and {@link #disallowAttributes `disallowAttributes()`} over the given view element by consuming relevant attributes.
|
|
231
|
+
* It returns the allowed attributes that were found on the given view element for further processing by integration code.
|
|
232
|
+
*
|
|
233
|
+
* dispatcher.on( 'element:myElement', ( evt, data, conversionApi ) => {
|
|
234
|
+
* // Get rid of disallowed and extract all allowed attributes from a viewElement.
|
|
235
|
+
* const viewAttributes = dataFilter.processViewAttributes( data.viewItem, conversionApi );
|
|
236
|
+
* // Do something with them, i.e. store inside a model as a dictionary.
|
|
237
|
+
* if ( viewAttributes ) {
|
|
238
|
+
* conversionApi.writer.setAttribute( 'htmlAttributesOfMyElement', viewAttributes, data.modelRange );
|
|
239
|
+
* }
|
|
240
|
+
* } );
|
|
212
241
|
*
|
|
213
|
-
* @
|
|
242
|
+
* @see module:engine/conversion/viewconsumable~ViewConsumable#consume
|
|
214
243
|
* @param {module:engine/view/element~Element} viewElement
|
|
215
|
-
* @param {module:engine/conversion/
|
|
244
|
+
* @param {module:engine/conversion/upcastdispatcher~UpcastConversionApi} conversionApi
|
|
216
245
|
* @returns {Object} [result]
|
|
217
246
|
* @returns {Object} result.attributes Set with matched attribute names.
|
|
218
247
|
* @returns {Object} result.styles Set with matched style names.
|
|
219
248
|
* @returns {Array.<String>} result.classes Set with matched class names.
|
|
220
249
|
*/
|
|
221
|
-
|
|
250
|
+
processViewAttributes( viewElement, conversionApi ) {
|
|
222
251
|
// Make sure that the disabled attributes are handled before the allowed attributes are called.
|
|
223
252
|
// For example, for block images the <figure> converter triggers conversion for <img> first and then for other elements, i.e. <a>.
|
|
224
253
|
consumeAttributes( viewElement, conversionApi, this._disallowedAttributes );
|
|
@@ -288,6 +317,91 @@ export default class DataFilter extends Plugin {
|
|
|
288
317
|
}, { priority: 'lowest' } );
|
|
289
318
|
}
|
|
290
319
|
|
|
320
|
+
/**
|
|
321
|
+
* Registers a model post-fixer that is removing coupled GHS attributes of inline elements. Those attributes
|
|
322
|
+
* are removed if a coupled feature attribute is removed.
|
|
323
|
+
*
|
|
324
|
+
* For example, consider following HTML:
|
|
325
|
+
*
|
|
326
|
+
* <a href="foo.html" id="myId">bar</a>
|
|
327
|
+
*
|
|
328
|
+
* Which would be upcasted to following text node in the model:
|
|
329
|
+
*
|
|
330
|
+
* <$text linkHref="foo.html" htmlA="{ attributes: { id: 'myId' } }">bar</$text>
|
|
331
|
+
*
|
|
332
|
+
* When the user removes the link from that text (using UI), only `linkHref` attribute would be removed:
|
|
333
|
+
*
|
|
334
|
+
* <$text htmlA="{ attributes: { id: 'myId' } }">bar</$text>
|
|
335
|
+
*
|
|
336
|
+
* The `htmlA` attribute would stay in the model and would cause GHS to generate an `<a>` element.
|
|
337
|
+
* This is incorrect from UX point of view, as the user wanted to remove the whole link (not only `href`).
|
|
338
|
+
*
|
|
339
|
+
* @private
|
|
340
|
+
*/
|
|
341
|
+
_registerModelPostFixer() {
|
|
342
|
+
const model = this.editor.model;
|
|
343
|
+
|
|
344
|
+
model.document.registerPostFixer( writer => {
|
|
345
|
+
const changes = model.document.differ.getChanges();
|
|
346
|
+
let changed = false;
|
|
347
|
+
|
|
348
|
+
const coupledAttributes = this._getCoupledAttributesMap();
|
|
349
|
+
|
|
350
|
+
for ( const change of changes ) {
|
|
351
|
+
// Handle only attribute removals.
|
|
352
|
+
if ( change.type != 'attribute' || change.attributeNewValue !== null ) {
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Find a list of coupled GHS attributes.
|
|
357
|
+
const attributeKeys = coupledAttributes.get( change.attributeKey );
|
|
358
|
+
|
|
359
|
+
if ( !attributeKeys ) {
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Remove the coupled GHS attributes on the same range as the feature attribute was removed.
|
|
364
|
+
for ( const { item } of change.range.getWalker( { shallow: true } ) ) {
|
|
365
|
+
for ( const attributeKey of attributeKeys ) {
|
|
366
|
+
if ( item.hasAttribute( attributeKey ) ) {
|
|
367
|
+
writer.removeAttribute( attributeKey, item );
|
|
368
|
+
changed = true;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return changed;
|
|
375
|
+
} );
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Collects the map of coupled attributes. The returned map is keyed by the feature attribute name
|
|
380
|
+
* and coupled GHS attribute names are stored in the value array .
|
|
381
|
+
*
|
|
382
|
+
* @private
|
|
383
|
+
* @returns {Map.<String,Array>}
|
|
384
|
+
*/
|
|
385
|
+
_getCoupledAttributesMap() {
|
|
386
|
+
if ( this._coupledAttributes ) {
|
|
387
|
+
return this._coupledAttributes;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
this._coupledAttributes = new Map();
|
|
391
|
+
|
|
392
|
+
for ( const definition of this._allowedElements ) {
|
|
393
|
+
if ( definition.coupledAttribute && definition.model ) {
|
|
394
|
+
const attributeNames = this._coupledAttributes.get( definition.coupledAttribute );
|
|
395
|
+
|
|
396
|
+
if ( attributeNames ) {
|
|
397
|
+
attributeNames.push( definition.model );
|
|
398
|
+
} else {
|
|
399
|
+
this._coupledAttributes.set( definition.coupledAttribute, [ definition.model ] );
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
291
405
|
/**
|
|
292
406
|
* Fires `register` event for the given element definition.
|
|
293
407
|
*
|
|
@@ -462,7 +576,7 @@ export default class DataFilter extends Plugin {
|
|
|
462
576
|
//
|
|
463
577
|
// @private
|
|
464
578
|
// @param {module:engine/view/element~Element} viewElement
|
|
465
|
-
// @param {module:engine/conversion/
|
|
579
|
+
// @param {module:engine/conversion/upcastdispatcher~UpcastConversionApi} conversionApi
|
|
466
580
|
// @param {module:engine/view/matcher~Matcher Matcher} matcher
|
|
467
581
|
// @returns {Object} [result]
|
|
468
582
|
// @returns {Object} result.attributes
|
|
@@ -496,7 +610,7 @@ function consumeAttributes( viewElement, conversionApi, matcher ) {
|
|
|
496
610
|
//
|
|
497
611
|
// @private
|
|
498
612
|
// @param {module:engine/view/element~Element} viewElement
|
|
499
|
-
// @param {module:engine/conversion/
|
|
613
|
+
// @param {module:engine/conversion/upcastdispatcher~UpcastConversionApi} conversionApi
|
|
500
614
|
// @param {module:engine/view/matcher~Matcher Matcher} matcher
|
|
501
615
|
// @returns {Array.<Object>} Array with match information about found attributes.
|
|
502
616
|
function consumeAttributeMatches( viewElement, { consumable }, matcher ) {
|
|
@@ -509,9 +623,8 @@ function consumeAttributeMatches( viewElement, { consumable }, matcher ) {
|
|
|
509
623
|
// We only want to consume attributes, so element can be still processed by other converters.
|
|
510
624
|
delete match.match.name;
|
|
511
625
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
}
|
|
626
|
+
consumable.consume( viewElement, match.match );
|
|
627
|
+
consumedMatches.push( match );
|
|
515
628
|
}
|
|
516
629
|
|
|
517
630
|
return consumedMatches;
|
|
@@ -521,7 +634,7 @@ function consumeAttributeMatches( viewElement, { consumable }, matcher ) {
|
|
|
521
634
|
//
|
|
522
635
|
// @private
|
|
523
636
|
// @param {module:engine/view/element~Element} viewElement
|
|
524
|
-
// @param {module:engine/conversion/
|
|
637
|
+
// @param {module:engine/conversion/viewconsumable~ViewConsumable} consumable
|
|
525
638
|
// @param {Object} match
|
|
526
639
|
function removeConsumedAttributes( consumable, viewElement, match ) {
|
|
527
640
|
for ( const key of [ 'attributes', 'classes', 'styles' ] ) {
|
|
@@ -531,7 +644,8 @@ function removeConsumedAttributes( consumable, viewElement, match ) {
|
|
|
531
644
|
continue;
|
|
532
645
|
}
|
|
533
646
|
|
|
534
|
-
|
|
647
|
+
// Iterating over a copy of an array so removing items doesn't influence iteration.
|
|
648
|
+
for ( const value of Array.from( attributes ) ) {
|
|
535
649
|
if ( !consumable.test( viewElement, ( { [ key ]: [ value ] } ) ) ) {
|
|
536
650
|
removeItemFromArray( attributes, value );
|
|
537
651
|
}
|
package/src/dataschema.js
CHANGED
|
@@ -251,5 +251,8 @@ function testViewName( pattern, viewName ) {
|
|
|
251
251
|
* @property {Number} [priority] Element priority. Decides in what order elements are wrapped by
|
|
252
252
|
* {@link module:engine/view/downcastwriter~DowncastWriter}.
|
|
253
253
|
* Set by {@link module:html-support/dataschema~DataSchema#registerInlineElement} method.
|
|
254
|
+
* @property {String} [coupledAttribute] The name of the model attribute that generates the same view element. GHS inline attribute
|
|
255
|
+
* will be removed from the model tree as soon as the coupled attribute is removed. See
|
|
256
|
+
* {@link module:html-support/datafilter~DataFilter#_registerModelPostFixer GHS post-fixer} for more details.
|
|
254
257
|
* @extends module:html-support/dataschema~DataSchemaDefinition
|
|
255
258
|
*/
|
|
@@ -86,7 +86,7 @@ export default class GeneralHtmlSupport extends Plugin {
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
/**
|
|
89
|
-
* Updates GHS model attribute for a specified view element name, so it includes
|
|
89
|
+
* Updates GHS model attribute for a specified view element name, so it includes the given class name.
|
|
90
90
|
*
|
|
91
91
|
* @protected
|
|
92
92
|
* @param {String} viewElementName A view element name.
|
|
@@ -109,7 +109,7 @@ export default class GeneralHtmlSupport extends Plugin {
|
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
/**
|
|
112
|
-
* Updates GHS model attribute for a specified view element name, so it does not include
|
|
112
|
+
* Updates GHS model attribute for a specified view element name, so it does not include the given class name.
|
|
113
113
|
*
|
|
114
114
|
* @protected
|
|
115
115
|
* @param {String} viewElementName A view element name.
|
|
@@ -132,7 +132,7 @@ export default class GeneralHtmlSupport extends Plugin {
|
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
/**
|
|
135
|
-
* Updates GHS model attribute for a specified view element name, so it includes
|
|
135
|
+
* Updates GHS model attribute for a specified view element name, so it includes the given attribute.
|
|
136
136
|
*
|
|
137
137
|
* @protected
|
|
138
138
|
* @param {String} viewElementName A view element name.
|
|
@@ -155,7 +155,7 @@ export default class GeneralHtmlSupport extends Plugin {
|
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
/**
|
|
158
|
-
* Updates GHS model attribute for a specified view element name, so it does not include
|
|
158
|
+
* Updates GHS model attribute for a specified view element name, so it does not include the given attribute.
|
|
159
159
|
*
|
|
160
160
|
* @protected
|
|
161
161
|
* @param {String} viewElementName A view element name.
|
|
@@ -79,7 +79,7 @@ function viewToModelCodeBlockAttributeConverter( dataFilter ) {
|
|
|
79
79
|
preserveElementAttributes( viewCodeElement, 'htmlContentAttributes' );
|
|
80
80
|
|
|
81
81
|
function preserveElementAttributes( viewElement, attributeName ) {
|
|
82
|
-
const viewAttributes = dataFilter.
|
|
82
|
+
const viewAttributes = dataFilter.processViewAttributes( viewElement, conversionApi );
|
|
83
83
|
|
|
84
84
|
if ( viewAttributes ) {
|
|
85
85
|
conversionApi.writer.setAttribute( attributeName, viewAttributes, data.modelRange );
|
|
@@ -14,7 +14,7 @@ import { setViewAttributes } from '../conversionutils.js';
|
|
|
14
14
|
import DataFilter from '../datafilter';
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
* Provides the General HTML Support integration with {@link module:list/documentlist~DocumentList Document List} feature.
|
|
17
|
+
* Provides the General HTML Support integration with the {@link module:list/documentlist~DocumentList Document List} feature.
|
|
18
18
|
*
|
|
19
19
|
* @extends module:core/plugin~Plugin
|
|
20
20
|
*/
|
|
@@ -85,7 +85,7 @@ export default class DocumentListElementSupport extends Plugin {
|
|
|
85
85
|
} );
|
|
86
86
|
|
|
87
87
|
// Make sure that all items in a single list (items at the same level & listType) have the same properties.
|
|
88
|
-
// Note: This is almost exact copy from DocumentListPropertiesEditing.
|
|
88
|
+
// Note: This is almost an exact copy from DocumentListPropertiesEditing.
|
|
89
89
|
documentListEditing.on( 'postFixer', ( evt, { listNodes, writer } ) => {
|
|
90
90
|
const previousNodesByIndent = []; // Last seen nodes of lower indented lists.
|
|
91
91
|
|
|
@@ -180,7 +180,7 @@ function viewToModelListAttributeConverter( attributeName, dataFilter ) {
|
|
|
180
180
|
Object.assign( data, conversionApi.convertChildren( data.viewItem, data.modelCursor ) );
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
-
const viewAttributes = dataFilter.
|
|
183
|
+
const viewAttributes = dataFilter.processViewAttributes( viewElement, conversionApi );
|
|
184
184
|
|
|
185
185
|
for ( const item of data.modelRange.getItems( { shallow: true } ) ) {
|
|
186
186
|
// Apply only to list item blocks.
|
|
@@ -111,10 +111,19 @@ export default class DualContentModelElementSupport extends Plugin {
|
|
|
111
111
|
* @returns {Boolean}
|
|
112
112
|
*/
|
|
113
113
|
_hasBlockContent( viewElement ) {
|
|
114
|
-
const
|
|
114
|
+
const view = this.editor.editing.view;
|
|
115
|
+
const blockElements = view.domConverter.blockElements;
|
|
116
|
+
|
|
117
|
+
// Traversing the viewElement subtree looking for block elements.
|
|
118
|
+
// Especially for the cases like <div><a href="#"><p>foo</p></a></div>.
|
|
119
|
+
// https://github.com/ckeditor/ckeditor5/issues/11513
|
|
120
|
+
for ( const viewItem of view.createRangeIn( viewElement ).getItems() ) {
|
|
121
|
+
if ( viewItem.is( 'element' ) && blockElements.includes( viewItem.name ) ) {
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
115
125
|
|
|
116
|
-
return
|
|
117
|
-
.some( node => blockElements.includes( node.name ) );
|
|
126
|
+
return false;
|
|
118
127
|
}
|
|
119
128
|
|
|
120
129
|
/**
|
|
@@ -43,6 +43,10 @@ export default class ImageElementSupport extends Plugin {
|
|
|
43
43
|
const conversion = editor.conversion;
|
|
44
44
|
const dataFilter = editor.plugins.get( DataFilter );
|
|
45
45
|
|
|
46
|
+
dataFilter.on( 'register:figure', () => {
|
|
47
|
+
conversion.for( 'upcast' ).add( viewToModelFigureAttributeConverter( dataFilter ) );
|
|
48
|
+
} );
|
|
49
|
+
|
|
46
50
|
dataFilter.on( 'register:img', ( evt, definition ) => {
|
|
47
51
|
if ( definition.model !== 'imageBlock' && definition.model !== 'imageInline' ) {
|
|
48
52
|
return;
|
|
@@ -96,31 +100,46 @@ function viewToModelImageAttributeConverter( dataFilter ) {
|
|
|
96
100
|
|
|
97
101
|
preserveElementAttributes( viewImageElement, 'htmlAttributes' );
|
|
98
102
|
|
|
99
|
-
if ( viewContainerElement.is( 'element', '
|
|
100
|
-
preserveElementAttributes( viewContainerElement, 'htmlFigureAttributes' );
|
|
101
|
-
} else if ( viewContainerElement.is( 'element', 'a' ) ) {
|
|
103
|
+
if ( viewContainerElement.is( 'element', 'a' ) ) {
|
|
102
104
|
preserveLinkAttributes( viewContainerElement );
|
|
103
105
|
}
|
|
104
106
|
|
|
105
107
|
function preserveElementAttributes( viewElement, attributeName ) {
|
|
106
|
-
const viewAttributes = dataFilter.
|
|
108
|
+
const viewAttributes = dataFilter.processViewAttributes( viewElement, conversionApi );
|
|
107
109
|
|
|
108
110
|
if ( viewAttributes ) {
|
|
109
111
|
conversionApi.writer.setAttribute( attributeName, viewAttributes, data.modelRange );
|
|
110
112
|
}
|
|
111
113
|
}
|
|
112
114
|
|
|
113
|
-
// For a block image, we want to preserve the attributes on our own.
|
|
114
|
-
// The inline image attributes will be handled by the GHS automatically.
|
|
115
115
|
function preserveLinkAttributes( viewContainerElement ) {
|
|
116
116
|
if ( data.modelRange && data.modelRange.getContainedElement().is( 'element', 'imageBlock' ) ) {
|
|
117
117
|
preserveElementAttributes( viewContainerElement, 'htmlLinkAttributes' );
|
|
118
118
|
}
|
|
119
|
+
}
|
|
120
|
+
}, { priority: 'low' } );
|
|
121
|
+
};
|
|
122
|
+
}
|
|
119
123
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
+
// View-to-model conversion helper preserving allowed attributes on {@link module:image/image~Image Image}
|
|
125
|
+
// feature model element from figure view element.
|
|
126
|
+
//
|
|
127
|
+
// @private
|
|
128
|
+
// @param {module:html-support/datafilter~DataFilter} dataFilter
|
|
129
|
+
// @returns {Function} Returns a conversion callback.
|
|
130
|
+
function viewToModelFigureAttributeConverter( dataFilter ) {
|
|
131
|
+
return dispatcher => {
|
|
132
|
+
dispatcher.on( 'element:figure', ( evt, data, conversionApi ) => {
|
|
133
|
+
const viewFigureElement = data.viewItem;
|
|
134
|
+
|
|
135
|
+
if ( !data.modelRange || !viewFigureElement.hasClass( 'image' ) ) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const viewAttributes = dataFilter.processViewAttributes( viewFigureElement, conversionApi );
|
|
140
|
+
|
|
141
|
+
if ( viewAttributes ) {
|
|
142
|
+
conversionApi.writer.setAttribute( 'htmlFigureAttributes', viewAttributes, data.modelRange );
|
|
124
143
|
}
|
|
125
144
|
}, { priority: 'low' } );
|
|
126
145
|
};
|
|
@@ -44,6 +44,10 @@ export default class MediaEmbedElementSupport extends Plugin {
|
|
|
44
44
|
view: mediaElementName
|
|
45
45
|
} );
|
|
46
46
|
|
|
47
|
+
dataFilter.on( 'register:figure', ( ) => {
|
|
48
|
+
conversion.for( 'upcast' ).add( viewToModelFigureAttributesConverter( dataFilter ) );
|
|
49
|
+
} );
|
|
50
|
+
|
|
47
51
|
dataFilter.on( `register:${ mediaElementName }`, ( evt, definition ) => {
|
|
48
52
|
if ( definition.model !== 'media' ) {
|
|
49
53
|
return;
|
|
@@ -71,16 +75,11 @@ function viewToModelMediaAttributesConverter( dataFilter, mediaElementName ) {
|
|
|
71
75
|
|
|
72
76
|
function upcastMedia( evt, data, conversionApi ) {
|
|
73
77
|
const viewMediaElement = data.viewItem;
|
|
74
|
-
const viewParent = viewMediaElement.parent;
|
|
75
78
|
|
|
76
79
|
preserveElementAttributes( viewMediaElement, 'htmlAttributes' );
|
|
77
80
|
|
|
78
|
-
if ( viewParent.is( 'element', 'figure' ) && viewParent.hasClass( 'media' ) ) {
|
|
79
|
-
preserveElementAttributes( viewParent, 'htmlFigureAttributes' );
|
|
80
|
-
}
|
|
81
|
-
|
|
82
81
|
function preserveElementAttributes( viewElement, attributeName ) {
|
|
83
|
-
const viewAttributes = dataFilter.
|
|
82
|
+
const viewAttributes = dataFilter.processViewAttributes( viewElement, conversionApi );
|
|
84
83
|
|
|
85
84
|
if ( viewAttributes ) {
|
|
86
85
|
conversionApi.writer.setAttribute( attributeName, viewAttributes, data.modelRange );
|
|
@@ -89,6 +88,30 @@ function viewToModelMediaAttributesConverter( dataFilter, mediaElementName ) {
|
|
|
89
88
|
}
|
|
90
89
|
}
|
|
91
90
|
|
|
91
|
+
// View-to-model conversion helper preserving allowed attributes on {@link module:media-embed/mediaembed~MediaEmbed MediaEmbed}
|
|
92
|
+
// feature model element from figure view element.
|
|
93
|
+
//
|
|
94
|
+
// @private
|
|
95
|
+
// @param {module:html-support/datafilter~DataFilter} dataFilter
|
|
96
|
+
// @returns {Function} Returns a conversion callback.
|
|
97
|
+
function viewToModelFigureAttributesConverter( dataFilter ) {
|
|
98
|
+
return dispatcher => {
|
|
99
|
+
dispatcher.on( 'element:figure', ( evt, data, conversionApi ) => {
|
|
100
|
+
const viewFigureElement = data.viewItem;
|
|
101
|
+
|
|
102
|
+
if ( !data.modelRange || !viewFigureElement.hasClass( 'media' ) ) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const viewAttributes = dataFilter.processViewAttributes( viewFigureElement, conversionApi );
|
|
107
|
+
|
|
108
|
+
if ( viewAttributes ) {
|
|
109
|
+
conversionApi.writer.setAttribute( 'htmlFigureAttributes', viewAttributes, data.modelRange );
|
|
110
|
+
}
|
|
111
|
+
}, { priority: 'low' } );
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
92
115
|
function modelToViewMediaAttributeConverter( mediaElementName ) {
|
|
93
116
|
return dispatcher => {
|
|
94
117
|
addAttributeConversionDispatcherHandler( mediaElementName, 'htmlAttributes' );
|
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
|
|
10
10
|
import { Plugin } from 'ckeditor5/src/core';
|
|
11
11
|
import { setViewAttributes } from '../conversionutils.js';
|
|
12
|
-
|
|
13
12
|
import DataFilter from '../datafilter';
|
|
14
13
|
|
|
15
14
|
/**
|
|
@@ -39,6 +38,10 @@ export default class TableElementSupport extends Plugin {
|
|
|
39
38
|
const conversion = editor.conversion;
|
|
40
39
|
const dataFilter = editor.plugins.get( DataFilter );
|
|
41
40
|
|
|
41
|
+
dataFilter.on( 'register:figure', ( ) => {
|
|
42
|
+
conversion.for( 'upcast' ).add( viewToModelFigureAttributeConverter( dataFilter ) );
|
|
43
|
+
} );
|
|
44
|
+
|
|
42
45
|
dataFilter.on( 'register:table', ( evt, definition ) => {
|
|
43
46
|
if ( definition.model !== 'table' ) {
|
|
44
47
|
return;
|
|
@@ -74,11 +77,6 @@ function viewToModelTableAttributeConverter( dataFilter ) {
|
|
|
74
77
|
|
|
75
78
|
preserveElementAttributes( viewTableElement, 'htmlAttributes' );
|
|
76
79
|
|
|
77
|
-
const viewFigureElement = viewTableElement.parent;
|
|
78
|
-
if ( viewFigureElement.is( 'element', 'figure' ) ) {
|
|
79
|
-
preserveElementAttributes( viewFigureElement, 'htmlFigureAttributes' );
|
|
80
|
-
}
|
|
81
|
-
|
|
82
80
|
for ( const childNode of viewTableElement.getChildren() ) {
|
|
83
81
|
if ( childNode.is( 'element', 'thead' ) ) {
|
|
84
82
|
preserveElementAttributes( childNode, 'htmlTheadAttributes' );
|
|
@@ -90,12 +88,36 @@ function viewToModelTableAttributeConverter( dataFilter ) {
|
|
|
90
88
|
}
|
|
91
89
|
|
|
92
90
|
function preserveElementAttributes( viewElement, attributeName ) {
|
|
93
|
-
const viewAttributes = dataFilter.
|
|
91
|
+
const viewAttributes = dataFilter.processViewAttributes( viewElement, conversionApi );
|
|
94
92
|
|
|
95
93
|
if ( viewAttributes ) {
|
|
96
94
|
conversionApi.writer.setAttribute( attributeName, viewAttributes, data.modelRange );
|
|
97
95
|
}
|
|
98
96
|
}
|
|
97
|
+
} );
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// View-to-model conversion helper preserving allowed attributes on {@link module:table/table~Table Table}
|
|
102
|
+
// feature model element from figure view element.
|
|
103
|
+
//
|
|
104
|
+
// @private
|
|
105
|
+
// @param {module:html-support/datafilter~DataFilter} dataFilter
|
|
106
|
+
// @returns {Function} Returns a conversion callback.
|
|
107
|
+
function viewToModelFigureAttributeConverter( dataFilter ) {
|
|
108
|
+
return dispatcher => {
|
|
109
|
+
dispatcher.on( 'element:figure', ( evt, data, conversionApi ) => {
|
|
110
|
+
const viewFigureElement = data.viewItem;
|
|
111
|
+
|
|
112
|
+
if ( !data.modelRange || !viewFigureElement.hasClass( 'table' ) ) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const viewAttributes = dataFilter.processViewAttributes( viewFigureElement, conversionApi );
|
|
117
|
+
|
|
118
|
+
if ( viewAttributes ) {
|
|
119
|
+
conversionApi.writer.setAttribute( 'htmlFigureAttributes', viewAttributes, data.modelRange );
|
|
120
|
+
}
|
|
99
121
|
}, { priority: 'low' } );
|
|
100
122
|
};
|
|
101
123
|
}
|
package/src/schemadefinitions.js
CHANGED
|
@@ -577,6 +577,7 @@ export default {
|
|
|
577
577
|
model: 'htmlA',
|
|
578
578
|
view: 'a',
|
|
579
579
|
priority: 5,
|
|
580
|
+
coupledAttribute: 'linkHref',
|
|
580
581
|
attributeProperties: {
|
|
581
582
|
copyOnEnter: true
|
|
582
583
|
}
|
|
@@ -584,6 +585,7 @@ export default {
|
|
|
584
585
|
{
|
|
585
586
|
model: 'htmlStrong',
|
|
586
587
|
view: 'strong',
|
|
588
|
+
coupledAttribute: 'bold',
|
|
587
589
|
attributeProperties: {
|
|
588
590
|
copyOnEnter: true
|
|
589
591
|
}
|
|
@@ -591,6 +593,7 @@ export default {
|
|
|
591
593
|
{
|
|
592
594
|
model: 'htmlB',
|
|
593
595
|
view: 'b',
|
|
596
|
+
coupledAttribute: 'bold',
|
|
594
597
|
attributeProperties: {
|
|
595
598
|
copyOnEnter: true
|
|
596
599
|
}
|
|
@@ -598,6 +601,7 @@ export default {
|
|
|
598
601
|
{
|
|
599
602
|
model: 'htmlI',
|
|
600
603
|
view: 'i',
|
|
604
|
+
coupledAttribute: 'italic',
|
|
601
605
|
attributeProperties: {
|
|
602
606
|
copyOnEnter: true
|
|
603
607
|
}
|
|
@@ -605,6 +609,7 @@ export default {
|
|
|
605
609
|
{
|
|
606
610
|
model: 'htmlEm',
|
|
607
611
|
view: 'em',
|
|
612
|
+
coupledAttribute: 'italic',
|
|
608
613
|
attributeProperties: {
|
|
609
614
|
copyOnEnter: true
|
|
610
615
|
}
|
|
@@ -612,6 +617,7 @@ export default {
|
|
|
612
617
|
{
|
|
613
618
|
model: 'htmlS',
|
|
614
619
|
view: 's',
|
|
620
|
+
coupledAttribute: 'strikethrough',
|
|
615
621
|
attributeProperties: {
|
|
616
622
|
copyOnEnter: true
|
|
617
623
|
}
|
|
@@ -620,6 +626,7 @@ export default {
|
|
|
620
626
|
{
|
|
621
627
|
model: 'htmlDel',
|
|
622
628
|
view: 'del',
|
|
629
|
+
coupledAttribute: 'strikethrough',
|
|
623
630
|
attributeProperties: {
|
|
624
631
|
copyOnEnter: true
|
|
625
632
|
}
|
|
@@ -635,6 +642,7 @@ export default {
|
|
|
635
642
|
{
|
|
636
643
|
model: 'htmlU',
|
|
637
644
|
view: 'u',
|
|
645
|
+
coupledAttribute: 'underline',
|
|
638
646
|
attributeProperties: {
|
|
639
647
|
copyOnEnter: true
|
|
640
648
|
}
|
|
@@ -642,6 +650,7 @@ export default {
|
|
|
642
650
|
{
|
|
643
651
|
model: 'htmlSub',
|
|
644
652
|
view: 'sub',
|
|
653
|
+
coupledAttribute: 'subscript',
|
|
645
654
|
attributeProperties: {
|
|
646
655
|
copyOnEnter: true
|
|
647
656
|
}
|
|
@@ -649,6 +658,7 @@ export default {
|
|
|
649
658
|
{
|
|
650
659
|
model: 'htmlSup',
|
|
651
660
|
view: 'sup',
|
|
661
|
+
coupledAttribute: 'superscript',
|
|
652
662
|
attributeProperties: {
|
|
653
663
|
copyOnEnter: true
|
|
654
664
|
}
|
|
@@ -656,6 +666,7 @@ export default {
|
|
|
656
666
|
{
|
|
657
667
|
model: 'htmlCode',
|
|
658
668
|
view: 'code',
|
|
669
|
+
coupledAttribute: 'code',
|
|
659
670
|
attributeProperties: {
|
|
660
671
|
copyOnEnter: true
|
|
661
672
|
}
|