Skip to content

Commit

Permalink
Weekly Bump (#2171)
Browse files Browse the repository at this point in the history
* Use Content Model to handle Delete/Backspace key in more cases (#2162)

* Do not set focus when quite shadow edit (#2163)

* Fix #237217 (#2164)

* Fix #237735 (#2165)

* Fix #236416 (#2166)

* copyPaste

* update versions

* Fix #2160 (#2167)

* Fix #2160

* fix build

---------

Co-authored-by: Jiuqing Song <jisong@microsoft.com>
  • Loading branch information
BryanValverdeU and JiuqingSong authored Oct 27, 2023
1 parent f527296 commit 8a4f529
Show file tree
Hide file tree
Showing 17 changed files with 1,359 additions and 1,027 deletions.
Original file line number Diff line number Diff line change
@@ -1,23 +1,28 @@
import { getRegularSelectionOffsets } from '../utils/getRegularSelectionOffsets';
import { handleRegularSelection } from './childProcessor';
import { addSelectionMarker } from '../utils/addSelectionMarker';
import type { ElementProcessor } from 'roosterjs-content-model-types';

/**
* @internal
* @param group
* @param element
* @param node
* @param context
*/
export const delimiterProcessor: ElementProcessor<Node> = (group, element, context) => {
let index = 0;
const [nodeStartOffset, nodeEndOffset] = getRegularSelectionOffsets(context, element);
export const delimiterProcessor: ElementProcessor<Node> = (group, node, context) => {
const range = context.selection?.type == 'range' ? context.selection.range : null;

for (let child = element.firstChild; child; child = child.nextSibling) {
handleRegularSelection(index, context, group, nodeStartOffset, nodeEndOffset);
if (range) {
if (node.contains(range.startContainer)) {
context.isInSelection = true;

delimiterProcessor(group, child, context);
index++;
}
addSelectionMarker(group, context);
}

if (context.selection?.type == 'range' && node.contains(range.endContainer)) {
if (!context.selection.range.collapsed) {
addSelectionMarker(group, context);
}

handleRegularSelection(index, context, group, nodeStartOffset, nodeEndOffset);
context.isInSelection = false;
}
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ describe('delimiterProcessor', () => {
blockGroupType: 'Document',
blocks: [],
});
expect(delimiterProcessorFile.handleRegularSelection).toHaveBeenCalledTimes(3);
});

it('Delimiter with selection', () => {
Expand Down Expand Up @@ -65,4 +64,45 @@ describe('delimiterProcessor', () => {
});
expect(context.isInSelection).toBeTrue();
});

it('Delimiter with selection end', () => {
const doc = createContentModelDocument();
const text1 = document.createTextNode('test1');
const text2 = document.createTextNode('test2');
const span = document.createElement('span');
const span2 = document.createElement('span');
const div = document.createElement('div');

span.appendChild(text2);

div.appendChild(text1);
div.appendChild(span);
div.appendChild(span2);

context.selection = {
type: 'range',
range: createRange(text1, 2, text2, 3),
};

delimiterProcessor(doc, span, context);

expect(doc).toEqual({
blockGroupType: 'Document',
blocks: [
{
blockType: 'Paragraph',
isImplicit: true,
format: {},
segments: [
{
segmentType: 'SelectionMarker',
isSelected: true,
format: {},
},
],
},
],
});
expect(context.isInSelection).toBeFalse();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const setContentModel: SetContentModel = (core, model, option, onNodeCrea
const modelToDomContext = option
? createModelToDomContext(editorContext, ...(core.defaultModelToDomOptions || []), option)
: createModelToDomContextWithConfig(core.defaultModelToDomConfig, editorContext);

const selection = contentModelToDom(
core.contentDiv.ownerDocument,
core.contentDiv,
Expand All @@ -29,7 +30,11 @@ export const setContentModel: SetContentModel = (core, model, option, onNodeCrea
core.cache.cachedSelection = selection || undefined;

if (selection) {
core.api.setDOMSelection(core, selection);
if (!option?.ignoreSelection) {
core.api.setDOMSelection(core, selection);
} else if (selection.type == 'range') {
core.domEvent.selectionRange = selection.range;
}
}

core.cache.cachedModel = model;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { getSelectionPath } from 'roosterjs-editor-dom';
import { iterateSelections } from '../../modelApi/selection/iterateSelections';
import { PluginEventType } from 'roosterjs-editor-types';
import type { ContentModelEditorCore } from '../../publicTypes/ContentModelEditorCore';
import type { SwitchShadowEdit } from 'roosterjs-editor-types';
Expand Down Expand Up @@ -54,7 +55,12 @@ export const switchShadowEdit: SwitchShadowEdit = (editorCore, isOn): void => {
);

if (core.cache.cachedModel) {
core.api.setContentModel(core, core.cache.cachedModel);
// Force clear cached element from selected block
iterateSelections([core.cache.cachedModel], () => {});

core.api.setContentModel(core, core.cache.cachedModel, {
ignoreSelection: true, // Do not set focus and selection when quit shadow edit, focus may remain in UI control (picker, ...)
});
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,19 @@ export interface MergeModelOption {

/**
* @internal
* Merge source model into target mode
* @param target Target Content Model that will merge content into
* @param source Source Content Model will be merged to target model
* @param context Format context. When call this function inside formatWithContentModel, provide this context so that formatWithContentModel will do extra handling to the result
* @param options More options, see MergeModelOption
* @returns Insert point after merge, or null if there is no insert point
*/
export function mergeModel(
target: ContentModelDocument,
source: ContentModelDocument,
context?: FormatWithContentModelContext,
options?: MergeModelOption
) {
): InsertPoint | null {
const insertPosition =
options?.insertPosition ?? deleteSelection(target, [], context).insertPoint;

Expand Down Expand Up @@ -119,6 +125,8 @@ export function mergeModel(
}

normalizeContentModel(target);

return insertPosition;
}

function mergeParagraph(
Expand Down Expand Up @@ -186,7 +194,7 @@ function mergeTable(
newTable: ContentModelTable,
source: ContentModelDocument
) {
const { tableContext } = markerPosition;
const { tableContext, marker } = markerPosition;

if (tableContext && source.blocks.length == 1 && source.blocks[0] == newTable) {
const { table, colIndex, rowIndex } = tableContext;
Expand Down Expand Up @@ -226,10 +234,19 @@ function mergeTable(
}
}

const oldCell = table.rows[rowIndex + i].cells[colIndex + j];
table.rows[rowIndex + i].cells[colIndex + j] = newCell;

if (i == 0 && j == 0) {
addSegment(newCell, createSelectionMarker());
const newMarker = createSelectionMarker(marker.format);
const newPara = addSegment(newCell, newMarker);

if (markerPosition.path[0] == oldCell) {
// Update insert point to match the change result
markerPosition.path[0] = newCell;
markerPosition.marker = newMarker;
markerPosition.paragraph = newPara;
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,12 +115,7 @@ function setSelectionToTable(
setIsSelected(currentCell, isSelected);

if (!isSelected) {
setSelectionToBlockGroup(
currentCell,
false /*isInSelection*/,
null /*start*/,
null /*end*/
);
setSelectionToBlockGroup(currentCell, false /*isInSelection*/, start, end);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,16 +89,12 @@ function shouldDeleteWithContentModel(range: Range | null, rawEvent: KeyboardEve
}

function canDeleteBefore(rawEvent: KeyboardEvent, range: Range) {
return (
rawEvent.key == 'Backspace' &&
(range.startOffset > 1 || range.startContainer.previousSibling)
);
return rawEvent.key == 'Backspace' && range.startOffset > 1;
}

function canDeleteAfter(rawEvent: KeyboardEvent, range: Range) {
return (
rawEvent.key == 'Delete' &&
(range.startOffset < (range.startContainer.nodeValue?.length ?? 0) - 1 ||
range.startContainer.nextSibling)
range.startOffset < (range.startContainer.nodeValue?.length ?? 0) - 1
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ export default function editTable(editor: IContentModelEditor, operation: TableO
}
}

normalizeTable(tableModel);
normalizeTable(tableModel, model.format);

if (hasMetadata(tableModel)) {
applyTableFormat(tableModel, undefined /*newFormat*/, true /*keepCellShade*/);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { ChangeSource } from '../../publicTypes/event/ContentModelContentChanged
import { formatWithContentModel } from './formatWithContentModel';
import { GetContentMode, PasteType as OldPasteType, PluginEventType } from 'roosterjs-editor-types';
import { mergeModel } from '../../modelApi/common/mergeModel';
import { setPendingFormat } from '../../modelApi/format/pendingFormat';
import type { InsertPoint } from '../../publicTypes/selection/InsertPoint';
import type {
ContentModelDocument,
ContentModelSegmentFormat,
Expand Down Expand Up @@ -35,6 +37,19 @@ const PasteTypeMap: Record<PasteType, OldPasteType> = {
mergeFormat: OldPasteType.MergeFormat,
normal: OldPasteType.Normal,
};
const EmptySegmentFormat: Required<ContentModelSegmentFormat> = {
backgroundColor: '',
fontFamily: '',
fontSize: '',
fontWeight: '',
italic: false,
letterSpacing: '',
lineHeight: '',
strikethrough: false,
superOrSubScriptSequence: '',
textColor: '',
underline: false,
};

/**
* Paste into editor using a clipboardData object
Expand All @@ -55,6 +70,7 @@ export default function paste(
}

editor.focus();
let originalFormat: ContentModelSegmentFormat | undefined;

formatWithContentModel(
editor,
Expand All @@ -81,14 +97,18 @@ export default function paste(
createDomToModelContext(undefined /*editorContext*/, domToModelOption)
);

mergePasteContent(
const insertPoint = mergePasteContent(
model,
context,
pasteModel,
pasteType == 'mergeFormat',
customizedMerge
);

if (insertPoint) {
originalFormat = insertPoint.marker.format;
}

return true;
},

Expand All @@ -97,6 +117,17 @@ export default function paste(
getChangeData: () => clipboardData,
}
);

const pos = editor.getFocusedPosition();

if (originalFormat && pos) {
setPendingFormat(
editor,
{ ...EmptySegmentFormat, ...originalFormat }, // Use empty format as initial value to clear any other format inherits from pasted content
pos.node,
pos.offset
);
}
}

/**
Expand All @@ -110,16 +141,14 @@ export function mergePasteContent(
applyCurrentFormat: boolean,
customizedMerge:
| undefined
| ((source: ContentModelDocument, target: ContentModelDocument) => void)
) {
if (customizedMerge) {
customizedMerge(model, pasteModel);
} else {
mergeModel(model, pasteModel, context, {
mergeFormat: applyCurrentFormat ? 'keepSourceEmphasisFormat' : 'none',
mergeTable: shouldMergeTable(pasteModel),
});
}
| ((source: ContentModelDocument, target: ContentModelDocument) => InsertPoint | null)
): InsertPoint | null {
return customizedMerge
? customizedMerge(model, pasteModel)
: mergeModel(model, pasteModel, context, {
mergeFormat: applyCurrentFormat ? 'keepSourceEmphasisFormat' : 'none',
mergeTable: shouldMergeTable(pasteModel),
});
}

function shouldMergeTable(pasteModel: ContentModelDocument): boolean | undefined {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { InsertPoint } from '../selection/InsertPoint';
import type { ContentModelDocument, DomToModelOption } from 'roosterjs-content-model-types';
import type {
BeforePasteEvent,
Expand All @@ -16,7 +17,10 @@ export interface ContentModelBeforePasteEventData extends BeforePasteEventData {
/**
* customizedMerge Customized merge function to use when merging the paste fragment into the editor
*/
customizedMerge?: (target: ContentModelDocument, source: ContentModelDocument) => void;
customizedMerge?: (
target: ContentModelDocument,
source: ContentModelDocument
) => InsertPoint | null;
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import * as iterateSelections from '../../../lib/modelApi/selection/iterateSelections';
import { ContentModelEditorCore } from '../../../lib/publicTypes/ContentModelEditorCore';
import { PluginEventType } from 'roosterjs-editor-types';
import { switchShadowEdit } from '../../../lib/editor/coreApi/switchShadowEdit';
Expand Down Expand Up @@ -142,10 +143,14 @@ describe('switchShadowEdit', () => {
it('with cache, isOff', () => {
core.cache.cachedModel = mockedCachedModel;

spyOn(iterateSelections, 'iterateSelections');

switchShadowEdit(core, false);

expect(createContentModel).not.toHaveBeenCalled();
expect(setContentModel).toHaveBeenCalledWith(core, mockedCachedModel);
expect(setContentModel).toHaveBeenCalledWith(core, mockedCachedModel, {
ignoreSelection: true,
});
expect(core.cache.cachedModel).toBe(mockedCachedModel);

expect(triggerEvent).toHaveBeenCalledTimes(1);
Expand Down
Loading

0 comments on commit 8a4f529

Please sign in to comment.