notra-editor 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +95 -0
- package/dist/index.cjs +1195 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +81 -1
- package/dist/index.d.ts +81 -1
- package/dist/index.mjs +1182 -8
- package/dist/index.mjs.map +1 -1
- package/dist/styles/globals.css +1141 -0
- package/dist/themes/default/editor.css +238 -0
- package/package.json +17 -5
package/dist/index.cjs
CHANGED
|
@@ -30,17 +30,1048 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
+
BlockquoteButton: () => BlockquoteButton,
|
|
34
|
+
CodeBlockButton: () => CodeBlockButton,
|
|
35
|
+
DropdownMenu: () => DropdownMenu2,
|
|
36
|
+
HeadingDropdownMenu: () => HeadingDropdownMenu,
|
|
37
|
+
LinkPopover: () => LinkPopover,
|
|
38
|
+
ListDropdownMenu: () => ListDropdownMenu,
|
|
39
|
+
MarkButton: () => MarkButton,
|
|
33
40
|
NotraEditor: () => NotraEditor,
|
|
34
|
-
NotraReader: () => NotraReader
|
|
41
|
+
NotraReader: () => NotraReader,
|
|
42
|
+
Spacer: () => Spacer,
|
|
43
|
+
Toolbar: () => Toolbar,
|
|
44
|
+
ToolbarGroup: () => ToolbarGroup,
|
|
45
|
+
ToolbarSeparator: () => ToolbarSeparator,
|
|
46
|
+
UndoRedoButton: () => UndoRedoButton
|
|
35
47
|
});
|
|
36
48
|
module.exports = __toCommonJS(index_exports);
|
|
49
|
+
var import_globals = require("./styles/globals.css");
|
|
37
50
|
|
|
38
51
|
// src/notra-editor.tsx
|
|
39
|
-
var
|
|
52
|
+
var import_react18 = require("@tiptap/react");
|
|
40
53
|
|
|
41
|
-
// src/
|
|
42
|
-
var
|
|
54
|
+
// src/components/blockquote-button/blockquote-button.tsx
|
|
55
|
+
var import_lucide_react = require("lucide-react");
|
|
56
|
+
var import_react = require("react");
|
|
57
|
+
|
|
58
|
+
// src/components/ui/button.tsx
|
|
59
|
+
var import_class_variance_authority = require("class-variance-authority");
|
|
60
|
+
var import_radix_ui = require("radix-ui");
|
|
61
|
+
|
|
62
|
+
// src/lib/utils.ts
|
|
63
|
+
var import_clsx = require("clsx");
|
|
64
|
+
var import_tailwind_merge = require("tailwind-merge");
|
|
65
|
+
function cn(...inputs) {
|
|
66
|
+
return (0, import_tailwind_merge.twMerge)((0, import_clsx.clsx)(inputs));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// src/components/ui/button.tsx
|
|
70
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
71
|
+
var buttonVariants = (0, import_class_variance_authority.cva)(
|
|
72
|
+
"nt:group/button nt:inline-flex nt:shrink-0 nt:items-center nt:justify-center nt:rounded-lg nt:border nt:border-transparent nt:bg-clip-padding nt:text-sm nt:font-medium nt:whitespace-nowrap nt:transition-all nt:outline-none nt:select-none nt:focus-visible:border-ring nt:focus-visible:ring-3 nt:focus-visible:ring-ring/50 nt:active:not-aria-[haspopup]:translate-y-px nt:disabled:pointer-events-none nt:disabled:opacity-50 nt:aria-invalid:border-destructive nt:aria-invalid:ring-3 nt:aria-invalid:ring-destructive/20 nt:dark:aria-invalid:border-destructive/50 nt:dark:aria-invalid:ring-destructive/40 nt:[&_svg]:pointer-events-none nt:[&_svg]:shrink-0 nt:[&_svg:not([class*=size-])]:size-4",
|
|
73
|
+
{
|
|
74
|
+
variants: {
|
|
75
|
+
variant: {
|
|
76
|
+
default: "nt:bg-primary nt:text-primary-foreground nt:[a]:hover:bg-primary/80",
|
|
77
|
+
outline: "nt:border-border nt:bg-background nt:hover:bg-muted nt:hover:text-foreground nt:aria-expanded:bg-muted nt:aria-expanded:text-foreground nt:dark:border-input nt:dark:bg-input/30 nt:dark:hover:bg-input/50",
|
|
78
|
+
secondary: "nt:bg-secondary nt:text-secondary-foreground nt:hover:bg-secondary/80 nt:aria-expanded:bg-secondary nt:aria-expanded:text-secondary-foreground",
|
|
79
|
+
ghost: "nt:hover:bg-muted nt:hover:text-foreground nt:aria-expanded:bg-muted nt:aria-expanded:text-foreground nt:dark:hover:bg-muted/50",
|
|
80
|
+
destructive: "nt:bg-destructive/10 nt:text-destructive nt:hover:bg-destructive/20 nt:focus-visible:border-destructive/40 nt:focus-visible:ring-destructive/20 nt:dark:bg-destructive/20 nt:dark:hover:bg-destructive/30 nt:dark:focus-visible:ring-destructive/40",
|
|
81
|
+
link: "nt:text-primary nt:underline-offset-4 nt:hover:underline"
|
|
82
|
+
},
|
|
83
|
+
size: {
|
|
84
|
+
default: "nt:h-8 nt:gap-1.5 nt:px-2.5 nt:has-data-[icon=inline-end]:pr-2 nt:has-data-[icon=inline-start]:pl-2",
|
|
85
|
+
xs: "nt:h-6 nt:gap-1 nt:rounded-[min(var(--radius-md),10px)] nt:px-2 nt:text-xs nt:in-data-[slot=button-group]:rounded-lg nt:has-data-[icon=inline-end]:pr-1.5 nt:has-data-[icon=inline-start]:pl-1.5 nt:[&_svg:not([class*=size-])]:size-3",
|
|
86
|
+
sm: "nt:h-7 nt:gap-1 nt:rounded-[min(var(--radius-md),12px)] nt:px-2.5 nt:text-[0.8rem] nt:in-data-[slot=button-group]:rounded-lg nt:has-data-[icon=inline-end]:pr-1.5 nt:has-data-[icon=inline-start]:pl-1.5 nt:[&_svg:not([class*=size-])]:size-3.5",
|
|
87
|
+
lg: "nt:h-9 nt:gap-1.5 nt:px-2.5 nt:has-data-[icon=inline-end]:pr-2 nt:has-data-[icon=inline-start]:pl-2",
|
|
88
|
+
icon: "nt:size-8",
|
|
89
|
+
"icon-xs": "nt:size-6 nt:rounded-[min(var(--radius-md),10px)] nt:in-data-[slot=button-group]:rounded-lg nt:[&_svg:not([class*=size-])]:size-3",
|
|
90
|
+
"icon-sm": "nt:size-7 nt:rounded-[min(var(--radius-md),12px)] nt:in-data-[slot=button-group]:rounded-lg",
|
|
91
|
+
"icon-lg": "nt:size-9"
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
defaultVariants: {
|
|
95
|
+
variant: "default",
|
|
96
|
+
size: "default"
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
);
|
|
100
|
+
function Button({
|
|
101
|
+
className,
|
|
102
|
+
variant = "default",
|
|
103
|
+
size = "default",
|
|
104
|
+
asChild = false,
|
|
105
|
+
...props
|
|
106
|
+
}) {
|
|
107
|
+
const Comp = asChild ? import_radix_ui.Slot.Root : "button";
|
|
108
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
109
|
+
Comp,
|
|
110
|
+
{
|
|
111
|
+
className: cn(buttonVariants({ variant, size, className })),
|
|
112
|
+
"data-size": size,
|
|
113
|
+
"data-slot": "button",
|
|
114
|
+
"data-variant": variant,
|
|
115
|
+
...props
|
|
116
|
+
}
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// src/components/blockquote-button/blockquote-button.tsx
|
|
121
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
122
|
+
function canToggleBlockquote(editor) {
|
|
123
|
+
if (!editor || !editor.isEditable) return false;
|
|
124
|
+
return editor.can().toggleWrap("blockquote") || editor.can().clearNodes();
|
|
125
|
+
}
|
|
126
|
+
var BlockquoteButton = (0, import_react.forwardRef)(({ editor, onClick, ...buttonProps }, ref) => {
|
|
127
|
+
const [isActive, setIsActive] = (0, import_react.useState)(false);
|
|
128
|
+
const [canToggle, setCanToggle] = (0, import_react.useState)(false);
|
|
129
|
+
(0, import_react.useEffect)(() => {
|
|
130
|
+
if (!editor) return;
|
|
131
|
+
const update = () => {
|
|
132
|
+
setIsActive(editor.isActive("blockquote"));
|
|
133
|
+
setCanToggle(canToggleBlockquote(editor));
|
|
134
|
+
};
|
|
135
|
+
update();
|
|
136
|
+
editor.on("selectionUpdate", update);
|
|
137
|
+
editor.on("transaction", update);
|
|
138
|
+
return () => {
|
|
139
|
+
editor.off("selectionUpdate", update);
|
|
140
|
+
editor.off("transaction", update);
|
|
141
|
+
};
|
|
142
|
+
}, [editor]);
|
|
143
|
+
const handleClick = (0, import_react.useCallback)(
|
|
144
|
+
(event) => {
|
|
145
|
+
onClick?.(event);
|
|
146
|
+
if (event.defaultPrevented) return;
|
|
147
|
+
if (!editor) return;
|
|
148
|
+
if (editor.isActive("blockquote")) {
|
|
149
|
+
editor.chain().focus().lift("blockquote").run();
|
|
150
|
+
} else {
|
|
151
|
+
editor.chain().focus().clearNodes().wrapIn("blockquote").run();
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
[editor, onClick]
|
|
155
|
+
);
|
|
156
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
157
|
+
Button,
|
|
158
|
+
{
|
|
159
|
+
ref,
|
|
160
|
+
"aria-label": "Blockquote",
|
|
161
|
+
"aria-pressed": isActive,
|
|
162
|
+
"data-active-state": isActive ? "on" : "off",
|
|
163
|
+
disabled: !canToggle,
|
|
164
|
+
size: "icon",
|
|
165
|
+
tabIndex: -1,
|
|
166
|
+
type: "button",
|
|
167
|
+
variant: "ghost",
|
|
168
|
+
onClick: handleClick,
|
|
169
|
+
...buttonProps,
|
|
170
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
171
|
+
import_lucide_react.TextQuote,
|
|
172
|
+
{
|
|
173
|
+
className: isActive ? "nt:text-[var(--tt-brand-color-500)]" : void 0
|
|
174
|
+
}
|
|
175
|
+
)
|
|
176
|
+
}
|
|
177
|
+
);
|
|
178
|
+
});
|
|
179
|
+
BlockquoteButton.displayName = "BlockquoteButton";
|
|
180
|
+
|
|
181
|
+
// src/components/code-block-button/code-block-button.tsx
|
|
182
|
+
var import_lucide_react2 = require("lucide-react");
|
|
43
183
|
var import_react2 = require("react");
|
|
184
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
185
|
+
function canToggleCodeBlock(editor) {
|
|
186
|
+
if (!editor || !editor.isEditable) return false;
|
|
187
|
+
return editor.can().toggleNode("codeBlock", "paragraph") || editor.can().clearNodes();
|
|
188
|
+
}
|
|
189
|
+
var CodeBlockButton = (0, import_react2.forwardRef)(({ editor, onClick, ...buttonProps }, ref) => {
|
|
190
|
+
const [isActive, setIsActive] = (0, import_react2.useState)(false);
|
|
191
|
+
const [canToggle, setCanToggle] = (0, import_react2.useState)(false);
|
|
192
|
+
(0, import_react2.useEffect)(() => {
|
|
193
|
+
if (!editor) return;
|
|
194
|
+
const update = () => {
|
|
195
|
+
setIsActive(editor.isActive("codeBlock"));
|
|
196
|
+
setCanToggle(canToggleCodeBlock(editor));
|
|
197
|
+
};
|
|
198
|
+
update();
|
|
199
|
+
editor.on("selectionUpdate", update);
|
|
200
|
+
editor.on("transaction", update);
|
|
201
|
+
return () => {
|
|
202
|
+
editor.off("selectionUpdate", update);
|
|
203
|
+
editor.off("transaction", update);
|
|
204
|
+
};
|
|
205
|
+
}, [editor]);
|
|
206
|
+
const handleClick = (0, import_react2.useCallback)(
|
|
207
|
+
(event) => {
|
|
208
|
+
onClick?.(event);
|
|
209
|
+
if (event.defaultPrevented) return;
|
|
210
|
+
if (!editor) return;
|
|
211
|
+
if (editor.isActive("codeBlock")) {
|
|
212
|
+
editor.chain().focus().setNode("paragraph").run();
|
|
213
|
+
} else {
|
|
214
|
+
editor.chain().focus().clearNodes().toggleNode("codeBlock", "paragraph").run();
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
[editor, onClick]
|
|
218
|
+
);
|
|
219
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
220
|
+
Button,
|
|
221
|
+
{
|
|
222
|
+
ref,
|
|
223
|
+
"aria-label": "Code Block",
|
|
224
|
+
"aria-pressed": isActive,
|
|
225
|
+
"data-active-state": isActive ? "on" : "off",
|
|
226
|
+
disabled: !canToggle,
|
|
227
|
+
size: "icon",
|
|
228
|
+
tabIndex: -1,
|
|
229
|
+
type: "button",
|
|
230
|
+
variant: "ghost",
|
|
231
|
+
onClick: handleClick,
|
|
232
|
+
...buttonProps,
|
|
233
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
234
|
+
import_lucide_react2.SquareCode,
|
|
235
|
+
{
|
|
236
|
+
className: isActive ? "nt:text-[var(--tt-brand-color-500)]" : void 0
|
|
237
|
+
}
|
|
238
|
+
)
|
|
239
|
+
}
|
|
240
|
+
);
|
|
241
|
+
});
|
|
242
|
+
CodeBlockButton.displayName = "CodeBlockButton";
|
|
243
|
+
|
|
244
|
+
// src/components/heading-dropdown-menu/heading-dropdown-menu.tsx
|
|
245
|
+
var import_lucide_react5 = require("lucide-react");
|
|
246
|
+
var import_react5 = require("react");
|
|
247
|
+
|
|
248
|
+
// src/components/heading-dropdown-menu/heading-menu-item.tsx
|
|
249
|
+
var import_react4 = require("react");
|
|
250
|
+
|
|
251
|
+
// src/components/heading-dropdown-menu/use-heading.ts
|
|
252
|
+
var import_lucide_react3 = require("lucide-react");
|
|
253
|
+
var import_react3 = require("react");
|
|
254
|
+
var headingIcons = {
|
|
255
|
+
1: import_lucide_react3.Heading1,
|
|
256
|
+
2: import_lucide_react3.Heading2,
|
|
257
|
+
3: import_lucide_react3.Heading3,
|
|
258
|
+
4: import_lucide_react3.Heading4
|
|
259
|
+
};
|
|
260
|
+
var headingLabels = {
|
|
261
|
+
1: "Heading 1",
|
|
262
|
+
2: "Heading 2",
|
|
263
|
+
3: "Heading 3",
|
|
264
|
+
4: "Heading 4"
|
|
265
|
+
};
|
|
266
|
+
function canToggleHeading(editor, level) {
|
|
267
|
+
if (!editor || !editor.isEditable) return false;
|
|
268
|
+
return editor.can().setNode("heading", { level }) || editor.can().clearNodes();
|
|
269
|
+
}
|
|
270
|
+
function useHeading({
|
|
271
|
+
editor,
|
|
272
|
+
level
|
|
273
|
+
}) {
|
|
274
|
+
const [isActive, setIsActive] = (0, import_react3.useState)(false);
|
|
275
|
+
const [canToggle, setCanToggle] = (0, import_react3.useState)(false);
|
|
276
|
+
(0, import_react3.useEffect)(() => {
|
|
277
|
+
if (!editor) return;
|
|
278
|
+
const handleUpdate = () => {
|
|
279
|
+
setIsActive(editor.isActive("heading", { level }));
|
|
280
|
+
setCanToggle(canToggleHeading(editor, level));
|
|
281
|
+
};
|
|
282
|
+
handleUpdate();
|
|
283
|
+
editor.on("selectionUpdate", handleUpdate);
|
|
284
|
+
editor.on("transaction", handleUpdate);
|
|
285
|
+
return () => {
|
|
286
|
+
editor.off("selectionUpdate", handleUpdate);
|
|
287
|
+
editor.off("transaction", handleUpdate);
|
|
288
|
+
};
|
|
289
|
+
}, [editor, level]);
|
|
290
|
+
const handleToggle = (0, import_react3.useCallback)(() => {
|
|
291
|
+
if (!editor || !editor.isEditable) return false;
|
|
292
|
+
if (editor.isActive("heading", { level })) {
|
|
293
|
+
return editor.chain().focus().setNode("paragraph").run();
|
|
294
|
+
}
|
|
295
|
+
return editor.chain().focus().clearNodes().setNode("heading", { level }).run();
|
|
296
|
+
}, [editor, level]);
|
|
297
|
+
return {
|
|
298
|
+
isActive,
|
|
299
|
+
canToggle,
|
|
300
|
+
handleToggle,
|
|
301
|
+
label: headingLabels[level],
|
|
302
|
+
Icon: headingIcons[level]
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
function useActiveHeadingLevel(editor, levels) {
|
|
306
|
+
const [activeLevel, setActiveLevel] = (0, import_react3.useState)(null);
|
|
307
|
+
(0, import_react3.useEffect)(() => {
|
|
308
|
+
if (!editor) return;
|
|
309
|
+
const handleUpdate = () => {
|
|
310
|
+
const found = levels.find(
|
|
311
|
+
(level) => editor.isActive("heading", { level })
|
|
312
|
+
);
|
|
313
|
+
setActiveLevel(found ?? null);
|
|
314
|
+
};
|
|
315
|
+
handleUpdate();
|
|
316
|
+
editor.on("selectionUpdate", handleUpdate);
|
|
317
|
+
editor.on("transaction", handleUpdate);
|
|
318
|
+
return () => {
|
|
319
|
+
editor.off("selectionUpdate", handleUpdate);
|
|
320
|
+
editor.off("transaction", handleUpdate);
|
|
321
|
+
};
|
|
322
|
+
}, [editor, levels]);
|
|
323
|
+
return activeLevel;
|
|
324
|
+
}
|
|
325
|
+
function getHeadingTriggerIcon(activeLevel) {
|
|
326
|
+
if (activeLevel === null) return import_lucide_react3.Heading;
|
|
327
|
+
return headingIcons[activeLevel];
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// src/components/ui/dropdown-menu.tsx
|
|
331
|
+
var import_lucide_react4 = require("lucide-react");
|
|
332
|
+
var import_radix_ui2 = require("radix-ui");
|
|
333
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
334
|
+
function DropdownMenu({
|
|
335
|
+
...props
|
|
336
|
+
}) {
|
|
337
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_radix_ui2.DropdownMenu.Root, { "data-slot": "dropdown-menu", ...props });
|
|
338
|
+
}
|
|
339
|
+
function DropdownMenuTrigger({
|
|
340
|
+
...props
|
|
341
|
+
}) {
|
|
342
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
343
|
+
import_radix_ui2.DropdownMenu.Trigger,
|
|
344
|
+
{
|
|
345
|
+
"data-slot": "dropdown-menu-trigger",
|
|
346
|
+
...props
|
|
347
|
+
}
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
function DropdownMenuContent({
|
|
351
|
+
className,
|
|
352
|
+
align = "start",
|
|
353
|
+
sideOffset = 4,
|
|
354
|
+
...props
|
|
355
|
+
}) {
|
|
356
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_radix_ui2.DropdownMenu.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
357
|
+
import_radix_ui2.DropdownMenu.Content,
|
|
358
|
+
{
|
|
359
|
+
align,
|
|
360
|
+
className: cn(
|
|
361
|
+
"nt:z-50 nt:max-h-(--radix-dropdown-menu-content-available-height) nt:w-(--radix-dropdown-menu-trigger-width) nt:min-w-32 nt:origin-(--radix-dropdown-menu-content-transform-origin) nt:overflow-x-hidden nt:overflow-y-auto nt:rounded-lg nt:bg-popover nt:p-1 nt:text-popover-foreground nt:shadow-md nt:ring-1 nt:ring-foreground/10 nt:duration-100 nt:data-[side=bottom]:slide-in-from-top-2 nt:data-[side=left]:slide-in-from-right-2 nt:data-[side=right]:slide-in-from-left-2 nt:data-[side=top]:slide-in-from-bottom-2 nt:data-[state=closed]:overflow-hidden nt:data-open:animate-in nt:data-open:fade-in-0 nt:data-open:zoom-in-95 nt:data-closed:animate-out nt:data-closed:fade-out-0 nt:data-closed:zoom-out-95",
|
|
362
|
+
className
|
|
363
|
+
),
|
|
364
|
+
"data-slot": "dropdown-menu-content",
|
|
365
|
+
sideOffset,
|
|
366
|
+
...props
|
|
367
|
+
}
|
|
368
|
+
) });
|
|
369
|
+
}
|
|
370
|
+
function DropdownMenuGroup({
|
|
371
|
+
...props
|
|
372
|
+
}) {
|
|
373
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_radix_ui2.DropdownMenu.Group, { "data-slot": "dropdown-menu-group", ...props });
|
|
374
|
+
}
|
|
375
|
+
function DropdownMenuItem({
|
|
376
|
+
className,
|
|
377
|
+
inset,
|
|
378
|
+
variant = "default",
|
|
379
|
+
...props
|
|
380
|
+
}) {
|
|
381
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
382
|
+
import_radix_ui2.DropdownMenu.Item,
|
|
383
|
+
{
|
|
384
|
+
className: cn(
|
|
385
|
+
"nt:group/dropdown-menu-item nt:relative nt:flex nt:cursor-default nt:items-center nt:gap-1.5 nt:rounded-md nt:px-1.5 nt:py-1 nt:text-sm nt:outline-hidden nt:select-none nt:focus:bg-accent nt:focus:text-accent-foreground nt:not-data-[variant=destructive]:focus:**:text-accent-foreground nt:data-inset:pl-7 nt:data-[variant=destructive]:text-destructive nt:data-[variant=destructive]:focus:bg-destructive/10 nt:data-[variant=destructive]:focus:text-destructive nt:dark:data-[variant=destructive]:focus:bg-destructive/20 nt:data-disabled:pointer-events-none nt:data-disabled:opacity-50 nt:[&_svg]:pointer-events-none nt:[&_svg]:shrink-0 nt:[&_svg:not([class*=size-])]:size-4 nt:data-[variant=destructive]:*:[svg]:text-destructive",
|
|
386
|
+
className
|
|
387
|
+
),
|
|
388
|
+
"data-inset": inset,
|
|
389
|
+
"data-slot": "dropdown-menu-item",
|
|
390
|
+
"data-variant": variant,
|
|
391
|
+
...props
|
|
392
|
+
}
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// src/components/heading-dropdown-menu/heading-menu-item.tsx
|
|
397
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
398
|
+
var HeadingMenuItem = (0, import_react4.forwardRef)(
|
|
399
|
+
({ editor, level }, ref) => {
|
|
400
|
+
const { isActive, canToggle, handleToggle, label, Icon } = useHeading({
|
|
401
|
+
editor,
|
|
402
|
+
level
|
|
403
|
+
});
|
|
404
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
405
|
+
DropdownMenuItem,
|
|
406
|
+
{
|
|
407
|
+
ref,
|
|
408
|
+
"aria-label": label,
|
|
409
|
+
className: "nt:data-[active-state=on]:bg-accent nt:data-[active-state=on]:text-[var(--tt-brand-color-500)]",
|
|
410
|
+
"data-active-state": isActive ? "on" : "off",
|
|
411
|
+
disabled: !canToggle,
|
|
412
|
+
onSelect: handleToggle,
|
|
413
|
+
children: [
|
|
414
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Icon, {}),
|
|
415
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: label })
|
|
416
|
+
]
|
|
417
|
+
}
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
);
|
|
421
|
+
HeadingMenuItem.displayName = "HeadingMenuItem";
|
|
422
|
+
|
|
423
|
+
// src/components/heading-dropdown-menu/heading-dropdown-menu.tsx
|
|
424
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
425
|
+
var HeadingDropdownMenu = (0, import_react5.forwardRef)(({ editor, levels = [1, 2, 3, 4], ...buttonProps }, ref) => {
|
|
426
|
+
const activeLevel = useActiveHeadingLevel(editor, levels);
|
|
427
|
+
const TriggerIcon = getHeadingTriggerIcon(activeLevel);
|
|
428
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(DropdownMenu, { children: [
|
|
429
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
430
|
+
Button,
|
|
431
|
+
{
|
|
432
|
+
ref,
|
|
433
|
+
"aria-label": "Heading",
|
|
434
|
+
className: "nt:gap-1 nt:px-2",
|
|
435
|
+
"data-active-state": activeLevel !== null ? "on" : "off",
|
|
436
|
+
size: "default",
|
|
437
|
+
tabIndex: -1,
|
|
438
|
+
type: "button",
|
|
439
|
+
variant: "ghost",
|
|
440
|
+
...buttonProps,
|
|
441
|
+
children: [
|
|
442
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
443
|
+
TriggerIcon,
|
|
444
|
+
{
|
|
445
|
+
className: activeLevel !== null ? "nt:text-[var(--tt-brand-color-500)]" : void 0
|
|
446
|
+
}
|
|
447
|
+
),
|
|
448
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react5.ChevronDown, { className: "nt:size-3" })
|
|
449
|
+
]
|
|
450
|
+
}
|
|
451
|
+
) }),
|
|
452
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(DropdownMenuContent, { align: "start", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(DropdownMenuGroup, { children: levels.map((level) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(HeadingMenuItem, { editor, level }, level)) }) })
|
|
453
|
+
] });
|
|
454
|
+
});
|
|
455
|
+
HeadingDropdownMenu.displayName = "HeadingDropdownMenu";
|
|
456
|
+
|
|
457
|
+
// src/components/link-popover/link-popover.tsx
|
|
458
|
+
var import_lucide_react6 = require("lucide-react");
|
|
459
|
+
var import_react7 = require("react");
|
|
460
|
+
|
|
461
|
+
// src/components/link-popover/use-link-popover.ts
|
|
462
|
+
var import_react6 = require("react");
|
|
463
|
+
function useLinkPopover({ editor }) {
|
|
464
|
+
const [url, setUrl] = (0, import_react6.useState)("");
|
|
465
|
+
const [isActive, setIsActive] = (0, import_react6.useState)(false);
|
|
466
|
+
const [canSet, setCanSet] = (0, import_react6.useState)(false);
|
|
467
|
+
(0, import_react6.useEffect)(() => {
|
|
468
|
+
if (!editor) return;
|
|
469
|
+
const handleUpdate = () => {
|
|
470
|
+
const active = editor.isActive("link");
|
|
471
|
+
setIsActive(active);
|
|
472
|
+
setCanSet(editor.isEditable);
|
|
473
|
+
if (active) {
|
|
474
|
+
setUrl(editor.getAttributes("link").href ?? "");
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
handleUpdate();
|
|
478
|
+
editor.on("selectionUpdate", handleUpdate);
|
|
479
|
+
editor.on("transaction", handleUpdate);
|
|
480
|
+
return () => {
|
|
481
|
+
editor.off("selectionUpdate", handleUpdate);
|
|
482
|
+
editor.off("transaction", handleUpdate);
|
|
483
|
+
};
|
|
484
|
+
}, [editor]);
|
|
485
|
+
const setLink = (0, import_react6.useCallback)(() => {
|
|
486
|
+
if (!editor) return;
|
|
487
|
+
if (!url) {
|
|
488
|
+
editor.chain().focus().extendMarkRange("link").unsetLink().run();
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
editor.chain().focus().extendMarkRange("link").setLink({ href: url }).run();
|
|
492
|
+
}, [editor, url]);
|
|
493
|
+
const removeLink = (0, import_react6.useCallback)(() => {
|
|
494
|
+
if (!editor) return;
|
|
495
|
+
editor.chain().focus().extendMarkRange("link").unsetLink().run();
|
|
496
|
+
setUrl("");
|
|
497
|
+
}, [editor]);
|
|
498
|
+
const openLink = (0, import_react6.useCallback)(() => {
|
|
499
|
+
if (!url) return;
|
|
500
|
+
const sanitized = /^https?:\/\//i.test(url) ? url : `https://${url}`;
|
|
501
|
+
window.open(sanitized, "_blank", "noopener,noreferrer");
|
|
502
|
+
}, [url]);
|
|
503
|
+
return { url, setUrl, isActive, canSet, setLink, removeLink, openLink };
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// src/components/ui/input.tsx
|
|
507
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
508
|
+
function Input({ className, type, ...props }) {
|
|
509
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
510
|
+
"input",
|
|
511
|
+
{
|
|
512
|
+
className: cn(
|
|
513
|
+
"nt:flex nt:h-9 nt:w-full nt:min-w-0 nt:rounded-md nt:border nt:border-input nt:bg-transparent nt:px-3 nt:py-1 nt:text-base nt:shadow-xs nt:transition-[color,box-shadow] nt:outline-none nt:file:inline-flex nt:file:h-7 nt:file:border-0 nt:file:bg-transparent nt:file:text-sm nt:file:font-medium nt:file:text-foreground nt:placeholder:text-muted-foreground nt:selection:bg-primary nt:selection:text-primary-foreground nt:dark:bg-input/30 nt:md:text-sm nt:focus-visible:border-ring nt:focus-visible:ring-3 nt:focus-visible:ring-ring/50 nt:aria-invalid:border-destructive nt:aria-invalid:ring-3 nt:aria-invalid:ring-destructive/20 nt:dark:aria-invalid:ring-destructive/40 nt:disabled:cursor-not-allowed nt:disabled:opacity-50",
|
|
514
|
+
className
|
|
515
|
+
),
|
|
516
|
+
"data-slot": "input",
|
|
517
|
+
type,
|
|
518
|
+
...props
|
|
519
|
+
}
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// src/components/ui/popover.tsx
|
|
524
|
+
var import_radix_ui3 = require("radix-ui");
|
|
525
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
526
|
+
function Popover({
|
|
527
|
+
...props
|
|
528
|
+
}) {
|
|
529
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_radix_ui3.Popover.Root, { "data-slot": "popover", ...props });
|
|
530
|
+
}
|
|
531
|
+
function PopoverTrigger({
|
|
532
|
+
...props
|
|
533
|
+
}) {
|
|
534
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_radix_ui3.Popover.Trigger, { "data-slot": "popover-trigger", ...props });
|
|
535
|
+
}
|
|
536
|
+
function PopoverContent({
|
|
537
|
+
className,
|
|
538
|
+
align = "center",
|
|
539
|
+
sideOffset = 4,
|
|
540
|
+
...props
|
|
541
|
+
}) {
|
|
542
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_radix_ui3.Popover.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
543
|
+
import_radix_ui3.Popover.Content,
|
|
544
|
+
{
|
|
545
|
+
align,
|
|
546
|
+
className: cn(
|
|
547
|
+
"nt:z-50 nt:w-72 nt:origin-(--radix-popover-content-transform-origin) nt:rounded-lg nt:bg-popover nt:p-4 nt:text-popover-foreground nt:shadow-md nt:ring-1 nt:ring-foreground/10 nt:outline-none nt:data-[side=bottom]:slide-in-from-top-2 nt:data-[side=left]:slide-in-from-right-2 nt:data-[side=right]:slide-in-from-left-2 nt:data-[side=top]:slide-in-from-bottom-2 nt:data-open:animate-in nt:data-open:fade-in-0 nt:data-open:zoom-in-95 nt:data-closed:animate-out nt:data-closed:fade-out-0 nt:data-closed:zoom-out-95",
|
|
548
|
+
className
|
|
549
|
+
),
|
|
550
|
+
"data-slot": "popover-content",
|
|
551
|
+
sideOffset,
|
|
552
|
+
...props
|
|
553
|
+
}
|
|
554
|
+
) });
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// src/components/ui/separator.tsx
|
|
558
|
+
var import_radix_ui4 = require("radix-ui");
|
|
559
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
560
|
+
function Separator({
|
|
561
|
+
className,
|
|
562
|
+
orientation = "horizontal",
|
|
563
|
+
decorative = true,
|
|
564
|
+
...props
|
|
565
|
+
}) {
|
|
566
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
567
|
+
import_radix_ui4.Separator.Root,
|
|
568
|
+
{
|
|
569
|
+
className: cn(
|
|
570
|
+
"nt:shrink-0 nt:bg-border nt:data-[orientation=horizontal]:h-px nt:data-[orientation=horizontal]:w-full nt:data-[orientation=vertical]:h-full nt:data-[orientation=vertical]:w-px",
|
|
571
|
+
className
|
|
572
|
+
),
|
|
573
|
+
"data-slot": "separator",
|
|
574
|
+
decorative,
|
|
575
|
+
orientation,
|
|
576
|
+
...props
|
|
577
|
+
}
|
|
578
|
+
);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// src/components/link-popover/link-popover.tsx
|
|
582
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
583
|
+
var LinkPopover = (0, import_react7.forwardRef)(
|
|
584
|
+
({ editor, ...buttonProps }, ref) => {
|
|
585
|
+
const [isOpen, setIsOpen] = (0, import_react7.useState)(false);
|
|
586
|
+
const { url, setUrl, isActive, canSet, setLink, removeLink, openLink } = useLinkPopover({ editor });
|
|
587
|
+
(0, import_react7.useEffect)(() => {
|
|
588
|
+
if (isActive) {
|
|
589
|
+
setIsOpen(true);
|
|
590
|
+
}
|
|
591
|
+
}, [isActive]);
|
|
592
|
+
const handleSetLink = (0, import_react7.useCallback)(() => {
|
|
593
|
+
setLink();
|
|
594
|
+
setIsOpen(false);
|
|
595
|
+
}, [setLink]);
|
|
596
|
+
const handleRemoveLink = (0, import_react7.useCallback)(() => {
|
|
597
|
+
removeLink();
|
|
598
|
+
setIsOpen(false);
|
|
599
|
+
}, [removeLink]);
|
|
600
|
+
const handleKeyDown = (0, import_react7.useCallback)(
|
|
601
|
+
(event) => {
|
|
602
|
+
if (event.key === "Enter") {
|
|
603
|
+
event.preventDefault();
|
|
604
|
+
handleSetLink();
|
|
605
|
+
}
|
|
606
|
+
},
|
|
607
|
+
[handleSetLink]
|
|
608
|
+
);
|
|
609
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Popover, { open: isOpen, onOpenChange: setIsOpen, children: [
|
|
610
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
611
|
+
Button,
|
|
612
|
+
{
|
|
613
|
+
ref,
|
|
614
|
+
"aria-label": "Link",
|
|
615
|
+
"aria-pressed": isActive,
|
|
616
|
+
"data-active-state": isActive ? "on" : "off",
|
|
617
|
+
disabled: !canSet,
|
|
618
|
+
size: "icon",
|
|
619
|
+
tabIndex: -1,
|
|
620
|
+
type: "button",
|
|
621
|
+
variant: "ghost",
|
|
622
|
+
...buttonProps,
|
|
623
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
624
|
+
import_lucide_react6.Link,
|
|
625
|
+
{
|
|
626
|
+
className: isActive ? "nt:text-[var(--tt-brand-color-500)]" : void 0
|
|
627
|
+
}
|
|
628
|
+
)
|
|
629
|
+
}
|
|
630
|
+
) }),
|
|
631
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
|
|
632
|
+
PopoverContent,
|
|
633
|
+
{
|
|
634
|
+
align: "start",
|
|
635
|
+
className: "nt:flex nt:w-auto nt:items-center nt:gap-1 nt:p-1",
|
|
636
|
+
children: [
|
|
637
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
638
|
+
Input,
|
|
639
|
+
{
|
|
640
|
+
autoFocus: true,
|
|
641
|
+
className: "nt:h-7 nt:min-w-48 nt:border-none nt:shadow-none nt:focus-visible:ring-0",
|
|
642
|
+
placeholder: "Paste a link...",
|
|
643
|
+
type: "url",
|
|
644
|
+
value: url,
|
|
645
|
+
onChange: (e) => setUrl(e.target.value),
|
|
646
|
+
onKeyDown: handleKeyDown
|
|
647
|
+
}
|
|
648
|
+
),
|
|
649
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
650
|
+
Button,
|
|
651
|
+
{
|
|
652
|
+
"aria-label": "Apply link",
|
|
653
|
+
disabled: !url && !isActive,
|
|
654
|
+
size: "icon-sm",
|
|
655
|
+
tabIndex: -1,
|
|
656
|
+
type: "button",
|
|
657
|
+
variant: "ghost",
|
|
658
|
+
onClick: handleSetLink,
|
|
659
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react6.CornerDownLeft, {})
|
|
660
|
+
}
|
|
661
|
+
),
|
|
662
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Separator, { className: "nt:h-5", orientation: "vertical" }),
|
|
663
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
664
|
+
Button,
|
|
665
|
+
{
|
|
666
|
+
"aria-label": "Open link in new window",
|
|
667
|
+
size: "icon-sm",
|
|
668
|
+
tabIndex: -1,
|
|
669
|
+
type: "button",
|
|
670
|
+
variant: "ghost",
|
|
671
|
+
onClick: openLink,
|
|
672
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react6.ExternalLink, {})
|
|
673
|
+
}
|
|
674
|
+
),
|
|
675
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
676
|
+
Button,
|
|
677
|
+
{
|
|
678
|
+
"aria-label": "Remove link",
|
|
679
|
+
size: "icon-sm",
|
|
680
|
+
tabIndex: -1,
|
|
681
|
+
type: "button",
|
|
682
|
+
variant: "ghost",
|
|
683
|
+
onClick: handleRemoveLink,
|
|
684
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react6.Trash2, {})
|
|
685
|
+
}
|
|
686
|
+
)
|
|
687
|
+
]
|
|
688
|
+
}
|
|
689
|
+
)
|
|
690
|
+
] });
|
|
691
|
+
}
|
|
692
|
+
);
|
|
693
|
+
LinkPopover.displayName = "LinkPopover";
|
|
694
|
+
|
|
695
|
+
// src/components/list-dropdown-menu/list-dropdown-menu.tsx
|
|
696
|
+
var import_lucide_react8 = require("lucide-react");
|
|
697
|
+
var import_react10 = require("react");
|
|
698
|
+
|
|
699
|
+
// src/components/list-dropdown-menu/list-menu-item.tsx
|
|
700
|
+
var import_react9 = require("react");
|
|
701
|
+
|
|
702
|
+
// src/components/list-dropdown-menu/use-list.ts
|
|
703
|
+
var import_lucide_react7 = require("lucide-react");
|
|
704
|
+
var import_react8 = require("react");
|
|
705
|
+
var listIcons = {
|
|
706
|
+
bulletList: import_lucide_react7.List,
|
|
707
|
+
orderedList: import_lucide_react7.ListOrdered,
|
|
708
|
+
taskList: import_lucide_react7.ListTodo
|
|
709
|
+
};
|
|
710
|
+
var listLabels = {
|
|
711
|
+
bulletList: "Bullet List",
|
|
712
|
+
orderedList: "Ordered List",
|
|
713
|
+
taskList: "Task List"
|
|
714
|
+
};
|
|
715
|
+
var listItemTypes = {
|
|
716
|
+
bulletList: "listItem",
|
|
717
|
+
orderedList: "listItem",
|
|
718
|
+
taskList: "taskItem"
|
|
719
|
+
};
|
|
720
|
+
function canToggleList(editor) {
|
|
721
|
+
if (!editor || !editor.isEditable) return false;
|
|
722
|
+
return editor.can().toggleList("bulletList", "listItem") || editor.can().clearNodes();
|
|
723
|
+
}
|
|
724
|
+
function useList({
|
|
725
|
+
editor,
|
|
726
|
+
type
|
|
727
|
+
}) {
|
|
728
|
+
const [isActive, setIsActive] = (0, import_react8.useState)(false);
|
|
729
|
+
const [canToggle, setCanToggle] = (0, import_react8.useState)(false);
|
|
730
|
+
(0, import_react8.useEffect)(() => {
|
|
731
|
+
if (!editor) return;
|
|
732
|
+
const handleUpdate = () => {
|
|
733
|
+
setIsActive(editor.isActive(type));
|
|
734
|
+
setCanToggle(canToggleList(editor));
|
|
735
|
+
};
|
|
736
|
+
handleUpdate();
|
|
737
|
+
editor.on("selectionUpdate", handleUpdate);
|
|
738
|
+
editor.on("transaction", handleUpdate);
|
|
739
|
+
return () => {
|
|
740
|
+
editor.off("selectionUpdate", handleUpdate);
|
|
741
|
+
editor.off("transaction", handleUpdate);
|
|
742
|
+
};
|
|
743
|
+
}, [editor, type]);
|
|
744
|
+
const handleToggle = (0, import_react8.useCallback)(() => {
|
|
745
|
+
if (!editor || !editor.isEditable) return false;
|
|
746
|
+
const itemType = listItemTypes[type];
|
|
747
|
+
if (editor.isActive(type)) {
|
|
748
|
+
return editor.chain().focus().clearNodes().run();
|
|
749
|
+
}
|
|
750
|
+
return editor.chain().focus().clearNodes().toggleList(type, itemType).run();
|
|
751
|
+
}, [editor, type]);
|
|
752
|
+
return {
|
|
753
|
+
isActive,
|
|
754
|
+
canToggle,
|
|
755
|
+
handleToggle,
|
|
756
|
+
label: listLabels[type],
|
|
757
|
+
Icon: listIcons[type]
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
function useActiveListType(editor, types) {
|
|
761
|
+
const [activeType, setActiveType] = (0, import_react8.useState)(null);
|
|
762
|
+
(0, import_react8.useEffect)(() => {
|
|
763
|
+
if (!editor) return;
|
|
764
|
+
const handleUpdate = () => {
|
|
765
|
+
const found = types.find((type) => editor.isActive(type));
|
|
766
|
+
setActiveType(found ?? null);
|
|
767
|
+
};
|
|
768
|
+
handleUpdate();
|
|
769
|
+
editor.on("selectionUpdate", handleUpdate);
|
|
770
|
+
editor.on("transaction", handleUpdate);
|
|
771
|
+
return () => {
|
|
772
|
+
editor.off("selectionUpdate", handleUpdate);
|
|
773
|
+
editor.off("transaction", handleUpdate);
|
|
774
|
+
};
|
|
775
|
+
}, [editor, types]);
|
|
776
|
+
return activeType;
|
|
777
|
+
}
|
|
778
|
+
function getListTriggerIcon(activeType) {
|
|
779
|
+
if (activeType === null) return import_lucide_react7.List;
|
|
780
|
+
return listIcons[activeType];
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
// src/components/list-dropdown-menu/list-menu-item.tsx
|
|
784
|
+
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
785
|
+
var ListMenuItem = (0, import_react9.forwardRef)(
|
|
786
|
+
({ editor, listType }, ref) => {
|
|
787
|
+
const { isActive, canToggle, handleToggle, label, Icon } = useList({
|
|
788
|
+
editor,
|
|
789
|
+
type: listType
|
|
790
|
+
});
|
|
791
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
792
|
+
DropdownMenuItem,
|
|
793
|
+
{
|
|
794
|
+
ref,
|
|
795
|
+
"aria-label": label,
|
|
796
|
+
className: "nt:data-[active-state=on]:bg-accent nt:data-[active-state=on]:text-[var(--tt-brand-color-500)]",
|
|
797
|
+
"data-active-state": isActive ? "on" : "off",
|
|
798
|
+
disabled: !canToggle,
|
|
799
|
+
onSelect: handleToggle,
|
|
800
|
+
children: [
|
|
801
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Icon, {}),
|
|
802
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: label })
|
|
803
|
+
]
|
|
804
|
+
}
|
|
805
|
+
);
|
|
806
|
+
}
|
|
807
|
+
);
|
|
808
|
+
ListMenuItem.displayName = "ListMenuItem";
|
|
809
|
+
|
|
810
|
+
// src/components/list-dropdown-menu/list-dropdown-menu.tsx
|
|
811
|
+
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
812
|
+
var ListDropdownMenu = (0, import_react10.forwardRef)(
|
|
813
|
+
({
|
|
814
|
+
editor,
|
|
815
|
+
types = ["bulletList", "orderedList", "taskList"],
|
|
816
|
+
...buttonProps
|
|
817
|
+
}, ref) => {
|
|
818
|
+
const activeType = useActiveListType(editor, types);
|
|
819
|
+
const TriggerIcon = getListTriggerIcon(activeType);
|
|
820
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(DropdownMenu, { children: [
|
|
821
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
|
|
822
|
+
Button,
|
|
823
|
+
{
|
|
824
|
+
ref,
|
|
825
|
+
"aria-label": "List",
|
|
826
|
+
className: "nt:gap-1 nt:px-2",
|
|
827
|
+
"data-active-state": activeType !== null ? "on" : "off",
|
|
828
|
+
size: "default",
|
|
829
|
+
tabIndex: -1,
|
|
830
|
+
type: "button",
|
|
831
|
+
variant: "ghost",
|
|
832
|
+
...buttonProps,
|
|
833
|
+
children: [
|
|
834
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
835
|
+
TriggerIcon,
|
|
836
|
+
{
|
|
837
|
+
className: activeType !== null ? "nt:text-[var(--tt-brand-color-500)]" : void 0
|
|
838
|
+
}
|
|
839
|
+
),
|
|
840
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react8.ChevronDown, { className: "nt:size-3" })
|
|
841
|
+
]
|
|
842
|
+
}
|
|
843
|
+
) }),
|
|
844
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(DropdownMenuContent, { align: "start", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(DropdownMenuGroup, { children: types.map((type) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ListMenuItem, { editor, listType: type }, type)) }) })
|
|
845
|
+
] });
|
|
846
|
+
}
|
|
847
|
+
);
|
|
848
|
+
ListDropdownMenu.displayName = "ListDropdownMenu";
|
|
849
|
+
|
|
850
|
+
// src/components/mark-button/mark-button.tsx
|
|
851
|
+
var import_react12 = require("react");
|
|
852
|
+
|
|
853
|
+
// src/components/mark-button/use-mark.ts
|
|
854
|
+
var import_lucide_react9 = require("lucide-react");
|
|
855
|
+
var import_react11 = require("react");
|
|
856
|
+
var markLabels = {
|
|
857
|
+
bold: "Bold",
|
|
858
|
+
italic: "Italic",
|
|
859
|
+
strike: "Strikethrough",
|
|
860
|
+
code: "Code"
|
|
861
|
+
};
|
|
862
|
+
var markIcons = {
|
|
863
|
+
bold: import_lucide_react9.Bold,
|
|
864
|
+
italic: import_lucide_react9.Italic,
|
|
865
|
+
strike: import_lucide_react9.Strikethrough,
|
|
866
|
+
code: import_lucide_react9.Code
|
|
867
|
+
};
|
|
868
|
+
function useMark({ editor, type }) {
|
|
869
|
+
const [isActive, setIsActive] = (0, import_react11.useState)(false);
|
|
870
|
+
const [canToggle, setCanToggle] = (0, import_react11.useState)(false);
|
|
871
|
+
(0, import_react11.useEffect)(() => {
|
|
872
|
+
if (!editor) return;
|
|
873
|
+
const handleUpdate = () => {
|
|
874
|
+
setIsActive(editor.isActive(type));
|
|
875
|
+
setCanToggle(editor.isEditable && editor.can().toggleMark(type));
|
|
876
|
+
};
|
|
877
|
+
handleUpdate();
|
|
878
|
+
editor.on("selectionUpdate", handleUpdate);
|
|
879
|
+
editor.on("transaction", handleUpdate);
|
|
880
|
+
return () => {
|
|
881
|
+
editor.off("selectionUpdate", handleUpdate);
|
|
882
|
+
editor.off("transaction", handleUpdate);
|
|
883
|
+
};
|
|
884
|
+
}, [editor, type]);
|
|
885
|
+
const handleToggle = (0, import_react11.useCallback)(() => {
|
|
886
|
+
if (!editor || !editor.isEditable) return false;
|
|
887
|
+
return editor.chain().focus().toggleMark(type).run();
|
|
888
|
+
}, [editor, type]);
|
|
889
|
+
return {
|
|
890
|
+
isActive,
|
|
891
|
+
canToggle,
|
|
892
|
+
handleToggle,
|
|
893
|
+
label: markLabels[type],
|
|
894
|
+
Icon: markIcons[type]
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
// src/components/mark-button/mark-button.tsx
|
|
899
|
+
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
900
|
+
var MarkButton = (0, import_react12.forwardRef)(
|
|
901
|
+
({ editor, type, onClick, ...buttonProps }, ref) => {
|
|
902
|
+
const { isActive, canToggle, handleToggle, label, Icon } = useMark({
|
|
903
|
+
editor,
|
|
904
|
+
type
|
|
905
|
+
});
|
|
906
|
+
const handleClick = (0, import_react12.useCallback)(
|
|
907
|
+
(event) => {
|
|
908
|
+
onClick?.(event);
|
|
909
|
+
if (event.defaultPrevented) return;
|
|
910
|
+
handleToggle();
|
|
911
|
+
},
|
|
912
|
+
[handleToggle, onClick]
|
|
913
|
+
);
|
|
914
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
915
|
+
Button,
|
|
916
|
+
{
|
|
917
|
+
ref,
|
|
918
|
+
"aria-label": label,
|
|
919
|
+
"aria-pressed": isActive,
|
|
920
|
+
"data-active-state": isActive ? "on" : "off",
|
|
921
|
+
disabled: !canToggle,
|
|
922
|
+
size: "icon",
|
|
923
|
+
tabIndex: -1,
|
|
924
|
+
type: "button",
|
|
925
|
+
variant: "ghost",
|
|
926
|
+
onClick: handleClick,
|
|
927
|
+
...buttonProps,
|
|
928
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
929
|
+
Icon,
|
|
930
|
+
{
|
|
931
|
+
className: isActive ? "nt:text-[var(--tt-brand-color-500)]" : void 0
|
|
932
|
+
}
|
|
933
|
+
)
|
|
934
|
+
}
|
|
935
|
+
);
|
|
936
|
+
}
|
|
937
|
+
);
|
|
938
|
+
MarkButton.displayName = "MarkButton";
|
|
939
|
+
|
|
940
|
+
// src/components/toolbar/toolbar.tsx
|
|
941
|
+
var import_react13 = require("react");
|
|
942
|
+
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
943
|
+
var Toolbar = (0, import_react13.forwardRef)(
|
|
944
|
+
({ children, className, variant = "fixed", ...props }, ref) => {
|
|
945
|
+
const classNames = ["tiptap-toolbar", className].filter(Boolean).join(" ");
|
|
946
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
947
|
+
"div",
|
|
948
|
+
{
|
|
949
|
+
ref,
|
|
950
|
+
"aria-label": "toolbar",
|
|
951
|
+
className: classNames,
|
|
952
|
+
"data-variant": variant,
|
|
953
|
+
role: "toolbar",
|
|
954
|
+
...props,
|
|
955
|
+
children
|
|
956
|
+
}
|
|
957
|
+
);
|
|
958
|
+
}
|
|
959
|
+
);
|
|
960
|
+
Toolbar.displayName = "Toolbar";
|
|
961
|
+
function ToolbarGroup({
|
|
962
|
+
children,
|
|
963
|
+
className,
|
|
964
|
+
...props
|
|
965
|
+
}) {
|
|
966
|
+
const classNames = ["tiptap-toolbar-group", className].filter(Boolean).join(" ");
|
|
967
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: classNames, role: "group", ...props, children });
|
|
968
|
+
}
|
|
969
|
+
function ToolbarSeparator({
|
|
970
|
+
orientation = "vertical",
|
|
971
|
+
className,
|
|
972
|
+
...props
|
|
973
|
+
}) {
|
|
974
|
+
const classNames = ["tiptap-separator", className].filter(Boolean).join(" ");
|
|
975
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
976
|
+
"div",
|
|
977
|
+
{
|
|
978
|
+
"aria-orientation": orientation === "vertical" ? orientation : void 0,
|
|
979
|
+
className: classNames,
|
|
980
|
+
"data-orientation": orientation,
|
|
981
|
+
role: "separator",
|
|
982
|
+
...props
|
|
983
|
+
}
|
|
984
|
+
);
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
// src/components/ui-primitive/spacer.tsx
|
|
988
|
+
var import_jsx_runtime15 = require("react/jsx-runtime");
|
|
989
|
+
function Spacer() {
|
|
990
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { style: { flex: 1 } });
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
// src/components/undo-redo-button/undo-redo-button.tsx
|
|
994
|
+
var import_react15 = require("react");
|
|
995
|
+
|
|
996
|
+
// src/components/undo-redo-button/use-undo-redo.ts
|
|
997
|
+
var import_lucide_react10 = require("lucide-react");
|
|
998
|
+
var import_react14 = require("react");
|
|
999
|
+
var actionLabels = {
|
|
1000
|
+
undo: "Undo",
|
|
1001
|
+
redo: "Redo"
|
|
1002
|
+
};
|
|
1003
|
+
var actionIcons = {
|
|
1004
|
+
undo: import_lucide_react10.Undo2,
|
|
1005
|
+
redo: import_lucide_react10.Redo2
|
|
1006
|
+
};
|
|
1007
|
+
function canExecuteAction(editor, action) {
|
|
1008
|
+
if (!editor || !editor.isEditable) return false;
|
|
1009
|
+
return action === "undo" ? editor.can().undo() : editor.can().redo();
|
|
1010
|
+
}
|
|
1011
|
+
function useUndoRedo({ editor, action }) {
|
|
1012
|
+
const [canExecute, setCanExecute] = (0, import_react14.useState)(false);
|
|
1013
|
+
(0, import_react14.useEffect)(() => {
|
|
1014
|
+
if (!editor) return;
|
|
1015
|
+
const handleUpdate = () => {
|
|
1016
|
+
setCanExecute(canExecuteAction(editor, action));
|
|
1017
|
+
};
|
|
1018
|
+
handleUpdate();
|
|
1019
|
+
editor.on("transaction", handleUpdate);
|
|
1020
|
+
return () => {
|
|
1021
|
+
editor.off("transaction", handleUpdate);
|
|
1022
|
+
};
|
|
1023
|
+
}, [editor, action]);
|
|
1024
|
+
const handleAction = (0, import_react14.useCallback)(() => {
|
|
1025
|
+
if (!editor || !editor.isEditable) return false;
|
|
1026
|
+
if (!canExecuteAction(editor, action)) return false;
|
|
1027
|
+
const chain = editor.chain().focus();
|
|
1028
|
+
return action === "undo" ? chain.undo().run() : chain.redo().run();
|
|
1029
|
+
}, [editor, action]);
|
|
1030
|
+
return {
|
|
1031
|
+
canExecute,
|
|
1032
|
+
handleAction,
|
|
1033
|
+
label: actionLabels[action],
|
|
1034
|
+
Icon: actionIcons[action]
|
|
1035
|
+
};
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
// src/components/undo-redo-button/undo-redo-button.tsx
|
|
1039
|
+
var import_jsx_runtime16 = require("react/jsx-runtime");
|
|
1040
|
+
var UndoRedoButton = (0, import_react15.forwardRef)(({ editor, action, onClick, ...buttonProps }, ref) => {
|
|
1041
|
+
const { canExecute, handleAction, label, Icon } = useUndoRedo({
|
|
1042
|
+
editor,
|
|
1043
|
+
action
|
|
1044
|
+
});
|
|
1045
|
+
const handleClick = (0, import_react15.useCallback)(
|
|
1046
|
+
(event) => {
|
|
1047
|
+
onClick?.(event);
|
|
1048
|
+
if (event.defaultPrevented) return;
|
|
1049
|
+
handleAction();
|
|
1050
|
+
},
|
|
1051
|
+
[handleAction, onClick]
|
|
1052
|
+
);
|
|
1053
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
1054
|
+
Button,
|
|
1055
|
+
{
|
|
1056
|
+
ref,
|
|
1057
|
+
"aria-label": label,
|
|
1058
|
+
disabled: !canExecute,
|
|
1059
|
+
size: "icon",
|
|
1060
|
+
tabIndex: -1,
|
|
1061
|
+
type: "button",
|
|
1062
|
+
variant: "ghost",
|
|
1063
|
+
onClick: handleClick,
|
|
1064
|
+
...buttonProps,
|
|
1065
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Icon, {})
|
|
1066
|
+
}
|
|
1067
|
+
);
|
|
1068
|
+
});
|
|
1069
|
+
UndoRedoButton.displayName = "UndoRedoButton";
|
|
1070
|
+
|
|
1071
|
+
// src/hooks/use-markdown-editor.ts
|
|
1072
|
+
var import_state = require("@tiptap/pm/state");
|
|
1073
|
+
var import_react16 = require("@tiptap/react");
|
|
1074
|
+
var import_react17 = require("react");
|
|
44
1075
|
|
|
45
1076
|
// src/extensions/shared.ts
|
|
46
1077
|
var import_extension_list = require("@tiptap/extension-list");
|
|
@@ -91,10 +1122,10 @@ function useMarkdownEditor({
|
|
|
91
1122
|
onChange,
|
|
92
1123
|
editable = true
|
|
93
1124
|
}) {
|
|
94
|
-
const externalValue = (0,
|
|
95
|
-
const onChangeRef = (0,
|
|
1125
|
+
const externalValue = (0, import_react17.useRef)(value);
|
|
1126
|
+
const onChangeRef = (0, import_react17.useRef)(onChange);
|
|
96
1127
|
onChangeRef.current = onChange;
|
|
97
|
-
const editor = (0,
|
|
1128
|
+
const editor = (0, import_react16.useEditor)({
|
|
98
1129
|
extensions: editorExtensions,
|
|
99
1130
|
editable,
|
|
100
1131
|
content: value,
|
|
@@ -104,15 +1135,28 @@ function useMarkdownEditor({
|
|
|
104
1135
|
);
|
|
105
1136
|
externalValue.current = md;
|
|
106
1137
|
onChangeRef.current(md);
|
|
1138
|
+
},
|
|
1139
|
+
onCreate({ editor: editor2 }) {
|
|
1140
|
+
setTimeout(() => {
|
|
1141
|
+
if (editor2.isDestroyed) return;
|
|
1142
|
+
const { state } = editor2;
|
|
1143
|
+
const freshState = import_state.EditorState.create({
|
|
1144
|
+
doc: state.doc,
|
|
1145
|
+
selection: state.selection,
|
|
1146
|
+
plugins: state.plugins
|
|
1147
|
+
});
|
|
1148
|
+
editor2.view.updateState(freshState);
|
|
1149
|
+
editor2.view.dispatch(editor2.view.state.tr);
|
|
1150
|
+
}, 0);
|
|
107
1151
|
}
|
|
108
1152
|
});
|
|
109
|
-
(0,
|
|
1153
|
+
(0, import_react17.useEffect)(() => {
|
|
110
1154
|
if (!editor) return;
|
|
111
1155
|
if (value === externalValue.current) return;
|
|
112
1156
|
externalValue.current = value;
|
|
113
1157
|
editor.commands.setContent(value);
|
|
114
1158
|
}, [value, editor]);
|
|
115
|
-
(0,
|
|
1159
|
+
(0, import_react17.useEffect)(() => {
|
|
116
1160
|
if (!editor) return;
|
|
117
1161
|
editor.setEditable(editable);
|
|
118
1162
|
}, [editable, editor]);
|
|
@@ -120,7 +1164,7 @@ function useMarkdownEditor({
|
|
|
120
1164
|
}
|
|
121
1165
|
|
|
122
1166
|
// src/notra-editor.tsx
|
|
123
|
-
var
|
|
1167
|
+
var import_jsx_runtime17 = require("react/jsx-runtime");
|
|
124
1168
|
function NotraEditor({
|
|
125
1169
|
value,
|
|
126
1170
|
onChange,
|
|
@@ -135,11 +1179,42 @@ function NotraEditor({
|
|
|
135
1179
|
editable: !readOnly
|
|
136
1180
|
});
|
|
137
1181
|
const classNames = ["notra", "notra-editor", className].filter(Boolean).join(" ");
|
|
138
|
-
return /* @__PURE__ */ (0,
|
|
1182
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: classNames, children: [
|
|
1183
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(Toolbar, { variant: "fixed", children: [
|
|
1184
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Spacer, {}),
|
|
1185
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(ToolbarGroup, { children: [
|
|
1186
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(UndoRedoButton, { action: "undo", editor }),
|
|
1187
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(UndoRedoButton, { action: "redo", editor })
|
|
1188
|
+
] }),
|
|
1189
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(ToolbarSeparator, {}),
|
|
1190
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(ToolbarGroup, { children: [
|
|
1191
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(HeadingDropdownMenu, { editor, levels: [1, 2, 3, 4] }),
|
|
1192
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
1193
|
+
ListDropdownMenu,
|
|
1194
|
+
{
|
|
1195
|
+
editor,
|
|
1196
|
+
types: ["bulletList", "orderedList", "taskList"]
|
|
1197
|
+
}
|
|
1198
|
+
),
|
|
1199
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(BlockquoteButton, { editor }),
|
|
1200
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(CodeBlockButton, { editor })
|
|
1201
|
+
] }),
|
|
1202
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(ToolbarSeparator, {}),
|
|
1203
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(ToolbarGroup, { children: [
|
|
1204
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(MarkButton, { editor, type: "bold" }),
|
|
1205
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(MarkButton, { editor, type: "italic" }),
|
|
1206
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(MarkButton, { editor, type: "strike" }),
|
|
1207
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(MarkButton, { editor, type: "code" }),
|
|
1208
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(LinkPopover, { editor })
|
|
1209
|
+
] }),
|
|
1210
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Spacer, {})
|
|
1211
|
+
] }),
|
|
1212
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_react18.EditorContent, { className: "notra-editor-content", editor })
|
|
1213
|
+
] });
|
|
139
1214
|
}
|
|
140
1215
|
|
|
141
1216
|
// src/notra-reader.tsx
|
|
142
|
-
var
|
|
1217
|
+
var import_react19 = require("@tiptap/static-renderer/pm/react");
|
|
143
1218
|
|
|
144
1219
|
// src/utils/markdown-to-json.ts
|
|
145
1220
|
var import_core = require("@tiptap/core");
|
|
@@ -165,19 +1240,123 @@ function markdownToJSON(markdown) {
|
|
|
165
1240
|
}
|
|
166
1241
|
|
|
167
1242
|
// src/notra-reader.tsx
|
|
168
|
-
var
|
|
1243
|
+
var import_jsx_runtime18 = require("react/jsx-runtime");
|
|
169
1244
|
function NotraReader({ content, className }) {
|
|
170
1245
|
const json = markdownToJSON(content);
|
|
171
|
-
const rendered = (0,
|
|
1246
|
+
const rendered = (0, import_react19.renderToReactElement)({
|
|
172
1247
|
extensions: sharedExtensions,
|
|
173
1248
|
content: json
|
|
174
1249
|
});
|
|
175
1250
|
const classNames = ["notra", "notra-reader", className].filter(Boolean).join(" ");
|
|
176
|
-
return /* @__PURE__ */ (0,
|
|
1251
|
+
return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: classNames, children: rendered });
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
// src/components/ui-primitive/dropdown-menu.tsx
|
|
1255
|
+
var import_react20 = require("react");
|
|
1256
|
+
var import_react_dom = require("react-dom");
|
|
1257
|
+
var import_jsx_runtime19 = require("react/jsx-runtime");
|
|
1258
|
+
function DropdownMenu2({
|
|
1259
|
+
trigger,
|
|
1260
|
+
children,
|
|
1261
|
+
open: controlledOpen,
|
|
1262
|
+
onOpenChange
|
|
1263
|
+
}) {
|
|
1264
|
+
const isControlled = controlledOpen !== void 0;
|
|
1265
|
+
const [uncontrolledOpen, setUncontrolledOpen] = (0, import_react20.useState)(false);
|
|
1266
|
+
const open = isControlled ? controlledOpen : uncontrolledOpen;
|
|
1267
|
+
const triggerRef = (0, import_react20.useRef)(null);
|
|
1268
|
+
const contentRef = (0, import_react20.useRef)(null);
|
|
1269
|
+
const [position, setPosition] = (0, import_react20.useState)({ top: 0, left: 0 });
|
|
1270
|
+
const setOpen = (value) => {
|
|
1271
|
+
if (!isControlled) {
|
|
1272
|
+
setUncontrolledOpen(value);
|
|
1273
|
+
}
|
|
1274
|
+
onOpenChange?.(value);
|
|
1275
|
+
};
|
|
1276
|
+
(0, import_react20.useEffect)(() => {
|
|
1277
|
+
if (!open || !triggerRef.current) return;
|
|
1278
|
+
const updatePosition = () => {
|
|
1279
|
+
if (!triggerRef.current) return;
|
|
1280
|
+
const rect = triggerRef.current.getBoundingClientRect();
|
|
1281
|
+
setPosition({
|
|
1282
|
+
top: rect.bottom + 4,
|
|
1283
|
+
left: rect.left + rect.width / 2
|
|
1284
|
+
});
|
|
1285
|
+
};
|
|
1286
|
+
updatePosition();
|
|
1287
|
+
window.addEventListener("scroll", updatePosition, true);
|
|
1288
|
+
window.addEventListener("resize", updatePosition);
|
|
1289
|
+
return () => {
|
|
1290
|
+
window.removeEventListener("scroll", updatePosition, true);
|
|
1291
|
+
window.removeEventListener("resize", updatePosition);
|
|
1292
|
+
};
|
|
1293
|
+
}, [open]);
|
|
1294
|
+
(0, import_react20.useEffect)(() => {
|
|
1295
|
+
if (!open) return;
|
|
1296
|
+
const handleMouseDown = (event) => {
|
|
1297
|
+
const target = event.target;
|
|
1298
|
+
if (triggerRef.current?.contains(target) || contentRef.current?.contains(target)) {
|
|
1299
|
+
return;
|
|
1300
|
+
}
|
|
1301
|
+
setOpen(false);
|
|
1302
|
+
};
|
|
1303
|
+
document.addEventListener("mousedown", handleMouseDown);
|
|
1304
|
+
return () => document.removeEventListener("mousedown", handleMouseDown);
|
|
1305
|
+
}, [open]);
|
|
1306
|
+
(0, import_react20.useEffect)(() => {
|
|
1307
|
+
if (!open) return;
|
|
1308
|
+
const handleKeyDown = (event) => {
|
|
1309
|
+
if (event.key === "Escape") {
|
|
1310
|
+
setOpen(false);
|
|
1311
|
+
}
|
|
1312
|
+
};
|
|
1313
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
1314
|
+
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
1315
|
+
}, [open]);
|
|
1316
|
+
return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_jsx_runtime19.Fragment, { children: [
|
|
1317
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { ref: triggerRef, onClick: () => setOpen(!open), children: trigger }),
|
|
1318
|
+
open && (0, import_react_dom.createPortal)(
|
|
1319
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "notra-editor", children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
1320
|
+
"div",
|
|
1321
|
+
{
|
|
1322
|
+
ref: contentRef,
|
|
1323
|
+
className: "tiptap-dropdown-menu-content",
|
|
1324
|
+
"data-state": "open",
|
|
1325
|
+
role: "menu",
|
|
1326
|
+
style: {
|
|
1327
|
+
position: "fixed",
|
|
1328
|
+
top: position.top,
|
|
1329
|
+
left: position.left
|
|
1330
|
+
},
|
|
1331
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
1332
|
+
"div",
|
|
1333
|
+
{
|
|
1334
|
+
className: "tiptap-dropdown-menu-group",
|
|
1335
|
+
onClick: () => setOpen(false),
|
|
1336
|
+
children
|
|
1337
|
+
}
|
|
1338
|
+
)
|
|
1339
|
+
}
|
|
1340
|
+
) }),
|
|
1341
|
+
document.body
|
|
1342
|
+
)
|
|
1343
|
+
] });
|
|
177
1344
|
}
|
|
178
1345
|
// Annotate the CommonJS export names for ESM import in node:
|
|
179
1346
|
0 && (module.exports = {
|
|
1347
|
+
BlockquoteButton,
|
|
1348
|
+
CodeBlockButton,
|
|
1349
|
+
DropdownMenu,
|
|
1350
|
+
HeadingDropdownMenu,
|
|
1351
|
+
LinkPopover,
|
|
1352
|
+
ListDropdownMenu,
|
|
1353
|
+
MarkButton,
|
|
180
1354
|
NotraEditor,
|
|
181
|
-
NotraReader
|
|
1355
|
+
NotraReader,
|
|
1356
|
+
Spacer,
|
|
1357
|
+
Toolbar,
|
|
1358
|
+
ToolbarGroup,
|
|
1359
|
+
ToolbarSeparator,
|
|
1360
|
+
UndoRedoButton
|
|
182
1361
|
});
|
|
183
1362
|
//# sourceMappingURL=index.cjs.map
|