UNPKG

@ckeditor/ckeditor5-paragraph

Version:

Paragraph feature for CKEditor 5.

299 lines (293 loc) • 9.26 kB
/** * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options */ import { Command, Plugin } from "@ckeditor/ckeditor5-core"; import { first } from "@ckeditor/ckeditor5-utils"; import { ButtonView } from "@ckeditor/ckeditor5-ui"; import { IconParagraph } from "@ckeditor/ckeditor5-icons"; /** * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options */ /** * @module paragraph/paragraphcommand */ /** * The paragraph command. */ var ParagraphCommand = class extends Command { constructor(editor) { super(editor); this._isEnabledBasedOnSelection = false; } /** * @inheritDoc */ refresh() { const model = this.editor.model; const document = model.document; const block = first(document.selection.getSelectedBlocks()); this.value = !!block && block.is("element", "paragraph"); this.isEnabled = !!block && checkCanBecomeParagraph(block, model.schema); } /** * Executes the command. All the blocks (see {@link module:engine/model/schema~ModelSchema}) in the selection * will be turned to paragraphs. * * @fires execute * @param options Options for the executed command. * @param options.selection The selection that the command should be applied to. By default, * if not provided, the command is applied to the {@link module:engine/model/document~ModelDocument#selection}. */ execute(options = {}) { const model = this.editor.model; const document = model.document; const selection = options.selection || document.selection; if (!model.canEditAt(selection)) return; model.change((writer) => { const blocks = selection.getSelectedBlocks(); for (const block of blocks) if (!block.is("element", "paragraph") && checkCanBecomeParagraph(block, model.schema)) writer.rename(block, "paragraph"); }); } }; /** * Checks whether the given block can be replaced by a paragraph. * * @param block A block to be tested. * @param schema The schema of the document. */ function checkCanBecomeParagraph(block, schema) { return schema.checkChild(block.parent, "paragraph") && !schema.isObject(block); } /** * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options */ /** * @module paragraph/insertparagraphcommand */ /** * The insert paragraph command. It inserts a new paragraph at a specific * {@link module:engine/model/position~ModelPosition document position}. * * ```ts * // Insert a new paragraph before an element in the document. * editor.execute( 'insertParagraph', { * position: editor.model.createPositionBefore( element ) * } ); * ``` * * If a paragraph is disallowed in the context of the specific position, the command * will attempt to split position ancestors to find a place where it is possible * to insert a paragraph. * * **Note**: This command moves the selection to the inserted paragraph. */ var InsertParagraphCommand = class extends Command { constructor(editor) { super(editor); this._isEnabledBasedOnSelection = false; } /** * Executes the command. * * @param options Options for the executed command. * @param options.position The model position at which the new paragraph will be inserted. * @param options.attributes Attributes keys and values to set on a inserted paragraph. * @fires execute */ execute(options) { const model = this.editor.model; const attributes = options.attributes; let position = options.position; if (!model.canEditAt(position)) return null; return model.change((writer) => { position = this._findPositionToInsertParagraph(position, writer); if (!position) return null; const paragraph = writer.createElement("paragraph"); if (attributes) model.schema.setAllowedAttributes(paragraph, attributes, writer); model.insertContent(paragraph, position); writer.setSelection(paragraph, "in"); return writer.createPositionAt(paragraph, 0); }); } /** * Returns the best position to insert a new paragraph. */ _findPositionToInsertParagraph(position, writer) { const model = this.editor.model; if (model.schema.checkChild(position, "paragraph")) return position; const allowedParent = model.schema.findAllowedParent(position, "paragraph"); if (!allowedParent) return null; const positionParent = position.parent; const isTextAllowed = model.schema.checkChild(positionParent, "$text"); if (positionParent.isEmpty || isTextAllowed && position.isAtEnd) return model.createPositionAfter(positionParent); if (!positionParent.isEmpty && isTextAllowed && position.isAtStart) return model.createPositionBefore(positionParent); return writer.split(position, allowedParent).position; } }; /** * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options */ /** * @module paragraph/paragraph */ /** * The paragraph feature for the editor. * * It introduces the `<paragraph>` element in the model which renders as a `<p>` element in the DOM and data. * * It also brings two editors commands: * * * The {@link module:paragraph/paragraphcommand~ParagraphCommand `'paragraph'`} command that converts all * blocks in the model selection into paragraphs. * * The {@link module:paragraph/insertparagraphcommand~InsertParagraphCommand `'insertParagraph'`} command * that inserts a new paragraph at a specified location in the model. */ var Paragraph = class Paragraph extends Plugin { /** * @inheritDoc */ static get pluginName() { return "Paragraph"; } /** * @inheritDoc */ static get isOfficialPlugin() { return true; } /** * @inheritDoc */ init() { const editor = this.editor; const model = editor.model; editor.commands.add("paragraph", new ParagraphCommand(editor)); editor.commands.add("insertParagraph", new InsertParagraphCommand(editor)); model.schema.register("paragraph", { inheritAllFrom: "$block" }); editor.conversion.elementToElement({ model: "paragraph", view: "p" }); editor.conversion.for("upcast").elementToElement({ model: (viewElement, { writer }) => { if (!Paragraph.paragraphLikeElements.has(viewElement.name)) return null; if (viewElement.isEmpty) return null; return writer.createElement("paragraph"); }, view: /.+/, converterPriority: "low" }); } /** * A list of element names which should be treated by the autoparagraphing algorithms as * paragraph-like. This means that e.g. the following content: * * ```html * <h1>Foo</h1> * <table> * <tr> * <td>X</td> * <td> * <ul> * <li>Y</li> * <li>Z</li> * </ul> * </td> * </tr> * </table> * ``` * * contains five paragraph-like elements: `<h1>`, two `<td>`s and two `<li>`s. * Hence, if none of the features is going to convert those elements the above content will be automatically handled * by the paragraph feature and converted to: * * ```html * <p>Foo</p> * <p>X</p> * <p>Y</p> * <p>Z</p> * ``` * * Note: The `<td>` containing two `<li>` elements was ignored as the innermost paragraph-like elements * have a priority upon conversion. */ static paragraphLikeElements = new Set([ "blockquote", "dd", "div", "dt", "h1", "h2", "h3", "h4", "h5", "h6", "li", "p", "td", "th" ]); }; /** * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options */ /** * @module paragraph/paragraphbuttonui */ /** * This plugin defines the `'paragraph'` button. It can be used together with * {@link module:heading/headingbuttonsui~HeadingButtonsUI} to replace the standard heading dropdown. * * This plugin is not loaded automatically by the {@link module:paragraph/paragraph~Paragraph} plugin. It must * be added manually. * * ```ts * ClassicEditor * .create( { * plugins: [ ..., Heading, Paragraph, HeadingButtonsUI, ParagraphButtonUI ] * toolbar: [ 'paragraph', 'heading1', 'heading2', 'heading3' ] * } ) * .then( ... ) * .catch( ... ); * ``` */ var ParagraphButtonUI = class extends Plugin { /** * @inheritDoc */ static get requires() { return [Paragraph]; } /** * @inheritDoc */ init() { const editor = this.editor; const t = editor.t; editor.ui.componentFactory.add("paragraph", (locale) => { const view = new ButtonView(locale); const command = editor.commands.get("paragraph"); view.label = t("Paragraph"); view.icon = IconParagraph; view.tooltip = true; view.isToggleable = true; view.bind("isEnabled").to(command); view.bind("isOn").to(command, "value"); view.on("execute", () => { editor.execute("paragraph"); }); return view; }); } }; /** * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options */ export { InsertParagraphCommand, Paragraph, ParagraphButtonUI, ParagraphCommand }; //# sourceMappingURL=index.js.map