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 paragraph/insertparagraphcommand
|
7 | */
|
8 | import { Command } from '@ckeditor/ckeditor5-core';
|
9 | /**
|
10 | * The insert paragraph command. It inserts a new paragraph at a specific
|
11 | * {@link module:engine/model/position~Position document position}.
|
12 | *
|
13 | * ```ts
|
14 | * // Insert a new paragraph before an element in the document.
|
15 | * editor.execute( 'insertParagraph', {
|
16 | * position: editor.model.createPositionBefore( element )
|
17 | * } );
|
18 | * ```
|
19 | *
|
20 | * If a paragraph is disallowed in the context of the specific position, the command
|
21 | * will attempt to split position ancestors to find a place where it is possible
|
22 | * to insert a paragraph.
|
23 | *
|
24 | * **Note**: This command moves the selection to the inserted paragraph.
|
25 | */
|
26 | export default class InsertParagraphCommand extends Command {
|
27 | constructor(editor) {
|
28 | super(editor);
|
29 | // Since this command passes position in execution block instead of selection, it should be checked directly.
|
30 | this._isEnabledBasedOnSelection = false;
|
31 | }
|
32 | /**
|
33 | * Executes the command.
|
34 | *
|
35 | * @param options Options for the executed command.
|
36 | * @param options.position The model position at which the new paragraph will be inserted.
|
37 | * @param options.attributes Attributes keys and values to set on a inserted paragraph.
|
38 | * @fires execute
|
39 | */
|
40 | execute(options) {
|
41 | const model = this.editor.model;
|
42 | const attributes = options.attributes;
|
43 | let position = options.position;
|
44 | // Don't execute command if position is in non-editable place.
|
45 | if (!model.canEditAt(position)) {
|
46 | return;
|
47 | }
|
48 | model.change(writer => {
|
49 | position = this._findPositionToInsertParagraph(position, writer);
|
50 | if (!position) {
|
51 | return;
|
52 | }
|
53 | const paragraph = writer.createElement('paragraph');
|
54 | if (attributes) {
|
55 | model.schema.setAllowedAttributes(paragraph, attributes, writer);
|
56 | }
|
57 | model.insertContent(paragraph, position);
|
58 | writer.setSelection(paragraph, 'in');
|
59 | });
|
60 | }
|
61 | /**
|
62 | * Returns the best position to insert a new paragraph.
|
63 | */
|
64 | _findPositionToInsertParagraph(position, writer) {
|
65 | const model = this.editor.model;
|
66 | if (model.schema.checkChild(position, 'paragraph')) {
|
67 | return position;
|
68 | }
|
69 | const allowedParent = model.schema.findAllowedParent(position, 'paragraph');
|
70 | // It could be there's no ancestor limit that would allow paragraph.
|
71 | // In theory, "paragraph" could be disallowed even in the "$root".
|
72 | if (!allowedParent) {
|
73 | return null;
|
74 | }
|
75 | const positionParent = position.parent;
|
76 | const isTextAllowed = model.schema.checkChild(positionParent, '$text');
|
77 | // At empty $block or at the end of $block.
|
78 | // <paragraph>[]</paragraph> ---> <paragraph></paragraph><paragraph>[]</paragraph>
|
79 | // <paragraph>foo[]</paragraph> ---> <paragraph>foo</paragraph><paragraph>[]</paragraph>
|
80 | if (positionParent.isEmpty || isTextAllowed && position.isAtEnd) {
|
81 | return model.createPositionAfter(positionParent);
|
82 | }
|
83 | // At the start of $block with text.
|
84 | // <paragraph>[]foo</paragraph> ---> <paragraph>[]</paragraph><paragraph>foo</paragraph>
|
85 | if (!positionParent.isEmpty && isTextAllowed && position.isAtStart) {
|
86 | return model.createPositionBefore(positionParent);
|
87 | }
|
88 | return writer.split(position, allowedParent).position;
|
89 | }
|
90 | }
|