@ckeditor/ckeditor5-link 28.0.0 → 30.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 (73) hide show
  1. package/LICENSE.md +1 -1
  2. package/README.md +6 -2
  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/lang/translations/ro.po +1 -1
  59. package/package.json +25 -20
  60. package/src/autolink.js +13 -1
  61. package/src/link.js +14 -2
  62. package/src/linkcommand.js +13 -15
  63. package/src/linkediting.js +15 -5
  64. package/src/linkimageediting.js +79 -67
  65. package/src/linkimageui.js +21 -19
  66. package/src/linkui.js +20 -12
  67. package/src/unlinkcommand.js +6 -8
  68. package/src/utils/automaticdecorators.js +33 -6
  69. package/src/utils/manualdecorator.js +31 -1
  70. package/src/utils.js +3 -3
  71. package/theme/linkimage.css +9 -12
  72. package/CHANGELOG.md +0 -313
  73. package/build/link.js.map +0 -1
package/package.json CHANGED
@@ -1,36 +1,40 @@
1
1
  {
2
2
  "name": "@ckeditor/ckeditor5-link",
3
- "version": "28.0.0",
3
+ "version": "30.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": "^28.0.0",
15
- "ckeditor5": "^28.0.0",
15
+ "@ckeditor/ckeditor5-ui": "^30.0.0",
16
+ "ckeditor5": "^30.0.0",
16
17
  "lodash-es": "^4.17.15"
17
18
  },
18
19
  "devDependencies": {
19
- "@ckeditor/ckeditor5-basic-styles": "^28.0.0",
20
- "@ckeditor/ckeditor5-block-quote": "^28.0.0",
21
- "@ckeditor/ckeditor5-clipboard": "^28.0.0",
22
- "@ckeditor/ckeditor5-code-block": "^28.0.0",
23
- "@ckeditor/ckeditor5-core": "^28.0.0",
24
- "@ckeditor/ckeditor5-dev-utils": "^25.0.0",
25
- "@ckeditor/ckeditor5-editor-classic": "^28.0.0",
26
- "@ckeditor/ckeditor5-engine": "^28.0.0",
27
- "@ckeditor/ckeditor5-enter": "^28.0.0",
28
- "@ckeditor/ckeditor5-image": "^28.0.0",
29
- "@ckeditor/ckeditor5-paragraph": "^28.0.0",
30
- "@ckeditor/ckeditor5-theme-lark": "^28.0.0",
31
- "@ckeditor/ckeditor5-typing": "^28.0.0",
32
- "@ckeditor/ckeditor5-undo": "^28.0.0",
33
- "@ckeditor/ckeditor5-utils": "^28.0.0",
20
+ "@ckeditor/ckeditor5-basic-styles": "^30.0.0",
21
+ "@ckeditor/ckeditor5-block-quote": "^30.0.0",
22
+ "@ckeditor/ckeditor5-clipboard": "^30.0.0",
23
+ "@ckeditor/ckeditor5-cloud-services": "^30.0.0",
24
+ "@ckeditor/ckeditor5-code-block": "^30.0.0",
25
+ "@ckeditor/ckeditor5-core": "^30.0.0",
26
+ "@ckeditor/ckeditor5-dev-utils": "^25.4.0",
27
+ "@ckeditor/ckeditor5-easy-image": "^30.0.0",
28
+ "@ckeditor/ckeditor5-editor-classic": "^30.0.0",
29
+ "@ckeditor/ckeditor5-engine": "^30.0.0",
30
+ "@ckeditor/ckeditor5-enter": "^30.0.0",
31
+ "@ckeditor/ckeditor5-image": "^30.0.0",
32
+ "@ckeditor/ckeditor5-paragraph": "^30.0.0",
33
+ "@ckeditor/ckeditor5-theme-lark": "^30.0.0",
34
+ "@ckeditor/ckeditor5-typing": "^30.0.0",
35
+ "@ckeditor/ckeditor5-undo": "^30.0.0",
36
+ "@ckeditor/ckeditor5-utils": "^30.0.0",
37
+ "@ckeditor/ckeditor5-widget": "^30.0.0",
34
38
  "webpack": "^4.43.0",
35
39
  "webpack-cli": "^3.3.11"
36
40
  },
@@ -51,7 +55,8 @@
51
55
  "lang",
52
56
  "src",
53
57
  "theme",
54
- "build"
58
+ "build",
59
+ "ckeditor5-metadata.json"
55
60
  ],
56
61
  "scripts": {
57
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
  */
@@ -9,10 +9,10 @@
9
9
 
10
10
  import { Command } from 'ckeditor5/src/core';
11
11
  import { findAttributeRange } from 'ckeditor5/src/typing';
12
- import { Collection, toMap, first } from 'ckeditor5/src/utils';
12
+ import { Collection, first, toMap } from 'ckeditor5/src/utils';
13
13
 
14
14
  import AutomaticDecorators from './utils/automaticdecorators';
15
- import { isImageAllowed } from './utils';
15
+ import { isLinkableElement } from './utils';
16
16
 
17
17
  /**
18
18
  * The link command. It is used by the {@link module:link/link~Link link feature}.
@@ -44,7 +44,7 @@ export default class LinkCommand extends Command {
44
44
 
45
45
  /**
46
46
  * An instance of the helper that ties together all {@link module:link/link~LinkDecoratorAutomaticDefinition}
47
- * that are used by the {@glink features/link link} and the {@glink features/image#linking-images linking images} features.
47
+ * that are used by the {@glink features/link link} and the {@glink features/images/images-linking linking images} features.
48
48
  *
49
49
  * @readonly
50
50
  * @type {module:link/utils~AutomaticDecorators}
@@ -66,18 +66,17 @@ export default class LinkCommand extends Command {
66
66
  */
67
67
  refresh() {
68
68
  const model = this.editor.model;
69
- const doc = model.document;
70
-
71
- const selectedElement = first( doc.selection.getSelectedBlocks() );
69
+ const selection = model.document.selection;
70
+ const selectedElement = selection.getSelectedElement() || first( selection.getSelectedBlocks() );
72
71
 
73
- // A check for the `LinkImage` plugin. If the selection contains an element, get values from the element.
72
+ // A check for any integration that allows linking elements (e.g. `LinkImage`).
74
73
  // Currently the selection reads attributes from text nodes only. See #7429 and #7465.
75
- if ( isImageAllowed( selectedElement, model.schema ) ) {
74
+ if ( isLinkableElement( selectedElement, model.schema ) ) {
76
75
  this.value = selectedElement.getAttribute( 'linkHref' );
77
76
  this.isEnabled = model.schema.checkAttribute( selectedElement, 'linkHref' );
78
77
  } else {
79
- this.value = doc.selection.getAttribute( 'linkHref' );
80
- this.isEnabled = model.schema.checkAttributeInSelection( doc.selection, 'linkHref' );
78
+ this.value = selection.getAttribute( 'linkHref' );
79
+ this.isEnabled = model.schema.checkAttributeInSelection( selection, 'linkHref' );
81
80
  }
82
81
 
83
82
  for ( const manualDecorator of this.manualDecorators ) {
@@ -258,17 +257,16 @@ export default class LinkCommand extends Command {
258
257
  */
259
258
  _getDecoratorStateFromModel( decoratorName ) {
260
259
  const model = this.editor.model;
261
- const doc = model.document;
262
-
263
- const selectedElement = first( doc.selection.getSelectedBlocks() );
260
+ const selection = model.document.selection;
261
+ const selectedElement = selection.getSelectedElement();
264
262
 
265
263
  // A check for the `LinkImage` plugin. If the selection contains an element, get values from the element.
266
264
  // Currently the selection reads attributes from text nodes only. See #7429 and #7465.
267
- if ( isImageAllowed( selectedElement, model.schema ) ) {
265
+ if ( isLinkableElement( selectedElement, model.schema ) ) {
268
266
  return selectedElement.getAttribute( decoratorName );
269
267
  }
270
268
 
271
- return doc.selection.getAttribute( decoratorName );
269
+ return selection.getAttribute( decoratorName );
272
270
  }
273
271
 
274
272
  /**
@@ -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>}
@@ -13,12 +13,10 @@ import { toMap } from 'ckeditor5/src/utils';
13
13
 
14
14
  import LinkEditing from './linkediting';
15
15
 
16
- import linkIcon from '../theme/icons/link.svg';
17
-
18
16
  /**
19
17
  * The link image engine feature.
20
18
  *
21
- * It accepts the `linkHref="url"` attribute in the model for the {@link module:image/image~Image `<image>`} element
19
+ * It accepts the `linkHref="url"` attribute in the model for the {@link module:image/image~Image `<imageBlock>`} element
22
20
  * which allows linking images.
23
21
  *
24
22
  * @extends module:core/plugin~Plugin
@@ -28,7 +26,7 @@ export default class LinkImageEditing extends Plugin {
28
26
  * @inheritDoc
29
27
  */
30
28
  static get requires() {
31
- return [ 'ImageEditing', LinkEditing ];
29
+ return [ 'ImageEditing', 'ImageUtils', LinkEditing ];
32
30
  }
33
31
 
34
32
  /**
@@ -40,12 +38,18 @@ export default class LinkImageEditing extends Plugin {
40
38
 
41
39
  init() {
42
40
  const editor = this.editor;
41
+ const schema = editor.model.schema;
43
42
 
44
- editor.model.schema.extend( 'image', { allowAttributes: [ 'linkHref' ] } );
43
+ if ( editor.plugins.has( 'ImageBlockEditing' ) ) {
44
+ schema.extend( 'imageBlock', { allowAttributes: [ 'linkHref' ] } );
45
+ }
45
46
 
46
- editor.conversion.for( 'upcast' ).add( upcastLink() );
47
- editor.conversion.for( 'editingDowncast' ).add( downcastImageLink( { attachIconIndicator: true } ) );
48
- editor.conversion.for( 'dataDowncast' ).add( downcastImageLink( { attachIconIndicator: false } ) );
47
+ if ( editor.plugins.has( 'ImageInlineEditing' ) ) {
48
+ schema.extend( 'imageInline', { allowAttributes: [ 'linkHref' ] } );
49
+ }
50
+
51
+ editor.conversion.for( 'upcast' ).add( upcastLink( editor ) );
52
+ editor.conversion.for( 'downcast' ).add( downcastImageLink( editor ) );
49
53
 
50
54
  // Definitions for decorators are provided by the `link` command and the `LinkEditing` plugin.
51
55
  this._enableAutomaticDecorators();
@@ -77,30 +81,56 @@ export default class LinkImageEditing extends Plugin {
77
81
  _enableManualDecorators() {
78
82
  const editor = this.editor;
79
83
  const command = editor.commands.get( 'link' );
80
- const manualDecorators = command.manualDecorators;
81
84
 
82
85
  for ( const decorator of command.manualDecorators ) {
83
- editor.model.schema.extend( 'image', { allowAttributes: decorator.id } );
84
- editor.conversion.for( 'downcast' ).add( downcastImageLinkManualDecorator( manualDecorators, decorator ) );
85
- editor.conversion.for( 'upcast' ).add( upcastImageLinkManualDecorator( manualDecorators, decorator ) );
86
+ if ( editor.plugins.has( 'ImageBlockEditing' ) ) {
87
+ editor.model.schema.extend( 'imageBlock', { allowAttributes: decorator.id } );
88
+ }
89
+
90
+ if ( editor.plugins.has( 'ImageInlineEditing' ) ) {
91
+ editor.model.schema.extend( 'imageInline', { allowAttributes: decorator.id } );
92
+ }
93
+
94
+ editor.conversion.for( 'downcast' ).add( downcastImageLinkManualDecorator( decorator ) );
95
+ editor.conversion.for( 'upcast' ).add( upcastImageLinkManualDecorator( editor, decorator ) );
86
96
  }
87
97
  }
88
98
  }
89
99
 
90
- // Returns a converter that consumes the 'href' attribute if a link contains an image.
100
+ // Returns a converter for linked block images that consumes the "href" attribute
101
+ // if a link contains an image.
91
102
  //
92
103
  // @private
104
+ // @param {module:core/editor/editor~Editor} editor The editor instance.
93
105
  // @returns {Function}
94
- function upcastLink() {
106
+ function upcastLink( editor ) {
107
+ const isImageInlinePluginLoaded = editor.plugins.has( 'ImageInlineEditing' );
108
+ const imageUtils = editor.plugins.get( 'ImageUtils' );
109
+
95
110
  return dispatcher => {
96
111
  dispatcher.on( 'element:a', ( evt, data, conversionApi ) => {
97
112
  const viewLink = data.viewItem;
98
- const imageInLink = getFirstImage( viewLink );
113
+ const imageInLink = imageUtils.findViewImgElement( viewLink );
99
114
 
100
115
  if ( !imageInLink ) {
101
116
  return;
102
117
  }
103
118
 
119
+ const blockImageView = imageInLink.findAncestor( element => imageUtils.isBlockImageView( element ) );
120
+
121
+ // There are four possible cases to consider here
122
+ //
123
+ // 1. A "root > ... > figure.image > 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.
127
+ //
128
+ // but the last 2 cases should only be considered by this converter when the inline image plugin
129
+ // is NOT loaded in the editor (because otherwise, that would be a plain, linked inline image).
130
+ if ( isImageInlinePluginLoaded && !blockImageView ) {
131
+ return;
132
+ }
133
+
104
134
  // There's an image inside an <a> element - we consume it so it won't be picked up by the Link plugin.
105
135
  const consumableAttributes = { attributes: [ 'href' ] };
106
136
 
@@ -121,7 +151,7 @@ function upcastLink() {
121
151
  // figure > a > img: parent of the view link element is an image element (figure).
122
152
  let modelElement = data.modelCursor.parent;
123
153
 
124
- if ( !modelElement.is( 'element', 'image' ) ) {
154
+ if ( !modelElement.is( 'element', 'imageBlock' ) ) {
125
155
  // a > img: parent of the view link is not the image (figure) element. We need to convert it manually.
126
156
  const conversionResult = conversionApi.convertItem( imageInLink, data.modelCursor );
127
157
 
@@ -134,7 +164,7 @@ function upcastLink() {
134
164
  modelElement = data.modelCursor.nodeBefore;
135
165
  }
136
166
 
137
- if ( modelElement && modelElement.is( 'element', 'image' ) ) {
167
+ if ( modelElement && modelElement.is( 'element', 'imageBlock' ) ) {
138
168
  // Set the linkHref attribute from link element on model image element.
139
169
  conversionApi.writer.setAttribute( 'linkHref', linkHref, modelElement );
140
170
  }
@@ -144,42 +174,34 @@ function upcastLink() {
144
174
  };
145
175
  }
146
176
 
147
- // Return a converter that adds the `<a>` element to data.
177
+ // Creates a converter that adds `<a>` to linked block image view elements.
148
178
  //
149
179
  // @private
150
- // @params {Object} options
151
- // @params {Boolean} options.attachIconIndicator=false If set to `true`, an icon that informs about the linked image will be added.
152
- // @returns {Function}
153
- function downcastImageLink( options ) {
180
+ function downcastImageLink( editor ) {
181
+ const imageUtils = editor.plugins.get( 'ImageUtils' );
182
+
154
183
  return dispatcher => {
155
- dispatcher.on( 'attribute:linkHref:image', ( evt, data, conversionApi ) => {
184
+ dispatcher.on( 'attribute:linkHref:imageBlock', ( evt, data, conversionApi ) => {
185
+ if ( !conversionApi.consumable.consume( data.item, evt.name ) ) {
186
+ return;
187
+ }
188
+
156
189
  // The image will be already converted - so it will be present in the view.
157
190
  const viewFigure = conversionApi.mapper.toViewElement( data.item );
158
191
  const writer = conversionApi.writer;
159
192
 
160
193
  // But we need to check whether the link element exists.
161
194
  const linkInImage = Array.from( viewFigure.getChildren() ).find( child => child.name === 'a' );
162
-
163
- let linkIconIndicator;
164
-
165
- if ( options.attachIconIndicator ) {
166
- // Create an icon indicator for a linked image.
167
- linkIconIndicator = writer.createUIElement( 'span', { class: 'ck ck-link-image_icon' }, function( domDocument ) {
168
- const domElement = this.toDomElement( domDocument );
169
- domElement.innerHTML = linkIcon;
170
-
171
- return domElement;
172
- } );
173
- }
195
+ const viewImage = imageUtils.findViewImgElement( viewFigure );
196
+ // <picture>...<img/></picture> or <img/>
197
+ const viewImgOrPicture = viewImage.parent.is( 'element', 'picture' ) ? viewImage.parent : viewImage;
174
198
 
175
199
  // If so, update the attribute if it's defined or remove the entire link if the attribute is empty.
176
200
  if ( linkInImage ) {
177
201
  if ( data.attributeNewValue ) {
178
202
  writer.setAttribute( 'href', data.attributeNewValue, linkInImage );
179
203
  } else {
180
- const viewImage = Array.from( linkInImage.getChildren() ).find( child => child.name === 'img' );
181
-
182
- writer.move( writer.createRangeOn( viewImage ), writer.createPositionAt( viewFigure, 0 ) );
204
+ writer.move( writer.createRangeOn( viewImgOrPicture ), writer.createPositionAt( viewFigure, 0 ) );
183
205
  writer.remove( linkInImage );
184
206
  }
185
207
  } else {
@@ -191,14 +213,9 @@ function downcastImageLink( options ) {
191
213
  writer.insert( writer.createPositionAt( viewFigure, 0 ), linkElement );
192
214
 
193
215
  // 3. Move the image to the link.
194
- writer.move( writer.createRangeOn( viewFigure.getChild( 1 ) ), writer.createPositionAt( linkElement, 0 ) );
195
-
196
- // 4. Inset the linked image icon indicator while downcast to editing.
197
- if ( linkIconIndicator ) {
198
- writer.insert( writer.createPositionAt( linkElement, 'end' ), linkIconIndicator );
199
- }
216
+ writer.move( writer.createRangeOn( viewImgOrPicture ), writer.createPositionAt( linkElement, 0 ) );
200
217
  }
201
- } );
218
+ }, { priority: 'high' } );
202
219
  };
203
220
  }
204
221
 
@@ -206,11 +223,9 @@ function downcastImageLink( options ) {
206
223
  //
207
224
  // @private
208
225
  // @returns {Function}
209
- function downcastImageLinkManualDecorator( manualDecorators, decorator ) {
226
+ function downcastImageLinkManualDecorator( decorator ) {
210
227
  return dispatcher => {
211
- dispatcher.on( `attribute:${ decorator.id }:image`, ( evt, data, conversionApi ) => {
212
- const attributes = manualDecorators.get( decorator.id ).attributes;
213
-
228
+ dispatcher.on( `attribute:${ decorator.id }:imageBlock`, ( evt, data, conversionApi ) => {
214
229
  const viewFigure = conversionApi.mapper.toViewElement( data.item );
215
230
  const linkInImage = Array.from( viewFigure.getChildren() ).find( child => child.name === 'a' );
216
231
 
@@ -221,9 +236,17 @@ function downcastImageLinkManualDecorator( manualDecorators, decorator ) {
221
236
  return;
222
237
  }
223
238
 
224
- for ( const [ key, val ] of toMap( attributes ) ) {
239
+ for ( const [ key, val ] of toMap( decorator.attributes ) ) {
225
240
  conversionApi.writer.setAttribute( key, val, linkInImage );
226
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
+ }
227
250
  } );
228
251
  };
229
252
  }
@@ -232,11 +255,13 @@ function downcastImageLinkManualDecorator( manualDecorators, decorator ) {
232
255
  //
233
256
  // @private
234
257
  // @returns {Function}
235
- function upcastImageLinkManualDecorator( manualDecorators, decorator ) {
258
+ function upcastImageLinkManualDecorator( editor, decorator ) {
259
+ const imageUtils = editor.plugins.get( 'ImageUtils' );
260
+
236
261
  return dispatcher => {
237
262
  dispatcher.on( 'element:a', ( evt, data, conversionApi ) => {
238
263
  const viewLink = data.viewItem;
239
- const imageInLink = getFirstImage( viewLink );
264
+ const imageInLink = imageUtils.findViewImgElement( viewLink );
240
265
 
241
266
  // We need to check whether an image is inside a link because the converter handles
242
267
  // only manual decorators for linked images. See #7975.
@@ -244,11 +269,7 @@ function upcastImageLinkManualDecorator( manualDecorators, decorator ) {
244
269
  return;
245
270
  }
246
271
 
247
- const consumableAttributes = {
248
- attributes: manualDecorators.get( decorator.id ).attributes
249
- };
250
-
251
- const matcher = new Matcher( consumableAttributes );
272
+ const matcher = new Matcher( decorator._createPattern() );
252
273
  const result = matcher.match( viewLink );
253
274
 
254
275
  // The link element does not have required attributes or/and proper values.
@@ -261,7 +282,7 @@ function upcastImageLinkManualDecorator( manualDecorators, decorator ) {
261
282
  return;
262
283
  }
263
284
 
264
- // At this stage we can assume that we have the `<image>` element.
285
+ // At this stage we can assume that we have the `<imageBlock>` element.
265
286
  // `nodeBefore` comes after conversion: `<a><img></a>`.
266
287
  // `parent` comes with full image definition: `<figure><a><img></a></figure>.
267
288
  // See the body of the `upcastLink()` function.
@@ -272,12 +293,3 @@ function upcastImageLinkManualDecorator( manualDecorators, decorator ) {
272
293
  // Using the same priority that `upcastLink()` converter guarantees that the linked image was properly converted.
273
294
  };
274
295
  }
275
-
276
- // Returns the first image in a given view element.
277
- //
278
- // @private
279
- // @param {module:engine/view/element~Element}
280
- // @returns {module:engine/view/element~Element|undefined}
281
- function getFirstImage( viewElement ) {
282
- return Array.from( viewElement.getChildren() ).find( child => child.name === 'img' );
283
- }
@@ -30,7 +30,7 @@ export default class LinkImageUI extends Plugin {
30
30
  * @inheritDoc
31
31
  */
32
32
  static get requires() {
33
- return [ LinkEditing, LinkUI, 'Image' ];
33
+ return [ LinkEditing, LinkUI, 'ImageBlockEditing' ];
34
34
  }
35
35
 
36
36
  /**
@@ -48,12 +48,15 @@ export default class LinkImageUI extends Plugin {
48
48
  const viewDocument = editor.editing.view.document;
49
49
 
50
50
  this.listenTo( viewDocument, 'click', ( evt, data ) => {
51
- const hasLink = isImageLinked( viewDocument.selection.getSelectedElement(), editor.plugins.get( 'Image' ) );
52
-
53
- if ( hasLink ) {
51
+ if ( this._isSelectedLinkedImage( editor.model.document.selection ) ) {
52
+ // Prevent browser navigation when clicking a linked image.
54
53
  data.preventDefault();
54
+
55
+ // Block the `LinkUI` plugin when an image was clicked.
56
+ // In such a case, we'd like to display the image toolbar.
57
+ evt.stop();
55
58
  }
56
- } );
59
+ }, { priority: 'high' } );
57
60
 
58
61
  this._createToolbarLinkImageButton();
59
62
  }
@@ -91,9 +94,7 @@ export default class LinkImageUI extends Plugin {
91
94
 
92
95
  // Show the actionsView or formView (both from LinkUI) on button click depending on whether the image is linked already.
93
96
  this.listenTo( button, 'execute', () => {
94
- const hasLink = isImageLinked( editor.editing.view.document.selection.getSelectedElement(), editor.plugins.get( 'Image' ) );
95
-
96
- if ( hasLink ) {
97
+ if ( this._isSelectedLinkedImage( editor.model.document.selection ) ) {
97
98
  plugin._addActionsView();
98
99
  } else {
99
100
  plugin._showUI( true );
@@ -103,18 +104,19 @@ export default class LinkImageUI extends Plugin {
103
104
  return button;
104
105
  } );
105
106
  }
106
- }
107
107
 
108
- // A helper function that checks whether the element is a linked image.
109
- //
110
- // @param {module:engine/model/element~Element} element
111
- // @returns {Boolean}
112
- function isImageLinked( element, image ) {
113
- const isImage = element && image.isImageWidget( element );
108
+ /**
109
+ * Returns true if a linked image (either block or inline) is the only selected element
110
+ * in the model document.
111
+ *
112
+ * @private
113
+ * @param {module:engine/model/selection~Selection} selection
114
+ * @returns {Boolean}
115
+ */
116
+ _isSelectedLinkedImage( selection ) {
117
+ const selectedModelElement = selection.getSelectedElement();
118
+ const imageUtils = this.editor.plugins.get( 'ImageUtils' );
114
119
 
115
- if ( !isImage ) {
116
- return false;
120
+ return imageUtils.isImage( selectedModelElement ) && selectedModelElement.hasAttribute( 'linkHref' );
117
121
  }
118
-
119
- return element.getChild( 0 ).is( 'element', 'a' );
120
122
  }