-
Notifications
You must be signed in to change notification settings - Fork 157
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Content Model: Move pending format into editor core (#2188)
* Move formatWithContentModel to be a core API * Content Model: Allow clear cache from formatContentModel * Content Model: Move pending format into editor core
- Loading branch information
1 parent
331b5a3
commit cbba2ce
Showing
45 changed files
with
1,347 additions
and
1,143 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
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
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
105 changes: 105 additions & 0 deletions
105
...es-content-model/roosterjs-content-model-editor/lib/modelApi/format/applyDefaultFormat.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,105 @@ | ||
import { DeleteResult } from '../../modelApi/edit/utils/DeleteSelectionStep'; | ||
import { deleteSelection } from '../../modelApi/edit/deleteSelection'; | ||
import { isBlockElement, isNodeOfType, normalizeContentModel } from 'roosterjs-content-model-dom'; | ||
import type { ContentModelSegmentFormat } from 'roosterjs-content-model-types'; | ||
import type { IContentModelEditor } from '../../publicTypes/IContentModelEditor'; | ||
|
||
/** | ||
* @internal | ||
* When necessary, set default format as current pending format so it will be applied when Input event is fired | ||
* @param editor The Content Model Editor | ||
* @param defaultFormat The default segment format to apply | ||
*/ | ||
export function applyDefaultFormat( | ||
editor: IContentModelEditor, | ||
defaultFormat: ContentModelSegmentFormat | ||
) { | ||
const selection = editor.getDOMSelection(); | ||
const range = selection?.type == 'range' ? selection.range : null; | ||
const posContainer = range?.startContainer ?? null; | ||
const posOffset = range?.startOffset ?? null; | ||
|
||
if (posContainer) { | ||
let node: Node | null = posContainer; | ||
|
||
while (node && editor.contains(node)) { | ||
if (isNodeOfType(node, 'ELEMENT_NODE')) { | ||
if (node.getAttribute?.('style')) { | ||
return; | ||
} else if (isBlockElement(node)) { | ||
break; | ||
} | ||
} | ||
|
||
node = node.parentNode; | ||
} | ||
} else { | ||
return; | ||
} | ||
|
||
editor.formatContentModel((model, context) => { | ||
const result = deleteSelection(model, [], context); | ||
|
||
if (result.deleteResult == DeleteResult.Range) { | ||
normalizeContentModel(model); | ||
editor.addUndoSnapshot(); | ||
|
||
return true; | ||
} else if ( | ||
result.deleteResult == DeleteResult.NotDeleted && | ||
result.insertPoint && | ||
posContainer && | ||
posOffset !== null | ||
) { | ||
const { paragraph, path, marker } = result.insertPoint; | ||
const blocks = path[0].blocks; | ||
const blockCount = blocks.length; | ||
const blockIndex = blocks.indexOf(paragraph); | ||
|
||
if ( | ||
paragraph.isImplicit && | ||
paragraph.segments.length == 1 && | ||
paragraph.segments[0] == marker && | ||
blockCount > 0 && | ||
blockIndex == blockCount - 1 | ||
) { | ||
// Focus is in the last paragraph which is implicit and there is not other segments. | ||
// This can happen when focus is moved after all other content under current block group. | ||
// We need to check if browser will merge focus into previous paragraph by checking if | ||
// previous block is block. If previous block is paragraph, browser will most likely merge | ||
// the input into previous paragraph, then nothing need to do here. Otherwise we need to | ||
// apply pending format since this input event will start a new real paragraph. | ||
const previousBlock = blocks[blockIndex - 1]; | ||
|
||
if (previousBlock?.blockType != 'Paragraph') { | ||
context.newPendingFormat = getNewPendingFormat( | ||
editor, | ||
defaultFormat, | ||
marker.format | ||
); | ||
} | ||
} else if (paragraph.segments.every(x => x.segmentType != 'Text')) { | ||
context.newPendingFormat = getNewPendingFormat( | ||
editor, | ||
defaultFormat, | ||
marker.format | ||
); | ||
} | ||
} | ||
|
||
// We didn't do any change but just apply default format to pending format, so no need to write back | ||
return false; | ||
}); | ||
} | ||
|
||
function getNewPendingFormat( | ||
editor: IContentModelEditor, | ||
defaultFormat: ContentModelSegmentFormat, | ||
markerFormat: ContentModelSegmentFormat | ||
): ContentModelSegmentFormat { | ||
return { | ||
...defaultFormat, | ||
...editor.getPendingFormat(), | ||
...markerFormat, | ||
}; | ||
} |
75 changes: 75 additions & 0 deletions
75
...es-content-model/roosterjs-content-model-editor/lib/modelApi/format/applyPendingFormat.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,75 @@ | ||
import { iterateSelections } from '../../modelApi/selection/iterateSelections'; | ||
import type { ContentModelSegmentFormat } from 'roosterjs-content-model-types'; | ||
import type { IContentModelEditor } from '../../publicTypes/IContentModelEditor'; | ||
import { | ||
createText, | ||
normalizeContentModel, | ||
setParagraphNotImplicit, | ||
} from 'roosterjs-content-model-dom'; | ||
|
||
const ANSI_SPACE = '\u0020'; | ||
const NON_BREAK_SPACE = '\u00A0'; | ||
|
||
/** | ||
* @internal | ||
* Apply pending format to the text user just input | ||
* @param editor The editor to get format from | ||
* @param data The text user just input | ||
*/ | ||
export function applyPendingFormat( | ||
editor: IContentModelEditor, | ||
data: string, | ||
format: ContentModelSegmentFormat | ||
) { | ||
let isChanged = false; | ||
|
||
editor.formatContentModel( | ||
(model, context) => { | ||
iterateSelections(model, (_, __, block, segments) => { | ||
if ( | ||
block?.blockType == 'Paragraph' && | ||
segments?.length == 1 && | ||
segments[0].segmentType == 'SelectionMarker' | ||
) { | ||
const marker = segments[0]; | ||
const index = block.segments.indexOf(marker); | ||
const previousSegment = block.segments[index - 1]; | ||
|
||
if (previousSegment?.segmentType == 'Text') { | ||
const text = previousSegment.text; | ||
const subStr = text.substr(-data.length, data.length); | ||
|
||
// For space, there can be   (space) or   ( ), we treat them as the same | ||
if (subStr == data || (data == ANSI_SPACE && subStr == NON_BREAK_SPACE)) { | ||
marker.format = { ...format }; | ||
previousSegment.text = text.substring(0, text.length - data.length); | ||
|
||
const newText = createText( | ||
data == ANSI_SPACE ? NON_BREAK_SPACE : data, | ||
{ | ||
...previousSegment.format, | ||
...format, | ||
} | ||
); | ||
|
||
block.segments.splice(index, 0, newText); | ||
setParagraphNotImplicit(block); | ||
isChanged = true; | ||
} | ||
} | ||
} | ||
return true; | ||
}); | ||
|
||
if (isChanged) { | ||
normalizeContentModel(model); | ||
context.skipUndoSnapshot = true; | ||
} | ||
|
||
return isChanged; | ||
}, | ||
{ | ||
apiName: 'applyPendingFormat', | ||
} | ||
); | ||
} |
Oops, something went wrong.