|
|
@@ -4,7 +4,6 @@
|
|
|
*/
|
|
|
|
|
|
import Range from '../engine/model/range.js';
|
|
|
-import RootElement from '../engine/model/rootelement.js';
|
|
|
|
|
|
/**
|
|
|
* A paragraph feature for editor.
|
|
|
@@ -15,68 +14,56 @@ import RootElement from '../engine/model/rootelement.js';
|
|
|
*/
|
|
|
export default class InlineAutoformatEngine {
|
|
|
|
|
|
- constructor( editor, pattern, command, delimiterLen ) {
|
|
|
+ constructor( editor, testCallback, formatCallback ) {
|
|
|
this.editor = editor;
|
|
|
- const doc = editor.document;
|
|
|
-
|
|
|
- // Listen to model changes and add attributes.
|
|
|
- editor.document.on( 'change', ( evt, type, data ) => {
|
|
|
- if ( type === 'insert' ) {
|
|
|
- const insertPosition = data.range.start;
|
|
|
- const insertBlock = findTopmostBlock( insertPosition );
|
|
|
-
|
|
|
- applyAttributes( insertBlock );
|
|
|
- } else
|
|
|
- if ( type === 'remove' ) {
|
|
|
- const removePosition = data.sourcePosition;
|
|
|
- const removeBlock = findTopmostBlock( removePosition );
|
|
|
-
|
|
|
- if ( removeBlock !== null ) {
|
|
|
- applyAttributes( removeBlock );
|
|
|
- }
|
|
|
+
|
|
|
+ editor.document.on( 'change', ( evt, type ) => {
|
|
|
+ if ( type !== 'insert' ) {
|
|
|
+ return;
|
|
|
}
|
|
|
- } );
|
|
|
|
|
|
- function applyAttributes( block ) {
|
|
|
+ const batch = editor.document.batch();
|
|
|
+ const block = editor.document.selection.focus.parent;
|
|
|
const text = getText( block );
|
|
|
- let result;
|
|
|
- let index = 0;
|
|
|
|
|
|
- while ( ( result = pattern.exec( text ) ) !== null ) {
|
|
|
- let matched;
|
|
|
+ if ( block.name !== 'paragraph' ) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const ranges = testCallback( text );
|
|
|
|
|
|
- if ( result[ 1 ] ) {
|
|
|
- matched = result[ 1 ];
|
|
|
- } else {
|
|
|
+ // Apply format before deleting text.
|
|
|
+ ranges.format.forEach( ( range ) => {
|
|
|
+ if ( !range || range[ 0 ] === undefined || range[ 1 ] === undefined ) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- index = text.indexOf( matched, index )
|
|
|
-
|
|
|
- doc.enqueueChanges( () => {
|
|
|
- const batch = doc.batch();
|
|
|
- const rangeToDeleteStart = Range.createFromParentsAndOffsets(
|
|
|
- block, index,
|
|
|
- block, index + delimiterLen
|
|
|
- );
|
|
|
- const rangeToDeleteEnd = Range.createFromParentsAndOffsets(
|
|
|
- block, index + matched.length - delimiterLen,
|
|
|
- block, index + matched.length
|
|
|
- );
|
|
|
-
|
|
|
- // Delete from the end to not change indices.
|
|
|
- batch.remove( rangeToDeleteEnd );
|
|
|
- batch.remove( rangeToDeleteStart );
|
|
|
-
|
|
|
- const range = Range.createFromParentsAndOffsets(
|
|
|
- block, index,
|
|
|
- block, index + matched.length - delimiterLen * 2
|
|
|
- );
|
|
|
-
|
|
|
- batch.setAttribute( range, command, true );
|
|
|
+ const rangeToFormat = Range.createFromParentsAndOffsets(
|
|
|
+ block, range[ 0 ],
|
|
|
+ block, range[ 1 ]
|
|
|
+ );
|
|
|
+
|
|
|
+ editor.document.enqueueChanges( () => {
|
|
|
+ formatCallback( this.editor, rangeToFormat, batch );
|
|
|
} );
|
|
|
- }
|
|
|
- }
|
|
|
+ } );
|
|
|
+
|
|
|
+ // Reverse order of deleted ranges to not mix the positions.
|
|
|
+ ranges.remove.slice().reverse().forEach( ( range ) => {
|
|
|
+ if ( !range || range[ 0 ] === undefined || range[ 1 ] === undefined ) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const rangeToDelete = Range.createFromParentsAndOffsets(
|
|
|
+ block, range[ 0 ],
|
|
|
+ block, range[ 1 ]
|
|
|
+ );
|
|
|
+
|
|
|
+ editor.document.enqueueChanges( () => {
|
|
|
+ batch.remove( rangeToDelete );
|
|
|
+ } );
|
|
|
+ } );
|
|
|
+ } );
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -93,28 +80,3 @@ function getText( element ) {
|
|
|
|
|
|
return text;
|
|
|
}
|
|
|
-
|
|
|
-// Looks for topmost element from position parent to element placed in root.
|
|
|
-//
|
|
|
-// NOTE: This method does not checks schema directly - assumes that only block elements can be placed directly inside
|
|
|
-// root.
|
|
|
-//
|
|
|
-// @private
|
|
|
-// @param {engine.model.Position} position
|
|
|
-// @param {Boolean} [nodeAfter=true] When position is placed inside root element this will determine if element before
|
|
|
-// or after given position will be returned.
|
|
|
-// @returns {engine.model.Element}
|
|
|
-export function findTopmostBlock( position, nodeAfter = true ) {
|
|
|
- let parent = position.parent;
|
|
|
-
|
|
|
- // If position is placed inside root - get element after/before it.
|
|
|
- if ( parent instanceof RootElement ) {
|
|
|
- return nodeAfter ? position.nodeAfter : position.nodeBefore;
|
|
|
- }
|
|
|
-
|
|
|
- while ( !( parent.parent instanceof RootElement ) ) {
|
|
|
- parent = parent.parent;
|
|
|
- }
|
|
|
-
|
|
|
- return parent;
|
|
|
-}
|