notra-editor 0.3.0 → 0.4.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.
Files changed (221) hide show
  1. package/README.md +4 -8
  2. package/dist/components/blockquote-button/blockquote-button.cjs +91 -0
  3. package/dist/components/blockquote-button/blockquote-button.cjs.map +1 -0
  4. package/dist/components/blockquote-button/blockquote-button.d.cts +9 -0
  5. package/dist/components/blockquote-button/blockquote-button.d.ts +9 -0
  6. package/dist/components/blockquote-button/blockquote-button.mjs +67 -0
  7. package/dist/components/blockquote-button/blockquote-button.mjs.map +1 -0
  8. package/dist/components/code-block-button/code-block-button.cjs +91 -0
  9. package/dist/components/code-block-button/code-block-button.cjs.map +1 -0
  10. package/dist/components/code-block-button/code-block-button.d.cts +9 -0
  11. package/dist/components/code-block-button/code-block-button.d.ts +9 -0
  12. package/dist/components/code-block-button/code-block-button.mjs +67 -0
  13. package/dist/components/code-block-button/code-block-button.mjs.map +1 -0
  14. package/dist/components/code-block-view.cjs +39 -0
  15. package/dist/components/code-block-view.cjs.map +1 -0
  16. package/dist/components/code-block-view.d.cts +12 -0
  17. package/dist/components/code-block-view.d.ts +12 -0
  18. package/dist/components/code-block-view.mjs +17 -0
  19. package/dist/components/code-block-view.mjs.map +1 -0
  20. package/dist/components/copy-button.cjs +49 -0
  21. package/dist/components/copy-button.cjs.map +1 -0
  22. package/dist/components/copy-button.d.cts +9 -0
  23. package/dist/components/copy-button.d.ts +9 -0
  24. package/dist/components/copy-button.mjs +25 -0
  25. package/dist/components/copy-button.mjs.map +1 -0
  26. package/dist/components/heading-dropdown-menu/heading-dropdown-menu.cjs +67 -0
  27. package/dist/components/heading-dropdown-menu/heading-dropdown-menu.cjs.map +1 -0
  28. package/dist/components/heading-dropdown-menu/heading-dropdown-menu.d.cts +17 -0
  29. package/dist/components/heading-dropdown-menu/heading-dropdown-menu.d.ts +17 -0
  30. package/dist/components/heading-dropdown-menu/heading-dropdown-menu.mjs +51 -0
  31. package/dist/components/heading-dropdown-menu/heading-dropdown-menu.mjs.map +1 -0
  32. package/dist/components/heading-dropdown-menu/heading-menu-item.cjs +56 -0
  33. package/dist/components/heading-dropdown-menu/heading-menu-item.cjs.map +1 -0
  34. package/dist/components/heading-dropdown-menu/heading-menu-item.d.cts +12 -0
  35. package/dist/components/heading-dropdown-menu/heading-menu-item.d.ts +12 -0
  36. package/dist/components/heading-dropdown-menu/heading-menu-item.mjs +32 -0
  37. package/dist/components/heading-dropdown-menu/heading-menu-item.mjs.map +1 -0
  38. package/dist/components/heading-dropdown-menu/use-heading.cjs +109 -0
  39. package/dist/components/heading-dropdown-menu/use-heading.cjs.map +1 -0
  40. package/dist/components/heading-dropdown-menu/use-heading.d.cts +19 -0
  41. package/dist/components/heading-dropdown-menu/use-heading.d.ts +19 -0
  42. package/dist/components/heading-dropdown-menu/use-heading.mjs +83 -0
  43. package/dist/components/heading-dropdown-menu/use-heading.mjs.map +1 -0
  44. package/dist/components/link-popover/link-popover.cjs +148 -0
  45. package/dist/components/link-popover/link-popover.cjs.map +1 -0
  46. package/dist/components/link-popover/link-popover.d.cts +9 -0
  47. package/dist/components/link-popover/link-popover.d.ts +9 -0
  48. package/dist/components/link-popover/link-popover.mjs +129 -0
  49. package/dist/components/link-popover/link-popover.mjs.map +1 -0
  50. package/dist/components/link-popover/use-link-popover.cjs +71 -0
  51. package/dist/components/link-popover/use-link-popover.cjs.map +1 -0
  52. package/dist/components/link-popover/use-link-popover.d.cts +17 -0
  53. package/dist/components/link-popover/use-link-popover.d.ts +17 -0
  54. package/dist/components/link-popover/use-link-popover.mjs +47 -0
  55. package/dist/components/link-popover/use-link-popover.mjs.map +1 -0
  56. package/dist/components/list-dropdown-menu/list-dropdown-menu.cjs +73 -0
  57. package/dist/components/list-dropdown-menu/list-dropdown-menu.cjs.map +1 -0
  58. package/dist/components/list-dropdown-menu/list-dropdown-menu.d.cts +17 -0
  59. package/dist/components/list-dropdown-menu/list-dropdown-menu.d.ts +17 -0
  60. package/dist/components/list-dropdown-menu/list-dropdown-menu.mjs +57 -0
  61. package/dist/components/list-dropdown-menu/list-dropdown-menu.mjs.map +1 -0
  62. package/dist/components/list-dropdown-menu/list-menu-item.cjs +56 -0
  63. package/dist/components/list-dropdown-menu/list-menu-item.cjs.map +1 -0
  64. package/dist/components/list-dropdown-menu/list-menu-item.d.cts +12 -0
  65. package/dist/components/list-dropdown-menu/list-menu-item.d.ts +12 -0
  66. package/dist/components/list-dropdown-menu/list-menu-item.mjs +32 -0
  67. package/dist/components/list-dropdown-menu/list-menu-item.mjs.map +1 -0
  68. package/dist/components/list-dropdown-menu/use-list.cjs +111 -0
  69. package/dist/components/list-dropdown-menu/use-list.cjs.map +1 -0
  70. package/dist/components/list-dropdown-menu/use-list.d.cts +19 -0
  71. package/dist/components/list-dropdown-menu/use-list.d.ts +19 -0
  72. package/dist/components/list-dropdown-menu/use-list.mjs +85 -0
  73. package/dist/components/list-dropdown-menu/use-list.mjs.map +1 -0
  74. package/dist/components/mark-button/mark-button.cjs +72 -0
  75. package/dist/components/mark-button/mark-button.cjs.map +1 -0
  76. package/dist/components/mark-button/mark-button.d.cts +12 -0
  77. package/dist/components/mark-button/mark-button.d.ts +12 -0
  78. package/dist/components/mark-button/mark-button.mjs +48 -0
  79. package/dist/components/mark-button/mark-button.mjs.map +1 -0
  80. package/dist/components/mark-button/use-mark.cjs +71 -0
  81. package/dist/components/mark-button/use-mark.cjs.map +1 -0
  82. package/dist/components/mark-button/use-mark.d.cts +17 -0
  83. package/dist/components/mark-button/use-mark.d.ts +17 -0
  84. package/dist/components/mark-button/use-mark.mjs +47 -0
  85. package/dist/components/mark-button/use-mark.mjs.map +1 -0
  86. package/dist/components/toolbar/toolbar.cjs +77 -0
  87. package/dist/components/toolbar/toolbar.cjs.map +1 -0
  88. package/dist/components/toolbar/toolbar.d.cts +14 -0
  89. package/dist/components/toolbar/toolbar.d.ts +14 -0
  90. package/dist/components/toolbar/toolbar.mjs +51 -0
  91. package/dist/components/toolbar/toolbar.mjs.map +1 -0
  92. package/dist/components/ui/button.cjs +82 -0
  93. package/dist/components/ui/button.cjs.map +1 -0
  94. package/dist/components/ui/button.d.cts +14 -0
  95. package/dist/components/ui/button.d.ts +14 -0
  96. package/dist/components/ui/button.mjs +57 -0
  97. package/dist/components/ui/button.mjs.map +1 -0
  98. package/dist/components/ui/dropdown-menu.cjs +290 -0
  99. package/dist/components/ui/dropdown-menu.cjs.map +1 -0
  100. package/dist/components/ui/dropdown-menu.d.cts +32 -0
  101. package/dist/components/ui/dropdown-menu.d.ts +32 -0
  102. package/dist/components/ui/dropdown-menu.mjs +252 -0
  103. package/dist/components/ui/dropdown-menu.mjs.map +1 -0
  104. package/dist/components/ui/input.cjs +44 -0
  105. package/dist/components/ui/input.cjs.map +1 -0
  106. package/dist/components/ui/input.d.cts +6 -0
  107. package/dist/components/ui/input.d.ts +6 -0
  108. package/dist/components/ui/input.mjs +20 -0
  109. package/dist/components/ui/input.mjs.map +1 -0
  110. package/dist/components/ui/popover.cjs +72 -0
  111. package/dist/components/ui/popover.cjs.map +1 -0
  112. package/dist/components/ui/popover.d.cts +10 -0
  113. package/dist/components/ui/popover.d.ts +10 -0
  114. package/dist/components/ui/popover.mjs +45 -0
  115. package/dist/components/ui/popover.mjs.map +1 -0
  116. package/dist/components/ui/separator.cjs +51 -0
  117. package/dist/components/ui/separator.cjs.map +1 -0
  118. package/dist/components/ui/separator.d.cts +7 -0
  119. package/dist/components/ui/separator.d.ts +7 -0
  120. package/dist/components/ui/separator.mjs +27 -0
  121. package/dist/components/ui/separator.mjs.map +1 -0
  122. package/dist/components/ui/spacer.cjs +32 -0
  123. package/dist/components/ui/spacer.cjs.map +1 -0
  124. package/dist/components/ui/spacer.d.cts +5 -0
  125. package/dist/components/ui/spacer.d.ts +5 -0
  126. package/dist/components/ui/spacer.mjs +8 -0
  127. package/dist/components/ui/spacer.mjs.map +1 -0
  128. package/dist/components/undo-redo-button/undo-redo-button.cjs +63 -0
  129. package/dist/components/undo-redo-button/undo-redo-button.cjs.map +1 -0
  130. package/dist/components/undo-redo-button/undo-redo-button.d.cts +12 -0
  131. package/dist/components/undo-redo-button/undo-redo-button.d.ts +12 -0
  132. package/dist/components/undo-redo-button/undo-redo-button.mjs +39 -0
  133. package/dist/components/undo-redo-button/undo-redo-button.mjs.map +1 -0
  134. package/dist/components/undo-redo-button/use-undo-redo.cjs +68 -0
  135. package/dist/components/undo-redo-button/use-undo-redo.cjs.map +1 -0
  136. package/dist/components/undo-redo-button/use-undo-redo.d.cts +17 -0
  137. package/dist/components/undo-redo-button/use-undo-redo.d.ts +17 -0
  138. package/dist/components/undo-redo-button/use-undo-redo.mjs +44 -0
  139. package/dist/components/undo-redo-button/use-undo-redo.mjs.map +1 -0
  140. package/dist/extensions/code-block.cjs +46 -0
  141. package/dist/extensions/code-block.cjs.map +1 -0
  142. package/dist/extensions/code-block.d.cts +6 -0
  143. package/dist/extensions/code-block.d.ts +6 -0
  144. package/dist/extensions/code-block.mjs +12 -0
  145. package/dist/extensions/code-block.mjs.map +1 -0
  146. package/dist/extensions/editor.cjs +53 -0
  147. package/dist/extensions/editor.cjs.map +1 -0
  148. package/dist/extensions/editor.d.cts +9 -0
  149. package/dist/extensions/editor.d.ts +9 -0
  150. package/dist/extensions/editor.mjs +19 -0
  151. package/dist/extensions/editor.mjs.map +1 -0
  152. package/dist/extensions/index.cjs +32 -0
  153. package/dist/extensions/index.cjs.map +1 -0
  154. package/dist/extensions/index.d.cts +7 -0
  155. package/dist/extensions/index.d.ts +7 -0
  156. package/dist/extensions/index.mjs +7 -0
  157. package/dist/extensions/index.mjs.map +1 -0
  158. package/dist/extensions/shared.cjs +64 -0
  159. package/dist/extensions/shared.cjs.map +1 -0
  160. package/dist/extensions/shared.d.cts +8 -0
  161. package/dist/extensions/shared.d.ts +8 -0
  162. package/dist/extensions/shared.mjs +29 -0
  163. package/dist/extensions/shared.mjs.map +1 -0
  164. package/dist/hooks/use-copy-to-clipboard.cjs +50 -0
  165. package/dist/hooks/use-copy-to-clipboard.cjs.map +1 -0
  166. package/dist/hooks/use-copy-to-clipboard.d.cts +10 -0
  167. package/dist/hooks/use-copy-to-clipboard.d.ts +10 -0
  168. package/dist/hooks/use-copy-to-clipboard.mjs +26 -0
  169. package/dist/hooks/use-copy-to-clipboard.mjs.map +1 -0
  170. package/dist/hooks/use-markdown-editor.cjs +80 -0
  171. package/dist/hooks/use-markdown-editor.cjs.map +1 -0
  172. package/dist/hooks/use-markdown-editor.d.cts +13 -0
  173. package/dist/hooks/use-markdown-editor.d.ts +13 -0
  174. package/dist/hooks/use-markdown-editor.mjs +56 -0
  175. package/dist/hooks/use-markdown-editor.mjs.map +1 -0
  176. package/dist/icons/redo-icon.cjs +54 -0
  177. package/dist/icons/redo-icon.cjs.map +1 -0
  178. package/dist/icons/redo-icon.d.cts +7 -0
  179. package/dist/icons/redo-icon.d.ts +7 -0
  180. package/dist/icons/redo-icon.mjs +30 -0
  181. package/dist/icons/redo-icon.mjs.map +1 -0
  182. package/dist/icons/undo-icon.cjs +54 -0
  183. package/dist/icons/undo-icon.cjs.map +1 -0
  184. package/dist/icons/undo-icon.d.cts +7 -0
  185. package/dist/icons/undo-icon.d.ts +7 -0
  186. package/dist/icons/undo-icon.mjs +30 -0
  187. package/dist/icons/undo-icon.mjs.map +1 -0
  188. package/dist/index.cjs +24 -1322
  189. package/dist/index.cjs.map +1 -1
  190. package/dist/index.d.cts +22 -105
  191. package/dist/index.d.ts +22 -105
  192. package/dist/index.mjs +14 -1301
  193. package/dist/index.mjs.map +1 -1
  194. package/dist/lib/utils.cjs +33 -0
  195. package/dist/lib/utils.cjs.map +1 -0
  196. package/dist/lib/utils.d.cts +5 -0
  197. package/dist/lib/utils.d.ts +5 -0
  198. package/dist/lib/utils.mjs +9 -0
  199. package/dist/lib/utils.mjs.map +1 -0
  200. package/dist/notra-editor.cjs +88 -0
  201. package/dist/notra-editor.cjs.map +1 -0
  202. package/dist/notra-editor.d.cts +17 -0
  203. package/dist/notra-editor.d.ts +17 -0
  204. package/dist/notra-editor.mjs +68 -0
  205. package/dist/notra-editor.mjs.map +1 -0
  206. package/dist/notra-reader.cjs +47 -0
  207. package/dist/notra-reader.cjs.map +1 -0
  208. package/dist/notra-reader.d.cts +11 -0
  209. package/dist/notra-reader.d.ts +11 -0
  210. package/dist/notra-reader.mjs +23 -0
  211. package/dist/notra-reader.mjs.map +1 -0
  212. package/dist/styles/globals.css +29 -0
  213. package/dist/themes/default/editor.css +2 -0
  214. package/dist/themes/default/reader.css +2 -0
  215. package/dist/utils/markdown-to-json.cjs +50 -0
  216. package/dist/utils/markdown-to-json.cjs.map +1 -0
  217. package/dist/utils/markdown-to-json.d.cts +7 -0
  218. package/dist/utils/markdown-to-json.d.ts +7 -0
  219. package/dist/utils/markdown-to-json.mjs +26 -0
  220. package/dist/utils/markdown-to-json.mjs.map +1 -0
  221. package/package.json +2 -1
package/dist/index.mjs CHANGED
@@ -1,1309 +1,22 @@
1
- // src/index.ts
2
1
  import "./styles/globals.css";
3
-
4
- // src/notra-editor.tsx
5
- import { EditorContent } from "@tiptap/react";
6
-
7
- // src/components/blockquote-button/blockquote-button.tsx
8
- import { TextQuote } from "lucide-react";
9
- import { forwardRef, useCallback, useEffect, useState } from "react";
10
-
11
- // src/components/ui/button.tsx
12
- import { cva } from "class-variance-authority";
13
- import { Slot } from "radix-ui";
14
-
15
- // src/lib/utils.ts
16
- import { clsx } from "clsx";
17
- import { twMerge } from "tailwind-merge";
18
- function cn(...inputs) {
19
- return twMerge(clsx(inputs));
20
- }
21
-
22
- // src/components/ui/button.tsx
23
- import { jsx } from "react/jsx-runtime";
24
- var buttonVariants = cva(
25
- "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",
26
- {
27
- variants: {
28
- variant: {
29
- default: "nt:bg-primary nt:text-primary-foreground nt:[a]:hover:bg-primary/80",
30
- 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",
31
- secondary: "nt:bg-secondary nt:text-secondary-foreground nt:hover:bg-secondary/80 nt:aria-expanded:bg-secondary nt:aria-expanded:text-secondary-foreground",
32
- 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",
33
- 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",
34
- link: "nt:text-primary nt:underline-offset-4 nt:hover:underline"
35
- },
36
- size: {
37
- 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",
38
- 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",
39
- 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",
40
- 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",
41
- icon: "nt:size-8",
42
- "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",
43
- "icon-sm": "nt:size-7 nt:rounded-[min(var(--radius-md),12px)] nt:in-data-[slot=button-group]:rounded-lg",
44
- "icon-lg": "nt:size-9"
45
- }
46
- },
47
- defaultVariants: {
48
- variant: "default",
49
- size: "default"
50
- }
51
- }
52
- );
53
- function Button({
54
- className,
55
- variant = "default",
56
- size = "default",
57
- asChild = false,
58
- ...props
59
- }) {
60
- const Comp = asChild ? Slot.Root : "button";
61
- return /* @__PURE__ */ jsx(
62
- Comp,
63
- {
64
- className: cn(buttonVariants({ variant, size, className })),
65
- "data-size": size,
66
- "data-slot": "button",
67
- "data-variant": variant,
68
- ...props
69
- }
70
- );
71
- }
72
-
73
- // src/components/blockquote-button/blockquote-button.tsx
74
- import { jsx as jsx2 } from "react/jsx-runtime";
75
- function canToggleBlockquote(editor) {
76
- if (!editor || !editor.isEditable) return false;
77
- return editor.can().toggleWrap("blockquote") || editor.can().clearNodes();
78
- }
79
- var BlockquoteButton = forwardRef(({ editor, onClick, ...buttonProps }, ref) => {
80
- const [isActive, setIsActive] = useState(false);
81
- const [canToggle, setCanToggle] = useState(false);
82
- useEffect(() => {
83
- if (!editor) return;
84
- const update = () => {
85
- setIsActive(editor.isActive("blockquote"));
86
- setCanToggle(canToggleBlockquote(editor));
87
- };
88
- update();
89
- editor.on("selectionUpdate", update);
90
- editor.on("transaction", update);
91
- return () => {
92
- editor.off("selectionUpdate", update);
93
- editor.off("transaction", update);
94
- };
95
- }, [editor]);
96
- const handleClick = useCallback(
97
- (event) => {
98
- onClick?.(event);
99
- if (event.defaultPrevented) return;
100
- if (!editor) return;
101
- if (editor.isActive("blockquote")) {
102
- editor.chain().focus().lift("blockquote").run();
103
- } else {
104
- editor.chain().focus().clearNodes().wrapIn("blockquote").run();
105
- }
106
- },
107
- [editor, onClick]
108
- );
109
- return /* @__PURE__ */ jsx2(
110
- Button,
111
- {
112
- ref,
113
- "aria-label": "Blockquote",
114
- "aria-pressed": isActive,
115
- "data-active-state": isActive ? "on" : "off",
116
- disabled: !canToggle,
117
- size: "icon",
118
- tabIndex: -1,
119
- type: "button",
120
- variant: "ghost",
121
- onClick: handleClick,
122
- ...buttonProps,
123
- children: /* @__PURE__ */ jsx2(
124
- TextQuote,
125
- {
126
- className: isActive ? "nt:text-[var(--tt-brand-color-500)]" : void 0
127
- }
128
- )
129
- }
130
- );
131
- });
132
- BlockquoteButton.displayName = "BlockquoteButton";
133
-
134
- // src/components/code-block-button/code-block-button.tsx
135
- import { SquareCode } from "lucide-react";
136
- import { forwardRef as forwardRef2, useCallback as useCallback2, useEffect as useEffect2, useState as useState2 } from "react";
137
- import { jsx as jsx3 } from "react/jsx-runtime";
138
- function canToggleCodeBlock(editor) {
139
- if (!editor || !editor.isEditable) return false;
140
- return editor.can().toggleNode("codeBlock", "paragraph") || editor.can().clearNodes();
141
- }
142
- var CodeBlockButton = forwardRef2(({ editor, onClick, ...buttonProps }, ref) => {
143
- const [isActive, setIsActive] = useState2(false);
144
- const [canToggle, setCanToggle] = useState2(false);
145
- useEffect2(() => {
146
- if (!editor) return;
147
- const update = () => {
148
- setIsActive(editor.isActive("codeBlock"));
149
- setCanToggle(canToggleCodeBlock(editor));
150
- };
151
- update();
152
- editor.on("selectionUpdate", update);
153
- editor.on("transaction", update);
154
- return () => {
155
- editor.off("selectionUpdate", update);
156
- editor.off("transaction", update);
157
- };
158
- }, [editor]);
159
- const handleClick = useCallback2(
160
- (event) => {
161
- onClick?.(event);
162
- if (event.defaultPrevented) return;
163
- if (!editor) return;
164
- if (editor.isActive("codeBlock")) {
165
- editor.chain().focus().setNode("paragraph").run();
166
- } else {
167
- editor.chain().focus().clearNodes().toggleNode("codeBlock", "paragraph").run();
168
- }
169
- },
170
- [editor, onClick]
171
- );
172
- return /* @__PURE__ */ jsx3(
173
- Button,
174
- {
175
- ref,
176
- "aria-label": "Code Block",
177
- "aria-pressed": isActive,
178
- "data-active-state": isActive ? "on" : "off",
179
- disabled: !canToggle,
180
- size: "icon",
181
- tabIndex: -1,
182
- type: "button",
183
- variant: "ghost",
184
- onClick: handleClick,
185
- ...buttonProps,
186
- children: /* @__PURE__ */ jsx3(
187
- SquareCode,
188
- {
189
- className: isActive ? "nt:text-[var(--tt-brand-color-500)]" : void 0
190
- }
191
- )
192
- }
193
- );
194
- });
195
- CodeBlockButton.displayName = "CodeBlockButton";
196
-
197
- // src/components/heading-dropdown-menu/heading-dropdown-menu.tsx
198
- import { ChevronDown } from "lucide-react";
199
- import { forwardRef as forwardRef4 } from "react";
200
-
201
- // src/components/heading-dropdown-menu/heading-menu-item.tsx
202
- import { forwardRef as forwardRef3 } from "react";
203
-
204
- // src/components/heading-dropdown-menu/use-heading.ts
205
- import { Heading, Heading1, Heading2, Heading3, Heading4 } from "lucide-react";
206
- import { useCallback as useCallback3, useEffect as useEffect3, useState as useState3 } from "react";
207
- var headingIcons = {
208
- 1: Heading1,
209
- 2: Heading2,
210
- 3: Heading3,
211
- 4: Heading4
212
- };
213
- var headingLabels = {
214
- 1: "Heading 1",
215
- 2: "Heading 2",
216
- 3: "Heading 3",
217
- 4: "Heading 4"
218
- };
219
- function canToggleHeading(editor, level) {
220
- if (!editor || !editor.isEditable) return false;
221
- return editor.can().setNode("heading", { level }) || editor.can().clearNodes();
222
- }
223
- function useHeading({
224
- editor,
225
- level
226
- }) {
227
- const [isActive, setIsActive] = useState3(false);
228
- const [canToggle, setCanToggle] = useState3(false);
229
- useEffect3(() => {
230
- if (!editor) return;
231
- const handleUpdate = () => {
232
- setIsActive(editor.isActive("heading", { level }));
233
- setCanToggle(canToggleHeading(editor, level));
234
- };
235
- handleUpdate();
236
- editor.on("selectionUpdate", handleUpdate);
237
- editor.on("transaction", handleUpdate);
238
- return () => {
239
- editor.off("selectionUpdate", handleUpdate);
240
- editor.off("transaction", handleUpdate);
241
- };
242
- }, [editor, level]);
243
- const handleToggle = useCallback3(() => {
244
- if (!editor || !editor.isEditable) return false;
245
- if (editor.isActive("heading", { level })) {
246
- return editor.chain().focus().setNode("paragraph").run();
247
- }
248
- return editor.chain().focus().clearNodes().setNode("heading", { level }).run();
249
- }, [editor, level]);
250
- return {
251
- isActive,
252
- canToggle,
253
- handleToggle,
254
- label: headingLabels[level],
255
- Icon: headingIcons[level]
256
- };
257
- }
258
- function useActiveHeadingLevel(editor, levels) {
259
- const [activeLevel, setActiveLevel] = useState3(null);
260
- useEffect3(() => {
261
- if (!editor) return;
262
- const handleUpdate = () => {
263
- const found = levels.find(
264
- (level) => editor.isActive("heading", { level })
265
- );
266
- setActiveLevel(found ?? null);
267
- };
268
- handleUpdate();
269
- editor.on("selectionUpdate", handleUpdate);
270
- editor.on("transaction", handleUpdate);
271
- return () => {
272
- editor.off("selectionUpdate", handleUpdate);
273
- editor.off("transaction", handleUpdate);
274
- };
275
- }, [editor, levels]);
276
- return activeLevel;
277
- }
278
- function getHeadingTriggerIcon(activeLevel) {
279
- if (activeLevel === null) return Heading;
280
- return headingIcons[activeLevel];
281
- }
282
-
283
- // src/components/ui/dropdown-menu.tsx
284
- import { CheckIcon, ChevronRightIcon } from "lucide-react";
285
- import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui";
286
- import { jsx as jsx4, jsxs } from "react/jsx-runtime";
287
- function DropdownMenu({
288
- ...props
289
- }) {
290
- return /* @__PURE__ */ jsx4(DropdownMenuPrimitive.Root, { "data-slot": "dropdown-menu", ...props });
291
- }
292
- function DropdownMenuTrigger({
293
- ...props
294
- }) {
295
- return /* @__PURE__ */ jsx4(
296
- DropdownMenuPrimitive.Trigger,
297
- {
298
- "data-slot": "dropdown-menu-trigger",
299
- ...props
300
- }
301
- );
302
- }
303
- function DropdownMenuContent({
304
- className,
305
- align = "start",
306
- sideOffset = 4,
307
- ...props
308
- }) {
309
- return /* @__PURE__ */ jsx4(DropdownMenuPrimitive.Portal, { children: /* @__PURE__ */ jsx4(
310
- DropdownMenuPrimitive.Content,
311
- {
312
- align,
313
- className: cn(
314
- "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",
315
- className
316
- ),
317
- "data-slot": "dropdown-menu-content",
318
- sideOffset,
319
- ...props
320
- }
321
- ) });
322
- }
323
- function DropdownMenuGroup({
324
- ...props
325
- }) {
326
- return /* @__PURE__ */ jsx4(DropdownMenuPrimitive.Group, { "data-slot": "dropdown-menu-group", ...props });
327
- }
328
- function DropdownMenuItem({
329
- className,
330
- inset,
331
- variant = "default",
332
- ...props
333
- }) {
334
- return /* @__PURE__ */ jsx4(
335
- DropdownMenuPrimitive.Item,
336
- {
337
- className: cn(
338
- "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",
339
- className
340
- ),
341
- "data-inset": inset,
342
- "data-slot": "dropdown-menu-item",
343
- "data-variant": variant,
344
- ...props
345
- }
346
- );
347
- }
348
-
349
- // src/components/heading-dropdown-menu/heading-menu-item.tsx
350
- import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
351
- var HeadingMenuItem = forwardRef3(
352
- ({ editor, level }, ref) => {
353
- const { isActive, canToggle, handleToggle, label, Icon } = useHeading({
354
- editor,
355
- level
356
- });
357
- return /* @__PURE__ */ jsxs2(
358
- DropdownMenuItem,
359
- {
360
- ref,
361
- "aria-label": label,
362
- className: "nt:data-[active-state=on]:bg-accent nt:data-[active-state=on]:text-[var(--tt-brand-color-500)]",
363
- "data-active-state": isActive ? "on" : "off",
364
- disabled: !canToggle,
365
- onSelect: handleToggle,
366
- children: [
367
- /* @__PURE__ */ jsx5(Icon, {}),
368
- /* @__PURE__ */ jsx5("span", { children: label })
369
- ]
370
- }
371
- );
372
- }
373
- );
374
- HeadingMenuItem.displayName = "HeadingMenuItem";
375
-
376
- // src/components/heading-dropdown-menu/heading-dropdown-menu.tsx
377
- import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
378
- var HeadingDropdownMenu = forwardRef4(({ editor, levels = [1, 2, 3, 4], ...buttonProps }, ref) => {
379
- const activeLevel = useActiveHeadingLevel(editor, levels);
380
- const TriggerIcon = getHeadingTriggerIcon(activeLevel);
381
- return /* @__PURE__ */ jsxs3(DropdownMenu, { children: [
382
- /* @__PURE__ */ jsx6(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs3(
383
- Button,
384
- {
385
- ref,
386
- "aria-label": "Heading",
387
- className: "nt:gap-1 nt:px-2",
388
- "data-active-state": activeLevel !== null ? "on" : "off",
389
- size: "default",
390
- tabIndex: -1,
391
- type: "button",
392
- variant: "ghost",
393
- ...buttonProps,
394
- children: [
395
- /* @__PURE__ */ jsx6(
396
- TriggerIcon,
397
- {
398
- className: activeLevel !== null ? "nt:text-[var(--tt-brand-color-500)]" : void 0
399
- }
400
- ),
401
- /* @__PURE__ */ jsx6(ChevronDown, { className: "nt:size-3" })
402
- ]
403
- }
404
- ) }),
405
- /* @__PURE__ */ jsx6(DropdownMenuContent, { align: "start", children: /* @__PURE__ */ jsx6(DropdownMenuGroup, { children: levels.map((level) => /* @__PURE__ */ jsx6(HeadingMenuItem, { editor, level }, level)) }) })
406
- ] });
407
- });
408
- HeadingDropdownMenu.displayName = "HeadingDropdownMenu";
409
-
410
- // src/components/link-popover/link-popover.tsx
2
+ import { NotraEditor } from "./notra-editor";
3
+ import { NotraReader } from "./notra-reader";
411
4
  import {
412
- CornerDownLeft,
413
- ExternalLink,
414
- Link as LinkIcon,
415
- Trash2
416
- } from "lucide-react";
417
- import { forwardRef as forwardRef5, useCallback as useCallback5, useEffect as useEffect5, useState as useState5 } from "react";
418
-
419
- // src/components/link-popover/use-link-popover.ts
420
- import { useCallback as useCallback4, useEffect as useEffect4, useState as useState4 } from "react";
421
- function useLinkPopover({ editor }) {
422
- const [url, setUrl] = useState4("");
423
- const [isActive, setIsActive] = useState4(false);
424
- const [canSet, setCanSet] = useState4(false);
425
- useEffect4(() => {
426
- if (!editor) return;
427
- const handleUpdate = () => {
428
- const active = editor.isActive("link");
429
- setIsActive(active);
430
- setCanSet(editor.isEditable);
431
- if (active) {
432
- setUrl(editor.getAttributes("link").href ?? "");
433
- }
434
- };
435
- handleUpdate();
436
- editor.on("selectionUpdate", handleUpdate);
437
- editor.on("transaction", handleUpdate);
438
- return () => {
439
- editor.off("selectionUpdate", handleUpdate);
440
- editor.off("transaction", handleUpdate);
441
- };
442
- }, [editor]);
443
- const setLink = useCallback4(() => {
444
- if (!editor) return;
445
- if (!url) {
446
- editor.chain().focus().extendMarkRange("link").unsetLink().run();
447
- return;
448
- }
449
- editor.chain().focus().extendMarkRange("link").setLink({ href: url }).run();
450
- }, [editor, url]);
451
- const removeLink = useCallback4(() => {
452
- if (!editor) return;
453
- editor.chain().focus().extendMarkRange("link").unsetLink().run();
454
- setUrl("");
455
- }, [editor]);
456
- const openLink = useCallback4(() => {
457
- if (!url) return;
458
- const sanitized = /^https?:\/\//i.test(url) ? url : `https://${url}`;
459
- window.open(sanitized, "_blank", "noopener,noreferrer");
460
- }, [url]);
461
- return { url, setUrl, isActive, canSet, setLink, removeLink, openLink };
462
- }
463
-
464
- // src/components/ui/input.tsx
465
- import { jsx as jsx7 } from "react/jsx-runtime";
466
- function Input({ className, type, ...props }) {
467
- return /* @__PURE__ */ jsx7(
468
- "input",
469
- {
470
- className: cn(
471
- "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",
472
- className
473
- ),
474
- "data-slot": "input",
475
- type,
476
- ...props
477
- }
478
- );
479
- }
480
-
481
- // src/components/ui/popover.tsx
482
- import { Popover as PopoverPrimitive } from "radix-ui";
483
- import { jsx as jsx8 } from "react/jsx-runtime";
484
- function Popover({
485
- ...props
486
- }) {
487
- return /* @__PURE__ */ jsx8(PopoverPrimitive.Root, { "data-slot": "popover", ...props });
488
- }
489
- function PopoverTrigger({
490
- ...props
491
- }) {
492
- return /* @__PURE__ */ jsx8(PopoverPrimitive.Trigger, { "data-slot": "popover-trigger", ...props });
493
- }
494
- function PopoverContent({
495
- className,
496
- align = "center",
497
- sideOffset = 4,
498
- ...props
499
- }) {
500
- return /* @__PURE__ */ jsx8(PopoverPrimitive.Portal, { children: /* @__PURE__ */ jsx8(
501
- PopoverPrimitive.Content,
502
- {
503
- align,
504
- className: cn(
505
- "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",
506
- className
507
- ),
508
- "data-slot": "popover-content",
509
- sideOffset,
510
- ...props
511
- }
512
- ) });
513
- }
514
-
515
- // src/components/ui/separator.tsx
516
- import { Separator as SeparatorPrimitive } from "radix-ui";
517
- import { jsx as jsx9 } from "react/jsx-runtime";
518
- function Separator({
519
- className,
520
- orientation = "horizontal",
521
- decorative = true,
522
- ...props
523
- }) {
524
- return /* @__PURE__ */ jsx9(
525
- SeparatorPrimitive.Root,
526
- {
527
- className: cn(
528
- "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",
529
- className
530
- ),
531
- "data-slot": "separator",
532
- decorative,
533
- orientation,
534
- ...props
535
- }
536
- );
537
- }
538
-
539
- // src/components/link-popover/link-popover.tsx
540
- import { jsx as jsx10, jsxs as jsxs4 } from "react/jsx-runtime";
541
- var LinkPopover = forwardRef5(
542
- ({ editor, ...buttonProps }, ref) => {
543
- const [isOpen, setIsOpen] = useState5(false);
544
- const { url, setUrl, isActive, canSet, setLink, removeLink, openLink } = useLinkPopover({ editor });
545
- useEffect5(() => {
546
- if (isActive) {
547
- setIsOpen(true);
548
- }
549
- }, [isActive]);
550
- const handleSetLink = useCallback5(() => {
551
- setLink();
552
- setIsOpen(false);
553
- }, [setLink]);
554
- const handleRemoveLink = useCallback5(() => {
555
- removeLink();
556
- setIsOpen(false);
557
- }, [removeLink]);
558
- const handleKeyDown = useCallback5(
559
- (event) => {
560
- if (event.key === "Enter") {
561
- event.preventDefault();
562
- handleSetLink();
563
- }
564
- },
565
- [handleSetLink]
566
- );
567
- return /* @__PURE__ */ jsxs4(Popover, { open: isOpen, onOpenChange: setIsOpen, children: [
568
- /* @__PURE__ */ jsx10(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx10(
569
- Button,
570
- {
571
- ref,
572
- "aria-label": "Link",
573
- "aria-pressed": isActive,
574
- "data-active-state": isActive ? "on" : "off",
575
- disabled: !canSet,
576
- size: "icon",
577
- tabIndex: -1,
578
- type: "button",
579
- variant: "ghost",
580
- ...buttonProps,
581
- children: /* @__PURE__ */ jsx10(
582
- LinkIcon,
583
- {
584
- className: isActive ? "nt:text-[var(--tt-brand-color-500)]" : void 0
585
- }
586
- )
587
- }
588
- ) }),
589
- /* @__PURE__ */ jsxs4(
590
- PopoverContent,
591
- {
592
- align: "start",
593
- className: "nt:flex nt:w-auto nt:items-center nt:gap-1 nt:p-1",
594
- children: [
595
- /* @__PURE__ */ jsx10(
596
- Input,
597
- {
598
- autoFocus: true,
599
- className: "nt:h-7 nt:min-w-48 nt:border-none nt:shadow-none nt:focus-visible:ring-0",
600
- placeholder: "Paste a link...",
601
- type: "url",
602
- value: url,
603
- onChange: (e) => setUrl(e.target.value),
604
- onKeyDown: handleKeyDown
605
- }
606
- ),
607
- /* @__PURE__ */ jsx10(
608
- Button,
609
- {
610
- "aria-label": "Apply link",
611
- disabled: !url && !isActive,
612
- size: "icon-sm",
613
- tabIndex: -1,
614
- type: "button",
615
- variant: "ghost",
616
- onClick: handleSetLink,
617
- children: /* @__PURE__ */ jsx10(CornerDownLeft, {})
618
- }
619
- ),
620
- /* @__PURE__ */ jsx10(Separator, { className: "nt:h-5", orientation: "vertical" }),
621
- /* @__PURE__ */ jsx10(
622
- Button,
623
- {
624
- "aria-label": "Open link in new window",
625
- size: "icon-sm",
626
- tabIndex: -1,
627
- type: "button",
628
- variant: "ghost",
629
- onClick: openLink,
630
- children: /* @__PURE__ */ jsx10(ExternalLink, {})
631
- }
632
- ),
633
- /* @__PURE__ */ jsx10(
634
- Button,
635
- {
636
- "aria-label": "Remove link",
637
- size: "icon-sm",
638
- tabIndex: -1,
639
- type: "button",
640
- variant: "ghost",
641
- onClick: handleRemoveLink,
642
- children: /* @__PURE__ */ jsx10(Trash2, {})
643
- }
644
- )
645
- ]
646
- }
647
- )
648
- ] });
649
- }
650
- );
651
- LinkPopover.displayName = "LinkPopover";
652
-
653
- // src/components/list-dropdown-menu/list-dropdown-menu.tsx
654
- import { ChevronDown as ChevronDown2 } from "lucide-react";
655
- import { forwardRef as forwardRef7 } from "react";
656
-
657
- // src/components/list-dropdown-menu/list-menu-item.tsx
658
- import { forwardRef as forwardRef6 } from "react";
659
-
660
- // src/components/list-dropdown-menu/use-list.ts
661
- import { List, ListOrdered, ListTodo } from "lucide-react";
662
- import { useCallback as useCallback6, useEffect as useEffect6, useState as useState6 } from "react";
663
- var listIcons = {
664
- bulletList: List,
665
- orderedList: ListOrdered,
666
- taskList: ListTodo
667
- };
668
- var listLabels = {
669
- bulletList: "Bullet List",
670
- orderedList: "Ordered List",
671
- taskList: "Task List"
672
- };
673
- var listItemTypes = {
674
- bulletList: "listItem",
675
- orderedList: "listItem",
676
- taskList: "taskItem"
677
- };
678
- function canToggleList(editor) {
679
- if (!editor || !editor.isEditable) return false;
680
- return editor.can().toggleList("bulletList", "listItem") || editor.can().clearNodes();
681
- }
682
- function useList({
683
- editor,
684
- type
685
- }) {
686
- const [isActive, setIsActive] = useState6(false);
687
- const [canToggle, setCanToggle] = useState6(false);
688
- useEffect6(() => {
689
- if (!editor) return;
690
- const handleUpdate = () => {
691
- setIsActive(editor.isActive(type));
692
- setCanToggle(canToggleList(editor));
693
- };
694
- handleUpdate();
695
- editor.on("selectionUpdate", handleUpdate);
696
- editor.on("transaction", handleUpdate);
697
- return () => {
698
- editor.off("selectionUpdate", handleUpdate);
699
- editor.off("transaction", handleUpdate);
700
- };
701
- }, [editor, type]);
702
- const handleToggle = useCallback6(() => {
703
- if (!editor || !editor.isEditable) return false;
704
- const itemType = listItemTypes[type];
705
- if (editor.isActive(type)) {
706
- return editor.chain().focus().clearNodes().run();
707
- }
708
- return editor.chain().focus().clearNodes().toggleList(type, itemType).run();
709
- }, [editor, type]);
710
- return {
711
- isActive,
712
- canToggle,
713
- handleToggle,
714
- label: listLabels[type],
715
- Icon: listIcons[type]
716
- };
717
- }
718
- function useActiveListType(editor, types) {
719
- const [activeType, setActiveType] = useState6(null);
720
- useEffect6(() => {
721
- if (!editor) return;
722
- const handleUpdate = () => {
723
- const found = types.find((type) => editor.isActive(type));
724
- setActiveType(found ?? null);
725
- };
726
- handleUpdate();
727
- editor.on("selectionUpdate", handleUpdate);
728
- editor.on("transaction", handleUpdate);
729
- return () => {
730
- editor.off("selectionUpdate", handleUpdate);
731
- editor.off("transaction", handleUpdate);
732
- };
733
- }, [editor, types]);
734
- return activeType;
735
- }
736
- function getListTriggerIcon(activeType) {
737
- if (activeType === null) return List;
738
- return listIcons[activeType];
739
- }
740
-
741
- // src/components/list-dropdown-menu/list-menu-item.tsx
742
- import { jsx as jsx11, jsxs as jsxs5 } from "react/jsx-runtime";
743
- var ListMenuItem = forwardRef6(
744
- ({ editor, listType }, ref) => {
745
- const { isActive, canToggle, handleToggle, label, Icon } = useList({
746
- editor,
747
- type: listType
748
- });
749
- return /* @__PURE__ */ jsxs5(
750
- DropdownMenuItem,
751
- {
752
- ref,
753
- "aria-label": label,
754
- className: "nt:data-[active-state=on]:bg-accent nt:data-[active-state=on]:text-[var(--tt-brand-color-500)]",
755
- "data-active-state": isActive ? "on" : "off",
756
- disabled: !canToggle,
757
- onSelect: handleToggle,
758
- children: [
759
- /* @__PURE__ */ jsx11(Icon, {}),
760
- /* @__PURE__ */ jsx11("span", { children: label })
761
- ]
762
- }
763
- );
764
- }
765
- );
766
- ListMenuItem.displayName = "ListMenuItem";
767
-
768
- // src/components/list-dropdown-menu/list-dropdown-menu.tsx
769
- import { jsx as jsx12, jsxs as jsxs6 } from "react/jsx-runtime";
770
- var ListDropdownMenu = forwardRef7(
771
- ({
772
- editor,
773
- types = ["bulletList", "orderedList", "taskList"],
774
- ...buttonProps
775
- }, ref) => {
776
- const activeType = useActiveListType(editor, types);
777
- const TriggerIcon = getListTriggerIcon(activeType);
778
- return /* @__PURE__ */ jsxs6(DropdownMenu, { children: [
779
- /* @__PURE__ */ jsx12(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs6(
780
- Button,
781
- {
782
- ref,
783
- "aria-label": "List",
784
- className: "nt:gap-1 nt:px-2",
785
- "data-active-state": activeType !== null ? "on" : "off",
786
- size: "default",
787
- tabIndex: -1,
788
- type: "button",
789
- variant: "ghost",
790
- ...buttonProps,
791
- children: [
792
- /* @__PURE__ */ jsx12(
793
- TriggerIcon,
794
- {
795
- className: activeType !== null ? "nt:text-[var(--tt-brand-color-500)]" : void 0
796
- }
797
- ),
798
- /* @__PURE__ */ jsx12(ChevronDown2, { className: "nt:size-3" })
799
- ]
800
- }
801
- ) }),
802
- /* @__PURE__ */ jsx12(DropdownMenuContent, { align: "start", children: /* @__PURE__ */ jsx12(DropdownMenuGroup, { children: types.map((type) => /* @__PURE__ */ jsx12(ListMenuItem, { editor, listType: type }, type)) }) })
803
- ] });
804
- }
805
- );
806
- ListDropdownMenu.displayName = "ListDropdownMenu";
807
-
808
- // src/components/mark-button/mark-button.tsx
809
- import { forwardRef as forwardRef8, useCallback as useCallback8 } from "react";
810
-
811
- // src/components/mark-button/use-mark.ts
812
- import { Bold, Code, Italic, Strikethrough } from "lucide-react";
813
- import { useCallback as useCallback7, useEffect as useEffect7, useState as useState7 } from "react";
814
- var markLabels = {
815
- bold: "Bold",
816
- italic: "Italic",
817
- strike: "Strikethrough",
818
- code: "Code"
819
- };
820
- var markIcons = {
821
- bold: Bold,
822
- italic: Italic,
823
- strike: Strikethrough,
824
- code: Code
825
- };
826
- function useMark({ editor, type }) {
827
- const [isActive, setIsActive] = useState7(false);
828
- const [canToggle, setCanToggle] = useState7(false);
829
- useEffect7(() => {
830
- if (!editor) return;
831
- const handleUpdate = () => {
832
- setIsActive(editor.isActive(type));
833
- setCanToggle(editor.isEditable && editor.can().toggleMark(type));
834
- };
835
- handleUpdate();
836
- editor.on("selectionUpdate", handleUpdate);
837
- editor.on("transaction", handleUpdate);
838
- return () => {
839
- editor.off("selectionUpdate", handleUpdate);
840
- editor.off("transaction", handleUpdate);
841
- };
842
- }, [editor, type]);
843
- const handleToggle = useCallback7(() => {
844
- if (!editor || !editor.isEditable) return false;
845
- return editor.chain().focus().toggleMark(type).run();
846
- }, [editor, type]);
847
- return {
848
- isActive,
849
- canToggle,
850
- handleToggle,
851
- label: markLabels[type],
852
- Icon: markIcons[type]
853
- };
854
- }
855
-
856
- // src/components/mark-button/mark-button.tsx
857
- import { jsx as jsx13 } from "react/jsx-runtime";
858
- var MarkButton = forwardRef8(
859
- ({ editor, type, onClick, ...buttonProps }, ref) => {
860
- const { isActive, canToggle, handleToggle, label, Icon } = useMark({
861
- editor,
862
- type
863
- });
864
- const handleClick = useCallback8(
865
- (event) => {
866
- onClick?.(event);
867
- if (event.defaultPrevented) return;
868
- handleToggle();
869
- },
870
- [handleToggle, onClick]
871
- );
872
- return /* @__PURE__ */ jsx13(
873
- Button,
874
- {
875
- ref,
876
- "aria-label": label,
877
- "aria-pressed": isActive,
878
- "data-active-state": isActive ? "on" : "off",
879
- disabled: !canToggle,
880
- size: "icon",
881
- tabIndex: -1,
882
- type: "button",
883
- variant: "ghost",
884
- onClick: handleClick,
885
- ...buttonProps,
886
- children: /* @__PURE__ */ jsx13(
887
- Icon,
888
- {
889
- className: isActive ? "nt:text-[var(--tt-brand-color-500)]" : void 0
890
- }
891
- )
892
- }
893
- );
894
- }
895
- );
896
- MarkButton.displayName = "MarkButton";
897
-
898
- // src/components/toolbar/toolbar.tsx
899
- import { forwardRef as forwardRef9 } from "react";
900
- import { jsx as jsx14 } from "react/jsx-runtime";
901
- var Toolbar = forwardRef9(
902
- ({ children, className, variant = "fixed", ...props }, ref) => {
903
- const classNames = ["tiptap-toolbar", className].filter(Boolean).join(" ");
904
- return /* @__PURE__ */ jsx14(
905
- "div",
906
- {
907
- ref,
908
- "aria-label": "toolbar",
909
- className: classNames,
910
- "data-variant": variant,
911
- role: "toolbar",
912
- ...props,
913
- children
914
- }
915
- );
916
- }
917
- );
918
- Toolbar.displayName = "Toolbar";
919
- function ToolbarGroup({
920
- children,
921
- className,
922
- ...props
923
- }) {
924
- const classNames = ["tiptap-toolbar-group", className].filter(Boolean).join(" ");
925
- return /* @__PURE__ */ jsx14("div", { className: classNames, role: "group", ...props, children });
926
- }
927
- function ToolbarSeparator({
928
- orientation = "vertical",
929
- className,
930
- ...props
931
- }) {
932
- const classNames = ["tiptap-separator", className].filter(Boolean).join(" ");
933
- return /* @__PURE__ */ jsx14(
934
- "div",
935
- {
936
- "aria-orientation": orientation === "vertical" ? orientation : void 0,
937
- className: classNames,
938
- "data-orientation": orientation,
939
- role: "separator",
940
- ...props
941
- }
942
- );
943
- }
944
-
945
- // src/components/ui-primitive/spacer.tsx
946
- import { jsx as jsx15 } from "react/jsx-runtime";
947
- function Spacer() {
948
- return /* @__PURE__ */ jsx15("div", { style: { flex: 1 } });
949
- }
950
-
951
- // src/components/undo-redo-button/undo-redo-button.tsx
952
- import { forwardRef as forwardRef10, useCallback as useCallback10 } from "react";
953
-
954
- // src/components/undo-redo-button/use-undo-redo.ts
955
- import { Undo2, Redo2 } from "lucide-react";
956
- import { useCallback as useCallback9, useEffect as useEffect8, useState as useState8 } from "react";
957
- var actionLabels = {
958
- undo: "Undo",
959
- redo: "Redo"
960
- };
961
- var actionIcons = {
962
- undo: Undo2,
963
- redo: Redo2
964
- };
965
- function canExecuteAction(editor, action) {
966
- if (!editor || !editor.isEditable) return false;
967
- return action === "undo" ? editor.can().undo() : editor.can().redo();
968
- }
969
- function useUndoRedo({ editor, action }) {
970
- const [canExecute, setCanExecute] = useState8(false);
971
- useEffect8(() => {
972
- if (!editor) return;
973
- const handleUpdate = () => {
974
- setCanExecute(canExecuteAction(editor, action));
975
- };
976
- handleUpdate();
977
- editor.on("transaction", handleUpdate);
978
- return () => {
979
- editor.off("transaction", handleUpdate);
980
- };
981
- }, [editor, action]);
982
- const handleAction = useCallback9(() => {
983
- if (!editor || !editor.isEditable) return false;
984
- if (!canExecuteAction(editor, action)) return false;
985
- const chain = editor.chain().focus();
986
- return action === "undo" ? chain.undo().run() : chain.redo().run();
987
- }, [editor, action]);
988
- return {
989
- canExecute,
990
- handleAction,
991
- label: actionLabels[action],
992
- Icon: actionIcons[action]
993
- };
994
- }
995
-
996
- // src/components/undo-redo-button/undo-redo-button.tsx
997
- import { jsx as jsx16 } from "react/jsx-runtime";
998
- var UndoRedoButton = forwardRef10(({ editor, action, onClick, ...buttonProps }, ref) => {
999
- const { canExecute, handleAction, label, Icon } = useUndoRedo({
1000
- editor,
1001
- action
1002
- });
1003
- const handleClick = useCallback10(
1004
- (event) => {
1005
- onClick?.(event);
1006
- if (event.defaultPrevented) return;
1007
- handleAction();
1008
- },
1009
- [handleAction, onClick]
1010
- );
1011
- return /* @__PURE__ */ jsx16(
1012
- Button,
1013
- {
1014
- ref,
1015
- "aria-label": label,
1016
- disabled: !canExecute,
1017
- size: "icon",
1018
- tabIndex: -1,
1019
- type: "button",
1020
- variant: "ghost",
1021
- onClick: handleClick,
1022
- ...buttonProps,
1023
- children: /* @__PURE__ */ jsx16(Icon, {})
1024
- }
1025
- );
1026
- });
1027
- UndoRedoButton.displayName = "UndoRedoButton";
1028
-
1029
- // src/hooks/use-markdown-editor.ts
1030
- import { EditorState } from "@tiptap/pm/state";
1031
- import { useEditor } from "@tiptap/react";
1032
- import { useEffect as useEffect9, useRef } from "react";
1033
-
1034
- // src/extensions/shared.ts
1035
- import { ListKit } from "@tiptap/extension-list";
1036
- import StarterKit from "@tiptap/starter-kit";
1037
- var starterKitBaseConfig = {
1038
- heading: { levels: [1, 2, 3, 4, 5, 6] },
1039
- link: {
1040
- openOnClick: false,
1041
- autolink: true
1042
- },
1043
- // Disable StarterKit's built-in list handling; use @tiptap/extension-list instead
1044
- bulletList: false,
1045
- orderedList: false,
1046
- listItem: false,
1047
- listKeymap: false
1048
- };
1049
- var sharedExtensions = [
1050
- StarterKit.configure({
1051
- ...starterKitBaseConfig,
1052
- dropcursor: false,
1053
- gapcursor: false,
1054
- undoRedo: false,
1055
- trailingNode: false
1056
- }),
1057
- ListKit
1058
- ];
1059
-
1060
- // src/extensions/editor.ts
1061
- import { ListKit as ListKit2 } from "@tiptap/extension-list";
1062
- import StarterKit2 from "@tiptap/starter-kit";
1063
- import { Markdown } from "tiptap-markdown";
1064
- var editorExtensions = [
1065
- StarterKit2.configure(starterKitBaseConfig),
1066
- ListKit2,
1067
- Markdown.configure({
1068
- html: false,
1069
- transformPastedText: true,
1070
- transformCopiedText: true
1071
- })
1072
- ];
1073
-
1074
- // src/hooks/use-markdown-editor.ts
1075
- function getMarkdown(storage) {
1076
- return storage.markdown.getMarkdown();
1077
- }
1078
- function useMarkdownEditor({
1079
- value,
1080
- onChange,
1081
- editable = true
1082
- }) {
1083
- const externalValue = useRef(value);
1084
- const onChangeRef = useRef(onChange);
1085
- onChangeRef.current = onChange;
1086
- const editor = useEditor({
1087
- extensions: editorExtensions,
1088
- editable,
1089
- content: value,
1090
- onUpdate({ editor: editor2 }) {
1091
- const md = getMarkdown(
1092
- editor2.storage
1093
- );
1094
- externalValue.current = md;
1095
- onChangeRef.current(md);
1096
- },
1097
- onCreate({ editor: editor2 }) {
1098
- setTimeout(() => {
1099
- if (editor2.isDestroyed) return;
1100
- const { state } = editor2;
1101
- const freshState = EditorState.create({
1102
- doc: state.doc,
1103
- selection: state.selection,
1104
- plugins: state.plugins
1105
- });
1106
- editor2.view.updateState(freshState);
1107
- editor2.view.dispatch(editor2.view.state.tr);
1108
- }, 0);
1109
- }
1110
- });
1111
- useEffect9(() => {
1112
- if (!editor) return;
1113
- if (value === externalValue.current) return;
1114
- externalValue.current = value;
1115
- editor.commands.setContent(value);
1116
- }, [value, editor]);
1117
- useEffect9(() => {
1118
- if (!editor) return;
1119
- editor.setEditable(editable);
1120
- }, [editable, editor]);
1121
- return { editor };
1122
- }
1123
-
1124
- // src/notra-editor.tsx
1125
- import { jsx as jsx17, jsxs as jsxs7 } from "react/jsx-runtime";
1126
- function NotraEditor({
1127
- value,
1128
- onChange,
1129
- placeholder,
1130
- readOnly = false,
1131
- className
1132
- }) {
1133
- const { editor } = useMarkdownEditor({
1134
- value,
1135
- onChange,
1136
- placeholder,
1137
- editable: !readOnly
1138
- });
1139
- const classNames = ["notra", "notra-editor", className].filter(Boolean).join(" ");
1140
- return /* @__PURE__ */ jsxs7("div", { className: classNames, children: [
1141
- /* @__PURE__ */ jsxs7(Toolbar, { variant: "fixed", children: [
1142
- /* @__PURE__ */ jsx17(Spacer, {}),
1143
- /* @__PURE__ */ jsxs7(ToolbarGroup, { children: [
1144
- /* @__PURE__ */ jsx17(UndoRedoButton, { action: "undo", editor }),
1145
- /* @__PURE__ */ jsx17(UndoRedoButton, { action: "redo", editor })
1146
- ] }),
1147
- /* @__PURE__ */ jsx17(ToolbarSeparator, {}),
1148
- /* @__PURE__ */ jsxs7(ToolbarGroup, { children: [
1149
- /* @__PURE__ */ jsx17(HeadingDropdownMenu, { editor, levels: [1, 2, 3, 4] }),
1150
- /* @__PURE__ */ jsx17(
1151
- ListDropdownMenu,
1152
- {
1153
- editor,
1154
- types: ["bulletList", "orderedList", "taskList"]
1155
- }
1156
- ),
1157
- /* @__PURE__ */ jsx17(BlockquoteButton, { editor }),
1158
- /* @__PURE__ */ jsx17(CodeBlockButton, { editor })
1159
- ] }),
1160
- /* @__PURE__ */ jsx17(ToolbarSeparator, {}),
1161
- /* @__PURE__ */ jsxs7(ToolbarGroup, { children: [
1162
- /* @__PURE__ */ jsx17(MarkButton, { editor, type: "bold" }),
1163
- /* @__PURE__ */ jsx17(MarkButton, { editor, type: "italic" }),
1164
- /* @__PURE__ */ jsx17(MarkButton, { editor, type: "strike" }),
1165
- /* @__PURE__ */ jsx17(MarkButton, { editor, type: "code" }),
1166
- /* @__PURE__ */ jsx17(LinkPopover, { editor })
1167
- ] }),
1168
- /* @__PURE__ */ jsx17(Spacer, {})
1169
- ] }),
1170
- /* @__PURE__ */ jsx17(EditorContent, { className: "notra-editor-content", editor })
1171
- ] });
1172
- }
1173
-
1174
- // src/notra-reader.tsx
1175
- import { renderToReactElement } from "@tiptap/static-renderer/pm/react";
1176
-
1177
- // src/utils/markdown-to-json.ts
1178
- import { Editor } from "@tiptap/core";
1179
- import { Markdown as Markdown2 } from "tiptap-markdown";
1180
- var parserExtensions = [
1181
- ...sharedExtensions,
1182
- Markdown2.configure({ html: false })
1183
- ];
1184
- var parserEditor = null;
1185
- function getParserEditor() {
1186
- if (!parserEditor) {
1187
- parserEditor = new Editor({
1188
- extensions: parserExtensions,
1189
- content: ""
1190
- });
1191
- }
1192
- return parserEditor;
1193
- }
1194
- function markdownToJSON(markdown) {
1195
- const editor = getParserEditor();
1196
- editor.commands.setContent(markdown);
1197
- return editor.getJSON();
1198
- }
1199
-
1200
- // src/notra-reader.tsx
1201
- import { jsx as jsx18 } from "react/jsx-runtime";
1202
- function NotraReader({ content, className }) {
1203
- const json = markdownToJSON(content);
1204
- const rendered = renderToReactElement({
1205
- extensions: sharedExtensions,
1206
- content: json
1207
- });
1208
- const classNames = ["notra", "notra-reader", className].filter(Boolean).join(" ");
1209
- return /* @__PURE__ */ jsx18("div", { className: classNames, children: rendered });
1210
- }
1211
-
1212
- // src/components/ui-primitive/dropdown-menu.tsx
1213
- import { useEffect as useEffect10, useRef as useRef2, useState as useState9 } from "react";
1214
- import { createPortal } from "react-dom";
1215
- import { Fragment, jsx as jsx19, jsxs as jsxs8 } from "react/jsx-runtime";
1216
- function DropdownMenu2({
1217
- trigger,
1218
- children,
1219
- open: controlledOpen,
1220
- onOpenChange
1221
- }) {
1222
- const isControlled = controlledOpen !== void 0;
1223
- const [uncontrolledOpen, setUncontrolledOpen] = useState9(false);
1224
- const open = isControlled ? controlledOpen : uncontrolledOpen;
1225
- const triggerRef = useRef2(null);
1226
- const contentRef = useRef2(null);
1227
- const [position, setPosition] = useState9({ top: 0, left: 0 });
1228
- const setOpen = (value) => {
1229
- if (!isControlled) {
1230
- setUncontrolledOpen(value);
1231
- }
1232
- onOpenChange?.(value);
1233
- };
1234
- useEffect10(() => {
1235
- if (!open || !triggerRef.current) return;
1236
- const updatePosition = () => {
1237
- if (!triggerRef.current) return;
1238
- const rect = triggerRef.current.getBoundingClientRect();
1239
- setPosition({
1240
- top: rect.bottom + 4,
1241
- left: rect.left + rect.width / 2
1242
- });
1243
- };
1244
- updatePosition();
1245
- window.addEventListener("scroll", updatePosition, true);
1246
- window.addEventListener("resize", updatePosition);
1247
- return () => {
1248
- window.removeEventListener("scroll", updatePosition, true);
1249
- window.removeEventListener("resize", updatePosition);
1250
- };
1251
- }, [open]);
1252
- useEffect10(() => {
1253
- if (!open) return;
1254
- const handleMouseDown = (event) => {
1255
- const target = event.target;
1256
- if (triggerRef.current?.contains(target) || contentRef.current?.contains(target)) {
1257
- return;
1258
- }
1259
- setOpen(false);
1260
- };
1261
- document.addEventListener("mousedown", handleMouseDown);
1262
- return () => document.removeEventListener("mousedown", handleMouseDown);
1263
- }, [open]);
1264
- useEffect10(() => {
1265
- if (!open) return;
1266
- const handleKeyDown = (event) => {
1267
- if (event.key === "Escape") {
1268
- setOpen(false);
1269
- }
1270
- };
1271
- document.addEventListener("keydown", handleKeyDown);
1272
- return () => document.removeEventListener("keydown", handleKeyDown);
1273
- }, [open]);
1274
- return /* @__PURE__ */ jsxs8(Fragment, { children: [
1275
- /* @__PURE__ */ jsx19("div", { ref: triggerRef, onClick: () => setOpen(!open), children: trigger }),
1276
- open && createPortal(
1277
- /* @__PURE__ */ jsx19("div", { className: "notra-editor", children: /* @__PURE__ */ jsx19(
1278
- "div",
1279
- {
1280
- ref: contentRef,
1281
- className: "tiptap-dropdown-menu-content",
1282
- "data-state": "open",
1283
- role: "menu",
1284
- style: {
1285
- position: "fixed",
1286
- top: position.top,
1287
- left: position.left
1288
- },
1289
- children: /* @__PURE__ */ jsx19(
1290
- "div",
1291
- {
1292
- className: "tiptap-dropdown-menu-group",
1293
- onClick: () => setOpen(false),
1294
- children
1295
- }
1296
- )
1297
- }
1298
- ) }),
1299
- document.body
1300
- )
1301
- ] });
1302
- }
5
+ Toolbar,
6
+ ToolbarGroup,
7
+ ToolbarSeparator
8
+ } from "./components/toolbar/toolbar";
9
+ import { UndoRedoButton } from "./components/undo-redo-button/undo-redo-button";
10
+ import { Spacer } from "./components/ui/spacer";
11
+ import { MarkButton } from "./components/mark-button/mark-button";
12
+ import { HeadingDropdownMenu } from "./components/heading-dropdown-menu/heading-dropdown-menu";
13
+ import { ListDropdownMenu } from "./components/list-dropdown-menu/list-dropdown-menu";
14
+ import { BlockquoteButton } from "./components/blockquote-button/blockquote-button";
15
+ import { CodeBlockButton } from "./components/code-block-button/code-block-button";
16
+ import { LinkPopover } from "./components/link-popover/link-popover";
1303
17
  export {
1304
18
  BlockquoteButton,
1305
19
  CodeBlockButton,
1306
- DropdownMenu2 as DropdownMenu,
1307
20
  HeadingDropdownMenu,
1308
21
  LinkPopover,
1309
22
  ListDropdownMenu,