Skip to content

Commit

Permalink
Standalone Editor: Add a SelectionPlugin (#2228)
Browse files Browse the repository at this point in the history
  • Loading branch information
JiuqingSong authored Dec 1, 2023
1 parent e014196 commit 89a61d0
Show file tree
Hide file tree
Showing 20 changed files with 229 additions and 219 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const setContentModel: SetContentModel = (core, model, option, onNodeCrea
if (!option?.ignoreSelection) {
core.api.setDOMSelection(core, selection);
} else if (selection.type == 'range') {
core.domEvent.selectionRange = selection.range;
core.selection.selectionRange = selection.range;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,8 @@ class DOMEventPlugin implements PluginWithState<DOMEventPluginState> {
this.state = {
isInIME: false,
scrollContainer: options.scrollContainer || contentDiv,
selectionRange: null,
contextMenuProviders:
options.plugins?.filter<ContextMenuProvider<any>>(isContextMenuProvider) || [],
tableSelectionRange: null,
imageSelectionRange: null,
mouseDownX: null,
mouseDownY: null,
mouseUpEventListerAdded: false,
Expand Down Expand Up @@ -90,27 +87,13 @@ class DOMEventPlugin implements PluginWithState<DOMEventPluginState> {
dragstart: this.onDragStart,
drop: this.onDrop,

// 5. Focus management
focus: this.onFocus,

// 6. Input event
// 5. Input event
input: this.getEventHandler(PluginEventType.Input),
};

const env = this.editor.getEnvironment();

// 7. onBlur handlers
if (env.isSafari) {
document.addEventListener('mousedown', this.onMouseDownDocument, true /*useCapture*/);
document.addEventListener('keydown', this.onKeyDownDocument);
document.defaultView?.addEventListener('blur', this.cacheSelection);
} else {
eventHandlers.blur = this.cacheSelection;
}

this.disposer = editor.addDomEventHandler(<Record<string, DOMEventHandler>>eventHandlers);

// 8. Scroll event
// 7. Scroll event
this.state.scrollContainer.addEventListener('scroll', this.onScroll);
document.defaultView?.addEventListener('scroll', this.onScroll);
document.defaultView?.addEventListener('resize', this.onScroll);
Expand All @@ -123,15 +106,6 @@ class DOMEventPlugin implements PluginWithState<DOMEventPluginState> {
this.removeMouseUpEventListener();

const document = this.editor?.getDocument();
if (document) {
document.removeEventListener(
'mousedown',
this.onMouseDownDocument,
true /*useCapture*/
);
document.removeEventListener('keydown', this.onKeyDownDocument);
document.defaultView?.removeEventListener('blur', this.cacheSelection);
}

document?.defaultView?.removeEventListener('resize', this.onScroll);
document?.defaultView?.removeEventListener('scroll', this.onScroll);
Expand Down Expand Up @@ -162,43 +136,6 @@ class DOMEventPlugin implements PluginWithState<DOMEventPluginState> {
});
};

private onFocus = () => {
if (!this.state.skipReselectOnFocus) {
const { table, coordinates } = this.state.tableSelectionRange || {};
const { image } = this.state.imageSelectionRange || {};

if (table && coordinates) {
this.editor?.select(table, coordinates);
} else if (image) {
this.editor?.select(image);
} else if (this.state.selectionRange) {
this.editor?.select(this.state.selectionRange);
}
}

this.state.selectionRange = null;
};
private onKeyDownDocument = (event: KeyboardEvent) => {
if (event.which == Keys.TAB && !event.defaultPrevented) {
this.cacheSelection();
}
};

private onMouseDownDocument = (event: MouseEvent) => {
if (
this.editor &&
!this.state.selectionRange &&
!this.editor.contains(event.target as Node)
) {
this.cacheSelection();
}
};

private cacheSelection = () => {
if (!this.state.selectionRange && this.editor) {
this.state.selectionRange = this.editor.getSelectionRange(false /*tryGetFromCache*/);
}
};
private onScroll = (e: Event) => {
this.editor?.triggerPluginEvent(PluginEventType.Scroll, {
rawEvent: e,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import type { IEditor, PluginWithState } from 'roosterjs-editor-types';
import type {
IStandaloneEditor,
SelectionPluginState,
StandaloneEditorOptions,
} from 'roosterjs-content-model-types';

class SelectionPlugin implements PluginWithState<SelectionPluginState> {
private editor: (IStandaloneEditor & IEditor) | null = null;
private state: SelectionPluginState;
private disposer: (() => void) | null = null;

constructor(options: StandaloneEditorOptions) {
this.state = {
selectionRange: null,
tableSelectionRange: null,
imageSelectionRange: null,
selectionStyleNode: null,
imageSelectionBorderColor: options.imageSelectionBorderColor, // TODO: Move to Selection core plugin
};
}

getName() {
return 'Selection';
}

initialize(editor: IEditor) {
this.editor = editor as IEditor & IStandaloneEditor;

const doc = this.editor.getDocument();
const styleNode = doc.createElement('style');

doc.head.appendChild(styleNode);
this.state.selectionStyleNode = styleNode;

const env = this.editor.getEnvironment();
const document = this.editor.getDocument();

if (env.isSafari) {
document.addEventListener('mousedown', this.onMouseDownDocument, true /*useCapture*/);
document.addEventListener('keydown', this.onKeyDownDocument);
document.defaultView?.addEventListener('blur', this.onBlur);
this.disposer = this.editor.addDomEventHandler('focus', this.onFocus);
} else {
this.disposer = this.editor.addDomEventHandler({
focus: this.onFocus,
blur: this.onBlur,
});
}
}

dispose() {
if (this.state.selectionStyleNode) {
this.state.selectionStyleNode.parentNode?.removeChild(this.state.selectionStyleNode);
this.state.selectionStyleNode = null;
}

if (this.disposer) {
this.disposer();
this.disposer = null;
}

if (this.editor) {
const document = this.editor.getDocument();

document.removeEventListener(
'mousedown',
this.onMouseDownDocument,
true /*useCapture*/
);
document.removeEventListener('keydown', this.onKeyDownDocument);
document.defaultView?.removeEventListener('blur', this.onBlur);

this.editor = null;
}
}

getState(): SelectionPluginState {
return this.state;
}

private onFocus = () => {
if (!this.state.skipReselectOnFocus && this.editor) {
const { table, coordinates } = this.state.tableSelectionRange || {};
const { image } = this.state.imageSelectionRange || {};

if (table && coordinates) {
this.editor.select(table, coordinates);
} else if (image) {
this.editor.select(image);
} else if (this.state.selectionRange) {
this.editor.select(this.state.selectionRange);
}
}

this.state.selectionRange = null;
};

private onBlur = () => {
if (!this.state.selectionRange && this.editor) {
this.state.selectionRange = this.editor.getSelectionRange(false /*tryGetFromCache*/);
}
};

private onKeyDownDocument = (event: KeyboardEvent) => {
if (event.key == 'Tab' && !event.defaultPrevented) {
this.onBlur();
}
};

private onMouseDownDocument = (event: MouseEvent) => {
if (this.editor && !this.editor.contains(event.target as Node)) {
this.onBlur();
}
};
}

/**
* @internal
* Create a new instance of SelectionPlugin.
* @param option The editor option
*/
export function createSelectionPlugin(
options: StandaloneEditorOptions
): PluginWithState<SelectionPluginState> {
return new SelectionPlugin(options);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { createContentModelFormatPlugin } from './ContentModelFormatPlugin';
import { createDOMEventPlugin } from './DOMEventPlugin';
import { createEntityPlugin } from './EntityPlugin';
import { createLifecyclePlugin } from './LifecyclePlugin';
import { createSelectionPlugin } from './SelectionPlugin';
import type {
StandaloneEditorCorePlugins,
StandaloneEditorOptions,
Expand All @@ -25,5 +26,6 @@ export function createStandaloneEditorCorePlugins(
domEvent: createDOMEventPlugin(options, contentDiv),
lifecycle: createLifecyclePlugin(options, contentDiv),
entity: createEntityPlugin(),
selection: createSelectionPlugin(options),
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,13 @@ export function createStandaloneEditorCore(
corePlugins.format,
corePlugins.copyPaste,
corePlugins.domEvent,
corePlugins.selection,
corePlugins.entity,
...tempPlugins,
corePlugins.lifecycle,
],
environment: createEditorEnvironment(),
darkColorHandler: new DarkColorHandlerImpl(contentDiv, options.baseDarkColor),
imageSelectionBorderColor: options.imageSelectionBorderColor, // TODO: Move to Selection core plugin
trustedHTMLHandler: options.trustedHTMLHandler || defaultTrustHtmlHandler,
...createStandaloneEditorDefaultSettings(options),
...getPluginState(corePlugins),
Expand Down Expand Up @@ -79,5 +79,6 @@ function getPluginState(corePlugins: StandaloneEditorCorePlugins): StandaloneEdi
format: corePlugins.format.getState(),
lifecycle: corePlugins.lifecycle.getState(),
entity: corePlugins.entity.getState(),
selection: corePlugins.selection.getState(),
};
}
Loading

0 comments on commit 89a61d0

Please sign in to comment.