Press n or j to go to the next uncovered block, b, p or k for the previous block.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | 1x 1x 1x 111x 111x 111x 111x 1x 111x 111x 111x 2x 111x 1x 78x 78x 78x 1x 111x 111x 2x 2x 2x 2x 2x 2x 2x 4x 4x 4x 2x 4x 2x 1x 1x 1x 1x 1x 1x 1x 97x 1x 111x | import { getSelectionRootNode } from 'roosterjs-content-model-dom'; import type { ContextMenuPluginState, ContextMenuProvider, IEditor, PluginWithState, EditorOptions, DOMEventRecord, } from 'roosterjs-content-model-types'; const ContextMenuButton = 2; /** * Edit Component helps handle Content edit features */ class ContextMenuPlugin implements PluginWithState<ContextMenuPluginState> { private editor: IEditor | null = null; private state: ContextMenuPluginState; private disposer: (() => void) | null = null; /** * Construct a new instance of EditPlugin * @param options The editor options */ constructor(options: EditorOptions) { this.state = { contextMenuProviders: options.plugins?.filter<ContextMenuProvider<any>>(isContextMenuProvider) || [], }; } /** * Get a friendly name of this plugin */ getName() { return 'ContextMenu'; } /** * Initialize this plugin. This should only be called from Editor * @param editor Editor instance */ initialize(editor: IEditor) { this.editor = editor; const eventHandlers: Partial< { [P in keyof HTMLElementEventMap]: DOMEventRecord<HTMLElementEventMap[P]> } > = { contextmenu: { beforeDispatch: (event: MouseEvent | PointerEvent) => this.onContextMenuEvent(event), }, }; this.disposer = this.editor.attachDomEvent(<Record<string, DOMEventRecord>>eventHandlers); } /** * Dispose this plugin */ dispose() { this.disposer?.(); this.disposer = null; this.editor = null; } /** * Get plugin state object */ getState() { return this.state; } private onContextMenuEvent = (e: MouseEvent | PointerEvent) => { Eif (this.editor) { const allItems: any[] = []; const mouseEvent = e as MouseEvent; const pointerEvent = e as PointerEvent; // ContextMenu event can be triggered from mouse right click or keyboard (e.g. Shift+F10 on Windows) // Need to check if this is from keyboard, we need to get target node from selection because in that case // event.target is always the element that attached context menu event, here it will be editor content div. const targetNode = mouseEvent.button == ContextMenuButton ? (mouseEvent.target as Node) : pointerEvent?.pointerType === 'touch' || pointerEvent?.pointerType === 'pen' ? (pointerEvent.target as Node) : this.getFocusedNode(this.editor); Eif (targetNode) { this.state.contextMenuProviders.forEach(provider => { const items = provider.getContextMenuItems(targetNode) ?? []; Eif (items?.length > 0) { if (allItems.length > 0) { allItems.push(null); } allItems.push(...items); } }); } this.editor?.triggerEvent('contextMenu', { rawEvent: mouseEvent, items: allItems, }); } }; private getFocusedNode(editor: IEditor) { const selection = editor.getDOMSelection(); Eif (selection) { Eif (selection.type == 'range') { selection.range.collapse(true /*toStart*/); } return getSelectionRootNode(selection) || null; } else { return null; } } } function isContextMenuProvider(source: unknown): source is ContextMenuProvider<any> { return !!(<ContextMenuProvider<any>>source)?.getContextMenuItems; } /** * @internal * Create a new instance of EditPlugin. */ export function createContextMenuPlugin( options: EditorOptions ): PluginWithState<ContextMenuPluginState> { return new ContextMenuPlugin(options); } |