@ckeditor/ckeditor5-block-quote
Version:
Block quote feature for CKEditor 5.
119 lines (118 loc) • 5.06 kB
JavaScript
/**
* @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/**
* @module block-quote/blockquoteediting
*/
import { Plugin } from 'ckeditor5/src/core';
import { Enter } from 'ckeditor5/src/enter';
import { Delete } from 'ckeditor5/src/typing';
import BlockQuoteCommand from './blockquotecommand';
/**
* The block quote editing.
*
* Introduces the `'blockQuote'` command and the `'blockQuote'` model element.
*
* @extends module:core/plugin~Plugin
*/
export default class BlockQuoteEditing extends Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'BlockQuoteEditing';
}
/**
* @inheritDoc
*/
static get requires() {
return [Enter, Delete];
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const schema = editor.model.schema;
editor.commands.add('blockQuote', new BlockQuoteCommand(editor));
schema.register('blockQuote', {
inheritAllFrom: '$container'
});
editor.conversion.elementToElement({ model: 'blockQuote', view: 'blockquote' });
// Postfixer which cleans incorrect model states connected with block quotes.
editor.model.document.registerPostFixer(writer => {
const changes = editor.model.document.differ.getChanges();
for (const entry of changes) {
if (entry.type == 'insert') {
const element = entry.position.nodeAfter;
if (!element) {
// We are inside a text node.
continue;
}
if (element.is('element', 'blockQuote') && element.isEmpty) {
// Added an empty blockQuote - remove it.
writer.remove(element);
return true;
}
else if (element.is('element', 'blockQuote') && !schema.checkChild(entry.position, element)) {
// Added a blockQuote in incorrect place. Unwrap it so the content inside is not lost.
writer.unwrap(element);
return true;
}
else if (element.is('element')) {
// Just added an element. Check that all children meet the scheme rules.
const range = writer.createRangeIn(element);
for (const child of range.getItems()) {
if (child.is('element', 'blockQuote') &&
!schema.checkChild(writer.createPositionBefore(child), child)) {
writer.unwrap(child);
return true;
}
}
}
}
else if (entry.type == 'remove') {
const parent = entry.position.parent;
if (parent.is('element', 'blockQuote') && parent.isEmpty) {
// Something got removed and now blockQuote is empty. Remove the blockQuote as well.
writer.remove(parent);
return true;
}
}
}
return false;
});
const viewDocument = this.editor.editing.view.document;
const selection = editor.model.document.selection;
const blockQuoteCommand = editor.commands.get('blockQuote');
// Overwrite default Enter key behavior.
// If Enter key is pressed with selection collapsed in empty block inside a quote, break the quote.
this.listenTo(viewDocument, 'enter', (evt, data) => {
if (!selection.isCollapsed || !blockQuoteCommand.value) {
return;
}
const positionParent = selection.getLastPosition().parent;
if (positionParent.isEmpty) {
editor.execute('blockQuote');
editor.editing.view.scrollToTheSelection();
data.preventDefault();
evt.stop();
}
}, { context: 'blockquote' });
// Overwrite default Backspace key behavior.
// If Backspace key is pressed with selection collapsed in first empty block inside a quote, break the quote.
this.listenTo(viewDocument, 'delete', (evt, data) => {
if (data.direction != 'backward' || !selection.isCollapsed || !blockQuoteCommand.value) {
return;
}
const positionParent = selection.getLastPosition().parent;
if (positionParent.isEmpty && !positionParent.previousSibling) {
editor.execute('blockQuote');
editor.editing.view.scrollToTheSelection();
data.preventDefault();
evt.stop();
}
}, { context: 'blockquote' });
}
}