@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.
- package/LICENSE.md +1 -1
- package/README.md +6 -2
- package/build/link.js +1 -1
- package/build/translations/ar.js +1 -0
- package/build/translations/ast.js +1 -0
- package/build/translations/az.js +1 -0
- package/build/translations/bg.js +1 -0
- package/build/translations/cs.js +1 -0
- package/build/translations/da.js +1 -0
- package/build/translations/de-ch.js +1 -0
- package/build/translations/de.js +1 -0
- package/build/translations/el.js +1 -0
- package/build/translations/en-au.js +1 -0
- package/build/translations/en-gb.js +1 -0
- package/build/translations/eo.js +1 -0
- package/build/translations/es.js +1 -0
- package/build/translations/et.js +1 -0
- package/build/translations/eu.js +1 -0
- package/build/translations/fa.js +1 -0
- package/build/translations/fi.js +1 -0
- package/build/translations/fr.js +1 -0
- package/build/translations/gl.js +1 -0
- package/build/translations/he.js +1 -0
- package/build/translations/hi.js +1 -0
- package/build/translations/hr.js +1 -0
- package/build/translations/hu.js +1 -0
- package/build/translations/id.js +1 -0
- package/build/translations/it.js +1 -0
- package/build/translations/ja.js +1 -0
- package/build/translations/km.js +1 -0
- package/build/translations/kn.js +1 -0
- package/build/translations/ko.js +1 -0
- package/build/translations/ku.js +1 -0
- package/build/translations/lt.js +1 -0
- package/build/translations/lv.js +1 -0
- package/build/translations/nb.js +1 -0
- package/build/translations/ne.js +1 -0
- package/build/translations/nl.js +1 -0
- package/build/translations/no.js +1 -0
- package/build/translations/pl.js +1 -0
- package/build/translations/pt-br.js +1 -0
- package/build/translations/pt.js +1 -0
- package/build/translations/ro.js +1 -0
- package/build/translations/ru.js +1 -0
- package/build/translations/sk.js +1 -0
- package/build/translations/sq.js +1 -0
- package/build/translations/sr-latn.js +1 -0
- package/build/translations/sr.js +1 -0
- package/build/translations/sv.js +1 -0
- package/build/translations/tk.js +1 -0
- package/build/translations/tr.js +1 -0
- package/build/translations/ug.js +1 -0
- package/build/translations/uk.js +1 -0
- package/build/translations/vi.js +1 -0
- package/build/translations/zh-cn.js +1 -0
- package/build/translations/zh.js +1 -0
- package/ckeditor5-metadata.json +76 -0
- package/lang/translations/ro.po +1 -1
- package/package.json +25 -20
- package/src/autolink.js +13 -1
- package/src/link.js +14 -2
- package/src/linkcommand.js +13 -15
- package/src/linkediting.js +15 -5
- package/src/linkimageediting.js +79 -67
- package/src/linkimageui.js +21 -19
- package/src/linkui.js +20 -12
- package/src/unlinkcommand.js +6 -8
- package/src/utils/automaticdecorators.js +33 -6
- package/src/utils/manualdecorator.js +31 -1
- package/src/utils.js +3 -3
- package/theme/linkimage.css +9 -12
- package/CHANGELOG.md +0 -313
- 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": "
|
|
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": "^
|
|
15
|
-
"ckeditor5": "^
|
|
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": "^
|
|
20
|
-
"@ckeditor/ckeditor5-block-quote": "^
|
|
21
|
-
"@ckeditor/ckeditor5-clipboard": "^
|
|
22
|
-
"@ckeditor/ckeditor5-
|
|
23
|
-
"@ckeditor/ckeditor5-
|
|
24
|
-
"@ckeditor/ckeditor5-
|
|
25
|
-
"@ckeditor/ckeditor5-
|
|
26
|
-
"@ckeditor/ckeditor5-
|
|
27
|
-
"@ckeditor/ckeditor5-
|
|
28
|
-
"@ckeditor/ckeditor5-
|
|
29
|
-
"@ckeditor/ckeditor5-
|
|
30
|
-
"@ckeditor/ckeditor5-
|
|
31
|
-
"@ckeditor/ckeditor5-
|
|
32
|
-
"@ckeditor/ckeditor5-
|
|
33
|
-
"@ckeditor/ckeditor5-
|
|
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
|
*/
|
package/src/linkcommand.js
CHANGED
|
@@ -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,
|
|
12
|
+
import { Collection, first, toMap } from 'ckeditor5/src/utils';
|
|
13
13
|
|
|
14
14
|
import AutomaticDecorators from './utils/automaticdecorators';
|
|
15
|
-
import {
|
|
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/
|
|
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
|
|
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
|
|
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 (
|
|
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 =
|
|
80
|
-
this.isEnabled = model.schema.checkAttributeInSelection(
|
|
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
|
|
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 (
|
|
265
|
+
if ( isLinkableElement( selectedElement, model.schema ) ) {
|
|
268
266
|
return selectedElement.getAttribute( decoratorName );
|
|
269
267
|
}
|
|
270
268
|
|
|
271
|
-
return
|
|
269
|
+
return selection.getAttribute( decoratorName );
|
|
272
270
|
}
|
|
273
271
|
|
|
274
272
|
/**
|
package/src/linkediting.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
194
|
-
|
|
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
|
-
|
|
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>}
|
package/src/linkimageediting.js
CHANGED
|
@@ -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 `<
|
|
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.
|
|
43
|
+
if ( editor.plugins.has( 'ImageBlockEditing' ) ) {
|
|
44
|
+
schema.extend( 'imageBlock', { allowAttributes: [ 'linkHref' ] } );
|
|
45
|
+
}
|
|
45
46
|
|
|
46
|
-
editor.
|
|
47
|
-
|
|
48
|
-
|
|
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.
|
|
84
|
-
|
|
85
|
-
|
|
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
|
|
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 =
|
|
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', '
|
|
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', '
|
|
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
|
-
//
|
|
177
|
+
// Creates a converter that adds `<a>` to linked block image view elements.
|
|
148
178
|
//
|
|
149
179
|
// @private
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
226
|
+
function downcastImageLinkManualDecorator( decorator ) {
|
|
210
227
|
return dispatcher => {
|
|
211
|
-
dispatcher.on( `attribute:${ decorator.id }:
|
|
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(
|
|
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 =
|
|
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
|
|
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 `<
|
|
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
|
-
}
|
package/src/linkimageui.js
CHANGED
|
@@ -30,7 +30,7 @@ export default class LinkImageUI extends Plugin {
|
|
|
30
30
|
* @inheritDoc
|
|
31
31
|
*/
|
|
32
32
|
static get requires() {
|
|
33
|
-
return [ LinkEditing, LinkUI, '
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
|
|
116
|
-
return false;
|
|
120
|
+
return imageUtils.isImage( selectedModelElement ) && selectedModelElement.hasAttribute( 'linkHref' );
|
|
117
121
|
}
|
|
118
|
-
|
|
119
|
-
return element.getChild( 0 ).is( 'element', 'a' );
|
|
120
122
|
}
|