forked from microsoft/roosterjs
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Content Model: Support Ctrl+Delete/Backspace (Step 4 of 4): Finally s…
…upport Ctrl+Delete/Backspace (microsoft#1827) * Content Model: Do not hard code default format * Content Model: Support Ctrl+Backspace (1/2) * Content Model Ctrl+Delete step 2 * Step 3: Support Ctrl+Delete/Backspace * Step 4 * split change * add comment * fix build * improve * fix test * fix comment --------- Co-authored-by: Julia Roldi <[email protected]>
- Loading branch information
1 parent
1be0d27
commit 3431c34
Showing
17 changed files
with
851 additions
and
40 deletions.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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
10 changes: 0 additions & 10 deletions
10
packages/roosterjs-content-model/lib/domUtils/hasSpacesOnly.ts
This file was deleted.
Oops, something went wrong.
35 changes: 35 additions & 0 deletions
35
packages/roosterjs-content-model/lib/domUtils/stringUtil.ts
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,35 @@ | ||
// A regex to match text that only has space and CR | ||
// We use real space char " " (\u0020) here but not "\s" since "\s" will also match " " (\u00A0) which is something we need to keep | ||
const SPACE_TEXT_REGEX = /^[\r\n\t ]*$/; | ||
|
||
const SPACES_REGEX = /[\u2000\u2009\u200a\u200b\u202f\u205f\u3000\s\t\r\n]/gm; | ||
const PUNCTUATIONS = '.,?!:"()[]\\/'; | ||
|
||
/** | ||
* @internal | ||
*/ | ||
export function isPunctuation(char: string) { | ||
return PUNCTUATIONS.indexOf(char) >= 0; | ||
} | ||
|
||
/** | ||
* @internal | ||
*/ | ||
export function isSpace(char: string) { | ||
const code = char?.charCodeAt(0) ?? 0; | ||
return code == 160 || code == 32 || SPACES_REGEX.test(char); | ||
} | ||
|
||
/** | ||
* @internal | ||
*/ | ||
export function hasSpacesOnly(txt: string): boolean { | ||
return SPACE_TEXT_REGEX.test(txt); | ||
} | ||
|
||
/** | ||
* @internal | ||
*/ | ||
export function normalizeText(txt: string, isForward: boolean): string { | ||
return txt.replace(isForward ? /^\u0020+/ : /\u0020+$/, '\u00A0'); | ||
} |
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
2 changes: 1 addition & 1 deletion
2
packages/roosterjs-content-model/lib/modelApi/common/normalizeSegment.ts
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
20 changes: 20 additions & 0 deletions
20
packages/roosterjs-content-model/lib/modelApi/edit/deleteSteps/deleteAllSegmentBefore.ts
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,20 @@ | ||
import { DeleteResult, DeleteSelectionStep } from '../utils/DeleteSelectionStep'; | ||
import { deleteSegment } from '../utils/deleteSegment'; | ||
|
||
/** | ||
* @internal | ||
*/ | ||
export const deleteAllSegmentBefore: DeleteSelectionStep = (context, onDeleteEntity) => { | ||
const { paragraph, marker } = context.insertPoint; | ||
const index = paragraph.segments.indexOf(marker); | ||
|
||
for (let i = index - 1; i >= 0; i--) { | ||
const segment = paragraph.segments[i]; | ||
|
||
segment.isSelected = true; | ||
|
||
if (deleteSegment(paragraph, segment, onDeleteEntity)) { | ||
context.deleteResult = DeleteResult.Range; | ||
} | ||
} | ||
}; |
184 changes: 184 additions & 0 deletions
184
packages/roosterjs-content-model/lib/modelApi/edit/deleteSteps/deleteWordSelection.ts
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,184 @@ | ||
import { ContentModelParagraph } from '../../../publicTypes/block/ContentModelParagraph'; | ||
import { isPunctuation, isSpace, normalizeText } from '../../../domUtils/stringUtil'; | ||
import { isWhiteSpacePreserved } from '../../common/isWhiteSpacePreserved'; | ||
import { | ||
DeleteResult, | ||
DeleteSelectionContext, | ||
DeleteSelectionStep, | ||
} from '../utils/DeleteSelectionStep'; | ||
|
||
const enum DeleteWordState { | ||
Start, | ||
Punctuation, | ||
Text, | ||
NonText, | ||
Space, | ||
End, | ||
} | ||
|
||
interface CharInfo { | ||
text: boolean; | ||
space: boolean; | ||
punctuation: boolean; | ||
} | ||
|
||
function getDeleteWordSelection(direction: 'forward' | 'backward'): DeleteSelectionStep { | ||
return context => { | ||
const { marker, paragraph } = context.insertPoint; | ||
const startIndex = paragraph.segments.indexOf(marker); | ||
const deleteNext = direction == 'forward'; | ||
|
||
let iterator = iterateSegments(paragraph, startIndex, deleteNext, context); | ||
let curr = iterator.next(); | ||
|
||
for (let state = DeleteWordState.Start; state != DeleteWordState.End && !curr.done; ) { | ||
const { punctuation, space, text } = curr.value; | ||
|
||
// This is a state machine of how to delete a whole word together with space and punctuations. | ||
// For a full state machine chart, see | ||
// Forward delete: https://github.com/microsoft/roosterjs/blob/master/assets/design-charts/ForwardDeleteWord.png | ||
// Backward delete: https://github.com/microsoft/roosterjs/blob/master/assets/design-charts/BackwardDeleteWord.png | ||
switch (state) { | ||
case DeleteWordState.Start: | ||
state = space | ||
? DeleteWordState.Space | ||
: punctuation | ||
? DeleteWordState.Punctuation | ||
: DeleteWordState.Text; | ||
curr = iterator.next(true /*delete*/); | ||
break; | ||
|
||
case DeleteWordState.Punctuation: | ||
if (deleteNext && space) { | ||
state = DeleteWordState.NonText; | ||
curr = iterator.next(true /*delete*/); | ||
} else if (punctuation) { | ||
curr = iterator.next(true /*delete*/); | ||
} else { | ||
state = DeleteWordState.End; | ||
} | ||
break; | ||
|
||
case DeleteWordState.Text: | ||
if (deleteNext && space) { | ||
state = DeleteWordState.NonText; | ||
curr = iterator.next(true /*delete*/); | ||
} else if (text) { | ||
curr = iterator.next(true /*delete*/); | ||
} else { | ||
state = DeleteWordState.End; | ||
} | ||
break; | ||
|
||
case DeleteWordState.NonText: | ||
if (punctuation || !space) { | ||
state = DeleteWordState.End; | ||
} else { | ||
curr = iterator.next(true /*delete*/); | ||
} | ||
break; | ||
|
||
case DeleteWordState.Space: | ||
if (space) { | ||
curr = iterator.next(true /*delete*/); | ||
} else if (punctuation) { | ||
state = deleteNext ? DeleteWordState.NonText : DeleteWordState.Punctuation; | ||
curr = iterator.next(true /*delete*/); | ||
} else { | ||
state = deleteNext ? DeleteWordState.End : DeleteWordState.Text; | ||
} | ||
break; | ||
} | ||
} | ||
}; | ||
} | ||
|
||
function* iterateSegments( | ||
paragraph: ContentModelParagraph, | ||
markerIndex: number, | ||
forward: boolean, | ||
context: DeleteSelectionContext | ||
): Generator<CharInfo, null, boolean> { | ||
const step = forward ? 1 : -1; | ||
const segments = paragraph.segments; | ||
const preserveWhiteSpace = isWhiteSpacePreserved(paragraph); | ||
|
||
for (let i = markerIndex + step; i >= 0 && i < segments.length; i += step) { | ||
const segment = segments[i]; | ||
|
||
switch (segment.segmentType) { | ||
case 'Text': | ||
for ( | ||
let j = forward ? 0 : segment.text.length - 1; | ||
j >= 0 && j < segment.text.length; | ||
j += step | ||
) { | ||
const c = segment.text[j]; | ||
const punctuation = isPunctuation(c); | ||
const space = isSpace(c); | ||
const text = !punctuation && !space; | ||
|
||
if (yield { punctuation, space, text }) { | ||
let newText = segment.text; | ||
|
||
newText = newText.substring(0, j) + newText.substring(j + 1); | ||
|
||
if (!preserveWhiteSpace) { | ||
newText = normalizeText(newText, forward); | ||
} | ||
|
||
context.deleteResult = DeleteResult.Range; | ||
|
||
if (newText) { | ||
segment.text = newText; | ||
|
||
if (step > 0) { | ||
j -= step; | ||
} | ||
} else { | ||
segments.splice(i, 1); | ||
|
||
if (step > 0) { | ||
i -= step; | ||
} | ||
|
||
break; | ||
} | ||
} | ||
} | ||
break; | ||
|
||
case 'Image': | ||
if ( | ||
yield { punctuation: true, space: false, text: false } // Treat image as punctuation since they have the same behavior. | ||
) { | ||
segments.splice(i, 1); | ||
|
||
if (step > 0) { | ||
i -= step; | ||
} | ||
|
||
context.deleteResult = DeleteResult.Range; | ||
} | ||
break; | ||
|
||
case 'SelectionMarker': | ||
break; | ||
|
||
default: | ||
return null; | ||
} | ||
} | ||
|
||
return null; | ||
} | ||
|
||
/** | ||
* @internal | ||
*/ | ||
export const forwardDeleteWordSelection = getDeleteWordSelection('forward'); | ||
|
||
/** | ||
* @internal | ||
*/ | ||
export const backwardDeleteWordSelection = getDeleteWordSelection('backward'); |
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
Oops, something went wrong.