Skip to content

Commit 30fde5f

Browse files
authored
Added configurable context menu to editor (#451)
* Added configurable context menu to editor * Moved contextMenu above experimental properties, built API docs
1 parent 44b5415 commit 30fde5f

File tree

5 files changed

+40
-4
lines changed

5 files changed

+40
-4
lines changed

libs/shared-react/src/plugins/usj/ContextMenuPlugin.tsx

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,20 @@ import { ReactElement, useCallback, useEffect, useMemo, useRef, useState } from
1010
import * as ReactDOM from "react-dom";
1111
import { isImmutableChapterElement } from "shared";
1212

13+
/**
14+
* A context menu option to add to the editor context menu.
15+
*
16+
* @public
17+
*/
18+
export interface ContextMenuOptionConfig {
19+
/** Display title of the menu item. */
20+
title: string;
21+
/** Callback invoked when the menu item is selected. */
22+
onSelect: () => void;
23+
/** Whether the menu item is disabled. */
24+
isDisabled?: boolean;
25+
}
26+
1327
function ContextMenuItem({
1428
index,
1529
isSelected,
@@ -111,15 +125,19 @@ function isEditorInput(
111125
return element.classList.contains(editorInputClassName);
112126
}
113127

114-
export function ContextMenuPlugin(): ReactElement {
128+
export function ContextMenuPlugin({
129+
options: extraOptions,
130+
}: {
131+
options?: ContextMenuOptionConfig[];
132+
} = {}): ReactElement {
115133
const [editor] = useLexicalComposerContext();
116134
const [isReadonly, setIsReadonly] = useState(() => !editor.isEditable());
117135
const targetRef = useRef<HTMLElement | undefined>(undefined);
118136
const editorInputClassNameRef = useRef<string | undefined>(undefined);
119137
const closeMenuFnRef = useRef<(() => void) | undefined>(undefined);
120138

121139
const options = useMemo(() => {
122-
return [
140+
const builtIn = [
123141
new ContextMenuOption(`Cut`, {
124142
onSelect: () => {
125143
editor.dispatchCommand(CUT_COMMAND, null);
@@ -144,7 +162,12 @@ export function ContextMenuPlugin(): ReactElement {
144162
isDisabled: isReadonly,
145163
}),
146164
];
147-
}, [editor, isReadonly]);
165+
const extra = (extraOptions ?? []).map(
166+
(opt) =>
167+
new ContextMenuOption(opt.title, { onSelect: opt.onSelect, isDisabled: opt.isDisabled }),
168+
);
169+
return [...builtIn, ...extra];
170+
}, [editor, isReadonly, extraOptions]);
148171

149172
const onSelectOption = useCallback(
150173
(selectedOption: ContextMenuOption, targetNode: LexicalNode | null, closeMenu: () => void) => {

packages/platform/etc/platform-editor.api.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ export interface CommentBase {
3535
// @public
3636
export type Comments = (Thread | CommentBase)[];
3737

38+
// @public
39+
export interface ContextMenuOptionConfig {
40+
isDisabled?: boolean;
41+
onSelect: () => void;
42+
title: string;
43+
}
44+
3845
// @public
3946
export type DeltaOp = Op;
4047

@@ -61,6 +68,7 @@ export const Editorial: ForwardRefExoticComponent<EditorProps<LoggerBasic> & Ref
6168

6269
// @public
6370
export interface EditorOptions {
71+
contextMenu?: ContextMenuOptionConfig[];
6472
debug?: boolean;
6573
hasExternalUI?: boolean;
6674
hasSpellCheck?: boolean;

packages/platform/src/editor/Editor.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ const Editor = forwardRef(function Editor<TLogger extends LoggerBasic>(
154154
view: viewOptions = defaultViewOptions,
155155
nodes: nodeOptions = defaultNodeOptions,
156156
debug = false,
157+
contextMenu: contextMenuOptions,
157158
} = options ?? defaultOptions;
158159

159160
editorConfig.editable = !isReadonly;
@@ -379,7 +380,7 @@ const Editor = forwardRef(function Editor<TLogger extends LoggerBasic>(
379380
<CharNodePlugin />
380381
<ClipboardPlugin />
381382
<CommandMenuPlugin logger={logger} />
382-
<ContextMenuPlugin />
383+
<ContextMenuPlugin options={contextMenuOptions} />
383384
<NoteNodePlugin
384385
expandedNoteKeyRef={expandedNoteKeyRef}
385386
nodeOptions={nodeOptions}

packages/platform/src/editor/editor.model.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { RefObject } from "react";
44
import { LoggerBasic, TypedMarkOnClick, TypedMarkOnRemove } from "shared";
55
import {
66
AnnotationRange,
7+
ContextMenuOptionConfig,
78
DeltaOp,
89
DeltaSource,
910
SelectionRange,
@@ -154,6 +155,8 @@ export interface EditorOptions {
154155
markerMenuTrigger?: string;
155156
/** Options for some editor nodes. */
156157
nodes?: UsjNodeOptions;
158+
/** Additional items to append to the editor context menu. */
159+
contextMenu?: ContextMenuOptionConfig[];
157160
/**
158161
* EXPERIMENTAL: View options. Defaults to the formatted view mode which is currently the only
159162
* functional option.

packages/platform/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export type {
6060
export type {
6161
AddMissingComments,
6262
AnnotationRange,
63+
ContextMenuOptionConfig,
6364
DeltaOp,
6465
DeltaOpInsertNoteEmbed,
6566
DeltaSource,

0 commit comments

Comments
 (0)