2025-03-13 18:27:05 +02:00
|
|
|
/**
|
|
|
|
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
|
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
2025-03-13 19:02:10 +02:00
|
|
|
* @module admonition/admonitionediting
|
2025-03-13 18:27:05 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
import { Plugin } from 'ckeditor5/src/core.js';
|
|
|
|
import { Enter, type ViewDocumentEnterEvent } from 'ckeditor5/src/enter.js';
|
|
|
|
import { Delete, type ViewDocumentDeleteEvent } from 'ckeditor5/src/typing.js';
|
|
|
|
|
2025-03-13 18:35:10 +02:00
|
|
|
import AdmonitionCommand from './admonitioncommand.js';
|
2025-03-13 18:27:05 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The block quote editing.
|
|
|
|
*
|
2025-03-13 19:28:57 +02:00
|
|
|
* Introduces the `'admonition'` command and the `'aside'` model element.
|
2025-03-13 18:27:05 +02:00
|
|
|
*
|
|
|
|
* @extends module:core/plugin~Plugin
|
|
|
|
*/
|
2025-03-13 18:33:39 +02:00
|
|
|
export default class AdmonitionEditing extends Plugin {
|
2025-03-13 18:27:05 +02:00
|
|
|
/**
|
|
|
|
* @inheritDoc
|
|
|
|
*/
|
|
|
|
public static get pluginName() {
|
2025-03-13 18:41:37 +02:00
|
|
|
return 'AdmonitionEditing' as const;
|
2025-03-13 18:27:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @inheritDoc
|
|
|
|
*/
|
|
|
|
public static get requires() {
|
|
|
|
return [ Enter, Delete ] as const;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @inheritDoc
|
|
|
|
*/
|
|
|
|
public init(): void {
|
|
|
|
const editor = this.editor;
|
|
|
|
const schema = editor.model.schema;
|
|
|
|
|
2025-03-13 19:02:10 +02:00
|
|
|
editor.commands.add( 'admonition', new AdmonitionCommand( editor ) );
|
2025-03-13 18:27:05 +02:00
|
|
|
|
2025-03-13 19:28:57 +02:00
|
|
|
schema.register( 'aside', {
|
2025-03-13 18:27:05 +02:00
|
|
|
inheritAllFrom: '$container'
|
|
|
|
} );
|
|
|
|
|
2025-03-13 19:28:57 +02:00
|
|
|
editor.conversion.elementToElement( { model: 'aside', view: 'aside' } );
|
2025-03-13 18:27:05 +02:00
|
|
|
|
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
2025-03-13 19:28:57 +02:00
|
|
|
if ( element.is( 'element', 'aside' ) && element.isEmpty ) {
|
|
|
|
// Added an empty aside - remove it.
|
2025-03-13 18:27:05 +02:00
|
|
|
writer.remove( element );
|
|
|
|
|
|
|
|
return true;
|
2025-03-13 19:28:57 +02:00
|
|
|
} else if ( element.is( 'element', 'aside' ) && !schema.checkChild( entry.position, element ) ) {
|
|
|
|
// Added a aside in incorrect place. Unwrap it so the content inside is not lost.
|
2025-03-13 18:27:05 +02:00
|
|
|
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 (
|
2025-03-13 19:28:57 +02:00
|
|
|
child.is( 'element', 'aside' ) &&
|
2025-03-13 18:27:05 +02:00
|
|
|
!schema.checkChild( writer.createPositionBefore( child ), child )
|
|
|
|
) {
|
|
|
|
writer.unwrap( child );
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if ( entry.type == 'remove' ) {
|
|
|
|
const parent = entry.position.parent;
|
|
|
|
|
2025-03-13 19:28:57 +02:00
|
|
|
if ( parent.is( 'element', 'aside' ) && parent.isEmpty ) {
|
|
|
|
// Something got removed and now aside is empty. Remove the aside as well.
|
2025-03-13 18:27:05 +02:00
|
|
|
writer.remove( parent );
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
} );
|
|
|
|
|
|
|
|
const viewDocument = this.editor.editing.view.document;
|
|
|
|
const selection = editor.model.document.selection;
|
2025-03-13 19:02:10 +02:00
|
|
|
const admonitionCommand: AdmonitionCommand = editor.commands.get( 'admonition' )!;
|
2025-03-13 18:27:05 +02:00
|
|
|
|
|
|
|
// Overwrite default Enter key behavior.
|
|
|
|
// If Enter key is pressed with selection collapsed in empty block inside a quote, break the quote.
|
|
|
|
this.listenTo<ViewDocumentEnterEvent>( viewDocument, 'enter', ( evt, data ) => {
|
2025-03-13 19:02:10 +02:00
|
|
|
if ( !selection.isCollapsed || !admonitionCommand.value ) {
|
2025-03-13 18:27:05 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const positionParent = selection.getLastPosition()!.parent;
|
|
|
|
|
|
|
|
if ( positionParent.isEmpty ) {
|
2025-03-13 19:02:10 +02:00
|
|
|
editor.execute( 'admonition' );
|
2025-03-13 18:27:05 +02:00
|
|
|
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<ViewDocumentDeleteEvent>( viewDocument, 'delete', ( evt, data ) => {
|
2025-03-13 19:02:10 +02:00
|
|
|
if ( data.direction != 'backward' || !selection.isCollapsed || !admonitionCommand!.value ) {
|
2025-03-13 18:27:05 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const positionParent = selection.getLastPosition()!.parent;
|
|
|
|
|
|
|
|
if ( positionParent.isEmpty && !positionParent.previousSibling ) {
|
2025-03-13 19:02:10 +02:00
|
|
|
editor.execute( 'admonition' );
|
2025-03-13 18:27:05 +02:00
|
|
|
editor.editing.view.scrollToTheSelection();
|
|
|
|
|
|
|
|
data.preventDefault();
|
|
|
|
evt.stop();
|
|
|
|
}
|
|
|
|
}, { context: 'blockquote' } );
|
|
|
|
}
|
|
|
|
}
|