Skip to content

Instantly share code, notes, and snippets.

@neharkarvishal
Created September 4, 2022 15:20
Show Gist options
  • Save neharkarvishal/f2310e21ebb6726b000c0d523d47a375 to your computer and use it in GitHub Desktop.
Save neharkarvishal/f2310e21ebb6726b000c0d523d47a375 to your computer and use it in GitHub Desktop.
import { Extension } from '@tiptap/core';
import { ComponentType } from 'react';
import { ContextMenuPlugin, ContextMenuProps } from './ContextMenuPlugin';
interface ContextMenuOptions {
component: ComponentType<ContextMenuProps>;
}
export const ContextMenu = Extension.create<ContextMenuOptions>({
name: 'contextMenu',
addOptions() {
return {
element: null,
component: () => null,
};
},
addProseMirrorPlugins() {
return [
ContextMenuPlugin({
editor: this.editor,
component: this.options.component,
}),
];
},
});
import Tippy from '@tippyjs/react';
import { Editor } from '@tiptap/core';
import { ReactRenderer } from '@tiptap/react';
import { Plugin, PluginKey } from 'prosemirror-state';
import { EditorView } from 'prosemirror-view';
import React, { ComponentType } from 'react';
export interface ContextMenuProps {
editor: Editor;
clientRect: () => DOMRect;
onBlur: () => void;
onExit: () => void;
}
export interface ContextMenuPluginProps {
editor: Editor;
component: ComponentType<ContextMenuProps>;
}
export type ContextMenuViewProps = ContextMenuPluginProps & {
view: EditorView;
};
export class ContextMenuView {
editor: Editor;
view: EditorView;
component: ComponentType<ContextMenuProps>;
constructor({ editor, view, component }: ContextMenuViewProps) {
this.editor = editor;
this.view = view;
this.component = component;
this.onContextMenu = this.onContextMenu.bind(this);
this.view.dom.addEventListener('contextmenu', this.onContextMenu);
}
onContextMenu(event: Event) {
event.preventDefault();
const ev = event as MouseEvent;
const clientRect = () => ({
bottom: ev.clientY,
left: ev.clientX,
right: ev.clientX,
top: ev.clientY,
width: 0,
height: 0,
x: 0,
y: 0,
toJSON: () => ({}),
});
const component = (props: ContextMenuProps) => (
<Tippy
appendTo={() => document.body}
showOnCreate={true}
interactive={true}
trigger="manual"
placement="bottom-start"
getReferenceClientRect={props.clientRect}
onClickOutside={() => props.onBlur()}
render={() => <this.component {...props} />}
zIndex={1500}
/>
);
const renderer = new ReactRenderer(component, {
editor: this.editor,
props: {
editor: this.editor,
clientRect,
onBlur: () => renderer.destroy(),
onExit: () => renderer.destroy(),
},
});
}
destroy() {
this.view.dom.removeEventListener('contextmenu', this.onContextMenu);
}
}
export const ContextMenuPlugin = (options: ContextMenuPluginProps) => {
return new Plugin({
key: new PluginKey('contextMenu'),
view: (view) => new ContextMenuView({ view, ...options }),
});
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment