@ckeditor/ckeditor5-link 29.0.0 → 31.0.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.
Files changed (66) hide show
  1. package/LICENSE.md +1 -1
  2. package/README.md +4 -0
  3. package/build/link.js +1 -1
  4. package/build/translations/ar.js +1 -0
  5. package/build/translations/ast.js +1 -0
  6. package/build/translations/az.js +1 -0
  7. package/build/translations/bg.js +1 -0
  8. package/build/translations/cs.js +1 -0
  9. package/build/translations/da.js +1 -0
  10. package/build/translations/de-ch.js +1 -0
  11. package/build/translations/de.js +1 -0
  12. package/build/translations/el.js +1 -0
  13. package/build/translations/en-au.js +1 -0
  14. package/build/translations/en-gb.js +1 -0
  15. package/build/translations/eo.js +1 -0
  16. package/build/translations/es.js +1 -0
  17. package/build/translations/et.js +1 -0
  18. package/build/translations/eu.js +1 -0
  19. package/build/translations/fa.js +1 -0
  20. package/build/translations/fi.js +1 -0
  21. package/build/translations/fr.js +1 -0
  22. package/build/translations/gl.js +1 -0
  23. package/build/translations/he.js +1 -0
  24. package/build/translations/hi.js +1 -0
  25. package/build/translations/hr.js +1 -0
  26. package/build/translations/hu.js +1 -0
  27. package/build/translations/id.js +1 -0
  28. package/build/translations/it.js +1 -0
  29. package/build/translations/ja.js +1 -0
  30. package/build/translations/km.js +1 -0
  31. package/build/translations/kn.js +1 -0
  32. package/build/translations/ko.js +1 -0
  33. package/build/translations/ku.js +1 -0
  34. package/build/translations/lt.js +1 -0
  35. package/build/translations/lv.js +1 -0
  36. package/build/translations/nb.js +1 -0
  37. package/build/translations/ne.js +1 -0
  38. package/build/translations/nl.js +1 -0
  39. package/build/translations/no.js +1 -0
  40. package/build/translations/pl.js +1 -0
  41. package/build/translations/pt-br.js +1 -0
  42. package/build/translations/pt.js +1 -0
  43. package/build/translations/ro.js +1 -0
  44. package/build/translations/ru.js +1 -0
  45. package/build/translations/sk.js +1 -0
  46. package/build/translations/sq.js +1 -0
  47. package/build/translations/sr-latn.js +1 -0
  48. package/build/translations/sr.js +1 -0
  49. package/build/translations/sv.js +1 -0
  50. package/build/translations/tk.js +1 -0
  51. package/build/translations/tr.js +1 -0
  52. package/build/translations/ug.js +1 -0
  53. package/build/translations/uk.js +1 -0
  54. package/build/translations/vi.js +1 -0
  55. package/build/translations/zh-cn.js +1 -0
  56. package/build/translations/zh.js +1 -0
  57. package/ckeditor5-metadata.json +76 -0
  58. package/package.json +25 -23
  59. package/src/autolink.js +13 -1
  60. package/src/link.js +14 -2
  61. package/src/linkediting.js +15 -5
  62. package/src/linkimageediting.js +62 -56
  63. package/src/utils/automaticdecorators.js +33 -6
  64. package/src/utils/manualdecorator.js +31 -1
  65. package/CHANGELOG.md +0 -313
  66. package/build/link.js.map +0 -1
package/package.json CHANGED
@@ -1,39 +1,40 @@
1
1
  {
2
2
  "name": "@ckeditor/ckeditor5-link",
3
- "version": "29.0.0",
3
+ "version": "31.0.0",
4
4
  "description": "Link feature for CKEditor 5.",
5
5
  "keywords": [
6
6
  "ckeditor",
7
7
  "ckeditor5",
8
8
  "ckeditor 5",
9
9
  "ckeditor5-feature",
10
- "ckeditor5-plugin"
10
+ "ckeditor5-plugin",
11
+ "ckeditor5-dll"
11
12
  ],
12
13
  "main": "src/index.js",
13
14
  "dependencies": {
14
- "@ckeditor/ckeditor5-ui": "^29.0.0",
15
- "ckeditor5": "^29.0.0",
15
+ "@ckeditor/ckeditor5-ui": "^31.0.0",
16
+ "ckeditor5": "^31.0.0",
16
17
  "lodash-es": "^4.17.15"
17
18
  },
18
19
  "devDependencies": {
19
- "@ckeditor/ckeditor5-basic-styles": "^29.0.0",
20
- "@ckeditor/ckeditor5-block-quote": "^29.0.0",
21
- "@ckeditor/ckeditor5-clipboard": "^29.0.0",
22
- "@ckeditor/ckeditor5-cloud-services": "^29.0.0",
23
- "@ckeditor/ckeditor5-code-block": "^29.0.0",
24
- "@ckeditor/ckeditor5-core": "^29.0.0",
25
- "@ckeditor/ckeditor5-dev-utils": "^25.0.0",
26
- "@ckeditor/ckeditor5-easy-image": "^29.0.0",
27
- "@ckeditor/ckeditor5-editor-classic": "^29.0.0",
28
- "@ckeditor/ckeditor5-engine": "^29.0.0",
29
- "@ckeditor/ckeditor5-enter": "^29.0.0",
30
- "@ckeditor/ckeditor5-image": "^29.0.0",
31
- "@ckeditor/ckeditor5-paragraph": "^29.0.0",
32
- "@ckeditor/ckeditor5-theme-lark": "^29.0.0",
33
- "@ckeditor/ckeditor5-typing": "^29.0.0",
34
- "@ckeditor/ckeditor5-undo": "^29.0.0",
35
- "@ckeditor/ckeditor5-utils": "^29.0.0",
36
- "@ckeditor/ckeditor5-widget": "^29.0.0",
20
+ "@ckeditor/ckeditor5-basic-styles": "^31.0.0",
21
+ "@ckeditor/ckeditor5-block-quote": "^31.0.0",
22
+ "@ckeditor/ckeditor5-clipboard": "^31.0.0",
23
+ "@ckeditor/ckeditor5-cloud-services": "^31.0.0",
24
+ "@ckeditor/ckeditor5-code-block": "^31.0.0",
25
+ "@ckeditor/ckeditor5-core": "^31.0.0",
26
+ "@ckeditor/ckeditor5-dev-utils": "^25.4.0",
27
+ "@ckeditor/ckeditor5-easy-image": "^31.0.0",
28
+ "@ckeditor/ckeditor5-editor-classic": "^31.0.0",
29
+ "@ckeditor/ckeditor5-engine": "^31.0.0",
30
+ "@ckeditor/ckeditor5-enter": "^31.0.0",
31
+ "@ckeditor/ckeditor5-image": "^31.0.0",
32
+ "@ckeditor/ckeditor5-paragraph": "^31.0.0",
33
+ "@ckeditor/ckeditor5-theme-lark": "^31.0.0",
34
+ "@ckeditor/ckeditor5-typing": "^31.0.0",
35
+ "@ckeditor/ckeditor5-undo": "^31.0.0",
36
+ "@ckeditor/ckeditor5-utils": "^31.0.0",
37
+ "@ckeditor/ckeditor5-widget": "^31.0.0",
37
38
  "webpack": "^4.43.0",
38
39
  "webpack-cli": "^3.3.11"
39
40
  },
@@ -54,7 +55,8 @@
54
55
  "lang",
55
56
  "src",
56
57
  "theme",
57
- "build"
58
+ "build",
59
+ "ckeditor5-metadata.json"
58
60
  ],
59
61
  "scripts": {
60
62
  "dll:build": "webpack"
package/src/autolink.js CHANGED
@@ -8,7 +8,7 @@
8
8
  */
9
9
 
10
10
  import { Plugin } from 'ckeditor5/src/core';
11
- import { TextWatcher, getLastTextLine } from 'ckeditor5/src/typing';
11
+ import { Delete, TextWatcher, getLastTextLine } from 'ckeditor5/src/typing';
12
12
 
13
13
  import { addLinkProtocolIfApplicable } from './utils';
14
14
 
@@ -69,6 +69,13 @@ const URL_GROUP_IN_MATCH = 2;
69
69
  * @extends module:core/plugin~Plugin
70
70
  */
71
71
  export default class AutoLink extends Plugin {
72
+ /**
73
+ * @inheritDoc
74
+ */
75
+ static get requires() {
76
+ return [ Delete ];
77
+ }
78
+
72
79
  /**
73
80
  * @inheritDoc
74
81
  */
@@ -226,6 +233,7 @@ export default class AutoLink extends Plugin {
226
233
  */
227
234
  _applyAutoLink( link, range ) {
228
235
  const model = this.editor.model;
236
+ const deletePlugin = this.editor.plugins.get( 'Delete' );
229
237
 
230
238
  if ( !this.isEnabled || !isLinkAllowedOnRange( range, model ) ) {
231
239
  return;
@@ -236,6 +244,10 @@ export default class AutoLink extends Plugin {
236
244
  const defaultProtocol = this.editor.config.get( 'link.defaultProtocol' );
237
245
  const parsedUrl = addLinkProtocolIfApplicable( link, defaultProtocol );
238
246
  writer.setAttribute( 'linkHref', parsedUrl, range );
247
+
248
+ model.enqueueChange( () => {
249
+ deletePlugin.requestUndoOnBackspace();
250
+ } );
239
251
  } );
240
252
  }
241
253
  }
package/src/link.js CHANGED
@@ -215,9 +215,15 @@ export default class Link extends Plugin {
215
215
  * @typedef {Object} module:link/link~LinkDecoratorAutomaticDefinition
216
216
  * @property {'automatic'} mode Link decorator type. It is `'automatic'` for all automatic decorators.
217
217
  * @property {Function} callback Takes a `url` as a parameter and returns `true` if the `attributes` should be applied to the link.
218
- * @property {Object} attributes Key-value pairs used as link attributes added to the output during the
218
+ * @property {Object} [attributes] Key-value pairs used as link attributes added to the output during the
219
219
  * {@glink framework/guides/architecture/editing-engine#conversion downcasting}.
220
220
  * Attributes should follow the {@link module:engine/view/elementdefinition~ElementDefinition} syntax.
221
+ * @property {Object} [styles] Key-value pairs used as link styles added to the output during the
222
+ * {@glink framework/guides/architecture/editing-engine#conversion downcasting}.
223
+ * Styles should follow the {@link module:engine/view/elementdefinition~ElementDefinition} syntax.
224
+ * @property {String|Array.<String>} [classes] Class names used as link classes added to the output during the
225
+ * {@glink framework/guides/architecture/editing-engine#conversion downcasting}.
226
+ * Classes should follow the {@link module:engine/view/elementdefinition~ElementDefinition} syntax.
221
227
  */
222
228
 
223
229
  /**
@@ -241,8 +247,14 @@ export default class Link extends Plugin {
241
247
  * @typedef {Object} module:link/link~LinkDecoratorManualDefinition
242
248
  * @property {'manual'} mode Link decorator type. It is `'manual'` for all manual decorators.
243
249
  * @property {String} label The label of the UI button that the user can use to control the presence of link attributes.
244
- * @property {Object} attributes Key-value pairs used as link attributes added to the output during the
250
+ * @property {Object} [attributes] Key-value pairs used as link attributes added to the output during the
245
251
  * {@glink framework/guides/architecture/editing-engine#conversion downcasting}.
246
252
  * Attributes should follow the {@link module:engine/view/elementdefinition~ElementDefinition} syntax.
253
+ * @property {Object} [styles] Key-value pairs used as link styles added to the output during the
254
+ * {@glink framework/guides/architecture/editing-engine#conversion downcasting}.
255
+ * Styles should follow the {@link module:engine/view/elementdefinition~ElementDefinition} syntax.
256
+ * @property {String|Array.<String>} [classes] Class names used as link classes added to the output during the
257
+ * {@glink framework/guides/architecture/editing-engine#conversion downcasting}.
258
+ * Classes should follow the {@link module:engine/view/elementdefinition~ElementDefinition} syntax.
247
259
  * @property {Boolean} [defaultValue] Controls whether the decorator is "on" by default.
248
260
  */
@@ -184,14 +184,24 @@ export default class LinkEditing extends Plugin {
184
184
  editor.model.schema.extend( '$text', { allowAttributes: decorator.id } );
185
185
 
186
186
  // Keeps reference to manual decorator to decode its name to attributes during downcast.
187
- manualDecorators.add( new ManualDecorator( decorator ) );
187
+ decorator = new ManualDecorator( decorator );
188
+
189
+ manualDecorators.add( decorator );
188
190
 
189
191
  editor.conversion.for( 'downcast' ).attributeToElement( {
190
192
  model: decorator.id,
191
193
  view: ( manualDecoratorName, { writer } ) => {
192
194
  if ( manualDecoratorName ) {
193
- const attributes = manualDecorators.get( decorator.id ).attributes;
194
- const element = writer.createAttributeElement( 'a', attributes, { priority: 5 } );
195
+ const element = writer.createAttributeElement( 'a', decorator.attributes, { priority: 5 } );
196
+
197
+ if ( decorator.classes ) {
198
+ writer.addClass( decorator.classes, element );
199
+ }
200
+
201
+ for ( const key in decorator.styles ) {
202
+ writer.setStyle( key, decorator.styles[ key ], element );
203
+ }
204
+
195
205
  writer.setCustomProperty( 'link', true, element );
196
206
 
197
207
  return element;
@@ -201,7 +211,7 @@ export default class LinkEditing extends Plugin {
201
211
  editor.conversion.for( 'upcast' ).elementToAttribute( {
202
212
  view: {
203
213
  name: 'a',
204
- attributes: manualDecorators.get( decorator.id ).attributes
214
+ ...decorator._createPattern()
205
215
  },
206
216
  model: {
207
217
  key: decorator.id
@@ -572,7 +582,7 @@ function isTyping( editor ) {
572
582
  return input.isInput( editor.model.change( writer => writer.batch ) );
573
583
  }
574
584
 
575
- // Returns an array containing names of attributes allowed on `$text` that describes the link item.
585
+ // Returns an array containing names of the attributes allowed on `$text` that describes the link item.
576
586
  //
577
587
  // @param {module:engine/model/schema~Schema} schema
578
588
  // @returns {Array.<String>}
@@ -49,7 +49,7 @@ export default class LinkImageEditing extends Plugin {
49
49
  }
50
50
 
51
51
  editor.conversion.for( 'upcast' ).add( upcastLink( editor ) );
52
- editor.conversion.for( 'downcast' ).add( downcastImageLink );
52
+ editor.conversion.for( 'downcast' ).add( downcastImageLink( editor ) );
53
53
 
54
54
  // Definitions for decorators are provided by the `link` command and the `LinkEditing` plugin.
55
55
  this._enableAutomaticDecorators();
@@ -81,7 +81,6 @@ export default class LinkImageEditing extends Plugin {
81
81
  _enableManualDecorators() {
82
82
  const editor = this.editor;
83
83
  const command = editor.commands.get( 'link' );
84
- const manualDecorators = command.manualDecorators;
85
84
 
86
85
  for ( const decorator of command.manualDecorators ) {
87
86
  if ( editor.plugins.has( 'ImageBlockEditing' ) ) {
@@ -92,8 +91,8 @@ export default class LinkImageEditing extends Plugin {
92
91
  editor.model.schema.extend( 'imageInline', { allowAttributes: decorator.id } );
93
92
  }
94
93
 
95
- editor.conversion.for( 'downcast' ).add( downcastImageLinkManualDecorator( manualDecorators, decorator ) );
96
- editor.conversion.for( 'upcast' ).add( upcastImageLinkManualDecorator( manualDecorators, decorator ) );
94
+ editor.conversion.for( 'downcast' ).add( downcastImageLinkManualDecorator( decorator ) );
95
+ editor.conversion.for( 'upcast' ).add( upcastImageLinkManualDecorator( editor, decorator ) );
97
96
  }
98
97
  }
99
98
  }
@@ -106,25 +105,27 @@ export default class LinkImageEditing extends Plugin {
106
105
  // @returns {Function}
107
106
  function upcastLink( editor ) {
108
107
  const isImageInlinePluginLoaded = editor.plugins.has( 'ImageInlineEditing' );
109
- const linkUtils = editor.plugins.get( 'ImageUtils' );
108
+ const imageUtils = editor.plugins.get( 'ImageUtils' );
110
109
 
111
110
  return dispatcher => {
112
111
  dispatcher.on( 'element:a', ( evt, data, conversionApi ) => {
113
112
  const viewLink = data.viewItem;
114
- const imageInLink = getFirstImage( viewLink );
113
+ const imageInLink = imageUtils.findViewImgElement( viewLink );
115
114
 
116
115
  if ( !imageInLink ) {
117
116
  return;
118
117
  }
119
118
 
120
- const blockImageView = imageInLink.findAncestor( element => linkUtils.isBlockImageView( element ) );
119
+ const blockImageView = imageInLink.findAncestor( element => imageUtils.isBlockImageView( element ) );
121
120
 
122
- // There are two possible cases to consider here
121
+ // There are four possible cases to consider here
123
122
  //
124
123
  // 1. A "root > ... > figure.image > a > img" structure.
125
- // 2. A "root > ... > block > a > img" structure.
124
+ // 2. A "root > ... > figure.image > a > picture > img" structure.
125
+ // 3. A "root > ... > block > a > img" structure.
126
+ // 4. A "root > ... > block > a > picture > img" structure.
126
127
  //
127
- // but the latter should only be considered by this converter when the inline image plugin
128
+ // but the last 2 cases should only be considered by this converter when the inline image plugin
128
129
  // is NOT loaded in the editor (because otherwise, that would be a plain, linked inline image).
129
130
  if ( isImageInlinePluginLoaded && !blockImageView ) {
130
131
  return;
@@ -176,47 +177,55 @@ function upcastLink( editor ) {
176
177
  // Creates a converter that adds `<a>` to linked block image view elements.
177
178
  //
178
179
  // @private
179
- function downcastImageLink( dispatcher ) {
180
- dispatcher.on( 'attribute:linkHref:imageBlock', ( evt, data, conversionApi ) => {
181
- // The image will be already converted - so it will be present in the view.
182
- const viewFigure = conversionApi.mapper.toViewElement( data.item );
183
- const writer = conversionApi.writer;
184
-
185
- // But we need to check whether the link element exists.
186
- const linkInImage = Array.from( viewFigure.getChildren() ).find( child => child.name === 'a' );
187
-
188
- // If so, update the attribute if it's defined or remove the entire link if the attribute is empty.
189
- if ( linkInImage ) {
190
- if ( data.attributeNewValue ) {
191
- writer.setAttribute( 'href', data.attributeNewValue, linkInImage );
192
- } else {
193
- const viewImage = Array.from( linkInImage.getChildren() ).find( child => child.name === 'img' );
180
+ function downcastImageLink( editor ) {
181
+ const imageUtils = editor.plugins.get( 'ImageUtils' );
194
182
 
195
- writer.move( writer.createRangeOn( viewImage ), writer.createPositionAt( viewFigure, 0 ) );
196
- writer.remove( linkInImage );
183
+ return dispatcher => {
184
+ dispatcher.on( 'attribute:linkHref:imageBlock', ( evt, data, conversionApi ) => {
185
+ if ( !conversionApi.consumable.consume( data.item, evt.name ) ) {
186
+ return;
197
187
  }
198
- } else {
199
- // But if it does not exist. Let's wrap already converted image by newly created link element.
200
- // 1. Create an empty link element.
201
- const linkElement = writer.createContainerElement( 'a', { href: data.attributeNewValue } );
202
188
 
203
- // 2. Insert link inside the associated image.
204
- writer.insert( writer.createPositionAt( viewFigure, 0 ), linkElement );
189
+ // The image will be already converted - so it will be present in the view.
190
+ const viewFigure = conversionApi.mapper.toViewElement( data.item );
191
+ const writer = conversionApi.writer;
205
192
 
206
- // 3. Move the image to the link.
207
- writer.move( writer.createRangeOn( viewFigure.getChild( 1 ) ), writer.createPositionAt( linkElement, 0 ) );
208
- }
209
- } );
193
+ // But we need to check whether the link element exists.
194
+ const linkInImage = Array.from( viewFigure.getChildren() ).find( child => child.name === 'a' );
195
+ const viewImage = imageUtils.findViewImgElement( viewFigure );
196
+ // <picture>...<img/></picture> or <img/>
197
+ const viewImgOrPicture = viewImage.parent.is( 'element', 'picture' ) ? viewImage.parent : viewImage;
198
+
199
+ // If so, update the attribute if it's defined or remove the entire link if the attribute is empty.
200
+ if ( linkInImage ) {
201
+ if ( data.attributeNewValue ) {
202
+ writer.setAttribute( 'href', data.attributeNewValue, linkInImage );
203
+ } else {
204
+ writer.move( writer.createRangeOn( viewImgOrPicture ), writer.createPositionAt( viewFigure, 0 ) );
205
+ writer.remove( linkInImage );
206
+ }
207
+ } else {
208
+ // But if it does not exist. Let's wrap already converted image by newly created link element.
209
+ // 1. Create an empty link element.
210
+ const linkElement = writer.createContainerElement( 'a', { href: data.attributeNewValue } );
211
+
212
+ // 2. Insert link inside the associated image.
213
+ writer.insert( writer.createPositionAt( viewFigure, 0 ), linkElement );
214
+
215
+ // 3. Move the image to the link.
216
+ writer.move( writer.createRangeOn( viewImgOrPicture ), writer.createPositionAt( linkElement, 0 ) );
217
+ }
218
+ }, { priority: 'high' } );
219
+ };
210
220
  }
211
221
 
212
222
  // Returns a converter that decorates the `<a>` element when the image is the link label.
213
223
  //
214
224
  // @private
215
225
  // @returns {Function}
216
- function downcastImageLinkManualDecorator( manualDecorators, decorator ) {
226
+ function downcastImageLinkManualDecorator( decorator ) {
217
227
  return dispatcher => {
218
228
  dispatcher.on( `attribute:${ decorator.id }:imageBlock`, ( evt, data, conversionApi ) => {
219
- const attributes = manualDecorators.get( decorator.id ).attributes;
220
229
  const viewFigure = conversionApi.mapper.toViewElement( data.item );
221
230
  const linkInImage = Array.from( viewFigure.getChildren() ).find( child => child.name === 'a' );
222
231
 
@@ -227,9 +236,17 @@ function downcastImageLinkManualDecorator( manualDecorators, decorator ) {
227
236
  return;
228
237
  }
229
238
 
230
- for ( const [ key, val ] of toMap( attributes ) ) {
239
+ for ( const [ key, val ] of toMap( decorator.attributes ) ) {
231
240
  conversionApi.writer.setAttribute( key, val, linkInImage );
232
241
  }
242
+
243
+ if ( decorator.classes ) {
244
+ conversionApi.writer.addClass( decorator.classes, linkInImage );
245
+ }
246
+
247
+ for ( const key in decorator.styles ) {
248
+ conversionApi.writer.setStyle( key, decorator.styles[ key ], linkInImage );
249
+ }
233
250
  } );
234
251
  };
235
252
  }
@@ -238,11 +255,13 @@ function downcastImageLinkManualDecorator( manualDecorators, decorator ) {
238
255
  //
239
256
  // @private
240
257
  // @returns {Function}
241
- function upcastImageLinkManualDecorator( manualDecorators, decorator ) {
258
+ function upcastImageLinkManualDecorator( editor, decorator ) {
259
+ const imageUtils = editor.plugins.get( 'ImageUtils' );
260
+
242
261
  return dispatcher => {
243
262
  dispatcher.on( 'element:a', ( evt, data, conversionApi ) => {
244
263
  const viewLink = data.viewItem;
245
- const imageInLink = getFirstImage( viewLink );
264
+ const imageInLink = imageUtils.findViewImgElement( viewLink );
246
265
 
247
266
  // We need to check whether an image is inside a link because the converter handles
248
267
  // only manual decorators for linked images. See #7975.
@@ -250,11 +269,7 @@ function upcastImageLinkManualDecorator( manualDecorators, decorator ) {
250
269
  return;
251
270
  }
252
271
 
253
- const consumableAttributes = {
254
- attributes: manualDecorators.get( decorator.id ).attributes
255
- };
256
-
257
- const matcher = new Matcher( consumableAttributes );
272
+ const matcher = new Matcher( decorator._createPattern() );
258
273
  const result = matcher.match( viewLink );
259
274
 
260
275
  // The link element does not have required attributes or/and proper values.
@@ -278,12 +293,3 @@ function upcastImageLinkManualDecorator( manualDecorators, decorator ) {
278
293
  // Using the same priority that `upcastLink()` converter guarantees that the linked image was properly converted.
279
294
  };
280
295
  }
281
-
282
- // Returns the first image in a given view element.
283
- //
284
- // @private
285
- // @param {module:engine/view/element~Element}
286
- // @returns {module:engine/view/element~Element|undefined}
287
- function getFirstImage( viewElement ) {
288
- return Array.from( viewElement.getChildren() ).find( child => child.name === 'img' );
289
- }
@@ -73,6 +73,15 @@ export default class AutomaticDecorators {
73
73
  const viewElement = viewWriter.createAttributeElement( 'a', item.attributes, {
74
74
  priority: 5
75
75
  } );
76
+
77
+ if ( item.classes ) {
78
+ viewWriter.addClass( item.classes, viewElement );
79
+ }
80
+
81
+ for ( const key in item.styles ) {
82
+ viewWriter.setStyle( key, item.styles[ key ], viewElement );
83
+ }
84
+
76
85
  viewWriter.setCustomProperty( 'link', true, viewElement );
77
86
  if ( item.callback( data.attributeNewValue ) ) {
78
87
  if ( data.item.is( 'selection' ) ) {
@@ -97,8 +106,8 @@ export default class AutomaticDecorators {
97
106
  */
98
107
  getDispatcherForLinkedImage() {
99
108
  return dispatcher => {
100
- dispatcher.on( 'attribute:linkHref:imageBlock', ( evt, data, conversionApi ) => {
101
- const viewFigure = conversionApi.mapper.toViewElement( data.item );
109
+ dispatcher.on( 'attribute:linkHref:imageBlock', ( evt, data, { writer, mapper } ) => {
110
+ const viewFigure = mapper.toViewElement( data.item );
102
111
  const linkInImage = Array.from( viewFigure.getChildren() ).find( child => child.name === 'a' );
103
112
 
104
113
  for ( const item of this._definitions ) {
@@ -106,20 +115,38 @@ export default class AutomaticDecorators {
106
115
 
107
116
  if ( item.callback( data.attributeNewValue ) ) {
108
117
  for ( const [ key, val ] of attributes ) {
118
+ // Left for backward compatibility. Since v30 decorator should
119
+ // accept `classes` and `styles` separately from `attributes`.
109
120
  if ( key === 'class' ) {
110
- conversionApi.writer.addClass( val, linkInImage );
121
+ writer.addClass( val, linkInImage );
111
122
  } else {
112
- conversionApi.writer.setAttribute( key, val, linkInImage );
123
+ writer.setAttribute( key, val, linkInImage );
113
124
  }
114
125
  }
126
+
127
+ if ( item.classes ) {
128
+ writer.addClass( item.classes, linkInImage );
129
+ }
130
+
131
+ for ( const key in item.styles ) {
132
+ writer.setStyle( key, item.styles[ key ], linkInImage );
133
+ }
115
134
  } else {
116
135
  for ( const [ key, val ] of attributes ) {
117
136
  if ( key === 'class' ) {
118
- conversionApi.writer.removeClass( val, linkInImage );
137
+ writer.removeClass( val, linkInImage );
119
138
  } else {
120
- conversionApi.writer.removeAttribute( key, linkInImage );
139
+ writer.removeAttribute( key, linkInImage );
121
140
  }
122
141
  }
142
+
143
+ if ( item.classes ) {
144
+ writer.removeClass( item.classes, linkInImage );
145
+ }
146
+
147
+ for ( const key in item.styles ) {
148
+ writer.removeStyle( key, linkInImage );
149
+ }
123
150
  }
124
151
  }
125
152
  } );
@@ -28,7 +28,7 @@ export default class ManualDecorator {
28
28
  * Attributes should keep the format of attributes defined in {@link module:engine/view/elementdefinition~ElementDefinition}.
29
29
  * @param {Boolean} [config.defaultValue] Controls whether the decorator is "on" by default.
30
30
  */
31
- constructor( { id, label, attributes, defaultValue } ) {
31
+ constructor( { id, label, attributes, classes, styles, defaultValue } ) {
32
32
  /**
33
33
  * An ID of a manual decorator which is the name of the attribute in the model, for example: 'linkManualDecorator0'.
34
34
  *
@@ -65,6 +65,36 @@ export default class ManualDecorator {
65
65
  * @type {Object}
66
66
  */
67
67
  this.attributes = attributes;
68
+
69
+ /**
70
+ * A set of classes added to downcasted data when the decorator is activated for a specific link.
71
+ * Classes should be added in a form of classes defined in {@link module:engine/view/elementdefinition~ElementDefinition}.
72
+ *
73
+ * @type {Object}
74
+ */
75
+ this.classes = classes;
76
+
77
+ /**
78
+ * A set of styles added to downcasted data when the decorator is activated for a specific link.
79
+ * Styles should be added in a form of styles defined in {@link module:engine/view/elementdefinition~ElementDefinition}.
80
+ *
81
+ * @type {Object}
82
+ */
83
+ this.styles = styles;
84
+ }
85
+
86
+ /**
87
+ * Returns {@link module:engine/view/matcher~MatcherPattern} with decorator attributes.
88
+ *
89
+ * @protected
90
+ * @returns {module:engine/view/matcher~MatcherPattern}
91
+ */
92
+ _createPattern() {
93
+ return {
94
+ attributes: this.attributes,
95
+ classes: this.classes,
96
+ styles: this.styles
97
+ };
68
98
  }
69
99
  }
70
100