UNPKG

5.07 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 block-quote/blockquoteediting
7 */
8import { Plugin } from 'ckeditor5/src/core.js';
9import { Enter } from 'ckeditor5/src/enter.js';
10import { Delete } from 'ckeditor5/src/typing.js';
11import BlockQuoteCommand from './blockquotecommand.js';
12/**
13 * The block quote editing.
14 *
15 * Introduces the `'blockQuote'` command and the `'blockQuote'` model element.
16 *
17 * @extends module:core/plugin~Plugin
18 */
19export default class BlockQuoteEditing extends Plugin {
20 /**
21 * @inheritDoc
22 */
23 static get pluginName() {
24 return 'BlockQuoteEditing';
25 }
26 /**
27 * @inheritDoc
28 */
29 static get requires() {
30 return [Enter, Delete];
31 }
32 /**
33 * @inheritDoc
34 */
35 init() {
36 const editor = this.editor;
37 const schema = editor.model.schema;
38 editor.commands.add('blockQuote', new BlockQuoteCommand(editor));
39 schema.register('blockQuote', {
40 inheritAllFrom: '$container'
41 });
42 editor.conversion.elementToElement({ model: 'blockQuote', view: 'blockquote' });
43 // Postfixer which cleans incorrect model states connected with block quotes.
44 editor.model.document.registerPostFixer(writer => {
45 const changes = editor.model.document.differ.getChanges();
46 for (const entry of changes) {
47 if (entry.type == 'insert') {
48 const element = entry.position.nodeAfter;
49 if (!element) {
50 // We are inside a text node.
51 continue;
52 }
53 if (element.is('element', 'blockQuote') && element.isEmpty) {
54 // Added an empty blockQuote - remove it.
55 writer.remove(element);
56 return true;
57 }
58 else if (element.is('element', 'blockQuote') && !schema.checkChild(entry.position, element)) {
59 // Added a blockQuote in incorrect place. Unwrap it so the content inside is not lost.
60 writer.unwrap(element);
61 return true;
62 }
63 else if (element.is('element')) {
64 // Just added an element. Check that all children meet the scheme rules.
65 const range = writer.createRangeIn(element);
66 for (const child of range.getItems()) {
67 if (child.is('element', 'blockQuote') &&
68 !schema.checkChild(writer.createPositionBefore(child), child)) {
69 writer.unwrap(child);
70 return true;
71 }
72 }
73 }
74 }
75 else if (entry.type == 'remove') {
76 const parent = entry.position.parent;
77 if (parent.is('element', 'blockQuote') && parent.isEmpty) {
78 // Something got removed and now blockQuote is empty. Remove the blockQuote as well.
79 writer.remove(parent);
80 return true;
81 }
82 }
83 }
84 return false;
85 });
86 const viewDocument = this.editor.editing.view.document;
87 const selection = editor.model.document.selection;
88 const blockQuoteCommand = editor.commands.get('blockQuote');
89 // Overwrite default Enter key behavior.
90 // If Enter key is pressed with selection collapsed in empty block inside a quote, break the quote.
91 this.listenTo(viewDocument, 'enter', (evt, data) => {
92 if (!selection.isCollapsed || !blockQuoteCommand.value) {
93 return;
94 }
95 const positionParent = selection.getLastPosition().parent;
96 if (positionParent.isEmpty) {
97 editor.execute('blockQuote');
98 editor.editing.view.scrollToTheSelection();
99 data.preventDefault();
100 evt.stop();
101 }
102 }, { context: 'blockquote' });
103 // Overwrite default Backspace key behavior.
104 // If Backspace key is pressed with selection collapsed in first empty block inside a quote, break the quote.
105 this.listenTo(viewDocument, 'delete', (evt, data) => {
106 if (data.direction != 'backward' || !selection.isCollapsed || !blockQuoteCommand.value) {
107 return;
108 }
109 const positionParent = selection.getLastPosition().parent;
110 if (positionParent.isEmpty && !positionParent.previousSibling) {
111 editor.execute('blockQuote');
112 editor.editing.view.scrollToTheSelection();
113 data.preventDefault();
114 evt.stop();
115 }
116 }, { context: 'blockquote' });
117 }
118}