-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #13171 from ckeditor/ck/12996
Other (autoformat): Rewrite ckeditor5-autoformat to TypeScript. Closes #12996.
- Loading branch information
Showing
14 changed files
with
688 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,233 @@ | ||
/** | ||
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved. | ||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license | ||
*/ | ||
|
||
/** | ||
* @module autoformat/autoformat | ||
*/ | ||
import type { HeadingCommand } from '@ckeditor/ckeditor5-heading'; | ||
|
||
import { Plugin, type PluginDependencies, type Editor } from 'ckeditor5/src/core'; | ||
import type { Range, Writer } from 'ckeditor5/src/engine'; | ||
import { Delete } from 'ckeditor5/src/typing'; | ||
|
||
import blockAutoformatEditing from './blockautoformatediting'; | ||
import inlineAutoformatEditing from './inlineautoformatediting'; | ||
|
||
/** | ||
* Enables a set of predefined autoformatting actions. | ||
* | ||
* For a detailed overview, check the {@glink features/autoformat Autoformatting feature documentation} | ||
* and the {@glink api/autoformat package page}. | ||
*/ | ||
export default class Autoformat extends Plugin { | ||
/** | ||
* @inheritdoc | ||
*/ | ||
public static get requires(): PluginDependencies { | ||
return [ Delete ]; | ||
} | ||
|
||
/** | ||
* @inheritDoc | ||
*/ | ||
public static get pluginName(): 'Autoformat' { | ||
return 'Autoformat'; | ||
} | ||
|
||
/** | ||
* @inheritDoc | ||
*/ | ||
public afterInit(): void { | ||
this._addListAutoformats(); | ||
this._addBasicStylesAutoformats(); | ||
this._addHeadingAutoformats(); | ||
this._addBlockQuoteAutoformats(); | ||
this._addCodeBlockAutoformats(); | ||
this._addHorizontalLineAutoformats(); | ||
} | ||
|
||
/** | ||
* Adds autoformatting related to the {@link module:list/list~List}. | ||
* | ||
* When typed: | ||
* - `* ` or `- ` – A paragraph will be changed to a bulleted list. | ||
* - `1. ` or `1) ` – A paragraph will be changed to a numbered list ("1" can be any digit or a list of digits). | ||
* - `[] ` or `[ ] ` – A paragraph will be changed to a to-do list. | ||
* - `[x] ` or `[ x ] ` – A paragraph will be changed to a checked to-do list. | ||
*/ | ||
private _addListAutoformats(): void { | ||
const commands = this.editor.commands; | ||
|
||
if ( commands.get( 'bulletedList' ) ) { | ||
blockAutoformatEditing( this.editor, this, /^[*-]\s$/, 'bulletedList' ); | ||
} | ||
|
||
if ( commands.get( 'numberedList' ) ) { | ||
blockAutoformatEditing( this.editor, this, /^1[.|)]\s$/, 'numberedList' ); | ||
} | ||
|
||
if ( commands.get( 'todoList' ) ) { | ||
blockAutoformatEditing( this.editor, this, /^\[\s?\]\s$/, 'todoList' ); | ||
} | ||
|
||
if ( commands.get( 'checkTodoList' ) ) { | ||
blockAutoformatEditing( this.editor, this, /^\[\s?x\s?\]\s$/, () => { | ||
this.editor.execute( 'todoList' ); | ||
this.editor.execute( 'checkTodoList' ); | ||
} ); | ||
} | ||
} | ||
|
||
/** | ||
* Adds autoformatting related to the {@link module:basic-styles/bold~Bold}, | ||
* {@link module:basic-styles/italic~Italic}, {@link module:basic-styles/code~Code} | ||
* and {@link module:basic-styles/strikethrough~Strikethrough} | ||
* | ||
* When typed: | ||
* - `**foobar**` – `**` characters are removed and `foobar` is set to bold, | ||
* - `__foobar__` – `__` characters are removed and `foobar` is set to bold, | ||
* - `*foobar*` – `*` characters are removed and `foobar` is set to italic, | ||
* - `_foobar_` – `_` characters are removed and `foobar` is set to italic, | ||
* - ``` `foobar` – ``` ` ``` characters are removed and `foobar` is set to code, | ||
* - `~~foobar~~` – `~~` characters are removed and `foobar` is set to strikethrough. | ||
*/ | ||
private _addBasicStylesAutoformats(): void { | ||
const commands = this.editor.commands; | ||
|
||
if ( commands.get( 'bold' ) ) { | ||
const boldCallback = getCallbackFunctionForInlineAutoformat( this.editor, 'bold' ); | ||
|
||
inlineAutoformatEditing( this.editor, this, /(?:^|\s)(\*\*)([^*]+)(\*\*)$/g, boldCallback ); | ||
inlineAutoformatEditing( this.editor, this, /(?:^|\s)(__)([^_]+)(__)$/g, boldCallback ); | ||
} | ||
|
||
if ( commands.get( 'italic' ) ) { | ||
const italicCallback = getCallbackFunctionForInlineAutoformat( this.editor, 'italic' ); | ||
|
||
// The italic autoformatter cannot be triggered by the bold markers, so we need to check the | ||
// text before the pattern (e.g. `(?:^|[^\*])`). | ||
inlineAutoformatEditing( this.editor, this, /(?:^|\s)(\*)([^*_]+)(\*)$/g, italicCallback ); | ||
inlineAutoformatEditing( this.editor, this, /(?:^|\s)(_)([^_]+)(_)$/g, italicCallback ); | ||
} | ||
|
||
if ( commands.get( 'code' ) ) { | ||
const codeCallback = getCallbackFunctionForInlineAutoformat( this.editor, 'code' ); | ||
|
||
inlineAutoformatEditing( this.editor, this, /(`)([^`]+)(`)$/g, codeCallback ); | ||
} | ||
|
||
if ( commands.get( 'strikethrough' ) ) { | ||
const strikethroughCallback = getCallbackFunctionForInlineAutoformat( this.editor, 'strikethrough' ); | ||
|
||
inlineAutoformatEditing( this.editor, this, /(~~)([^~]+)(~~)$/g, strikethroughCallback ); | ||
} | ||
} | ||
|
||
/** | ||
* Adds autoformatting related to {@link module:heading/heading~Heading}. | ||
* | ||
* It is using a number at the end of the command name to associate it with the proper trigger: | ||
* | ||
* * `heading` with value `heading1` will be executed when typing `#`, | ||
* * `heading` with value `heading2` will be executed when typing `##`, | ||
* * ... up to `heading6` and `######`. | ||
*/ | ||
private _addHeadingAutoformats(): void { | ||
const command: HeadingCommand | undefined = this.editor.commands.get( 'heading' ); | ||
|
||
if ( command ) { | ||
command.modelElements | ||
.filter( name => name.match( /^heading[1-6]$/ ) ) | ||
.forEach( modelName => { | ||
const level = modelName[ 7 ]; | ||
const pattern = new RegExp( `^(#{${ level }})\\s$` ); | ||
|
||
blockAutoformatEditing( this.editor, this, pattern, () => { | ||
// Should only be active if command is enabled and heading style associated with pattern is inactive. | ||
if ( !command.isEnabled || command.value === modelName ) { | ||
return false; | ||
} | ||
|
||
this.editor.execute( 'heading', { value: modelName } ); | ||
} ); | ||
} ); | ||
} | ||
} | ||
|
||
/** | ||
* Adds autoformatting related to {@link module:block-quote/blockquote~BlockQuote}. | ||
* | ||
* When typed: | ||
* * `> ` – A paragraph will be changed to a block quote. | ||
*/ | ||
private _addBlockQuoteAutoformats(): void { | ||
if ( this.editor.commands.get( 'blockQuote' ) ) { | ||
blockAutoformatEditing( this.editor, this, /^>\s$/, 'blockQuote' ); | ||
} | ||
} | ||
|
||
/** | ||
* Adds autoformatting related to {@link module:code-block/codeblock~CodeBlock}. | ||
* | ||
* When typed: | ||
* - `` ``` `` – A paragraph will be changed to a code block. | ||
*/ | ||
private _addCodeBlockAutoformats(): void { | ||
const editor = this.editor; | ||
const selection = editor.model.document.selection; | ||
|
||
if ( editor.commands.get( 'codeBlock' ) ) { | ||
blockAutoformatEditing( editor, this, /^```$/, () => { | ||
if ( selection.getFirstPosition()!.parent.is( 'element', 'listItem' ) ) { | ||
return false; | ||
} | ||
this.editor.execute( 'codeBlock', { | ||
usePreviousLanguageChoice: true | ||
} ); | ||
} ); | ||
} | ||
} | ||
|
||
/** | ||
* Adds autoformatting related to {@link module:horizontal-line/horizontalline~HorizontalLine}. | ||
* | ||
* When typed: | ||
* - `` --- `` – Will be replaced with a horizontal line. | ||
*/ | ||
private _addHorizontalLineAutoformats(): void { | ||
if ( this.editor.commands.get( 'horizontalLine' ) ) { | ||
blockAutoformatEditing( this.editor, this, /^---$/, 'horizontalLine' ); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Helper function for getting `inlineAutoformatEditing` callbacks that checks if command is enabled. | ||
*/ | ||
function getCallbackFunctionForInlineAutoformat( editor: Editor, attributeKey: string ) { | ||
return ( writer: Writer, rangesToFormat: Array<Range> ): boolean | undefined => { | ||
const command = editor.commands.get( attributeKey )!; | ||
|
||
if ( !command.isEnabled ) { | ||
return false; | ||
} | ||
|
||
const validRanges = editor.model.schema.getValidRanges( rangesToFormat, attributeKey ); | ||
|
||
for ( const range of validRanges ) { | ||
writer.setAttribute( attributeKey, true, range ); | ||
} | ||
|
||
// After applying attribute to the text, remove given attribute from the selection. | ||
// This way user is able to type a text without attribute used by auto formatter. | ||
writer.removeSelectionAttribute( attributeKey ); | ||
}; | ||
} | ||
|
||
declare module '@ckeditor/ckeditor5-core' { | ||
interface PluginsMap { | ||
[ Autoformat.pluginName ]: Autoformat; | ||
} | ||
} |
Oops, something went wrong.