diff --git a/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/CHANGELOG.md b/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/CHANGELOG.md new file mode 100644 index 000000000..01711a3f7 --- /dev/null +++ b/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/CHANGELOG.md @@ -0,0 +1,4 @@ +Changelog +========= + +All changes in the package are documented in https://github.com/ckeditor/ckeditor5/blob/master/CHANGELOG.md. diff --git a/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/CONTRIBUTING.md b/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/CONTRIBUTING.md new file mode 100644 index 000000000..ae3ecb8ff --- /dev/null +++ b/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/CONTRIBUTING.md @@ -0,0 +1,4 @@ +Contributing +======================================== + +See the [official contributors' guide to CKEditor 5](https://ckeditor.com/docs/ckeditor5/latest/framework/contributing/contributing.html) to learn more. diff --git a/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/LICENSE.md b/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/LICENSE.md new file mode 100644 index 000000000..106d679d4 --- /dev/null +++ b/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/LICENSE.md @@ -0,0 +1,17 @@ +Software License Agreement +========================== + +**CKEditor 5 block quote feature** – https://github.com/ckeditor/ckeditor5-block-quote
+Copyright (c) 2003–2024, [CKSource Holding sp. z o.o.](https://cksource.com) All rights reserved. + +Licensed under the terms of [GNU General Public License Version 2 or later](http://www.gnu.org/licenses/gpl.html). + +Sources of Intellectual Property Included in CKEditor +----------------------------------------------------- + +Where not otherwise indicated, all CKEditor content is authored by CKSource engineers and consists of CKSource-owned intellectual property. In some specific instances, CKEditor will incorporate work done by developers outside of CKSource with their express permission. + +Trademarks +---------- + +**CKEditor** is a trademark of [CKSource Holding sp. z o.o.](https://cksource.com) All other brand and product names are trademarks, registered trademarks, or service marks of their respective holders. diff --git a/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/README.md b/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/README.md new file mode 100644 index 000000000..71656077a --- /dev/null +++ b/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/README.md @@ -0,0 +1,26 @@ +CKEditor 5 block quote feature +======================================== + +[![npm version](https://badge.fury.io/js/%40ckeditor%2Fckeditor5-block-quote.svg)](https://www.npmjs.com/package/@ckeditor/ckeditor5-block-quote) +[![Coverage Status](https://coveralls.io/repos/github/ckeditor/ckeditor5/badge.svg?branch=master)](https://coveralls.io/github/ckeditor/ckeditor5?branch=master) +[![Build Status](https://travis-ci.com/ckeditor/ckeditor5.svg?branch=master)](https://app.travis-ci.com/github/ckeditor/ckeditor5) + +This package implements block quote support for CKEditor 5. + +## Demo + +Check out the [demo in the block quote feature guide](https://ckeditor.com/docs/ckeditor5/latest/features/block-quote.html#demo). + +## Documentation + +See the [`@ckeditor/ckeditor5-block-quote` package](https://ckeditor.com/docs/ckeditor5/latest/api/block-quote.html) page in [CKEditor 5 documentation](https://ckeditor.com/docs/ckeditor5/latest/). + +## Installation + +```bash +npm install ckeditor5 +``` + +## License + +Licensed under the terms of [GNU General Public License Version 2 or later](http://www.gnu.org/licenses/gpl.html). For full details about the license, please check the `LICENSE.md` file or [https://ckeditor.com/legal/ckeditor-oss-license](https://ckeditor.com/legal/ckeditor-oss-license). diff --git a/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/ckeditor5-metadata.json b/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/ckeditor5-metadata.json new file mode 100644 index 000000000..3218dff50 --- /dev/null +++ b/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/ckeditor5-metadata.json @@ -0,0 +1,23 @@ +{ + "plugins": [ + { + "name": "Admonitions", + "className": "Admonition", + "description": "Implements admonitions (warning, info boxes) in a similar fashion to blockquotes", + "docs": "features/block-quote.html", + "path": "src/admonition.js", + "uiComponents": [ + { + "type": "Button", + "name": "admonition", + "iconPath": "theme/icons/admonition.svg" + } + ], + "htmlOutput": [ + { + "elements": "aside" + } + ] + } + ] +} diff --git a/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/lang/contexts.json b/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/lang/contexts.json new file mode 100644 index 000000000..421249ff6 --- /dev/null +++ b/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/lang/contexts.json @@ -0,0 +1,3 @@ +{ + "Admonition": "Toolbar button tooltip for the Admonition feature." +} diff --git a/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/lang/translations/en.po b/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/lang/translations/en.po new file mode 100644 index 000000000..b5032283a --- /dev/null +++ b/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/lang/translations/en.po @@ -0,0 +1,22 @@ +# Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. +# +# !!! IMPORTANT !!! +# +# Before you edit this file, please keep in mind that contributing to the project +# translations is possible ONLY via the Transifex online service. +# +# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5. +# +# To learn more, check out the official contributor's guide: +# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html +# +msgid "" +msgstr "" +"Language: \n" +"Language-Team: \n" +"Plural-Forms: \n" +"Content-Type: text/plain; charset=UTF-8\n" + +msgctxt "Toolbar button tooltip for the Admoniton feature." +msgid "Admonition" +msgstr "Admonition" diff --git a/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/package.json b/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/package.json new file mode 100644 index 000000000..e93c8fdfc --- /dev/null +++ b/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/package.json @@ -0,0 +1,62 @@ +{ + "name": "@ckeditor/ckeditor5-admonition", + "version": "43.2.0", + "description": "Admonition (info box, warning box) feature for CKEditor 5.", + "keywords": [ + "ckeditor", + "ckeditor5", + "ckeditor 5", + "ckeditor5-feature", + "ckeditor5-plugin", + "ckeditor5-dll" + ], + "type": "module", + "main": "src/index.ts", + "dependencies": { + "@ckeditor/ckeditor5-core": "43.2.0", + "@ckeditor/ckeditor5-enter": "43.2.0", + "@ckeditor/ckeditor5-typing": "43.2.0", + "@ckeditor/ckeditor5-ui": "43.2.0", + "@ckeditor/ckeditor5-utils": "43.2.0", + "ckeditor5": "43.2.0" + }, + "devDependencies": { + "@ckeditor/ckeditor5-basic-styles": "43.2.0", + "@ckeditor/ckeditor5-dev-utils": "^43.0.0", + "@ckeditor/ckeditor5-editor-classic": "43.2.0", + "@ckeditor/ckeditor5-engine": "43.2.0", + "@ckeditor/ckeditor5-heading": "43.2.0", + "@ckeditor/ckeditor5-image": "43.2.0", + "@ckeditor/ckeditor5-list": "43.2.0", + "@ckeditor/ckeditor5-paragraph": "43.2.0", + "@ckeditor/ckeditor5-table": "43.2.0", + "@ckeditor/ckeditor5-theme-lark": "43.2.0", + "typescript": "5.0.4", + "webpack": "^5.94.0", + "webpack-cli": "^5.1.4" + }, + "author": "CKSource (http://cksource.com/)", + "license": "GPL-2.0-or-later", + "homepage": "https://ckeditor.com/ckeditor-5", + "bugs": "https://github.com/ckeditor/ckeditor5/issues", + "repository": { + "type": "git", + "url": "https://github.com/ckeditor/ckeditor5.git", + "directory": "packages/ckeditor5-admonition" + }, + "files": [ + "dist", + "lang", + "src/**/*.js", + "src/**/*.d.ts", + "theme", + "build", + "ckeditor5-metadata.json", + "CHANGELOG.md" + ], + "scripts": { + "dll:build": "webpack", + "build": "tsc -p ./tsconfig.json", + "build:dist": "node ../../scripts/build-package.mjs" + } +} diff --git a/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/src/admonition.ts b/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/src/admonition.ts new file mode 100644 index 000000000..b09dfb78d --- /dev/null +++ b/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/src/admonition.ts @@ -0,0 +1,40 @@ +/** + * @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 + */ + +/** + * @module admonition/admonition + */ + +import { Plugin } from 'ckeditor5/src/core.js'; + +import AdmonitionEditing from './admonitionediting.js'; +import AdmonitionUI from './admonitionui.js'; +import AdmonitionAutoformat from './admonitionautoformat.js'; + +/** + * The block quote plugin. + * + * For more information about this feature check the {@glink api/block-quote package page}. + * + * This is a "glue" plugin which loads the {@link module:block-quote/blockquoteediting~BlockQuoteEditing block quote editing feature} + * and {@link module:block-quote/blockquoteui~BlockQuoteUI block quote UI feature}. + * + * @extends module:core/plugin~Plugin + */ +export default class Admonition extends Plugin { + /** + * @inheritDoc + */ + public static get requires() { + return [ AdmonitionEditing, AdmonitionUI, AdmonitionAutoformat ] as const; + } + + /** + * @inheritDoc + */ + public static get pluginName() { + return 'Admonition' as const; + } +} diff --git a/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/src/admonitionautoformat.ts b/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/src/admonitionautoformat.ts new file mode 100644 index 000000000..ac104a845 --- /dev/null +++ b/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/src/admonitionautoformat.ts @@ -0,0 +1,42 @@ +import Plugin from "@ckeditor/ckeditor5-core/src/plugin"; +import Autoformat from "@ckeditor/ckeditor5-autoformat/src/autoformat"; +import blockAutoformatEditing from "@ckeditor/ckeditor5-autoformat/src/blockautoformatediting"; +import { AdmonitionType, ADMONITION_TYPES } from "./admonitioncommand"; + +function tryParseAdmonitionType(match: RegExpMatchArray) { + if (match.length !== 2) { + return; + } + + if ((ADMONITION_TYPES as readonly string[]).includes(match[1])) { + return match[1] as AdmonitionType; + } +} + +export default class AdmonitionAutoformat extends Plugin { + static get requires() { + return [ Autoformat ]; + } + + afterInit() { + if (!this.editor.commands.get("admonition")) { + return; + } + + const instance = (this as any); + blockAutoformatEditing(this.editor, instance, /^\!\!\[*\! (.+) $/, ({ match }) => { + const type = tryParseAdmonitionType(match); + + if (type) { + // User has entered the admonition type, so we insert as-is. + this.editor.execute("admonition", { forceValue: type }); + } else { + // User has not entered a valid type, assume it's part of the text of the admonition. + this.editor.execute("admonition"); + if (match.length > 1) { + this.editor.execute("insertText", { text: (match[1] ?? "") + " " }); + } + } + }); + } +} diff --git a/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/src/admonitioncommand.ts b/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/src/admonitioncommand.ts new file mode 100644 index 000000000..c9a6cae49 --- /dev/null +++ b/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/src/admonitioncommand.ts @@ -0,0 +1,276 @@ +/** + * @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 + */ + +/** + * @module admonition/admonitioncommand + */ + +import { Command } from 'ckeditor5/src/core.js'; +import { first } from 'ckeditor5/src/utils.js'; +import type { DocumentFragment, Element, Position, Range, Schema, Writer } from 'ckeditor5/src/engine.js'; + +/** + * The block quote command plugin. + * + * @extends module:core/command~Command + */ + +export const ADMONITION_TYPES = [ "note", "tip", "important", "caution", "warning" ] as const; +export const ADMONITION_TYPE_ATTRIBUTE = "admonitionType"; +export const DEFAULT_ADMONITION_TYPE = ADMONITION_TYPES[0]; +export type AdmonitionType = typeof ADMONITION_TYPES[number]; + +interface ExecuteOpts { + /** + * If set, it will force the command behavior. If `true`, the command will apply a block quote, + * otherwise the command will remove the block quote. If not set, the command will act basing on its current value. + */ + forceValue?: AdmonitionType; + /** + * If set to true and `forceValue` is not specified, the command will apply the previous admonition type (if the command was already executed). + */ + usePreviousChoice?: boolean +} + +export default class AdmonitionCommand extends Command { + /** + * Whether the selection starts in a block quote. + * + * @observable + * @readonly + */ + declare public value: AdmonitionType | false; + + private _lastType?: AdmonitionType; + + /** + * @inheritDoc + */ + public override refresh(): void { + this.value = this._getValue(); + this.isEnabled = this._checkEnabled(); + } + + /** + * Executes the command. When the command {@link #value is on}, all top-most block quotes within + * the selection will be removed. If it is off, all selected blocks will be wrapped with + * a block quote. + * + * @fires execute + * @param options Command options. + */ + public override execute( options: ExecuteOpts = {} ): void { + const model = this.editor.model; + const schema = model.schema; + const selection = model.document.selection; + + const blocks = Array.from( selection.getSelectedBlocks() ); + + const value = this._getType(options); + + model.change( writer => { + if ( !value ) { + this._removeQuote( writer, blocks.filter( findQuote ) ); + } else { + const blocksToQuote = blocks.filter( block => { + // Already quoted blocks needs to be considered while quoting too + // in order to reuse their elements. + return findQuote( block ) || checkCanBeQuoted( schema, block ); + } ); + + this._applyQuote( writer, blocksToQuote, value); + } + } ); + } + + private _getType(options: ExecuteOpts): AdmonitionType | false { + const value = (options.forceValue === undefined) ? !this.value : options.forceValue; + + // Allow removing the admonition. + if (!value) { + return false; + } + + // Prefer the type from the command, if any. + if (typeof value === "string") { + return value; + } + + // See if we can restore the previous language. + if (options.usePreviousChoice && this._lastType) { + return this._lastType; + } + + // Otherwise return a default. + return "note"; + } + + /** + * Checks the command's {@link #value}. + */ + private _getValue(): AdmonitionType | false { + const selection = this.editor.model.document.selection; + const firstBlock = first( selection.getSelectedBlocks() ); + if (!firstBlock) { + return false; + } + + // In the current implementation, the admonition must be an immediate parent of a block element. + const firstQuote = findQuote( firstBlock ); + if (firstQuote?.is("element")) { + return firstQuote.getAttribute(ADMONITION_TYPE_ATTRIBUTE) as AdmonitionType; + } + + return false; + } + + /** + * Checks whether the command can be enabled in the current context. + * + * @returns Whether the command should be enabled. + */ + private _checkEnabled(): boolean { + if ( this.value ) { + return true; + } + + const selection = this.editor.model.document.selection; + const schema = this.editor.model.schema; + + const firstBlock = first( selection.getSelectedBlocks() ); + + if ( !firstBlock ) { + return false; + } + + return checkCanBeQuoted( schema, firstBlock ); + } + + /** + * Removes the quote from given blocks. + * + * If blocks which are supposed to be "unquoted" are in the middle of a quote, + * start it or end it, then the quote will be split (if needed) and the blocks + * will be moved out of it, so other quoted blocks remained quoted. + */ + private _removeQuote( writer: Writer, blocks: Array ): void { + // Unquote all groups of block. Iterate in the reverse order to not break following ranges. + getRangesOfBlockGroups( writer, blocks ).reverse().forEach( groupRange => { + if ( groupRange.start.isAtStart && groupRange.end.isAtEnd ) { + writer.unwrap( groupRange.start.parent as Element ); + + return; + } + + // The group of blocks are at the beginning of an so let's move them left (out of the ). + if ( groupRange.start.isAtStart ) { + const positionBefore = writer.createPositionBefore( groupRange.start.parent as Element ); + + writer.move( groupRange, positionBefore ); + + return; + } + + // The blocks are in the middle of an so we need to split the after the last block + // so we move the items there. + if ( !groupRange.end.isAtEnd ) { + writer.split( groupRange.end ); + } + + // Now we are sure that groupRange.end.isAtEnd is true, so let's move the blocks right. + + const positionAfter = writer.createPositionAfter( groupRange.end.parent as Element ); + + writer.move( groupRange, positionAfter ); + } ); + } + + /** + * Applies the quote to given blocks. + */ + private _applyQuote( writer: Writer, blocks: Array, type?: AdmonitionType): void { + this._lastType = type; + const quotesToMerge: Array = []; + + // Quote all groups of block. Iterate in the reverse order to not break following ranges. + getRangesOfBlockGroups( writer, blocks ).reverse().forEach( groupRange => { + let quote = findQuote( groupRange.start ); + + if ( !quote ) { + const attributes: Record = {}; + attributes[ADMONITION_TYPE_ATTRIBUTE] = type; + quote = writer.createElement( 'aside', attributes); + + writer.wrap( groupRange, quote ); + } else if (quote.is("element")) { + this.editor.model.change((writer) => { + writer.setAttribute(ADMONITION_TYPE_ATTRIBUTE, type, quote as Element); + }); + } + + quotesToMerge.push( quote ); + } ); + + // Merge subsequent elements. Reverse the order again because this time we want to go through + // the elements in the source order (due to how merge works – it moves the right element's content + // to the first element and removes the right one. Since we may need to merge a couple of subsequent `` elements + // we want to keep the reference to the first (furthest left) one. + quotesToMerge.reverse().reduce( ( currentQuote, nextQuote ) => { + if ( currentQuote.nextSibling == nextQuote ) { + writer.merge( writer.createPositionAfter( currentQuote ) ); + + return currentQuote; + } + + return nextQuote; + } ); + } +} + +function findQuote( elementOrPosition: Element | Position ): Element | DocumentFragment | null { + return elementOrPosition.parent!.name == 'aside' ? elementOrPosition.parent : null; +} + +/** + * Returns a minimal array of ranges containing groups of subsequent blocks. + * + * content: abcdefgh + * blocks: [ a, b, d, f, g, h ] + * output ranges: [ab]c[d]e[fgh] + */ +function getRangesOfBlockGroups( writer: Writer, blocks: Array ): Array { + let startPosition; + let i = 0; + const ranges = []; + + while ( i < blocks.length ) { + const block = blocks[ i ]; + const nextBlock = blocks[ i + 1 ]; + + if ( !startPosition ) { + startPosition = writer.createPositionBefore( block ); + } + + if ( !nextBlock || block.nextSibling != nextBlock ) { + ranges.push( writer.createRange( startPosition, writer.createPositionAfter( block ) ) ); + startPosition = null; + } + + i++; + } + + return ranges; +} + +/** + * Checks whether can wrap the block. + */ +function checkCanBeQuoted( schema: Schema, block: Element ): boolean { + // TMP will be replaced with schema.checkWrap(). + const isBQAllowed = schema.checkChild( block.parent as Element, 'aside' ); + const isBlockAllowedInBQ = schema.checkChild( [ '$root', 'aside' ], block ); + + return isBQAllowed && isBlockAllowedInBQ; +} diff --git a/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/src/admonitionediting.ts b/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/src/admonitionediting.ts new file mode 100644 index 000000000..6af25b965 --- /dev/null +++ b/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/src/admonitionediting.ts @@ -0,0 +1,177 @@ +/** + * @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 + */ + +/** + * @module admonition/admonitionediting + */ + +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'; + +import AdmonitionCommand, { AdmonitionType, ADMONITION_TYPES, DEFAULT_ADMONITION_TYPE, ADMONITION_TYPE_ATTRIBUTE } from './admonitioncommand.js'; + +/** + * The block quote editing. + * + * Introduces the `'admonition'` command and the `'aside'` model element. + * + * @extends module:core/plugin~Plugin + */ +export default class AdmonitionEditing extends Plugin { + /** + * @inheritDoc + */ + public static get pluginName() { + return 'AdmonitionEditing' as const; + } + + /** + * @inheritDoc + */ + public static get requires() { + return [ Enter, Delete ] as const; + } + + /** + * @inheritDoc + */ + public init(): void { + const editor = this.editor; + const schema = editor.model.schema; + + editor.commands.add( 'admonition', new AdmonitionCommand( editor ) ); + + schema.register( 'aside', { + inheritAllFrom: '$container', + allowAttributes: ADMONITION_TYPE_ATTRIBUTE + } ); + + editor.conversion.for("upcast").elementToElement({ + view: { + name: "aside", + classes: "admonition", + }, + model: (viewElement, { writer }) => { + let type: AdmonitionType = DEFAULT_ADMONITION_TYPE; + for (const className of viewElement.getClassNames()) { + if (className !== "admonition" && (ADMONITION_TYPES as readonly string[]).includes(className)) { + type = className as AdmonitionType; + } + } + + const attributes: Record = {}; + attributes[ADMONITION_TYPE_ATTRIBUTE] = type; + return writer.createElement("aside", attributes); + } + }); + + editor.conversion.for("downcast") + .elementToElement( { + model: 'aside', + view: "aside" + }) + .attributeToAttribute({ + model: ADMONITION_TYPE_ATTRIBUTE, + view: (value) => ({ + key: "class", + value: [ "admonition", value as string ] + }) + }); + + // 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', 'aside' ) && element.isEmpty ) { + // Added an empty aside - remove it. + writer.remove( element ); + + return true; + } 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. + 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', 'aside' ) && + !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', 'aside' ) && parent.isEmpty ) { + // Something got removed and now aside is empty. Remove the aside as well. + writer.remove( parent ); + + return true; + } + } + } + + return false; + } ); + + const viewDocument = this.editor.editing.view.document; + const selection = editor.model.document.selection; + const admonitionCommand: AdmonitionCommand = editor.commands.get( 'admonition' )!; + + // 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 || !admonitionCommand.value ) { + return; + } + + const positionParent = selection.getLastPosition()!.parent; + + if ( positionParent.isEmpty ) { + editor.execute( 'admonition' ); + editor.editing.view.scrollToTheSelection(); + + data.preventDefault(); + evt.stop(); + } + }, { context: 'aside' } ); + + // 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 || !admonitionCommand!.value ) { + return; + } + + const positionParent = selection.getLastPosition()!.parent; + + if ( positionParent.isEmpty && !positionParent.previousSibling ) { + editor.execute( 'admonition' ); + editor.editing.view.scrollToTheSelection(); + + data.preventDefault(); + evt.stop(); + } + }, { context: 'aside' } ); + } +} diff --git a/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/src/admonitionui.ts b/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/src/admonitionui.ts new file mode 100644 index 000000000..6e5b779fe --- /dev/null +++ b/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/src/admonitionui.ts @@ -0,0 +1,125 @@ +/** + * @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 + */ + +/** + * @module admonition/admonitionui + */ + +import { Plugin, } from 'ckeditor5/src/core.js'; +import { addListToDropdown, createDropdown, ListDropdownButtonDefinition, SplitButtonView, ViewModel } from 'ckeditor5/src/ui.js'; + +import '../theme/blockquote.css'; +import admonitionIcon from '../theme/icons/admonition.svg'; +import { Collection } from '@ckeditor/ckeditor5-utils'; +import AdmonitionCommand, { AdmonitionType } from './admonitioncommand'; + +interface AdmonitionDefinition { + title: string; +} + +export const ADMONITION_TYPES: Record = { + note: { + title: "Note" + }, + tip: { + title: "Tip" + }, + important: { + title: "Important" + }, + caution: { + title: "Caution" + }, + warning: { + title: "Warning" + } +}; + +/** + * The block quote UI plugin. + * + * It introduces the `'admonition'` button. + * + * @extends module:core/plugin~Plugin + */ +export default class AdmonitionUI extends Plugin { + /** + * @inheritDoc + */ + public static get pluginName() { + return 'AdmonitionUI' as const; + } + + /** + * @inheritDoc + */ + public init(): void { + const editor = this.editor; + + editor.ui.componentFactory.add( 'admonition', () => { + const buttonView = this._createButton(); + + return buttonView; + } ); + } + + /** + * Creates a button for admonition command to use either in toolbar or in menu bar. + */ + private _createButton() { + const editor = this.editor; + const locale = editor.locale; + const command = editor.commands.get( 'admonition' )!; + const dropdownView = createDropdown(locale, SplitButtonView); + const splitButtonView = dropdownView.buttonView; + const t = locale.t; + + addListToDropdown(dropdownView, this._getDropdownItems()) + + // Button configuration. + splitButtonView.set( { + label: t( 'Admonition' ), + icon: admonitionIcon, + isToggleable: true, + tooltip: true + } ); + splitButtonView.on("execute", () => { + editor.execute("admonition", { usePreviousChoice: true }); + editor.editing.view.focus(); + }); + splitButtonView.bind( 'isOn' ).to( command, 'value', value => (!!value) as boolean); + + // Dropdown configuration + dropdownView.bind( 'isEnabled' ).to( command, 'isEnabled' ); + dropdownView.on("execute", evt => { + editor.execute("admonition", { forceValue: ( evt.source as any ).commandParam } ); + editor.editing.view.focus(); + }); + + return dropdownView; + } + + private _getDropdownItems() { + const itemDefinitions = new Collection(); + const command = this.editor.commands.get("admonition") as AdmonitionCommand + + for (const [ type, admonition ] of Object.entries(ADMONITION_TYPES)) { + const definition: ListDropdownButtonDefinition = { + type: "button", + model: new ViewModel({ + commandParam: type, + label: admonition.title, + role: 'menuitemradio', + withText: true + }) + } + + definition.model.bind("isOn").to(command, "value", currentType => currentType === type); + itemDefinitions.add(definition); + } + + return itemDefinitions; + } +} diff --git a/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/src/augmentation.ts b/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/src/augmentation.ts new file mode 100644 index 000000000..5bf87e70e --- /dev/null +++ b/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/src/augmentation.ts @@ -0,0 +1,23 @@ +/** + * @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 + */ + +import type { + Admonition, + AdmonitionCommand, + AdmonitionEditing, + AdmonitionUI +} from './index.js'; + +declare module '@ckeditor/ckeditor5-core' { + interface PluginsMap { + [ Admonition.pluginName ]: Admonition; + [ AdmonitionEditing.pluginName ]: AdmonitionEditing; + [ AdmonitionUI.pluginName ]: AdmonitionUI; + } + + interface CommandsMap { + admonition: AdmonitionCommand; + } +} diff --git a/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/src/index.ts b/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/src/index.ts new file mode 100644 index 000000000..073365f5d --- /dev/null +++ b/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/src/index.ts @@ -0,0 +1,16 @@ +/** + * @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 + */ + +/** + * @module admonition + */ + +export { default as Admonition } from './admonition.js'; +export { default as AdmonitionEditing } from './admonitionediting.js'; +export { default as AdmonitionUI } from './admonitionui.js'; +export { default as AdmonitionAutoformat } from './admonitionautoformat.js'; +export type { default as AdmonitionCommand } from './admonitioncommand.js'; + +import './augmentation.js'; diff --git a/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/theme/blockquote.css b/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/theme/blockquote.css new file mode 100644 index 000000000..e456acb12 --- /dev/null +++ b/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/theme/blockquote.css @@ -0,0 +1,23 @@ +/* + * 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 + */ + +.ck-content blockquote { + /* See #12 */ + overflow: hidden; + + /* https://github.com/ckeditor/ckeditor5-block-quote/issues/15 */ + padding-right: 1.5em; + padding-left: 1.5em; + + margin-left: 0; + margin-right: 0; + font-style: italic; + border-left: solid 5px hsl(0, 0%, 80%); +} + +.ck-content[dir="rtl"] blockquote { + border-left: 0; + border-right: solid 5px hsl(0, 0%, 80%); +} diff --git a/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/theme/icons/admonition.svg b/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/theme/icons/admonition.svg new file mode 100644 index 000000000..0f497f8c6 --- /dev/null +++ b/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/theme/icons/admonition.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/tsconfig.dist.json b/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/tsconfig.dist.json new file mode 100644 index 000000000..d8e5823ec --- /dev/null +++ b/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/tsconfig.dist.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.dist.json", + "compilerOptions": { + "rootDir": "src", + "types": [ + "../../typings/types" + ] + }, + "include": [ + "src" + ] +} diff --git a/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/tsconfig.json b/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/tsconfig.json new file mode 100644 index 000000000..06d45c898 --- /dev/null +++ b/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.release.json", + "include": [ + "src", + "../../typings" + ], + "exclude": [ + "tests" + ] +} diff --git a/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/webpack.config.cjs b/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/webpack.config.cjs new file mode 100644 index 000000000..04e3af7a4 --- /dev/null +++ b/_regroup/ckeditor5-admonition/packages/ckeditor5-admonition/webpack.config.cjs @@ -0,0 +1,19 @@ +/** + * @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 + */ + +'use strict'; + +/* eslint-env node */ + +const { builds } = require( '@ckeditor/ckeditor5-dev-utils' ); +const webpack = require( 'webpack' ); + +module.exports = builds.getDllPluginWebpackConfig( webpack, { + themePath: require.resolve( '@ckeditor/ckeditor5-theme-lark' ), + packagePath: __dirname, + manifestPath: require.resolve( 'ckeditor5/build/ckeditor5-dll.manifest.json' ), + isDevelopmentMode: process.argv.includes( '--mode=development' ), + tsconfigPath: require.resolve( 'ckeditor5/tsconfig.dll.json' ) +} );