@ckeditor/ckeditor5-autoformat
Advanced tools
Comparing version 19.0.1 to 20.0.0
{ | ||
"name": "@ckeditor/ckeditor5-autoformat", | ||
"version": "19.0.1", | ||
"version": "20.0.0", | ||
"description": "Autoformatting feature for CKEditor 5.", | ||
@@ -13,19 +13,19 @@ "keywords": [ | ||
"dependencies": { | ||
"@ckeditor/ckeditor5-core": "^19.0.1", | ||
"@ckeditor/ckeditor5-typing": "^19.0.1" | ||
"@ckeditor/ckeditor5-core": "^20.0.0", | ||
"@ckeditor/ckeditor5-typing": "^20.0.0" | ||
}, | ||
"devDependencies": { | ||
"@ckeditor/ckeditor5-basic-styles": "^19.0.1", | ||
"@ckeditor/ckeditor5-block-quote": "^19.0.1", | ||
"@ckeditor/ckeditor5-code-block": "^19.0.1", | ||
"@ckeditor/ckeditor5-editor-classic": "^19.0.1", | ||
"@ckeditor/ckeditor5-engine": "^19.0.1", | ||
"@ckeditor/ckeditor5-enter": "^19.0.1", | ||
"@ckeditor/ckeditor5-heading": "^19.0.1", | ||
"@ckeditor/ckeditor5-list": "^19.0.1", | ||
"@ckeditor/ckeditor5-paragraph": "^19.1.0", | ||
"@ckeditor/ckeditor5-undo": "^19.0.1" | ||
"@ckeditor/ckeditor5-basic-styles": "^20.0.0", | ||
"@ckeditor/ckeditor5-block-quote": "^20.0.0", | ||
"@ckeditor/ckeditor5-code-block": "^20.0.0", | ||
"@ckeditor/ckeditor5-editor-classic": "^20.0.0", | ||
"@ckeditor/ckeditor5-engine": "^20.0.0", | ||
"@ckeditor/ckeditor5-enter": "^20.0.0", | ||
"@ckeditor/ckeditor5-heading": "^20.0.0", | ||
"@ckeditor/ckeditor5-list": "^20.0.0", | ||
"@ckeditor/ckeditor5-paragraph": "^20.0.0", | ||
"@ckeditor/ckeditor5-undo": "^20.0.0" | ||
}, | ||
"engines": { | ||
"node": ">=8.0.0", | ||
"node": ">=12.0.0", | ||
"npm": ">=5.7.1" | ||
@@ -32,0 +32,0 @@ }, |
@@ -10,4 +10,4 @@ /** | ||
import BlockAutoformatEditing from './blockautoformatediting'; | ||
import InlineAutoformatEditing from './inlineautoformatediting'; | ||
import blockAutoformatEditing from './blockautoformatediting'; | ||
import inlineAutoformatEditing from './inlineautoformatediting'; | ||
import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; | ||
@@ -55,9 +55,7 @@ | ||
if ( commands.get( 'bulletedList' ) ) { | ||
// eslint-disable-next-line no-new | ||
new BlockAutoformatEditing( this.editor, /^[*-]\s$/, 'bulletedList' ); | ||
blockAutoformatEditing( this.editor, this, /^[*-]\s$/, 'bulletedList' ); | ||
} | ||
if ( commands.get( 'numberedList' ) ) { | ||
// eslint-disable-next-line no-new | ||
new BlockAutoformatEditing( this.editor, /^1[.|)]\s$/, 'numberedList' ); | ||
blockAutoformatEditing( this.editor, this, /^1[.|)]\s$/, 'numberedList' ); | ||
} | ||
@@ -85,12 +83,9 @@ } | ||
if ( commands.get( 'bold' ) ) { | ||
/* eslint-disable no-new */ | ||
const boldCallback = getCallbackFunctionForInlineAutoformat( this.editor, 'bold' ); | ||
new InlineAutoformatEditing( this.editor, /(\*\*)([^*]+)(\*\*)$/g, boldCallback ); | ||
new InlineAutoformatEditing( this.editor, /(__)([^_]+)(__)$/g, boldCallback ); | ||
/* eslint-enable no-new */ | ||
inlineAutoformatEditing( this.editor, this, /(\*\*)([^*]+)(\*\*)$/g, boldCallback ); | ||
inlineAutoformatEditing( this.editor, this, /(__)([^_]+)(__)$/g, boldCallback ); | ||
} | ||
if ( commands.get( 'italic' ) ) { | ||
/* eslint-disable no-new */ | ||
const italicCallback = getCallbackFunctionForInlineAutoformat( this.editor, 'italic' ); | ||
@@ -100,21 +95,16 @@ | ||
// text before the pattern (e.g. `(?:^|[^\*])`). | ||
new InlineAutoformatEditing( this.editor, /(?:^|[^*])(\*)([^*_]+)(\*)$/g, italicCallback ); | ||
new InlineAutoformatEditing( this.editor, /(?:^|[^_])(_)([^_]+)(_)$/g, italicCallback ); | ||
/* eslint-enable no-new */ | ||
inlineAutoformatEditing( this.editor, this, /(?:^|[^*])(\*)([^*_]+)(\*)$/g, italicCallback ); | ||
inlineAutoformatEditing( this.editor, this, /(?:^|[^_])(_)([^_]+)(_)$/g, italicCallback ); | ||
} | ||
if ( commands.get( 'code' ) ) { | ||
/* eslint-disable no-new */ | ||
const codeCallback = getCallbackFunctionForInlineAutoformat( this.editor, 'code' ); | ||
new InlineAutoformatEditing( this.editor, /(`)([^`]+)(`)$/g, codeCallback ); | ||
/* eslint-enable no-new */ | ||
inlineAutoformatEditing( this.editor, this, /(`)([^`]+)(`)$/g, codeCallback ); | ||
} | ||
if ( commands.get( 'strikethrough' ) ) { | ||
/* eslint-disable no-new */ | ||
const strikethroughCallback = getCallbackFunctionForInlineAutoformat( this.editor, 'strikethrough' ); | ||
new InlineAutoformatEditing( this.editor, /(~~)([^~]+)(~~)$/g, strikethroughCallback ); | ||
/* eslint-enable no-new */ | ||
inlineAutoformatEditing( this.editor, this, /(~~)([^~]+)(~~)$/g, strikethroughCallback ); | ||
} | ||
@@ -144,4 +134,3 @@ } | ||
// eslint-disable-next-line no-new | ||
new BlockAutoformatEditing( this.editor, pattern, () => { | ||
blockAutoformatEditing( this.editor, this, pattern, () => { | ||
if ( !command.isEnabled ) { | ||
@@ -167,4 +156,3 @@ return false; | ||
if ( this.editor.commands.get( 'blockQuote' ) ) { | ||
// eslint-disable-next-line no-new | ||
new BlockAutoformatEditing( this.editor, /^>\s$/, 'blockQuote' ); | ||
blockAutoformatEditing( this.editor, this, /^>\s$/, 'blockQuote' ); | ||
} | ||
@@ -183,4 +171,3 @@ } | ||
if ( this.editor.commands.get( 'codeBlock' ) ) { | ||
// eslint-disable-next-line no-new | ||
new BlockAutoformatEditing( this.editor, /^```$/, 'codeBlock' ); | ||
blockAutoformatEditing( this.editor, this, /^```$/, 'codeBlock' ); | ||
} | ||
@@ -190,3 +177,3 @@ } | ||
// Helper function for getting `InlineAutoformatEditing` callbacks that checks if command is enabled. | ||
// Helper function for getting `inlineAutoformatEditing` callbacks that checks if command is enabled. | ||
// | ||
@@ -193,0 +180,0 @@ // @param {module:core/editor/editor~Editor} editor |
@@ -5,7 +5,2 @@ /** | ||
*/ | ||
/** | ||
* @module autoformat/blockautoformatediting | ||
*/ | ||
import LiveRange from '@ckeditor/ckeditor5-engine/src/model/liverange'; | ||
@@ -20,106 +15,102 @@ | ||
* | ||
* See the constructors documentation to learn how to create custom inline autoformatters. You can also use | ||
* See the {@link module:autoformat/blockautoformatediting~blockAutoformatEditing `blockAutoformatEditing`} documentation | ||
* to learn how to create custom block autoformatters. You can also use | ||
* the {@link module:autoformat/autoformat~Autoformat} feature which enables a set of default autoformatters | ||
* (lists, headings, bold and italic). | ||
* | ||
* @module autoformat/blockautoformatediting | ||
*/ | ||
export default class BlockAutoformatEditing { | ||
/** | ||
* @inheritDoc | ||
*/ | ||
static get pluginName() { | ||
return 'BlockAutoformatEditing'; | ||
} | ||
/** | ||
* Creates a listener triggered on `change` event in the document. | ||
* Calls the callback when inserted text matches the regular expression or the command name | ||
* if provided instead of the callback. | ||
* | ||
* Examples of usage: | ||
* | ||
* To convert a paragraph to heading 1 when `- ` is typed, using just the command name: | ||
* | ||
* new BlockAutoformatEditing( editor, /^\- $/, 'heading1' ); | ||
* | ||
* To convert a paragraph to heading 1 when `- ` is typed, using just the callback: | ||
* | ||
* new BlockAutoformatEditing( editor, /^\- $/, ( context ) => { | ||
* const { match } = context; | ||
* const headingLevel = match[ 1 ].length; | ||
* | ||
* editor.execute( 'heading', { | ||
* formatId: `heading${ headingLevel }` | ||
* } ); | ||
* } ); | ||
* | ||
* @param {module:core/editor/editor~Editor} editor The editor instance. | ||
* @param {RegExp} pattern The regular expression to execute on just inserted text. | ||
* @param {Function|String} callbackOrCommand The callback to execute or the command to run when the text is matched. | ||
* In case of providing the callback, it receives the following parameter: | ||
* * {Object} match RegExp.exec() result of matching the pattern to inserted text. | ||
*/ | ||
constructor( editor, pattern, callbackOrCommand ) { | ||
let callback; | ||
let command = null; | ||
/** | ||
* Creates a listener triggered on {@link module:engine/model/document~Document#event:change:data `change:data`} event in the document. | ||
* Calls the callback when inserted text matches the regular expression or the command name | ||
* if provided instead of the callback. | ||
* | ||
* Examples of usage: | ||
* | ||
* To convert a paragraph to heading 1 when `- ` is typed, using just the command name: | ||
* | ||
* blockAutoformatEditing( editor, plugin, /^\- $/, 'heading1' ); | ||
* | ||
* To convert a paragraph to heading 1 when `- ` is typed, using just the callback: | ||
* | ||
* blockAutoformatEditing( editor, plugin, /^\- $/, ( context ) => { | ||
* const { match } = context; | ||
* const headingLevel = match[ 1 ].length; | ||
* | ||
* editor.execute( 'heading', { | ||
* formatId: `heading${ headingLevel }` | ||
* } ); | ||
* } ); | ||
* | ||
* @param {module:core/editor/editor~Editor} editor The editor instance. | ||
* @param {module:autoformat/autoformat~Autoformat} plugin The autoformat plugin instance. | ||
* @param {RegExp} pattern The regular expression to execute on just inserted text. | ||
* @param {Function|String} callbackOrCommand The callback to execute or the command to run when the text is matched. | ||
* In case of providing the callback, it receives the following parameter: | ||
* * {Object} match RegExp.exec() result of matching the pattern to inserted text. | ||
*/ | ||
export default function blockAutoformatEditing( editor, plugin, pattern, callbackOrCommand ) { | ||
let callback; | ||
let command = null; | ||
if ( typeof callbackOrCommand == 'function' ) { | ||
callback = callbackOrCommand; | ||
} else { | ||
// We assume that the actual command name was provided. | ||
command = editor.commands.get( callbackOrCommand ); | ||
if ( typeof callbackOrCommand == 'function' ) { | ||
callback = callbackOrCommand; | ||
} else { | ||
// We assume that the actual command name was provided. | ||
command = editor.commands.get( callbackOrCommand ); | ||
callback = () => { | ||
editor.execute( callbackOrCommand ); | ||
}; | ||
callback = () => { | ||
editor.execute( callbackOrCommand ); | ||
}; | ||
} | ||
editor.model.document.on( 'change:data', ( evt, batch ) => { | ||
if ( command && !command.isEnabled || !plugin.isEnabled ) { | ||
return; | ||
} | ||
editor.model.document.on( 'change', ( evt, batch ) => { | ||
if ( command && !command.isEnabled ) { | ||
return; | ||
} | ||
if ( batch.type == 'transparent' ) { | ||
return; | ||
} | ||
if ( batch.type == 'transparent' ) { | ||
return; | ||
} | ||
const changes = Array.from( editor.model.document.differ.getChanges() ); | ||
const entry = changes[ 0 ]; | ||
const changes = Array.from( editor.model.document.differ.getChanges() ); | ||
const entry = changes[ 0 ]; | ||
// Typing is represented by only a single change. | ||
if ( changes.length != 1 || entry.type !== 'insert' || entry.name != '$text' || entry.length != 1 ) { | ||
return; | ||
} | ||
// Typing is represented by only a single change. | ||
if ( changes.length != 1 || entry.type !== 'insert' || entry.name != '$text' || entry.length != 1 ) { | ||
return; | ||
} | ||
const blockToFormat = entry.position.parent; | ||
const blockToFormat = entry.position.parent; | ||
// Block formatting should trigger only if the entire content of a paragraph is a single text node... (see ckeditor5#5671). | ||
if ( !blockToFormat.is( 'paragraph' ) || blockToFormat.childCount !== 1 ) { | ||
return; | ||
} | ||
// Block formatting should trigger only if the entire content of a paragraph is a single text node... (see ckeditor5#5671). | ||
if ( !blockToFormat.is( 'paragraph' ) || blockToFormat.childCount !== 1 ) { | ||
return; | ||
} | ||
const match = pattern.exec( blockToFormat.getChild( 0 ).data ); | ||
const match = pattern.exec( blockToFormat.getChild( 0 ).data ); | ||
// ...and this text node's data match the pattern. | ||
if ( !match ) { | ||
return; | ||
} | ||
// ...and this text node's data match the pattern. | ||
if ( !match ) { | ||
return; | ||
} | ||
// Use enqueueChange to create new batch to separate typing batch from the auto-format changes. | ||
editor.model.enqueueChange( writer => { | ||
// Matched range. | ||
const start = writer.createPositionAt( blockToFormat, 0 ); | ||
const end = writer.createPositionAt( blockToFormat, match[ 0 ].length ); | ||
const range = new LiveRange( start, end ); | ||
// Use enqueueChange to create new batch to separate typing batch from the auto-format changes. | ||
editor.model.enqueueChange( writer => { | ||
// Matched range. | ||
const start = writer.createPositionAt( blockToFormat, 0 ); | ||
const end = writer.createPositionAt( blockToFormat, match[ 0 ].length ); | ||
const range = new LiveRange( start, end ); | ||
const wasChanged = callback( { match } ); | ||
const wasChanged = callback( { match } ); | ||
// Remove matched text. | ||
if ( wasChanged !== false ) { | ||
writer.remove( range ); | ||
} | ||
// Remove matched text. | ||
if ( wasChanged !== false ) { | ||
writer.remove( range ); | ||
} | ||
range.detach(); | ||
} ); | ||
range.detach(); | ||
} ); | ||
} | ||
} ); | ||
} |
@@ -7,8 +7,2 @@ /** | ||
/** | ||
* @module autoformat/inlineautoformatediting | ||
*/ | ||
import getLastTextLine from '@ckeditor/ckeditor5-typing/src/utils/getlasttextline'; | ||
/** | ||
* The inline autoformatting engine. It allows to format various inline patterns. For example, | ||
@@ -20,195 +14,167 @@ * it can be configured to make "foo" bold when typed `**foo**` (the `**` markers will be removed). | ||
* | ||
* See the constructors documentation to learn how to create custom inline autoformatters. You can also use | ||
* See the {@link module:autoformat/inlineautoformatediting~inlineAutoformatEditing `inlineAutoformatEditing`} documentation | ||
* to learn how to create custom inline autoformatters. You can also use | ||
* the {@link module:autoformat/autoformat~Autoformat} feature which enables a set of default autoformatters | ||
* (lists, headings, bold and italic). | ||
* | ||
* @module autoformat/inlineautoformatediting | ||
*/ | ||
export default class InlineAutoformatEditing { | ||
/** | ||
* @inheritDoc | ||
*/ | ||
static get pluginName() { | ||
return 'InlineAutoformatEditing'; | ||
} | ||
/** | ||
* Enables autoformatting mechanism for a given {@link module:core/editor/editor~Editor}. | ||
* | ||
* It formats the matched text by applying the given model attribute or by running the provided formatting callback. | ||
* On every change applied to the model the autoformatting engine checks the text on the left of the selection | ||
* and executes the provided action if the text matches given criteria (regular expression or callback). | ||
* | ||
* @param {module:core/editor/editor~Editor} editor The editor instance. | ||
* @param {Function|RegExp} testRegexpOrCallback The regular expression or callback to execute on text. | ||
* Provided regular expression *must* have three capture groups. The first and the third capture group | ||
* should match opening and closing delimiters. The second capture group should match the text to format. | ||
* | ||
* // Matches the `**bold text**` pattern. | ||
* // There are three capturing groups: | ||
* // - The first to match the starting `**` delimiter. | ||
* // - The second to match the text to format. | ||
* // - The third to match the ending `**` delimiter. | ||
* new InlineAutoformatEditing( editor, /(\*\*)([^\*]+?)(\*\*)$/g, 'bold' ); | ||
* | ||
* When a function is provided instead of the regular expression, it will be executed with the text to match as a parameter. | ||
* The function should return proper "ranges" to delete and format. | ||
* | ||
* { | ||
* remove: [ | ||
* [ 0, 1 ], // Remove the first letter from the given text. | ||
* [ 5, 6 ] // Remove the 6th letter from the given text. | ||
* ], | ||
* format: [ | ||
* [ 1, 5 ] // Format all letters from 2nd to 5th. | ||
* ] | ||
* } | ||
* | ||
* @param {Function|String} attributeOrCallback The name of attribute to apply on matching text or a callback for manual | ||
* formatting. If callback is passed it should return `false` if changes should not be applied (e.g. if a command is disabled). | ||
* | ||
* // Use attribute name: | ||
* new InlineAutoformatEditing( editor, /(\*\*)([^\*]+?)(\*\*)$/g, 'bold' ); | ||
* | ||
* // Use formatting callback: | ||
* new InlineAutoformatEditing( editor, /(\*\*)([^\*]+?)(\*\*)$/g, ( writer, rangesToFormat ) => { | ||
* const command = editor.commands.get( 'bold' ); | ||
* | ||
* if ( !command.isEnabled ) { | ||
* return false; | ||
* } | ||
* | ||
* const validRanges = editor.model.schema.getValidRanges( rangesToFormat, 'bold' ); | ||
* | ||
* for ( let range of validRanges ) { | ||
* writer.setAttribute( 'bold', true, range ); | ||
* } | ||
* } ); | ||
*/ | ||
constructor( editor, testRegexpOrCallback, attributeOrCallback ) { | ||
let regExp; | ||
let attributeKey; | ||
let testCallback; | ||
let formatCallback; | ||
/** | ||
* Enables autoformatting mechanism for a given {@link module:core/editor/editor~Editor}. | ||
* | ||
* It formats the matched text by applying the given model attribute or by running the provided formatting callback. | ||
* On every {@link module:engine/model/document~Document#event:change:data data change} in the model document | ||
* the autoformatting engine checks the text on the left of the selection | ||
* and executes the provided action if the text matches given criteria (regular expression or callback). | ||
* | ||
* @param {module:core/editor/editor~Editor} editor The editor instance. | ||
* @param {module:autoformat/autoformat~Autoformat} plugin The autoformat plugin instance. | ||
* @param {Function|RegExp} testRegexpOrCallback The regular expression or callback to execute on text. | ||
* Provided regular expression *must* have three capture groups. The first and the third capture group | ||
* should match opening and closing delimiters. The second capture group should match the text to format. | ||
* | ||
* // Matches the `**bold text**` pattern. | ||
* // There are three capturing groups: | ||
* // - The first to match the starting `**` delimiter. | ||
* // - The second to match the text to format. | ||
* // - The third to match the ending `**` delimiter. | ||
* inlineAutoformatEditing( editor, plugin, /(\*\*)([^\*]+?)(\*\*)$/g, formatCallback ); | ||
* | ||
* When a function is provided instead of the regular expression, it will be executed with the text to match as a parameter. | ||
* The function should return proper "ranges" to delete and format. | ||
* | ||
* { | ||
* remove: [ | ||
* [ 0, 1 ], // Remove the first letter from the given text. | ||
* [ 5, 6 ] // Remove the 6th letter from the given text. | ||
* ], | ||
* format: [ | ||
* [ 1, 5 ] // Format all letters from 2nd to 5th. | ||
* ] | ||
* } | ||
* | ||
* @param {Function} formatCallback A callback to apply actual formatting. | ||
* It should return `false` if changes should not be applied (e.g. if a command is disabled). | ||
* | ||
* inlineAutoformatEditing( editor, plugin, /(\*\*)([^\*]+?)(\*\*)$/g, ( writer, rangesToFormat ) => { | ||
* const command = editor.commands.get( 'bold' ); | ||
* | ||
* if ( !command.isEnabled ) { | ||
* return false; | ||
* } | ||
* | ||
* const validRanges = editor.model.schema.getValidRanges( rangesToFormat, 'bold' ); | ||
* | ||
* for ( let range of validRanges ) { | ||
* writer.setAttribute( 'bold', true, range ); | ||
* } | ||
* } ); | ||
*/ | ||
export default function inlineAutoformatEditing( editor, plugin, testRegexpOrCallback, formatCallback ) { | ||
let regExp; | ||
let testCallback; | ||
if ( testRegexpOrCallback instanceof RegExp ) { | ||
regExp = testRegexpOrCallback; | ||
} else { | ||
testCallback = testRegexpOrCallback; | ||
} | ||
if ( testRegexpOrCallback instanceof RegExp ) { | ||
regExp = testRegexpOrCallback; | ||
} else { | ||
testCallback = testRegexpOrCallback; | ||
} | ||
if ( typeof attributeOrCallback == 'string' ) { | ||
attributeKey = attributeOrCallback; | ||
} else { | ||
formatCallback = attributeOrCallback; | ||
} | ||
// A test callback run on changed text. | ||
testCallback = testCallback || ( text => { | ||
let result; | ||
const remove = []; | ||
const format = []; | ||
// A test callback run on changed text. | ||
testCallback = testCallback || ( text => { | ||
let result; | ||
const remove = []; | ||
const format = []; | ||
while ( ( result = regExp.exec( text ) ) !== null ) { | ||
// There should be full match and 3 capture groups. | ||
if ( result && result.length < 4 ) { | ||
break; | ||
} | ||
while ( ( result = regExp.exec( text ) ) !== null ) { | ||
// There should be full match and 3 capture groups. | ||
if ( result && result.length < 4 ) { | ||
break; | ||
} | ||
let { | ||
index, | ||
'1': leftDel, | ||
'2': content, | ||
'3': rightDel | ||
} = result; | ||
let { | ||
index, | ||
'1': leftDel, | ||
'2': content, | ||
'3': rightDel | ||
} = result; | ||
// Real matched string - there might be some non-capturing groups so we need to recalculate starting index. | ||
const found = leftDel + content + rightDel; | ||
index += result[ 0 ].length - found.length; | ||
// Real matched string - there might be some non-capturing groups so we need to recalculate starting index. | ||
const found = leftDel + content + rightDel; | ||
index += result[ 0 ].length - found.length; | ||
// Start and End offsets of delimiters to remove. | ||
const delStart = [ | ||
index, | ||
index + leftDel.length | ||
]; | ||
const delEnd = [ | ||
index + leftDel.length + content.length, | ||
index + leftDel.length + content.length + rightDel.length | ||
]; | ||
// Start and End offsets of delimiters to remove. | ||
const delStart = [ | ||
index, | ||
index + leftDel.length | ||
]; | ||
const delEnd = [ | ||
index + leftDel.length + content.length, | ||
index + leftDel.length + content.length + rightDel.length | ||
]; | ||
remove.push( delStart ); | ||
remove.push( delEnd ); | ||
remove.push( delStart ); | ||
remove.push( delEnd ); | ||
format.push( [ index + leftDel.length, index + leftDel.length + content.length ] ); | ||
} | ||
format.push( [ index + leftDel.length, index + leftDel.length + content.length ] ); | ||
} | ||
return { | ||
remove, | ||
format | ||
}; | ||
} ); | ||
return { | ||
remove, | ||
format | ||
}; | ||
} ); | ||
editor.model.document.on( 'change:data', ( evt, batch ) => { | ||
if ( batch.type == 'transparent' || !plugin.isEnabled ) { | ||
return; | ||
} | ||
// A format callback run on matched text. | ||
formatCallback = formatCallback || ( ( writer, rangesToFormat ) => { | ||
const validRanges = editor.model.schema.getValidRanges( rangesToFormat, attributeKey ); | ||
const model = editor.model; | ||
const selection = model.document.selection; | ||
for ( const range of validRanges ) { | ||
writer.setAttribute( attributeKey, true, range ); | ||
} | ||
// Do nothing if selection is not collapsed. | ||
if ( !selection.isCollapsed ) { | ||
return; | ||
} | ||
// 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 ); | ||
} ); | ||
const changes = Array.from( model.document.differ.getChanges() ); | ||
const entry = changes[ 0 ]; | ||
editor.model.document.on( 'change', ( evt, batch ) => { | ||
if ( batch.type == 'transparent' ) { | ||
return; | ||
} | ||
// Typing is represented by only a single change. | ||
if ( changes.length != 1 || entry.type !== 'insert' || entry.name != '$text' || entry.length != 1 ) { | ||
return; | ||
} | ||
const model = editor.model; | ||
const selection = model.document.selection; | ||
const focus = selection.focus; | ||
const block = focus.parent; | ||
const { text, range } = getTextAfterCode( model.createRange( model.createPositionAt( block, 0 ), focus ), model ); | ||
const testOutput = testCallback( text ); | ||
const rangesToFormat = testOutputToRanges( range.start, testOutput.format, model ); | ||
const rangesToRemove = testOutputToRanges( range.start, testOutput.remove, model ); | ||
// Do nothing if selection is not collapsed. | ||
if ( !selection.isCollapsed ) { | ||
return; | ||
} | ||
if ( !( rangesToFormat.length && rangesToRemove.length ) ) { | ||
return; | ||
} | ||
const changes = Array.from( model.document.differ.getChanges() ); | ||
const entry = changes[ 0 ]; | ||
// Use enqueueChange to create new batch to separate typing batch from the auto-format changes. | ||
model.enqueueChange( writer => { | ||
// Apply format. | ||
const hasChanged = formatCallback( writer, rangesToFormat ); | ||
// Typing is represented by only a single change. | ||
if ( changes.length != 1 || entry.type !== 'insert' || entry.name != '$text' || entry.length != 1 ) { | ||
// Strict check on `false` to have backward compatibility (when callbacks were returning `undefined`). | ||
if ( hasChanged === false ) { | ||
return; | ||
} | ||
const focus = selection.focus; | ||
const block = focus.parent; | ||
const { text, range } = getLastTextLine( model.createRange( model.createPositionAt( block, 0 ), focus ), model ); | ||
const testOutput = testCallback( text ); | ||
const rangesToFormat = testOutputToRanges( range.start, testOutput.format, model ); | ||
const rangesToRemove = testOutputToRanges( range.start, testOutput.remove, model ); | ||
if ( !( rangesToFormat.length && rangesToRemove.length ) ) { | ||
return; | ||
// Remove delimiters - use reversed order to not mix the offsets while removing. | ||
for ( const range of rangesToRemove.reverse() ) { | ||
writer.remove( range ); | ||
} | ||
// Use enqueueChange to create new batch to separate typing batch from the auto-format changes. | ||
model.enqueueChange( writer => { | ||
// Apply format. | ||
const hasChanged = formatCallback( writer, rangesToFormat ); | ||
// Strict check on `false` to have backward compatibility (when callbacks were returning `undefined`). | ||
if ( hasChanged === false ) { | ||
return; | ||
} | ||
// Remove delimiters - use reversed order to not mix the offsets while removing. | ||
for ( const range of rangesToRemove.reverse() ) { | ||
writer.remove( range ); | ||
} | ||
} ); | ||
} ); | ||
} | ||
} ); | ||
} | ||
// Converts output of the test function provided to the InlineAutoformatEditing and converts it to the model ranges | ||
// Converts output of the test function provided to the inlineAutoformatEditing and converts it to the model ranges | ||
// inside provided block. | ||
@@ -227,1 +193,25 @@ // | ||
} | ||
// Returns the last text line after the last code element from the given range. | ||
// It is similar to {@link module:typing/utils/getlasttextline.getLastTextLine `getLastTextLine()`}, | ||
// but it ignores any text before the last `code`. | ||
// | ||
// @param {module:engine/model/range~Range} range | ||
// @param {module:engine/model/model~Model} model | ||
// @returns {module:typing/utils/getlasttextline~LastTextLineData} | ||
function getTextAfterCode( range, model ) { | ||
let start = range.start; | ||
const text = Array.from( range.getItems() ).reduce( ( rangeText, node ) => { | ||
// Trim text to a last occurrence of an inline element and update range start. | ||
if ( !( node.is( 'text' ) || node.is( 'textProxy' ) ) || node.getAttribute( 'code' ) ) { | ||
start = model.createPositionAfter( node ); | ||
return ''; | ||
} | ||
return rangeText + node.data; | ||
}, '' ); | ||
return { text, range: model.createRange( start, range.end ) }; | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
31818
451
+ Added@ckeditor/ckeditor5-core@20.0.0(transitive)
+ Added@ckeditor/ckeditor5-engine@20.0.0(transitive)
+ Added@ckeditor/ckeditor5-typing@20.0.0(transitive)
+ Added@ckeditor/ckeditor5-utils@20.0.0(transitive)
- Removed@ckeditor/ckeditor5-core@19.0.1(transitive)
- Removed@ckeditor/ckeditor5-engine@19.0.1(transitive)
- Removed@ckeditor/ckeditor5-typing@19.0.1(transitive)
- Removed@ckeditor/ckeditor5-utils@19.0.2(transitive)