UNPKG

4.11 kBJavaScriptView Raw
1/**
2 * @license Copyright (c) 2003-2024, 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 * @module image/image/imageplaceholder
7 */
8import { Plugin } from 'ckeditor5/src/core.js';
9import ImageUtils from '../imageutils.js';
10import ImageLoadObserver from './imageloadobserver.js';
11import '../../theme/imageplaceholder.css';
12/**
13 * Adds support for image placeholder that is automatically removed when the image is loaded.
14 */
15export default class ImagePlaceholder extends Plugin {
16 /**
17 * @inheritDoc
18 */
19 static get requires() {
20 return [ImageUtils];
21 }
22 /**
23 * @inheritDoc
24 */
25 static get pluginName() {
26 return 'ImagePlaceholder';
27 }
28 /**
29 * @inheritDoc
30 */
31 afterInit() {
32 this._setupSchema();
33 this._setupConversion();
34 this._setupLoadListener();
35 }
36 /**
37 * Extends model schema.
38 */
39 _setupSchema() {
40 const schema = this.editor.model.schema;
41 // Wait for ImageBlockEditing or ImageInlineEditing to register their elements first,
42 // that's why doing this in afterInit() instead of init().
43 if (schema.isRegistered('imageBlock')) {
44 schema.extend('imageBlock', {
45 allowAttributes: ['placeholder']
46 });
47 }
48 if (schema.isRegistered('imageInline')) {
49 schema.extend('imageInline', {
50 allowAttributes: ['placeholder']
51 });
52 }
53 }
54 /**
55 * Registers converters.
56 */
57 _setupConversion() {
58 const editor = this.editor;
59 const conversion = editor.conversion;
60 const imageUtils = editor.plugins.get('ImageUtils');
61 conversion.for('editingDowncast').add(dispatcher => {
62 dispatcher.on('attribute:placeholder', (evt, data, conversionApi) => {
63 if (!conversionApi.consumable.test(data.item, evt.name)) {
64 return;
65 }
66 if (!data.item.is('element', 'imageBlock') && !data.item.is('element', 'imageInline')) {
67 return;
68 }
69 conversionApi.consumable.consume(data.item, evt.name);
70 const viewWriter = conversionApi.writer;
71 const element = conversionApi.mapper.toViewElement(data.item);
72 const img = imageUtils.findViewImgElement(element);
73 if (data.attributeNewValue) {
74 viewWriter.addClass('image_placeholder', img);
75 viewWriter.setStyle('background-image', `url(${data.attributeNewValue})`, img);
76 viewWriter.setCustomProperty('editingPipeline:doNotReuseOnce', true, img);
77 }
78 else {
79 viewWriter.removeClass('image_placeholder', img);
80 viewWriter.removeStyle('background-image', img);
81 }
82 });
83 });
84 }
85 /**
86 * Prepares listener for image load.
87 */
88 _setupLoadListener() {
89 const editor = this.editor;
90 const model = editor.model;
91 const editing = editor.editing;
92 const editingView = editing.view;
93 const imageUtils = editor.plugins.get('ImageUtils');
94 editingView.addObserver(ImageLoadObserver);
95 this.listenTo(editingView.document, 'imageLoaded', (evt, domEvent) => {
96 const imgViewElement = editingView.domConverter.mapDomToView(domEvent.target);
97 if (!imgViewElement) {
98 return;
99 }
100 const viewElement = imageUtils.getImageWidgetFromImageView(imgViewElement);
101 if (!viewElement) {
102 return;
103 }
104 const modelElement = editing.mapper.toModelElement(viewElement);
105 if (!modelElement || !modelElement.hasAttribute('placeholder')) {
106 return;
107 }
108 model.enqueueChange({ isUndoable: false }, writer => {
109 writer.removeAttribute('placeholder', modelElement);
110 });
111 });
112 }
113}