Skip to content

Commit 770cb70

Browse files
authored
Merge pull request #305 from miikebar/fix/304-shared-slash-command-tunnel
fix: use per-editor instance of tunnel to render slash command popover
2 parents fe3d859 + d05c03f commit 770cb70

File tree

2 files changed

+97
-33
lines changed

2 files changed

+97
-33
lines changed
Lines changed: 52 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,23 @@
11
import { atom, useAtom, useSetAtom } from "jotai";
2-
import { useEffect, useRef, type ComponentPropsWithoutRef, forwardRef } from "react";
2+
import {
3+
useEffect,
4+
useRef,
5+
type ComponentPropsWithoutRef,
6+
forwardRef,
7+
createContext,
8+
} from "react";
39
import tunnel from "tunnel-rat";
410
import { novelStore } from "./editor";
511
import { Command } from "cmdk";
612
import type { Range } from "@tiptap/core";
713

8-
const t = tunnel();
9-
1014
export const queryAtom = atom("");
1115
export const rangeAtom = atom<Range | null>(null);
1216

17+
export const EditorCommandTunnelContext = createContext(
18+
{} as ReturnType<typeof tunnel>
19+
);
20+
1321
export const EditorCommandOut = ({
1422
query,
1523
range,
@@ -37,7 +45,11 @@ export const EditorCommandOut = ({
3745

3846
if (commandRef)
3947
commandRef.dispatchEvent(
40-
new KeyboardEvent("keydown", { key: e.key, cancelable: true, bubbles: true })
48+
new KeyboardEvent("keydown", {
49+
key: e.key,
50+
cancelable: true,
51+
bubbles: true,
52+
})
4153
);
4254

4355
return false;
@@ -49,28 +61,42 @@ export const EditorCommandOut = ({
4961
};
5062
}, []);
5163

52-
return <t.Out />;
64+
return (
65+
<EditorCommandTunnelContext.Consumer>
66+
{(tunnelInstance) => <tunnelInstance.Out />}
67+
</EditorCommandTunnelContext.Consumer>
68+
);
5369
};
5470

55-
export const EditorCommand = forwardRef<HTMLDivElement, ComponentPropsWithoutRef<typeof Command>>(
56-
({ children, className, ...rest }, ref) => {
57-
const commandListRef = useRef<HTMLDivElement>(null);
58-
const [query, setQuery] = useAtom(queryAtom);
71+
export const EditorCommand = forwardRef<
72+
HTMLDivElement,
73+
ComponentPropsWithoutRef<typeof Command>
74+
>(({ children, className, ...rest }, ref) => {
75+
const commandListRef = useRef<HTMLDivElement>(null);
76+
const [query, setQuery] = useAtom(queryAtom);
5977

60-
return (
61-
<t.In>
62-
<Command
63-
ref={ref}
64-
onKeyDown={(e) => {
65-
e.stopPropagation();
66-
}}
67-
id='slash-command'
68-
className={className}
69-
{...rest}>
70-
<Command.Input value={query} onValueChange={setQuery} style={{ display: "none" }} />
71-
<Command.List ref={commandListRef}>{children}</Command.List>
72-
</Command>
73-
</t.In>
74-
);
75-
}
76-
);
78+
return (
79+
<EditorCommandTunnelContext.Consumer>
80+
{(tunnelInstance) => (
81+
<tunnelInstance.In>
82+
<Command
83+
ref={ref}
84+
onKeyDown={(e) => {
85+
e.stopPropagation();
86+
}}
87+
id="slash-command"
88+
className={className}
89+
{...rest}
90+
>
91+
<Command.Input
92+
value={query}
93+
onValueChange={setQuery}
94+
style={{ display: "none" }}
95+
/>
96+
<Command.List ref={commandListRef}>{children}</Command.List>
97+
</Command>
98+
</tunnelInstance.In>
99+
)}
100+
</EditorCommandTunnelContext.Consumer>
101+
);
102+
});

packages/headless/src/components/editor.tsx

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,43 @@
1-
import { useMemo, type ReactNode, useState, useEffect, useRef, forwardRef } from "react";
2-
import { EditorProvider, type EditorProviderProps, type JSONContent } from "@tiptap/react";
1+
import {
2+
useMemo,
3+
type ReactNode,
4+
useState,
5+
useEffect,
6+
useRef,
7+
forwardRef,
8+
} from "react";
9+
import {
10+
EditorProvider,
11+
type EditorProviderProps,
12+
type JSONContent,
13+
} from "@tiptap/react";
314
import { Provider, createStore } from "jotai";
415
import { simpleExtensions } from "../extensions";
516
import { startImageUpload } from "../plugins/upload-images";
617
import { Editor } from "@tiptap/core";
18+
import tunnel from "tunnel-rat";
19+
import { EditorCommandTunnelContext } from "./editor-command";
720
export interface EditorProps {
821
children: ReactNode;
922
className?: string;
1023
}
1124

1225
export const novelStore = createStore();
1326

14-
export const EditorRoot = ({ children }: { children: ReactNode }): JSX.Element => {
15-
return <Provider store={novelStore}>{children}</Provider>;
27+
export const EditorRoot = ({
28+
children,
29+
}: {
30+
children: ReactNode;
31+
}): JSX.Element => {
32+
const tunnelInstance = useRef(tunnel()).current;
33+
34+
return (
35+
<Provider store={novelStore}>
36+
<EditorCommandTunnelContext.Provider value={tunnelInstance}>
37+
{children}
38+
</EditorCommandTunnelContext.Provider>
39+
</Provider>
40+
);
1641
};
1742

1843
export type EditorContentProps = {
@@ -29,7 +54,11 @@ export const EditorContent = forwardRef<HTMLDivElement, EditorContentProps>(
2954

3055
return (
3156
<div ref={ref} className={className}>
32-
<EditorProvider {...rest} content={initialContent} extensions={extensions}>
57+
<EditorProvider
58+
{...rest}
59+
content={initialContent}
60+
extensions={extensions}
61+
>
3362
{children}
3463
</EditorProvider>
3564
</div>
@@ -50,7 +79,11 @@ export const defaultEditorProps: EditorProviderProps["editorProps"] = {
5079
},
5180
},
5281
handlePaste: (view, event) => {
53-
if (event.clipboardData && event.clipboardData.files && event.clipboardData.files[0]) {
82+
if (
83+
event.clipboardData &&
84+
event.clipboardData.files &&
85+
event.clipboardData.files[0]
86+
) {
5487
event.preventDefault();
5588
const file = event.clipboardData.files[0];
5689
const pos = view.state.selection.from;
@@ -61,7 +94,12 @@ export const defaultEditorProps: EditorProviderProps["editorProps"] = {
6194
return false;
6295
},
6396
handleDrop: (view, event, _slice, moved) => {
64-
if (!moved && event.dataTransfer && event.dataTransfer.files && event.dataTransfer.files[0]) {
97+
if (
98+
!moved &&
99+
event.dataTransfer &&
100+
event.dataTransfer.files &&
101+
event.dataTransfer.files[0]
102+
) {
65103
event.preventDefault();
66104
const file = event.dataTransfer.files[0];
67105
const coordinates = view.posAtCoords({

0 commit comments

Comments
 (0)