Skip to content

Commit

Permalink
Add background to maintain selection highlight
Browse files Browse the repository at this point in the history
  • Loading branch information
JiuqingSong authored and francismengMS committed Jun 5, 2024
1 parent 68bfc33 commit f0b46d2
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import {
} from 'roosterjs-content-model-dom';
import type { SetContentModel } from 'roosterjs-content-model-types';

const SelectionClassName = '__persistedSelection';
const SelectionSelector = '.' + SelectionClassName;

/**
* @internal
* Set content with content model
Expand All @@ -15,6 +18,16 @@ import type { SetContentModel } from 'roosterjs-content-model-types';
*/
export const setContentModel: SetContentModel = (core, model, option, onNodeCreated) => {
const editorContext = core.api.createEditorContext(core, true /*saveIndex*/);

if (option?.shouldMaintainSelection) {
editorContext.selectionClassName = SelectionClassName;
core.api.setEditorStyle(core, SelectionClassName, 'background-color: #ddd!important', [
SelectionSelector,
]);
} else {
core.api.setEditorStyle(core, SelectionClassName, null /*rule*/);
}

const modelToDomContext = option
? createModelToDomContext(
editorContext,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { setContentModel } from '../../../lib/coreApi/setContentModel/setContent

const mockedDoc = 'DOCUMENT' as any;
const mockedModel = 'MODEL' as any;
const mockedEditorContext = 'EDITORCONTEXT' as any;
const mockedEditorContext = { context: 'EDITORCONTEXT' } as any;
const mockedContext = { name: 'CONTEXT' } as any;
const mockedDiv = { ownerDocument: mockedDoc } as any;
const mockedConfig = 'CONFIG' as any;
Expand All @@ -18,6 +18,7 @@ describe('setContentModel', () => {
let createModelToDomContextWithConfigSpy: jasmine.Spy;
let setDOMSelectionSpy: jasmine.Spy;
let getDOMSelectionSpy: jasmine.Spy;
let setEditorStyleSpy: jasmine.Spy;

beforeEach(() => {
contentModelToDomSpy = spyOn(contentModelToDom, 'contentModelToDom');
Expand All @@ -34,6 +35,7 @@ describe('setContentModel', () => {
).and.returnValue(mockedContext);
setDOMSelectionSpy = jasmine.createSpy('setDOMSelection');
getDOMSelectionSpy = jasmine.createSpy('getDOMSelection');
setEditorStyleSpy = jasmine.createSpy('setEditorStyle');

core = ({
physicalRoot: mockedDiv,
Expand All @@ -42,6 +44,7 @@ describe('setContentModel', () => {
createEditorContext,
setDOMSelection: setDOMSelectionSpy,
getDOMSelection: getDOMSelectionSpy,
setEditorStyle: setEditorStyleSpy,
},
lifecycle: {},
cache: {},
Expand Down Expand Up @@ -76,6 +79,7 @@ describe('setContentModel', () => {
expect(setDOMSelectionSpy).toHaveBeenCalledWith(core, mockedRange);
expect(core.cache.cachedSelection).toBe(mockedRange);
expect(core.cache.cachedModel).toBe(mockedModel);
expect(setEditorStyleSpy).toHaveBeenCalledWith(core, '__persistedSelection', null);
});

it('with default option, no shadow edit', () => {
Expand All @@ -98,6 +102,7 @@ describe('setContentModel', () => {
mockedContext
);
expect(setDOMSelectionSpy).toHaveBeenCalledWith(core, mockedRange);
expect(setEditorStyleSpy).toHaveBeenCalledWith(core, '__persistedSelection', null);
});

it('with default option, no shadow edit, with additional option', () => {
Expand Down Expand Up @@ -125,6 +130,7 @@ describe('setContentModel', () => {
mockedContext
);
expect(setDOMSelectionSpy).toHaveBeenCalledWith(core, mockedRange);
expect(setEditorStyleSpy).toHaveBeenCalledWith(core, '__persistedSelection', null);
});

it('no default option, with shadow edit', () => {
Expand Down Expand Up @@ -181,6 +187,7 @@ describe('setContentModel', () => {
);
expect(setDOMSelectionSpy).not.toHaveBeenCalled();
expect(core.selection.selection).toBe(mockedRange);
expect(setEditorStyleSpy).toHaveBeenCalledWith(core, '__persistedSelection', null);
});

it('restore range selection ', () => {
Expand Down Expand Up @@ -215,6 +222,7 @@ describe('setContentModel', () => {
);
expect(setDOMSelectionSpy).not.toHaveBeenCalled();
expect(core.selection.selection).toBe(mockedRange);
expect(setEditorStyleSpy).toHaveBeenCalledWith(core, '__persistedSelection', null);
});

it('restore null selection ', () => {
Expand Down Expand Up @@ -244,5 +252,6 @@ describe('setContentModel', () => {
);
expect(setDOMSelectionSpy).not.toHaveBeenCalled();
expect(core.selection.selection).toBe(null);
expect(setEditorStyleSpy).toHaveBeenCalledWith(core, '__persistedSelection', null);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,9 @@ export function handleSegmentCommon(

applyFormat(containerNode, context.formatAppliers.elementBasedSegment, segment.format, context);

if (segment.isSelected && context.selectionClassName) {
containerNode.className = context.selectionClassName;
}

context.onNodeCreated?.(segment, segmentNode);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createModelToDomContext } from '../../../lib/modelToDom/context/createModelToDomContext';
import { createText } from '../../../lib/modelApi/creators/createText';
import { expectHtml } from '../../testUtils';
import { handleSegmentCommon } from '../../../lib/modelToDom/utils/handleSegmentCommon';

describe('handleSegmentCommon', () => {
Expand Down Expand Up @@ -60,4 +61,42 @@ describe('handleSegmentCommon', () => {
expect(segmentNodes.length).toBe(1);
expect(segmentNodes[0]).toBe(parent);
});

it('selected text', () => {
const txt = document.createTextNode('test');
const container = document.createElement('span');
const segment = createText('test', {
textColor: 'red',
fontSize: '10pt',
lineHeight: '2',
fontWeight: 'bold',
});
const onNodeCreated = jasmine.createSpy('onNodeCreated');
const context = createModelToDomContext();

segment.isSelected = true;
context.onNodeCreated = onNodeCreated;
context.selectionClassName = 'test';

segment.link = {
dataset: {},
format: {
href: 'href',
},
};
container.appendChild(txt);
const segmentNodes: Node[] = [];

handleSegmentCommon(document, txt, container, segment, context, segmentNodes);

expect(context.regularSelection.current.segment).toBe(txt);
expectHtml(container.outerHTML, [
'<span class="test" style="font-size: 10pt; color: red; line-height: 2;"><b><a href="href">test</a></b></span>',
'<span style="font-size: 10pt; color: red; line-height: 2;" class="test"><b><a href="href">test</a></b></span>',
]);
expect(onNodeCreated).toHaveBeenCalledWith(segment, txt);
expect(segmentNodes.length).toBe(2);
expect(segmentNodes[0]).toBe(txt);
expect(segmentNodes[1]).toBe(txt.parentNode!);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,9 @@ export interface EditorContext {
* Enabled experimental features
*/
experimentalFeatures?: ReadonlyArray<string>;

/**
* Optional parameter that indicate the customized classes to be applied on selection block.
*/
selectionClassName?: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,9 @@ export interface ModelToDomOption {
* When set to true, selection from content model will not be applied
*/
ignoreSelection?: boolean;

/**
* When set to true, selection will be maintained on text even if cursor has moved away from editor.
*/
shouldMaintainSelection?: boolean;
}

0 comments on commit f0b46d2

Please sign in to comment.