@@ -16,6 +16,24 @@ limitations under the License.
1616*/
1717
1818import { diffAtCaret , diffDeletion } from "./diff" ;
19+ import DocumentPosition from "./position" ;
20+ import Range from "./range" ;
21+
22+ /**
23+ * @callback ModelCallback
24+ * @param {DocumentPosition? } caretPosition the position where the caret should be position
25+ * @param {string? } inputType the inputType of the DOM input event
26+ * @param {object? } diff an object with `removed` and `added` strings
27+ */
28+
29+ /**
30+ * @callback TransformCallback
31+ * @param {DocumentPosition? } caretPosition the position where the caret should be position
32+ * @param {string? } inputType the inputType of the DOM input event
33+ * @param {object? } diff an object with `removed` and `added` strings
34+ * @return {Number? } addedLen how many characters were added/removed (-) before the caret during the transformation step.
35+ * This is used to adjust the caret position.
36+ */
1937
2038export default class EditorModel {
2139 constructor ( parts , partCreator , updateCallback = null ) {
@@ -24,9 +42,26 @@ export default class EditorModel {
2442 this . _activePartIdx = null ;
2543 this . _autoComplete = null ;
2644 this . _autoCompletePartIdx = null ;
45+ this . _transformCallback = null ;
2746 this . setUpdateCallback ( updateCallback ) ;
47+ this . _updateInProgress = false ;
48+ }
49+
50+ /**
51+ * Set a callback for the transformation step.
52+ * While processing an update, right before calling the update callback,
53+ * a transform callback can be called, which serves to do modifications
54+ * on the model that can span multiple parts. Also see `startRange()`.
55+ * @param {TransformCallback } transformCallback
56+ */
57+ setTransformCallback ( transformCallback ) {
58+ this . _transformCallback = transformCallback ;
2859 }
2960
61+ /**
62+ * Set a callback for rerendering the model after it has been updated.
63+ * @param {ModelCallback } updateCallback
64+ */
3065 setUpdateCallback ( updateCallback ) {
3166 this . _updateCallback = updateCallback ;
3267 }
@@ -131,6 +166,7 @@ export default class EditorModel {
131166 }
132167
133168 update ( newValue , inputType , caret ) {
169+ this . _updateInProgress = true ;
134170 const diff = this . _diff ( newValue , inputType , caret ) ;
135171 const position = this . positionForOffset ( diff . at , caret . atNodeEnd ) ;
136172 let removedOffsetDecrease = 0 ;
@@ -145,11 +181,21 @@ export default class EditorModel {
145181 }
146182 this . _mergeAdjacentParts ( ) ;
147183 const caretOffset = diff . at - removedOffsetDecrease + addedLen ;
148- const newPosition = this . positionForOffset ( caretOffset , true ) ;
184+ let newPosition = this . positionForOffset ( caretOffset , true ) ;
149185 this . _setActivePart ( newPosition , canOpenAutoComplete ) ;
186+ if ( this . _transformCallback ) {
187+ const transformAddedLen = this . _transform ( newPosition , inputType , diff ) ;
188+ newPosition = this . positionForOffset ( caretOffset + transformAddedLen , true ) ;
189+ }
190+ this . _updateInProgress = false ;
150191 this . _updateCallback ( newPosition , inputType , diff ) ;
151192 }
152193
194+ _transform ( newPosition , inputType , diff ) {
195+ const result = this . _transformCallback ( newPosition , inputType , diff ) ;
196+ return Number . isFinite ( result ) ? result : 0 ;
197+ }
198+
153199 _setActivePart ( pos , canOpenAutoComplete ) {
154200 const { index} = pos ;
155201 const part = this . _parts [ index ] ;
@@ -197,7 +243,7 @@ export default class EditorModel {
197243 this . _updateCallback ( pos ) ;
198244 }
199245
200- _mergeAdjacentParts ( docPos ) {
246+ _mergeAdjacentParts ( ) {
201247 let prevPart ;
202248 for ( let i = 0 ; i < this . _parts . length ; ++ i ) {
203249 let part = this . _parts [ i ] ;
@@ -339,19 +385,39 @@ export default class EditorModel {
339385
340386 return new DocumentPosition ( index , totalOffset - currentOffset ) ;
341387 }
342- }
343388
344- class DocumentPosition {
345- constructor ( index , offset ) {
346- this . _index = index ;
347- this . _offset = offset ;
348- }
349-
350- get index ( ) {
351- return this . _index ;
389+ /**
390+ * Starts a range, which can span across multiple parts, to find and replace text.
391+ * @param {DocumentPosition } position where to start the range
392+ * @return {Range }
393+ */
394+ startRange ( position ) {
395+ return new Range ( this , position ) ;
352396 }
353397
354- get offset ( ) {
355- return this . _offset ;
398+ // called from Range.replace
399+ replaceRange ( startPosition , endPosition , parts ) {
400+ const newStartPartIndex = this . _splitAt ( startPosition ) ;
401+ const idxDiff = newStartPartIndex - startPosition . index ;
402+ // if both position are in the same part, and we split it at start position,
403+ // the offset of the end position needs to be decreased by the offset of the start position
404+ const removedOffset = startPosition . index === endPosition . index ? startPosition . offset : 0 ;
405+ const adjustedEndPosition = new DocumentPosition (
406+ endPosition . index + idxDiff ,
407+ endPosition . offset - removedOffset ,
408+ ) ;
409+ const newEndPartIndex = this . _splitAt ( adjustedEndPosition ) ;
410+ for ( let i = newEndPartIndex - 1 ; i >= newStartPartIndex ; -- i ) {
411+ this . _removePart ( i ) ;
412+ }
413+ let insertIdx = newStartPartIndex ;
414+ for ( const part of parts ) {
415+ this . _insertPart ( insertIdx , part ) ;
416+ insertIdx += 1 ;
417+ }
418+ this . _mergeAdjacentParts ( ) ;
419+ if ( ! this . _updateInProgress ) {
420+ this . _updateCallback ( ) ;
421+ }
356422 }
357423}
0 commit comments