1 | /**
|
2 | * @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
|
3 | * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
4 | */
|
5 |
|
6 | /**
|
7 | * @module image/image/imagetypecommand
|
8 | */
|
9 |
|
10 | import { Command } from 'ckeditor5/src/core';
|
11 |
|
12 | /**
|
13 | * The image type command. It changes the type of a selected image, depending on the configuration.
|
14 | *
|
15 | * @extends module:core/command~Command
|
16 | */
|
17 | export default class ImageTypeCommand extends Command {
|
18 | /**
|
19 | * @inheritDoc
|
20 | *
|
21 | * @param {module:core/editor/editor~Editor} editor
|
22 | * @param {'imageBlock'|'imageInline'} modelElementName Model element name the command converts to.
|
23 | */
|
24 | constructor( editor, modelElementName ) {
|
25 | super( editor );
|
26 |
|
27 | /**
|
28 | * Model element name the command converts to.
|
29 | *
|
30 | * @readonly
|
31 | * @private
|
32 | * @member {'imageBlock'|'imageInline'}
|
33 | */
|
34 | this._modelElementName = modelElementName;
|
35 | }
|
36 |
|
37 | /**
|
38 | * @inheritDoc
|
39 | */
|
40 | refresh() {
|
41 | const editor = this.editor;
|
42 | const imageUtils = editor.plugins.get( 'ImageUtils' );
|
43 | const element = imageUtils.getClosestSelectedImageElement( this.editor.model.document.selection );
|
44 |
|
45 | if ( this._modelElementName === 'imageBlock' ) {
|
46 | this.isEnabled = imageUtils.isInlineImage( element );
|
47 | } else {
|
48 | this.isEnabled = imageUtils.isBlockImage( element );
|
49 | }
|
50 | }
|
51 |
|
52 | /**
|
53 | * Executes the command and changes the type of a selected image.
|
54 | *
|
55 | * @fires execute
|
56 | * @returns {Object|null} An object containing references to old and new model image elements
|
57 | * (for before and after the change) so external integrations can hook into the decorated
|
58 | * `execute` event and handle this change. `null` if the type change failed.
|
59 | */
|
60 | execute() {
|
61 | const editor = this.editor;
|
62 | const model = this.editor.model;
|
63 | const imageUtils = editor.plugins.get( 'ImageUtils' );
|
64 | const oldElement = imageUtils.getClosestSelectedImageElement( model.document.selection );
|
65 | const attributes = Object.fromEntries( oldElement.getAttributes() );
|
66 |
|
67 | // Don't change image type if "src" is missing (a broken image), unless there's "uploadId" set.
|
68 | // This state may happen during image upload (before it finishes) and it should be possible to change type
|
69 | // of the image in the meantime.
|
70 | if ( !attributes.src && !attributes.uploadId ) {
|
71 | return null;
|
72 | }
|
73 |
|
74 | return model.change( writer => {
|
75 | // Get all markers that contain the old image element.
|
76 | const markers = Array.from( model.markers )
|
77 | .filter( marker => marker.getRange().containsItem( oldElement ) );
|
78 |
|
79 | const newElement = imageUtils.insertImage( attributes, model.createSelection( oldElement, 'on' ), this._modelElementName );
|
80 |
|
81 | if ( !newElement ) {
|
82 | return null;
|
83 | }
|
84 |
|
85 | const newElementRange = writer.createRangeOn( newElement );
|
86 |
|
87 | // Expand the previously intersecting markers' ranges to include the new image element.
|
88 | for ( const marker of markers ) {
|
89 | const markerRange = marker.getRange();
|
90 |
|
91 | // Join the survived part of the old marker range with the new element range
|
92 | // (loosely because there could be some new paragraph or the existing one might got split).
|
93 | const range = markerRange.root.rootName != '$graveyard' ?
|
94 | markerRange.getJoined( newElementRange, true ) : newElementRange;
|
95 |
|
96 | writer.updateMarker( marker, { range } );
|
97 | }
|
98 |
|
99 | return {
|
100 | oldElement,
|
101 | newElement
|
102 | };
|
103 | } );
|
104 | }
|
105 | }
|