quoin-editor 0.4.2 → 1.0.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 (231) hide show
  1. package/CHANGELOG.md +205 -0
  2. package/MIGRATION.md +233 -0
  3. package/README.md +78 -716
  4. package/dist/ai.cjs +1107 -0
  5. package/dist/ai.cjs.map +1 -0
  6. package/dist/ai.d.cts +68 -0
  7. package/dist/ai.d.ts +68 -0
  8. package/dist/ai.js +1107 -0
  9. package/dist/ai.js.map +1 -0
  10. package/dist/catalogue.cjs +59 -0
  11. package/dist/catalogue.cjs.map +1 -0
  12. package/dist/catalogue.d.cts +39 -0
  13. package/dist/catalogue.d.ts +39 -0
  14. package/dist/catalogue.js +59 -0
  15. package/dist/catalogue.js.map +1 -0
  16. package/dist/chunk-2QCRZ7OL.cjs +319 -0
  17. package/dist/chunk-2QCRZ7OL.cjs.map +1 -0
  18. package/dist/chunk-2VDK27IF.cjs +6666 -0
  19. package/dist/chunk-2VDK27IF.cjs.map +1 -0
  20. package/dist/chunk-47MLKSI2.js +326 -0
  21. package/dist/chunk-47MLKSI2.js.map +1 -0
  22. package/dist/chunk-5DNCCN6X.cjs +326 -0
  23. package/dist/chunk-5DNCCN6X.cjs.map +1 -0
  24. package/dist/chunk-AEJXZBVR.cjs +1378 -0
  25. package/dist/chunk-AEJXZBVR.cjs.map +1 -0
  26. package/dist/chunk-CDELOHDY.cjs +239 -0
  27. package/dist/chunk-CDELOHDY.cjs.map +1 -0
  28. package/dist/chunk-E75EHRPA.cjs +175 -0
  29. package/dist/chunk-E75EHRPA.cjs.map +1 -0
  30. package/dist/chunk-EL2OEJQC.cjs +562 -0
  31. package/dist/chunk-EL2OEJQC.cjs.map +1 -0
  32. package/dist/chunk-GLLXHMH2.cjs +25 -0
  33. package/dist/chunk-GLLXHMH2.cjs.map +1 -0
  34. package/dist/chunk-GTTPVJSD.cjs +3286 -0
  35. package/dist/chunk-GTTPVJSD.cjs.map +1 -0
  36. package/dist/chunk-HELJTLNC.cjs +100 -0
  37. package/dist/chunk-HELJTLNC.cjs.map +1 -0
  38. package/dist/chunk-HJIQT3S5.js +4047 -0
  39. package/dist/chunk-HJIQT3S5.js.map +1 -0
  40. package/dist/chunk-HZSSEYJS.cjs +504 -0
  41. package/dist/chunk-HZSSEYJS.cjs.map +1 -0
  42. package/dist/chunk-JDPTZUBW.js +184 -0
  43. package/dist/chunk-JDPTZUBW.js.map +1 -0
  44. package/dist/chunk-JEIFJHEI.js +14 -0
  45. package/dist/chunk-JEIFJHEI.js.map +1 -0
  46. package/dist/chunk-K574XHMM.js +25 -0
  47. package/dist/chunk-K574XHMM.js.map +1 -0
  48. package/dist/chunk-KAVU6T6J.js +249 -0
  49. package/dist/chunk-KAVU6T6J.js.map +1 -0
  50. package/dist/chunk-KXOBZGQO.js +1378 -0
  51. package/dist/chunk-KXOBZGQO.js.map +1 -0
  52. package/dist/chunk-LCQSG4M6.js +3335 -0
  53. package/dist/chunk-LCQSG4M6.js.map +1 -0
  54. package/dist/chunk-MQ2XQ4MX.js +100 -0
  55. package/dist/chunk-MQ2XQ4MX.js.map +1 -0
  56. package/dist/chunk-ND5XJVK2.cjs +3335 -0
  57. package/dist/chunk-ND5XJVK2.cjs.map +1 -0
  58. package/dist/chunk-NNDYYEW5.js +504 -0
  59. package/dist/chunk-NNDYYEW5.js.map +1 -0
  60. package/dist/chunk-NOKWNK2O.js +319 -0
  61. package/dist/chunk-NOKWNK2O.js.map +1 -0
  62. package/dist/chunk-OMOBMXD5.cjs +57 -0
  63. package/dist/chunk-OMOBMXD5.cjs.map +1 -0
  64. package/dist/chunk-PMWCQSQX.js +239 -0
  65. package/dist/chunk-PMWCQSQX.js.map +1 -0
  66. package/dist/chunk-POBM7MGR.js +30 -0
  67. package/dist/chunk-POBM7MGR.js.map +1 -0
  68. package/dist/chunk-PWUEPDA3.cjs +598 -0
  69. package/dist/chunk-PWUEPDA3.cjs.map +1 -0
  70. package/dist/chunk-PZ4GT5JO.cjs +4047 -0
  71. package/dist/chunk-PZ4GT5JO.cjs.map +1 -0
  72. package/dist/chunk-QAW5NH6S.cjs +14 -0
  73. package/dist/chunk-QAW5NH6S.cjs.map +1 -0
  74. package/dist/chunk-RDG4VI6E.cjs +706 -0
  75. package/dist/chunk-RDG4VI6E.cjs.map +1 -0
  76. package/dist/chunk-RUDWMKYX.js +562 -0
  77. package/dist/chunk-RUDWMKYX.js.map +1 -0
  78. package/dist/chunk-SG37UBDO.js +706 -0
  79. package/dist/chunk-SG37UBDO.js.map +1 -0
  80. package/dist/chunk-SLVDAHHK.cjs +30 -0
  81. package/dist/chunk-SLVDAHHK.cjs.map +1 -0
  82. package/dist/chunk-T7SW6OWV.js +598 -0
  83. package/dist/chunk-T7SW6OWV.js.map +1 -0
  84. package/dist/chunk-TC4DARU2.js +175 -0
  85. package/dist/chunk-TC4DARU2.js.map +1 -0
  86. package/dist/chunk-WQHV3MRE.cjs +184 -0
  87. package/dist/chunk-WQHV3MRE.cjs.map +1 -0
  88. package/dist/chunk-WXMYDXPN.cjs +33 -0
  89. package/dist/chunk-WXMYDXPN.cjs.map +1 -0
  90. package/dist/chunk-XEBWC7ZL.js +57 -0
  91. package/dist/chunk-XEBWC7ZL.js.map +1 -0
  92. package/dist/chunk-ZC24WB7Y.cjs +249 -0
  93. package/dist/chunk-ZC24WB7Y.cjs.map +1 -0
  94. package/dist/chunk-ZGZEJCRV.js +6666 -0
  95. package/dist/chunk-ZGZEJCRV.js.map +1 -0
  96. package/dist/chunk-ZHOSSWUM.js +33 -0
  97. package/dist/chunk-ZHOSSWUM.js.map +1 -0
  98. package/dist/chunk-ZS2WA357.js +3286 -0
  99. package/dist/chunk-ZS2WA357.js.map +1 -0
  100. package/dist/comments.cjs +993 -0
  101. package/dist/comments.cjs.map +1 -0
  102. package/dist/comments.d.cts +88 -0
  103. package/dist/comments.d.ts +88 -0
  104. package/dist/comments.js +993 -0
  105. package/dist/comments.js.map +1 -0
  106. package/dist/diff.cjs +343 -0
  107. package/dist/diff.cjs.map +1 -0
  108. package/dist/diff.d.cts +67 -0
  109. package/dist/diff.d.ts +67 -0
  110. package/dist/diff.js +343 -0
  111. package/dist/diff.js.map +1 -0
  112. package/dist/docx.cjs +854 -0
  113. package/dist/docx.cjs.map +1 -0
  114. package/dist/docx.d.cts +27 -0
  115. package/dist/docx.d.ts +27 -0
  116. package/dist/docx.js +854 -0
  117. package/dist/docx.js.map +1 -0
  118. package/dist/embed-provider-jXwlRmnp.d.cts +19 -0
  119. package/dist/embed-provider-jXwlRmnp.d.ts +19 -0
  120. package/dist/embed.cjs +17 -0
  121. package/dist/embed.cjs.map +1 -0
  122. package/dist/embed.d.cts +56 -0
  123. package/dist/embed.d.ts +56 -0
  124. package/dist/embed.js +17 -0
  125. package/dist/embed.js.map +1 -0
  126. package/dist/emoji-data.cjs +1968 -0
  127. package/dist/emoji-data.cjs.map +1 -0
  128. package/dist/emoji-data.js +1968 -0
  129. package/dist/emoji-data.js.map +1 -0
  130. package/dist/emoji-extension-C12gDPm6.d.cts +46 -0
  131. package/dist/emoji-extension-DpQ43HjE.d.ts +46 -0
  132. package/dist/emoji.cjs +18 -0
  133. package/dist/emoji.cjs.map +1 -0
  134. package/dist/emoji.d.cts +31 -0
  135. package/dist/emoji.d.ts +31 -0
  136. package/dist/emoji.js +18 -0
  137. package/dist/emoji.js.map +1 -0
  138. package/dist/engine.cjs +119 -0
  139. package/dist/engine.cjs.map +1 -0
  140. package/dist/engine.d.cts +144 -0
  141. package/dist/engine.d.ts +144 -0
  142. package/dist/engine.js +119 -0
  143. package/dist/engine.js.map +1 -0
  144. package/dist/find-extension-DKqLLSuP.d.ts +41 -0
  145. package/dist/find-extension-_sbZEt3R.d.cts +41 -0
  146. package/dist/find.cjs +20 -0
  147. package/dist/find.cjs.map +1 -0
  148. package/dist/find.d.cts +44 -0
  149. package/dist/find.d.ts +44 -0
  150. package/dist/find.js +20 -0
  151. package/dist/find.js.map +1 -0
  152. package/dist/headless.cjs +151 -518
  153. package/dist/headless.cjs.map +1 -1
  154. package/dist/headless.d.cts +102 -69
  155. package/dist/headless.d.ts +102 -69
  156. package/dist/headless.js +149 -467
  157. package/dist/headless.js.map +1 -1
  158. package/dist/index-CgD72kHB.d.cts +65 -0
  159. package/dist/index-DNYbTK3w.d.ts +65 -0
  160. package/dist/index.cjs +183 -2753
  161. package/dist/index.cjs.map +1 -1
  162. package/dist/index.d.cts +345 -253
  163. package/dist/index.d.ts +345 -253
  164. package/dist/index.js +168 -2696
  165. package/dist/index.js.map +1 -1
  166. package/dist/math.cjs +18 -0
  167. package/dist/math.cjs.map +1 -0
  168. package/dist/math.d.cts +49 -0
  169. package/dist/math.d.ts +49 -0
  170. package/dist/math.js +18 -0
  171. package/dist/math.js.map +1 -0
  172. package/dist/mention.cjs +18 -0
  173. package/dist/mention.cjs.map +1 -0
  174. package/dist/mention.d.cts +50 -0
  175. package/dist/mention.d.ts +50 -0
  176. package/dist/mention.js +18 -0
  177. package/dist/mention.js.map +1 -0
  178. package/dist/presence.cjs +565 -0
  179. package/dist/presence.cjs.map +1 -0
  180. package/dist/presence.d.cts +30 -0
  181. package/dist/presence.d.ts +30 -0
  182. package/dist/presence.js +565 -0
  183. package/dist/presence.js.map +1 -0
  184. package/dist/registry-CWBOSAHm.d.cts +121 -0
  185. package/dist/registry-CWBOSAHm.d.ts +121 -0
  186. package/dist/stats-extension-Bly88uio.d.ts +40 -0
  187. package/dist/stats-extension-CJPijvUb.d.cts +40 -0
  188. package/dist/stats.cjs +19 -0
  189. package/dist/stats.cjs.map +1 -0
  190. package/dist/stats.d.cts +19 -0
  191. package/dist/stats.d.ts +19 -0
  192. package/dist/stats.js +19 -0
  193. package/dist/stats.js.map +1 -0
  194. package/dist/styles.css +1 -1
  195. package/dist/table-extension-BTLxFy2S.d.cts +57 -0
  196. package/dist/table-extension-Dzf4k6hI.d.ts +57 -0
  197. package/dist/table.cjs +50 -0
  198. package/dist/table.cjs.map +1 -0
  199. package/dist/table.d.cts +57 -0
  200. package/dist/table.d.ts +57 -0
  201. package/dist/table.js +50 -0
  202. package/dist/table.js.map +1 -0
  203. package/dist/to-html-C5t7xyaM.d.ts +21 -0
  204. package/dist/to-html-CNCITCln.d.cts +21 -0
  205. package/dist/toc-extension-B3-s6h2x.d.cts +25 -0
  206. package/dist/toc-extension-DWm5alVj.d.ts +25 -0
  207. package/dist/toc.cjs +18 -0
  208. package/dist/toc.cjs.map +1 -0
  209. package/dist/toc.d.cts +24 -0
  210. package/dist/toc.d.ts +24 -0
  211. package/dist/toc.js +18 -0
  212. package/dist/toc.js.map +1 -0
  213. package/dist/types-Cz7XJLaK.d.cts +366 -0
  214. package/dist/types-DULc8wwg.d.cts +120 -0
  215. package/dist/types-DULc8wwg.d.ts +120 -0
  216. package/dist/types-a1CRBlEh.d.ts +366 -0
  217. package/dist/view-controller-DSHw0ltQ.d.cts +118 -0
  218. package/dist/view-controller-UbpzblGl.d.ts +118 -0
  219. package/dist/view.cjs +23 -0
  220. package/dist/view.cjs.map +1 -0
  221. package/dist/view.d.cts +40 -0
  222. package/dist/view.d.ts +40 -0
  223. package/dist/view.js +23 -0
  224. package/dist/view.js.map +1 -0
  225. package/dist/yjs.cjs +1026 -0
  226. package/dist/yjs.cjs.map +1 -0
  227. package/dist/yjs.d.cts +22 -0
  228. package/dist/yjs.d.ts +22 -0
  229. package/dist/yjs.js +1026 -0
  230. package/dist/yjs.js.map +1 -0
  231. package/package.json +217 -18
package/dist/index.js CHANGED
@@ -1,2712 +1,184 @@
1
- 'use client';
2
-
3
- // src/canvas/BlockCanvas.tsx
4
- import * as React15 from "react";
1
+ "use client";
5
2
  import {
6
- DndContext,
7
- KeyboardSensor,
8
- PointerSensor,
9
- closestCenter,
10
- useSensor,
11
- useSensors
12
- } from "@dnd-kit/core";
13
- import { restrictToVerticalAxis } from "@dnd-kit/modifiers";
14
- import { SortableContext, sortableKeyboardCoordinates, verticalListSortingStrategy } from "@dnd-kit/sortable";
15
-
16
- // src/context.tsx
17
- import * as React from "react";
18
- import { jsx } from "react/jsx-runtime";
19
- var QuoinContext = React.createContext(null);
20
- function QuoinProvider({
21
- value,
22
- children
23
- }) {
24
- const { blockTypes, upload, resolveAssetUrl, onError } = value;
25
- const config = React.useMemo(
26
- () => ({
27
- blockTypes,
28
- upload,
29
- // Identity default: an empty key resolves to an empty string, which stays falsy
30
- // wherever a resolved URL is tested for "no asset chosen yet".
31
- resolveAssetUrl: resolveAssetUrl ?? ((key) => key),
32
- onError: onError ?? ((error) => console.error(error))
33
- }),
34
- [blockTypes, upload, resolveAssetUrl, onError]
35
- );
36
- return /* @__PURE__ */ jsx(QuoinContext.Provider, { value: config, children });
37
- }
38
- function useQuoin() {
39
- const config = React.useContext(QuoinContext);
40
- if (!config) throw new Error("useQuoin must be used inside a QuoinProvider.");
41
- return config;
42
- }
43
-
44
- // src/registry.ts
45
- function defineBlock(definition) {
46
- return definition;
47
- }
48
- function blockByType(definitions, type) {
49
- for (let i = definitions.length - 1; i >= 0; i -= 1) {
50
- if (definitions[i].type === type) return definitions[i];
51
- }
52
- return void 0;
53
- }
54
- function paletteBlocks(definitions) {
55
- const seen2 = /* @__PURE__ */ new Set();
56
- const out = [];
57
- for (let i = definitions.length - 1; i >= 0; i -= 1) {
58
- const d = definitions[i];
59
- if (seen2.has(d.type)) continue;
60
- seen2.add(d.type);
61
- if (d.inSlashPalette === false) continue;
62
- out.push(d);
63
- }
64
- return out.reverse();
65
- }
66
-
67
- // src/blocks/core.ts
68
- import { Code as Code2, FileText, MessageSquare, Image, Paperclip } from "lucide-react";
69
-
70
- // src/blocks/MarkdownBlockEditor.tsx
71
- import * as React7 from "react";
72
-
73
- // src/upload/FilePickerButton.tsx
74
- import { Upload } from "lucide-react";
75
-
76
- // src/ui/button.tsx
77
- import * as React2 from "react";
78
- import { Slot } from "@radix-ui/react-slot";
79
- import { cva } from "class-variance-authority";
80
-
81
- // src/ui/cn.ts
82
- import { clsx } from "clsx";
83
- import { twMerge } from "tailwind-merge";
84
- function cn(...inputs) {
85
- return twMerge(clsx(inputs));
86
- }
87
-
88
- // src/ui/focus.ts
89
- var FOCUS_RING = [
90
- "outline-hidden",
91
- "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-solid",
92
- "focus-visible:outline-[var(--quoin-ring)]"
93
- ].join(" ");
94
- var HIGHLIGHT_BAR = "data-[highlighted]:shadow-[inset_2px_0_0_var(--quoin-foreground)]";
95
- var SELECTED_BAR = "data-[selected=true]:shadow-[inset_2px_0_0_var(--quoin-foreground)]";
96
-
97
- // src/ui/touch-target.ts
98
- var TAP_TARGET_FLOOR = "pointer-coarse:min-h-11";
99
- var SQUARE_TAP_TARGET_FLOOR = `${TAP_TARGET_FLOOR} pointer-coarse:min-w-11`;
100
- var ICON_TAP_TARGET_FLOOR = [
101
- "pointer-coarse:before:absolute pointer-coarse:before:top-1/2 pointer-coarse:before:left-1/2",
102
- "pointer-coarse:before:size-11 pointer-coarse:before:-translate-x-1/2 pointer-coarse:before:-translate-y-1/2",
103
- "pointer-coarse:before:content-['']"
104
- ].join(" ");
105
-
106
- // src/ui/button.tsx
107
- import { jsx as jsx2 } from "react/jsx-runtime";
108
- var buttonVariants = cva(
109
- [
110
- "inline-flex cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-[color,background-color,border-color,box-shadow,opacity] disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 shrink-0",
111
- FOCUS_RING
112
- ],
113
- {
114
- variants: {
115
- variant: {
116
- default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
117
- destructive: "bg-destructive text-white shadow-xs hover:bg-destructive/90 dark:bg-destructive/60 dark:hover:bg-destructive/75",
118
- outline: "border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground",
119
- secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
120
- ghost: "hover:bg-accent hover:text-accent-foreground",
121
- link: "text-primary underline-offset-4 hover:underline"
122
- },
123
- size: {
124
- default: `h-9 px-4 py-2 has-[>svg]:px-3 ${TAP_TARGET_FLOOR}`,
125
- xs: `h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3 ${TAP_TARGET_FLOOR}`,
126
- sm: `h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5 ${TAP_TARGET_FLOOR}`,
127
- lg: `h-10 rounded-md px-6 has-[>svg]:px-4 ${TAP_TARGET_FLOOR}`,
128
- icon: `size-9 ${SQUARE_TAP_TARGET_FLOOR}`,
129
- "icon-xs": `size-6 rounded-md [&_svg:not([class*='size-'])]:size-3 ${SQUARE_TAP_TARGET_FLOOR}`,
130
- "icon-sm": `size-8 ${SQUARE_TAP_TARGET_FLOOR}`,
131
- "icon-lg": `size-10 ${SQUARE_TAP_TARGET_FLOOR}`
132
- }
133
- },
134
- defaultVariants: { variant: "default", size: "default" }
135
- }
136
- );
137
- var Button = React2.forwardRef(function Button2({ className, variant = "default", size = "default", asChild = false, ...props }, ref) {
138
- const Comp = asChild ? Slot : "button";
139
- return /* @__PURE__ */ jsx2(
140
- Comp,
141
- {
142
- ref,
143
- "data-slot": "button",
144
- "data-variant": variant,
145
- "data-size": size,
146
- className: cn(buttonVariants({ variant, size, className })),
147
- ...props
148
- }
149
- );
150
- });
151
-
152
- // src/upload/FilePickerButton.tsx
153
- import { jsx as jsx3, jsxs } from "react/jsx-runtime";
154
- function FilePickerButton({ label, accept, disabled, onPick }) {
155
- return /* @__PURE__ */ jsxs("span", { className: "relative inline-flex rounded-md has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-offset-2 has-[:focus-visible]:outline-[var(--quoin-ring)]", children: [
156
- /* @__PURE__ */ jsx3(Button, { asChild: true, variant: "outline", size: "sm", children: /* @__PURE__ */ jsxs("span", { "aria-hidden": "true", className: disabled ? "pointer-events-none opacity-50" : void 0, children: [
157
- /* @__PURE__ */ jsx3(Upload, { "aria-hidden": "true" }),
158
- label
159
- ] }) }),
160
- /* @__PURE__ */ jsx3(
161
- "input",
162
- {
163
- type: "file",
164
- accept,
165
- "aria-label": label,
166
- disabled,
167
- className: "absolute inset-0 cursor-pointer opacity-0 disabled:cursor-not-allowed",
168
- onChange: (e) => {
169
- const file = e.target.files?.[0];
170
- e.target.value = "";
171
- if (file) onPick(file);
172
- }
173
- }
174
- )
175
- ] });
176
- }
177
-
178
- // src/markdown/edit.ts
179
- function changedSpan(before, after) {
180
- let start = 0;
181
- const shortest = Math.min(before.length, after.length);
182
- while (start < shortest && before[start] === after[start]) start += 1;
183
- let endBefore = before.length;
184
- let endAfter = after.length;
185
- while (endBefore > start && endAfter > start && before[endBefore - 1] === after[endAfter - 1]) {
186
- endBefore -= 1;
187
- endAfter -= 1;
188
- }
189
- return { start, endBefore, replacement: after.slice(start, endAfter) };
190
- }
191
- function writeNatively(el, replacement) {
192
- if (typeof document.execCommand !== "function") return false;
193
- try {
194
- return replacement === "" ? document.execCommand("delete") : document.execCommand("insertText", false, replacement);
195
- } catch {
196
- return false;
197
- }
198
- }
199
- function applyEdit(el, edit, commit) {
200
- if (el.value !== edit.value) {
201
- const { start, endBefore, replacement } = changedSpan(el.value, edit.value);
202
- el.setSelectionRange(start, endBefore);
203
- if (!writeNatively(el, replacement)) {
204
- el.value = edit.value;
205
- }
206
- commit(el.value);
207
- }
208
- el.setSelectionRange(edit.selectionStart, edit.selectionEnd);
209
- }
210
-
211
- // src/markdown/line-at.ts
212
- var FENCE = /^ {0,3}(```|~~~)/;
213
- function lineAt(value, offset) {
214
- const start = value.lastIndexOf("\n", offset - 1) + 1;
215
- const next = value.indexOf("\n", offset);
216
- const end = next === -1 ? value.length : next;
217
- return { start, end, text: value.slice(start, end) };
218
- }
219
- function isFenceDelimiter(text) {
220
- return FENCE.test(text);
221
- }
222
- function insideFence(value, offset) {
223
- const lines = value.slice(0, offset).split("\n");
224
- if (isFenceDelimiter(lines[lines.length - 1])) return true;
225
- let open = false;
226
- for (let i = 0; i < lines.length - 1; i += 1) {
227
- if (isFenceDelimiter(lines[i])) open = !open;
228
- }
229
- return open;
230
- }
231
-
232
- // src/markdown/insert-command.ts
233
- function insertCommand(command, value, caret) {
234
- const line = lineAt(value, caret);
235
- const before = value.slice(line.start, caret).replace(/[ \t]+$/, "");
236
- const cut = line.start + before.length;
237
- const prefix = before.trim() === "" ? "" : command.separate ? "\n\n" : "\n";
238
- const at = cut + prefix.length + command.caretOffset;
239
- return {
240
- value: value.slice(0, cut) + prefix + command.insert + value.slice(caret),
241
- selectionStart: at,
242
- selectionEnd: at + (command.selectLength ?? 0)
243
- };
244
- }
245
-
246
- // src/markdown/platform.ts
247
- function isMac() {
248
- if (typeof navigator === "undefined") return false;
249
- const { userAgentData, platform } = navigator;
250
- return /mac|iphone|ipad/i.test(userAgentData?.platform || platform || "");
251
- }
252
-
253
- // src/markdown/shortcut-label.ts
254
- var MAC_LABEL = { mod: "\u2318", alt: "\u2325", shift: "\u21E7" };
255
- var OTHER_LABEL = { mod: "Ctrl", alt: "Alt", shift: "Shift" };
256
- function formatShortcut(hint) {
257
- const mac = isMac();
258
- const labels = mac ? MAC_LABEL : OTHER_LABEL;
259
- const parts = hint.split("+").map((part) => labels[part] ?? part.toUpperCase());
260
- return parts.join(mac ? "" : "+");
261
- }
262
-
263
- // src/ui/dialog.tsx
264
- import * as DialogPrimitive from "@radix-ui/react-dialog";
265
- import { XIcon } from "lucide-react";
266
- import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
267
- function Dialog(props) {
268
- return /* @__PURE__ */ jsx4(DialogPrimitive.Root, { "data-slot": "dialog", ...props });
269
- }
270
- function DialogPortal(props) {
271
- return /* @__PURE__ */ jsx4(DialogPrimitive.Portal, { "data-slot": "dialog-portal", ...props });
272
- }
273
- function DialogOverlay({
274
- className,
275
- ...props
276
- }) {
277
- return /* @__PURE__ */ jsx4(
278
- DialogPrimitive.Overlay,
279
- {
280
- "data-slot": "dialog-overlay",
281
- className: cn(
282
- "quoin fixed inset-0 z-50 bg-black/50 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
283
- className
284
- ),
285
- ...props
286
- }
287
- );
288
- }
289
- function DialogContent({
290
- className,
291
- children,
292
- ...props
293
- }) {
294
- return /* @__PURE__ */ jsxs2(DialogPortal, { children: [
295
- /* @__PURE__ */ jsx4(DialogOverlay, {}),
296
- /* @__PURE__ */ jsxs2(
297
- DialogPrimitive.Content,
298
- {
299
- "data-slot": "dialog-content",
300
- className: cn(
301
- "quoin bg-popover fixed top-[50%] left-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
302
- className
303
- ),
304
- ...props,
305
- children: [
306
- children,
307
- /* @__PURE__ */ jsxs2(
308
- DialogPrimitive.Close,
309
- {
310
- className: cn(
311
- "absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100",
312
- FOCUS_RING,
313
- ICON_TAP_TARGET_FLOOR
314
- ),
315
- children: [
316
- /* @__PURE__ */ jsx4(XIcon, {}),
317
- /* @__PURE__ */ jsx4("span", { className: "sr-only", children: "Close" })
318
- ]
319
- }
320
- )
321
- ]
322
- }
323
- )
324
- ] });
325
- }
326
- function DialogHeader({ className, ...props }) {
327
- return /* @__PURE__ */ jsx4(
328
- "div",
329
- {
330
- "data-slot": "dialog-header",
331
- className: cn("flex flex-col gap-2 text-center sm:text-left", className),
332
- ...props
333
- }
334
- );
335
- }
336
- function DialogTitle({
337
- className,
338
- ...props
339
- }) {
340
- return /* @__PURE__ */ jsx4(
341
- DialogPrimitive.Title,
342
- {
343
- "data-slot": "dialog-title",
344
- className: cn("text-lg leading-none font-semibold", className),
345
- ...props
346
- }
347
- );
348
- }
349
- function DialogDescription({
350
- className,
351
- ...props
352
- }) {
353
- return /* @__PURE__ */ jsx4(
354
- DialogPrimitive.Description,
355
- {
356
- "data-slot": "dialog-description",
357
- className: cn("text-muted-foreground text-sm", className),
358
- ...props
359
- }
360
- );
361
- }
362
-
363
- // src/markdown/ShortcutsReference.tsx
364
- import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
365
- var ROWS = [
366
- { label: "Open the command palette", keys: "/" },
367
- { label: "Bold", keys: formatShortcut("mod+b") },
368
- { label: "Italic", keys: formatShortcut("mod+i") },
369
- { label: "Inline code", keys: formatShortcut("mod+e") },
370
- { label: "Link", keys: formatShortcut("mod+k") },
371
- { label: "Heading", keys: formatShortcut("mod+alt+1") },
372
- { label: "Subtitle", keys: formatShortcut("mod+alt+2") },
373
- { label: "Sub-subtitle", keys: formatShortcut("mod+alt+3") },
374
- { label: "Bullet list", keys: formatShortcut("mod+shift+8") },
375
- { label: "Numbered list", keys: formatShortcut("mod+shift+7") },
376
- { label: "Continue a list, or leave it when the item is empty", keys: "Enter" },
377
- { label: "Indent item", keys: "Tab" },
378
- { label: "Outdent item", keys: formatShortcut("shift") + "+Tab" }
379
- ];
380
- function ShortcutsReference({
381
- open,
382
- onOpenChange
383
- }) {
384
- return /* @__PURE__ */ jsx5(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs3(DialogContent, { className: "sm:max-w-md", children: [
385
- /* @__PURE__ */ jsxs3(DialogHeader, { children: [
386
- /* @__PURE__ */ jsx5(DialogTitle, { children: "Keyboard shortcuts" }),
387
- /* @__PURE__ */ jsx5(DialogDescription, { children: "Formatting without leaving the keyboard." })
388
- ] }),
389
- /* @__PURE__ */ jsx5("dl", { className: "grid grid-cols-[1fr_auto] gap-x-4 gap-y-2 text-sm", children: ROWS.map((row) => /* @__PURE__ */ jsxs3("div", { className: "col-span-2 grid grid-cols-subgrid items-baseline", children: [
390
- /* @__PURE__ */ jsx5("dt", { className: "text-muted-foreground", children: row.label }),
391
- /* @__PURE__ */ jsx5("dd", { children: /* @__PURE__ */ jsx5("kbd", { className: "rounded border bg-muted px-1.5 py-0.5 font-mono text-xs", children: row.keys }) })
392
- ] }, row.label)) })
393
- ] }) });
394
- }
395
-
396
- // src/markdown/SlashPalette.tsx
397
- import { Keyboard } from "lucide-react";
398
-
399
- // src/markdown/commands.ts
3
+ BlockCanvas,
4
+ FilePickerButton,
5
+ QuoinProvider,
6
+ UploadField,
7
+ coreBlocks,
8
+ englishLabels,
9
+ formatShortcut,
10
+ resolveLabels,
11
+ useAutosave,
12
+ useLabels,
13
+ useQuoin
14
+ } from "./chunk-ZGZEJCRV.js";
400
15
  import {
401
- Code,
402
- Heading1,
403
- Heading2,
404
- Heading3,
405
- List,
406
- ListOrdered,
407
- ListTodo,
408
- Minus,
409
- Quote,
410
- Table
411
- } from "lucide-react";
412
- var TABLE = ["| Column | Column |", "| --- | --- |", "| | |"].join("\n");
413
- var TEXT_COMMANDS = [
414
- {
415
- id: "heading",
416
- label: "Heading",
417
- keywords: ["h1", "title", "heading", "big"],
418
- icon: Heading1,
419
- hint: "mod+alt+1",
420
- insert: "# ",
421
- caretOffset: 2,
422
- separate: false
423
- },
424
- {
425
- id: "subtitle",
426
- label: "Subtitle",
427
- keywords: ["h2", "subtitle", "section", "subheading"],
428
- icon: Heading2,
429
- hint: "mod+alt+2",
430
- insert: "## ",
431
- caretOffset: 3,
432
- separate: false
433
- },
434
- {
435
- id: "subsubtitle",
436
- label: "Sub-subtitle",
437
- keywords: ["h3", "subsubtitle", "minor"],
438
- icon: Heading3,
439
- hint: "mod+alt+3",
440
- insert: "### ",
441
- caretOffset: 4,
442
- separate: false
443
- },
444
- {
445
- id: "bullet",
446
- label: "Bullet list",
447
- keywords: ["bullet", "unordered", "ul", "list", "point"],
448
- icon: List,
449
- hint: "mod+shift+8",
450
- insert: "- ",
451
- caretOffset: 2,
452
- separate: false
453
- },
454
- {
455
- id: "numbered",
456
- label: "Numbered list",
457
- keywords: ["numbered", "ordered", "ol", "steps"],
458
- icon: ListOrdered,
459
- hint: "mod+shift+7",
460
- insert: "1. ",
461
- caretOffset: 3,
462
- separate: false
463
- },
464
- {
465
- id: "task",
466
- label: "Task list",
467
- keywords: ["task", "todo", "checkbox", "checklist"],
468
- icon: ListTodo,
469
- insert: "- [ ] ",
470
- caretOffset: 6,
471
- separate: false
472
- },
473
- {
474
- id: "quote",
475
- label: "Quote",
476
- keywords: ["quote", "blockquote", "citation"],
477
- icon: Quote,
478
- insert: "> ",
479
- caretOffset: 2,
480
- separate: false
481
- },
482
- {
483
- id: "code",
484
- label: "Code block",
485
- keywords: ["code", "snippet", "fence", "pre"],
486
- icon: Code,
487
- insert: "```\n\n```",
488
- caretOffset: 3,
489
- separate: true
490
- },
491
- {
492
- id: "divider",
493
- label: "Divider",
494
- keywords: ["divider", "rule", "hr", "separator", "break"],
495
- icon: Minus,
496
- insert: "---\n",
497
- caretOffset: 4,
498
- separate: true
499
- },
500
- {
501
- id: "table",
502
- label: "Table",
503
- keywords: ["table", "grid", "rows", "columns"],
504
- icon: Table,
505
- insert: TABLE,
506
- caretOffset: 2,
507
- selectLength: 6,
508
- separate: true
509
- }
510
- ];
511
-
512
- // src/ui/command.tsx
513
- import { Command as CommandPrimitive } from "cmdk";
514
- import { SearchIcon } from "lucide-react";
515
- import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
516
- function Command({
517
- className,
518
- ...props
519
- }) {
520
- return /* @__PURE__ */ jsx6(
521
- CommandPrimitive,
522
- {
523
- "data-slot": "command",
524
- className: cn(
525
- "flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
526
- className
527
- ),
528
- ...props
529
- }
530
- );
531
- }
532
- function CommandInput({
533
- className,
534
- ...props
535
- }) {
536
- return /* @__PURE__ */ jsxs4(
537
- "div",
538
- {
539
- "data-slot": "command-input-wrapper",
540
- className: "flex h-9 items-center gap-2 border-b px-3",
541
- children: [
542
- /* @__PURE__ */ jsx6(SearchIcon, { className: "size-4 shrink-0 opacity-50" }),
543
- /* @__PURE__ */ jsx6(
544
- CommandPrimitive.Input,
545
- {
546
- "data-slot": "command-input",
547
- className: cn(
548
- "flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
549
- className
550
- ),
551
- ...props
552
- }
553
- )
554
- ]
555
- }
556
- );
557
- }
558
- function CommandList({
559
- className,
560
- ...props
561
- }) {
562
- return /* @__PURE__ */ jsx6(
563
- CommandPrimitive.List,
564
- {
565
- "data-slot": "command-list",
566
- className: cn(
567
- "max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto",
568
- className
569
- ),
570
- ...props
571
- }
572
- );
573
- }
574
- function CommandEmpty({
575
- ...props
576
- }) {
577
- return /* @__PURE__ */ jsx6(
578
- CommandPrimitive.Empty,
579
- {
580
- "data-slot": "command-empty",
581
- className: "py-6 text-center text-sm",
582
- ...props
583
- }
584
- );
585
- }
586
- function CommandGroup({
587
- className,
588
- ...props
589
- }) {
590
- return /* @__PURE__ */ jsx6(
591
- CommandPrimitive.Group,
592
- {
593
- "data-slot": "command-group",
594
- className: cn(
595
- "overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
596
- className
597
- ),
598
- ...props
599
- }
600
- );
601
- }
602
- function CommandItem({
603
- className,
604
- ...props
605
- }) {
606
- return /* @__PURE__ */ jsx6(
607
- CommandPrimitive.Item,
608
- {
609
- "data-slot": "command-item",
610
- className: cn(
611
- "relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground",
612
- SELECTED_BAR,
613
- className
614
- ),
615
- ...props
616
- }
617
- );
618
- }
619
- function CommandShortcut({
620
- className,
621
- ...props
622
- }) {
623
- return /* @__PURE__ */ jsx6(
624
- "span",
625
- {
626
- "data-slot": "command-shortcut",
627
- className: cn(
628
- "ml-auto text-xs tracking-widest text-muted-foreground",
629
- className
630
- ),
631
- ...props
632
- }
633
- );
634
- }
635
-
636
- // src/ui/popover.tsx
637
- import * as PopoverPrimitive from "@radix-ui/react-popover";
638
- import { jsx as jsx7 } from "react/jsx-runtime";
639
- function Popover(props) {
640
- return /* @__PURE__ */ jsx7(PopoverPrimitive.Root, { "data-slot": "popover", ...props });
641
- }
642
- function PopoverAnchor(props) {
643
- return /* @__PURE__ */ jsx7(PopoverPrimitive.Anchor, { "data-slot": "popover-anchor", ...props });
644
- }
645
- function PopoverContent({ className, align = "center", sideOffset = 4, ...props }) {
646
- return /* @__PURE__ */ jsx7(PopoverPrimitive.Portal, { children: /* @__PURE__ */ jsx7(
647
- PopoverPrimitive.Content,
648
- {
649
- "data-slot": "popover-content",
650
- align,
651
- sideOffset,
652
- className: cn(
653
- "quoin bg-popover text-popover-foreground z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden",
654
- className
655
- ),
656
- ...props
657
- }
658
- ) });
659
- }
660
-
661
- // src/markdown/SlashPalette.tsx
662
- import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
663
- function SlashPalette({
664
- open,
665
- caret,
666
- onOpenChange,
667
- onTextCommand,
668
- onBlockCommand,
669
- onShortcuts,
670
- onDismiss
671
- }) {
672
- const { blockTypes } = useQuoin();
673
- const blockOptions = paletteBlocks(blockTypes);
674
- return /* @__PURE__ */ jsxs5(Popover, { open, onOpenChange, children: [
675
- /* @__PURE__ */ jsx8(PopoverAnchor, { asChild: true, children: /* @__PURE__ */ jsx8(
676
- "span",
677
- {
678
- "aria-hidden": "true",
679
- className: "pointer-events-none absolute",
680
- style: caret ? { top: caret.top, left: caret.left, height: caret.height } : { top: 0, left: 0 }
681
- }
682
- ) }),
683
- /* @__PURE__ */ jsx8(
684
- PopoverContent,
685
- {
686
- "aria-label": "Command palette",
687
- align: "start",
688
- side: "bottom",
689
- sideOffset: 4,
690
- className: "w-72 p-0",
691
- onEscapeKeyDown: (event) => {
692
- event.preventDefault();
693
- onDismiss();
694
- },
695
- onCloseAutoFocus: (event) => event.preventDefault(),
696
- children: /* @__PURE__ */ jsxs5(Command, { label: "Search commands", children: [
697
- /* @__PURE__ */ jsx8(CommandInput, { placeholder: "Search commands" }),
698
- /* @__PURE__ */ jsxs5(CommandList, { children: [
699
- /* @__PURE__ */ jsx8(CommandEmpty, { children: "No command found." }),
700
- /* @__PURE__ */ jsx8(CommandGroup, { heading: "Text", children: TEXT_COMMANDS.map((command) => /* @__PURE__ */ jsxs5(
701
- CommandItem,
702
- {
703
- value: `${command.label} ${command.keywords.join(" ")}`,
704
- onSelect: () => onTextCommand(command),
705
- children: [
706
- /* @__PURE__ */ jsx8(command.icon, { "aria-hidden": "true" }),
707
- /* @__PURE__ */ jsx8("span", { children: command.label }),
708
- command.hint ? /* @__PURE__ */ jsx8(CommandShortcut, { children: formatShortcut(command.hint) }) : null
709
- ]
710
- },
711
- command.id
712
- )) }),
713
- /* @__PURE__ */ jsx8(CommandGroup, { heading: "Blocks", children: blockOptions.map((option) => /* @__PURE__ */ jsxs5(CommandItem, { value: option.label, onSelect: () => onBlockCommand(option.type), children: [
714
- /* @__PURE__ */ jsx8(option.icon, { "aria-hidden": "true" }),
715
- /* @__PURE__ */ jsx8("span", { children: option.label })
716
- ] }, option.type)) }),
717
- /* @__PURE__ */ jsx8(CommandGroup, { children: /* @__PURE__ */ jsxs5(CommandItem, { value: "keyboard shortcuts help", onSelect: onShortcuts, children: [
718
- /* @__PURE__ */ jsx8(Keyboard, { "aria-hidden": "true" }),
719
- /* @__PURE__ */ jsx8("span", { children: "Keyboard shortcuts" })
720
- ] }) })
721
- ] })
722
- ] })
723
- }
724
- )
725
- ] });
726
- }
727
-
728
- // src/markdown/useMarkdownKeymap.ts
729
- import * as React3 from "react";
730
-
731
- // src/markdown/list-continuation.ts
732
- var LIST_MARKER = /^(\s*)((?:[-*+] \[[ xX]\] )|(?:[-*+] )|(?:\d+[.)] )|(?:> ))(.*)$/;
733
- var INDENT = " ";
734
- function nextMarker(marker) {
735
- const ordered = /^(\d+)([.)] )$/.exec(marker);
736
- if (ordered) return `${Number(ordered[1]) + 1}${ordered[2]}`;
737
- const task = /^([-*+]) \[[ xX]\] $/.exec(marker);
738
- if (task) return `${task[1]} [ ] `;
739
- return marker;
740
- }
741
- function climbOut(value, line, indent, marker, content) {
742
- const outdent = indent.length >= INDENT.length;
743
- if (outdent) {
744
- const text2 = indent.slice(INDENT.length) + marker + content;
745
- const at2 = line.start + indent.length - INDENT.length + marker.length;
746
- return {
747
- value: value.slice(0, line.start) + text2 + value.slice(line.end),
748
- selectionStart: at2,
749
- selectionEnd: at2
750
- };
751
- }
752
- const leavesList = line.start > 0 && LIST_MARKER.test(lineAt(value, line.start - 1).text);
753
- const text = leavesList ? `
754
- ${content}` : content;
755
- const at = leavesList ? line.start + 1 : line.start;
756
- return {
757
- value: value.slice(0, line.start) + text + value.slice(line.end),
758
- selectionStart: at,
759
- selectionEnd: at
760
- };
761
- }
762
- function continueList(value, caret, key) {
763
- if (insideFence(value, caret)) return null;
764
- const line = lineAt(value, caret);
765
- const match = LIST_MARKER.exec(line.text);
766
- if (!match) return null;
767
- const [, indent, marker, content] = match;
768
- if (key === "Backspace") {
769
- if (caret !== line.start + indent.length + marker.length) return null;
770
- return climbOut(value, line, indent, marker, content);
771
- }
772
- if (content.trim() === "") return climbOut(value, line, indent, marker, content);
773
- const inserted = `
774
- ${indent}${nextMarker(marker)}`;
775
- const at = caret + inserted.length;
776
- return {
777
- value: value.slice(0, caret) + inserted + value.slice(caret),
778
- selectionStart: at,
779
- selectionEnd: at
780
- };
781
- }
782
-
783
- // src/markdown/indent-list.ts
784
- var INDENT2 = " ";
785
- function indentOf(text) {
786
- return /^\s*/.exec(text)?.[0].length ?? 0;
787
- }
788
- function shift(text, direction, cap) {
789
- const current = indentOf(text);
790
- if (direction === -1) return text.slice(Math.min(current, INDENT2.length));
791
- if (cap !== void 0 && current >= cap + INDENT2.length) return text;
792
- return INDENT2 + text;
793
- }
794
- function indentList(value, selectionStart, selectionEnd, direction) {
795
- if (insideFence(value, selectionStart)) return null;
796
- const first = lineAt(value, selectionStart);
797
- const last = lineAt(value, selectionEnd);
798
- const block = value.slice(first.start, last.end);
799
- const lines = block.split("\n");
800
- if (!lines.every((text) => LIST_MARKER.test(text))) return null;
801
- const cap = first.start === 0 ? 0 : indentOf(lineAt(value, first.start - 1).text);
802
- const shifted = lines.map((text, i) => shift(text, direction, i === 0 ? cap : void 0));
803
- if (shifted.every((text, i) => text === lines[i])) return null;
804
- const joined = shifted.join("\n");
805
- return {
806
- value: value.slice(0, first.start) + joined + value.slice(last.end),
807
- selectionStart: selectionStart + (shifted[0].length - lines[0].length),
808
- selectionEnd: selectionEnd + (joined.length - block.length)
809
- };
810
- }
811
-
812
- // src/markdown/line-toggles.ts
813
- var EXISTING = /^(\s*)(?:#{1,6} |[-*+] \[[ xX]\] |[-*+] |\d+[.)] )?/;
814
- var PREFIX = {
815
- h1: () => "# ",
816
- h2: () => "## ",
817
- h3: () => "### ",
818
- bullet: () => "- ",
819
- ordered: (index) => `${index + 1}. `
820
- };
821
- var CARRIES = {
822
- h1: /^\s*# /,
823
- h2: /^\s*## /,
824
- h3: /^\s*### /,
825
- bullet: /^\s*[-*+] /,
826
- ordered: /^\s*\d+[.)] /
827
- };
828
- function toggleLinePrefix(kind, value, selectionStart, selectionEnd) {
829
- const first = lineAt(value, selectionStart);
830
- const last = lineAt(value, selectionEnd);
831
- const block = value.slice(first.start, last.end);
832
- const lines = block.split("\n");
833
- let offset = first.start;
834
- const eligible = lines.map((text) => {
835
- const at = offset;
836
- offset += text.length + 1;
837
- return !insideFence(value, at) && !isFenceDelimiter(text);
838
- });
839
- const carried = lines.every((text, i) => !eligible[i] || CARRIES[kind].test(text));
840
- const rewritten = lines.map((text, i) => {
841
- if (!eligible[i]) return text;
842
- const match = EXISTING.exec(text);
843
- const indent = match?.[1] ?? "";
844
- const body = text.slice(match?.[0].length ?? 0);
845
- return carried ? indent + body : indent + PREFIX[kind](i) + body;
846
- });
847
- const joined = rewritten.join("\n");
848
- const next = value.slice(0, first.start) + joined + value.slice(last.end);
849
- if (selectionStart !== selectionEnd) {
850
- return { value: next, selectionStart: first.start, selectionEnd: first.start + joined.length };
851
- }
852
- const caret = Math.max(first.start, selectionStart + (rewritten[0].length - lines[0].length));
853
- return { value: next, selectionStart: caret, selectionEnd: caret };
854
- }
855
-
856
- // src/markdown/slash-trigger.ts
857
- function shouldOpenPalette(value, selectionStart, selectionEnd) {
858
- if (selectionStart !== selectionEnd) return false;
859
- if (insideFence(value, selectionStart)) return false;
860
- if (selectionStart === 0) return true;
861
- const previous = value[selectionStart - 1];
862
- return previous === "\n" || previous === " " || previous === " ";
863
- }
864
-
865
- // src/markdown/wrap-selection.ts
866
- var DELIMITER = {
867
- bold: "**",
868
- italic: "_",
869
- code: "`"
870
- };
871
- var WHOLE_LINK = /^\[([^\]]*)\]\(([^)]*)\)$/;
872
- var BARE_URL = /^https?:\/\/[^\s]+$/;
873
- function unwrap(value, start, end, delimiter) {
874
- const selected = value.slice(start, end);
875
- const width = delimiter.length;
876
- if (selected.length >= 2 * width && selected.startsWith(delimiter) && selected.endsWith(delimiter)) {
877
- const inner = selected.slice(width, selected.length - width);
878
- return {
879
- value: value.slice(0, start) + inner + value.slice(end),
880
- selectionStart: start,
881
- selectionEnd: start + inner.length
882
- };
883
- }
884
- const outside = start >= width && value.slice(start - width, start) === delimiter && value.slice(end, end + width) === delimiter;
885
- if (!outside) return null;
886
- return {
887
- value: value.slice(0, start - width) + selected + value.slice(end + width),
888
- selectionStart: start - width,
889
- selectionEnd: start - width + selected.length
890
- };
891
- }
892
- function link(value, start, end) {
893
- const selected = value.slice(start, end);
894
- const whole = WHOLE_LINK.exec(selected);
895
- if (whole) {
896
- const text = whole[1];
897
- return {
898
- value: value.slice(0, start) + text + value.slice(end),
899
- selectionStart: start,
900
- selectionEnd: start + text.length
901
- };
902
- }
903
- const opened = `[${selected}](`;
904
- const at = selected === "" ? start + 1 : start + opened.length;
905
- return {
906
- value: value.slice(0, start) + opened + ")" + value.slice(end),
907
- selectionStart: at,
908
- selectionEnd: at
909
- };
910
- }
911
- function wrapSelection(kind, value, selectionStart, selectionEnd) {
912
- if (kind === "link") return link(value, selectionStart, selectionEnd);
913
- const delimiter = DELIMITER[kind];
914
- const stripped = unwrap(value, selectionStart, selectionEnd, delimiter);
915
- if (stripped) return stripped;
916
- const selected = value.slice(selectionStart, selectionEnd);
917
- if (kind === "code" && selected.includes("\n")) {
918
- const at2 = selectionStart + 4;
919
- return {
920
- value: value.slice(0, selectionStart) + "```\n" + selected + "\n```" + value.slice(selectionEnd),
921
- selectionStart: at2,
922
- selectionEnd: at2 + selected.length
923
- };
924
- }
925
- const at = selectionStart + delimiter.length;
926
- return {
927
- value: value.slice(0, selectionStart) + delimiter + selected + delimiter + value.slice(selectionEnd),
928
- selectionStart: at,
929
- selectionEnd: at + selected.length
930
- };
931
- }
932
- function linkPaste(value, selectionStart, selectionEnd, pasted) {
933
- if (selectionStart === selectionEnd) return null;
934
- const url = pasted.trim();
935
- if (!BARE_URL.test(url)) return null;
936
- const inserted = `[${value.slice(selectionStart, selectionEnd)}](${url})`;
937
- const at = selectionStart + inserted.length;
938
- return {
939
- value: value.slice(0, selectionStart) + inserted + value.slice(selectionEnd),
940
- selectionStart: at,
941
- selectionEnd: at
942
- };
943
- }
944
-
945
- // src/markdown/useMarkdownKeymap.ts
946
- var WRAP = { b: "bold", i: "italic", e: "code", k: "link" };
947
- var HEADING = { Digit1: "h1", Digit2: "h2", Digit3: "h3" };
948
- var LIST = { Digit7: "ordered", Digit8: "bullet" };
949
- function useMarkdownKeymap({
950
- commit,
951
- onOpenPalette
952
- }) {
953
- const onKeyDown = React3.useCallback(
954
- (event) => {
955
- if (event.nativeEvent.isComposing || event.keyCode === 229) return;
956
- const el = event.currentTarget;
957
- const { value, selectionStart, selectionEnd } = el;
958
- const mod = isMac() ? event.metaKey : event.ctrlKey;
959
- const run = (edit) => {
960
- if (!edit) return;
961
- event.preventDefault();
962
- applyEdit(el, edit, commit);
963
- };
964
- if (mod && event.altKey) {
965
- const kind = HEADING[event.code];
966
- if (kind) run(toggleLinePrefix(kind, value, selectionStart, selectionEnd));
967
- return;
968
- }
969
- if (mod && event.shiftKey) {
970
- const kind = LIST[event.code];
971
- if (kind) run(toggleLinePrefix(kind, value, selectionStart, selectionEnd));
972
- return;
973
- }
974
- if (mod) {
975
- const kind = WRAP[event.key.toLowerCase()];
976
- if (kind) run(wrapSelection(kind, value, selectionStart, selectionEnd));
977
- return;
978
- }
979
- if (event.key === "/") {
980
- if (!shouldOpenPalette(value, selectionStart, selectionEnd)) return;
981
- event.preventDefault();
982
- onOpenPalette();
983
- return;
984
- }
985
- if (event.key === "Enter" && !event.shiftKey) {
986
- run(continueList(value, selectionStart, "Enter"));
987
- return;
988
- }
989
- if (event.key === "Backspace" && selectionStart === selectionEnd) {
990
- run(continueList(value, selectionStart, "Backspace"));
991
- return;
992
- }
993
- if (event.key === "Tab") {
994
- run(indentList(value, selectionStart, selectionEnd, event.shiftKey ? -1 : 1));
995
- }
996
- },
997
- [commit, onOpenPalette]
998
- );
999
- const onPaste = React3.useCallback(
1000
- (event) => {
1001
- const el = event.currentTarget;
1002
- const edit = linkPaste(
1003
- el.value,
1004
- el.selectionStart,
1005
- el.selectionEnd,
1006
- event.clipboardData.getData("text/plain")
1007
- );
1008
- if (!edit) return;
1009
- event.preventDefault();
1010
- applyEdit(el, edit, commit);
1011
- },
1012
- [commit]
1013
- );
1014
- return { onKeyDown, onPaste };
1015
- }
1016
-
1017
- // src/markdown/useSlashPalette.ts
1018
- import * as React4 from "react";
1019
-
1020
- // src/markdown/caret-coordinates.ts
1021
- var COPIED = [
1022
- "boxSizing",
1023
- "fontFamily",
1024
- "fontSize",
1025
- "fontStyle",
1026
- "fontWeight",
1027
- "letterSpacing",
1028
- "lineHeight",
1029
- "paddingBottom",
1030
- "paddingLeft",
1031
- "paddingRight",
1032
- "paddingTop",
1033
- "textIndent",
1034
- "textTransform",
1035
- "wordBreak",
1036
- "wordSpacing",
1037
- "borderBottomWidth",
1038
- "borderLeftWidth",
1039
- "borderRightWidth",
1040
- "borderTopWidth"
1041
- ];
1042
- function caretRect(el) {
1043
- if (el.offsetWidth === 0) return null;
1044
- const style = window.getComputedStyle(el);
1045
- const mirror = document.createElement("div");
1046
- for (const property of COPIED) mirror.style[property] = style[property];
1047
- mirror.style.position = "absolute";
1048
- mirror.style.top = "0";
1049
- mirror.style.left = "-9999px";
1050
- mirror.style.visibility = "hidden";
1051
- mirror.style.whiteSpace = "pre-wrap";
1052
- mirror.style.overflowWrap = "break-word";
1053
- mirror.style.width = `${el.clientWidth}px`;
1054
- mirror.textContent = el.value.slice(0, el.selectionStart);
1055
- const marker = document.createElement("span");
1056
- marker.textContent = "\u200B";
1057
- mirror.appendChild(marker);
1058
- document.body.appendChild(mirror);
1059
- const top = marker.offsetTop;
1060
- const left = marker.offsetLeft;
1061
- const height = marker.offsetHeight || Number.parseFloat(style.lineHeight) || 0;
1062
- mirror.remove();
1063
- if (height === 0) return null;
1064
- return { top: top - el.scrollTop, left, height };
1065
- }
1066
-
1067
- // src/markdown/useSlashPalette.ts
1068
- function useSlashPalette(ref) {
1069
- const [open, setOpen] = React4.useState(false);
1070
- const [caret, setCaret] = React4.useState(null);
1071
- const offsetRef = React4.useRef(0);
1072
- const openAtCaret = React4.useCallback(() => {
1073
- const el = ref.current;
1074
- if (!el) return;
1075
- offsetRef.current = el.selectionStart;
1076
- setCaret(caretRect(el));
1077
- setOpen(true);
1078
- }, [ref]);
1079
- return { open, setOpen, caret, offsetRef, openAtCaret };
1080
- }
1081
-
1082
- // src/hooks/useAutoGrow.ts
1083
- import * as React6 from "react";
1084
-
1085
- // src/hooks/use-isomorphic-layout-effect.ts
1086
- import * as React5 from "react";
1087
- var useIsomorphicLayoutEffect = typeof document === "undefined" ? React5.useEffect : React5.useLayoutEffect;
1088
-
1089
- // src/hooks/useAutoGrow.ts
1090
- function useAutoGrow(value) {
1091
- const ref = React6.useRef(null);
1092
- const lastWidth = React6.useRef(null);
1093
- const fit = React6.useCallback(() => {
1094
- const el = ref.current;
1095
- if (!el) return;
1096
- const scroller = document.scrollingElement ?? document.documentElement;
1097
- const scrollTop = scroller.scrollTop;
1098
- el.style.height = "auto";
1099
- if (el.scrollHeight > 0) el.style.height = `${el.scrollHeight}px`;
1100
- if (scroller.scrollTop !== scrollTop) scroller.scrollTop = scrollTop;
1101
- }, []);
1102
- useIsomorphicLayoutEffect(fit, [fit, value]);
1103
- React6.useEffect(() => {
1104
- const el = ref.current;
1105
- if (!el || typeof ResizeObserver === "undefined") return;
1106
- const observer = new ResizeObserver(([entry]) => {
1107
- const width = entry.contentRect.width;
1108
- if (width === lastWidth.current) return;
1109
- lastWidth.current = width;
1110
- fit();
1111
- });
1112
- observer.observe(el);
1113
- return () => observer.disconnect();
1114
- }, [fit]);
1115
- return ref;
1116
- }
1117
-
1118
- // src/blocks/str.ts
1119
- var str = (v) => typeof v === "string" ? v : "";
1120
-
1121
- // src/blocks/MarkdownBlockEditor.tsx
1122
- import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
1123
- var BOX = [
1124
- "-mx-2 min-h-7 w-[calc(100%+1rem)] resize-none rounded-md border-0 bg-transparent px-2 py-0",
1125
- "text-base leading-7 placeholder:text-muted-foreground",
1126
- "transition-[color,box-shadow]",
1127
- FOCUS_RING
1128
- ].join(" ");
1129
- function MarkdownBlockEditor({ data, onChange, onInsertBlock, onDone }) {
1130
- const { onError } = useQuoin();
1131
- const markdown = str(data.markdown);
1132
- const ref = useAutoGrow(markdown);
1133
- const palette = useSlashPalette(ref);
1134
- const [shortcutsOpen, setShortcutsOpen] = React7.useState(false);
1135
- const set = (patch) => onChange({ ...data, ...patch });
1136
- const commit = React7.useCallback(
1137
- (next) => onChange({ ...data, markdown: next }),
1138
- [data, onChange]
1139
- );
1140
- const keymap = useMarkdownKeymap({ commit, onOpenPalette: palette.openAtCaret });
1141
- function reopenBox() {
1142
- const el = ref.current;
1143
- el?.focus({ preventScroll: true });
1144
- return el ?? null;
1145
- }
1146
- function runTextCommand(command) {
1147
- palette.setOpen(false);
1148
- const el = reopenBox();
1149
- if (!el) return;
1150
- applyEdit(el, insertCommand(command, el.value, palette.offsetRef.current), commit);
1151
- }
1152
- function writeLiteralSlash() {
1153
- palette.setOpen(false);
1154
- const el = reopenBox();
1155
- if (!el) return;
1156
- const at = palette.offsetRef.current;
1157
- applyEdit(
1158
- el,
1159
- { value: `${el.value.slice(0, at)}/${el.value.slice(at)}`, selectionStart: at + 1, selectionEnd: at + 1 },
1160
- commit
1161
- );
1162
- }
1163
- function readMarkdownFile(file) {
1164
- const reader = new FileReader();
1165
- reader.onload = () => {
1166
- const text = typeof reader.result === "string" ? reader.result : "";
1167
- if (text.trim() === "") {
1168
- onError(new Error(`${file.name} is empty.`));
1169
- return;
1170
- }
1171
- set({ markdown: text });
1172
- onDone?.();
1173
- };
1174
- reader.onerror = () => onError(new Error(`Could not read ${file.name}.`));
1175
- reader.readAsText(file);
1176
- }
1177
- return /* @__PURE__ */ jsxs6("div", { className: "relative flex flex-col gap-3", children: [
1178
- /* @__PURE__ */ jsx9(
1179
- "textarea",
1180
- {
1181
- ref,
1182
- "aria-label": "Markdown",
1183
- value: markdown,
1184
- rows: 1,
1185
- placeholder: "Write, or press / for commands",
1186
- onChange: (e) => set({ markdown: e.target.value }),
1187
- onKeyDown: keymap.onKeyDown,
1188
- onPaste: keymap.onPaste,
1189
- className: BOX
1190
- }
1191
- ),
1192
- /* @__PURE__ */ jsx9(
1193
- SlashPalette,
1194
- {
1195
- open: palette.open,
1196
- caret: palette.caret,
1197
- onOpenChange: palette.setOpen,
1198
- onTextCommand: runTextCommand,
1199
- onBlockCommand: (type) => {
1200
- palette.setOpen(false);
1201
- onInsertBlock?.(type);
1202
- onDone?.();
1203
- },
1204
- onShortcuts: () => {
1205
- palette.setOpen(false);
1206
- setShortcutsOpen(true);
1207
- },
1208
- onDismiss: writeLiteralSlash
1209
- }
1210
- ),
1211
- /* @__PURE__ */ jsx9(ShortcutsReference, { open: shortcutsOpen, onOpenChange: setShortcutsOpen }),
1212
- markdown.trim() === "" && /* @__PURE__ */ jsx9(
1213
- FilePickerButton,
1214
- {
1215
- label: "Upload .md",
1216
- accept: ".md,text/markdown,text/plain",
1217
- onPick: readMarkdownFile
1218
- }
1219
- )
1220
- ] });
1221
- }
1222
-
1223
- // src/ui/select.tsx
1224
- import * as SelectPrimitive from "@radix-ui/react-select";
1225
- import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
1226
- import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
1227
- function Select(props) {
1228
- return /* @__PURE__ */ jsx10(SelectPrimitive.Root, { "data-slot": "select", ...props });
1229
- }
1230
- function SelectValue(props) {
1231
- return /* @__PURE__ */ jsx10(SelectPrimitive.Value, { "data-slot": "select-value", ...props });
1232
- }
1233
- function SelectTrigger({ className, children, ...props }) {
1234
- return /* @__PURE__ */ jsxs7(
1235
- SelectPrimitive.Trigger,
1236
- {
1237
- "data-slot": "select-trigger",
1238
- className: cn(
1239
- "border-input data-[placeholder]:text-muted-foreground flex h-9 w-full cursor-pointer items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm shadow-xs disabled:cursor-not-allowed disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
1240
- FOCUS_RING,
1241
- TAP_TARGET_FLOOR,
1242
- className
1243
- ),
1244
- ...props,
1245
- children: [
1246
- children,
1247
- /* @__PURE__ */ jsx10(SelectPrimitive.Icon, { asChild: true, children: /* @__PURE__ */ jsx10(ChevronDownIcon, { className: "size-4 opacity-50" }) })
1248
- ]
1249
- }
1250
- );
1251
- }
1252
- function SelectContent({ className, children, position = "popper", ...props }) {
1253
- return /* @__PURE__ */ jsx10(SelectPrimitive.Portal, { children: /* @__PURE__ */ jsxs7(
1254
- SelectPrimitive.Content,
1255
- {
1256
- "data-slot": "select-content",
1257
- className: cn(
1258
- "quoin bg-popover text-popover-foreground relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border shadow-md",
1259
- position === "popper" && "data-[side=bottom]:translate-y-1",
1260
- className
1261
- ),
1262
- position,
1263
- ...props,
1264
- children: [
1265
- /* @__PURE__ */ jsx10(SelectPrimitive.ScrollUpButton, { className: "flex cursor-default items-center justify-center py-1", children: /* @__PURE__ */ jsx10(ChevronUpIcon, { className: "size-4" }) }),
1266
- /* @__PURE__ */ jsx10(SelectPrimitive.Viewport, { className: cn("p-1", position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"), children }),
1267
- /* @__PURE__ */ jsx10(SelectPrimitive.ScrollDownButton, { className: "flex cursor-default items-center justify-center py-1", children: /* @__PURE__ */ jsx10(ChevronDownIcon, { className: "size-4" }) })
1268
- ]
1269
- }
1270
- ) });
1271
- }
1272
- function SelectItem({ className, children, ...props }) {
1273
- return /* @__PURE__ */ jsxs7(
1274
- SelectPrimitive.Item,
1275
- {
1276
- "data-slot": "select-item",
1277
- className: cn(
1278
- "focus:bg-accent focus:text-accent-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
1279
- HIGHLIGHT_BAR,
1280
- TAP_TARGET_FLOOR,
1281
- className
1282
- ),
1283
- ...props,
1284
- children: [
1285
- /* @__PURE__ */ jsx10("span", { className: "absolute right-2 flex size-3.5 items-center justify-center", children: /* @__PURE__ */ jsx10(SelectPrimitive.ItemIndicator, { children: /* @__PURE__ */ jsx10(CheckIcon, { className: "size-4" }) }) }),
1286
- /* @__PURE__ */ jsx10(SelectPrimitive.ItemText, { children })
1287
- ]
1288
- }
1289
- );
1290
- }
1291
-
1292
- // src/ui/textarea.tsx
1293
- import * as React8 from "react";
1294
- import { jsx as jsx11 } from "react/jsx-runtime";
1295
- var Textarea = React8.forwardRef(function Textarea2({ className, ...props }, ref) {
1296
- return /* @__PURE__ */ jsx11(
1297
- "textarea",
1298
- {
1299
- ref,
1300
- "data-slot": "textarea",
1301
- className: cn(
1302
- "border-input placeholder:text-muted-foreground flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] disabled:pointer-events-none disabled:opacity-50 md:text-sm",
1303
- FOCUS_RING,
1304
- "focus-visible:border-ring",
1305
- "aria-invalid:border-destructive",
1306
- className
1307
- ),
1308
- ...props
1309
- }
1310
- );
1311
- });
1312
-
1313
- // src/blocks/CalloutBlockEditor.tsx
1314
- import { jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
1315
- var CALLOUT_TONES = ["info", "tip", "warning", "success", "danger"];
1316
- function CalloutBlockEditor({ data, onChange }) {
1317
- const set = (patch) => onChange({ ...data, ...patch });
1318
- return /* @__PURE__ */ jsxs8("div", { className: "flex flex-col gap-2", children: [
1319
- /* @__PURE__ */ jsxs8("div", { className: "flex flex-col gap-1", children: [
1320
- /* @__PURE__ */ jsx12("span", { className: "text-sm font-medium leading-none", children: "Tone" }),
1321
- /* @__PURE__ */ jsxs8(Select, { value: str(data.tone) || "info", onValueChange: (v) => set({ tone: v }), children: [
1322
- /* @__PURE__ */ jsx12(SelectTrigger, { "aria-label": "Callout tone", className: "w-40 capitalize", children: /* @__PURE__ */ jsx12(SelectValue, {}) }),
1323
- /* @__PURE__ */ jsx12(SelectContent, { children: CALLOUT_TONES.map((t) => /* @__PURE__ */ jsx12(SelectItem, { value: t, className: "capitalize", children: t }, t)) })
1324
- ] })
1325
- ] }),
1326
- /* @__PURE__ */ jsxs8("label", { className: "flex flex-col gap-1", children: [
1327
- /* @__PURE__ */ jsx12("span", { className: "text-sm font-medium leading-none", children: "Callout text" }),
1328
- /* @__PURE__ */ jsx12(Textarea, { value: str(data.body), onChange: (e) => set({ body: e.target.value }), className: "text-sm" })
1329
- ] })
1330
- ] });
1331
- }
1332
-
1333
- // src/ui/input.tsx
1334
- import * as React9 from "react";
1335
- import { jsx as jsx13 } from "react/jsx-runtime";
1336
- var Input = React9.forwardRef(function Input2({ className, type, ...props }, ref) {
1337
- return /* @__PURE__ */ jsx13(
1338
- "input",
1339
- {
1340
- ref,
1341
- type,
1342
- "data-slot": "input",
1343
- className: cn(
1344
- "border-input placeholder:text-muted-foreground flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:opacity-50 md:text-sm",
1345
- FOCUS_RING,
1346
- "focus-visible:border-ring",
1347
- "aria-invalid:border-destructive",
1348
- TAP_TARGET_FLOOR,
1349
- className
1350
- ),
1351
- ...props
1352
- }
1353
- );
1354
- });
1355
-
1356
- // src/blocks/CodeBlockEditor.tsx
1357
- import { jsx as jsx14, jsxs as jsxs9 } from "react/jsx-runtime";
1358
- function CodeBlockEditor({ data, onChange }) {
1359
- const set = (patch) => onChange({ ...data, ...patch });
1360
- return /* @__PURE__ */ jsxs9("div", { className: "flex flex-col gap-2", children: [
1361
- /* @__PURE__ */ jsxs9("label", { className: "flex flex-col gap-1", children: [
1362
- /* @__PURE__ */ jsx14("span", { className: "text-sm font-medium leading-none", children: "Language" }),
1363
- /* @__PURE__ */ jsx14(
1364
- Input,
1365
- {
1366
- "aria-label": "Language",
1367
- value: str(data.language),
1368
- onChange: (e) => set({ language: e.target.value }),
1369
- className: "text-sm"
1370
- }
1371
- )
1372
- ] }),
1373
- /* @__PURE__ */ jsxs9("label", { className: "flex flex-col gap-1", children: [
1374
- /* @__PURE__ */ jsx14("span", { className: "text-sm font-medium leading-none", children: "Code" }),
1375
- /* @__PURE__ */ jsx14(
1376
- Textarea,
1377
- {
1378
- "aria-label": "Code",
1379
- value: str(data.code),
1380
- onChange: (e) => set({ code: e.target.value }),
1381
- className: "min-h-32 font-mono text-sm"
1382
- }
1383
- )
1384
- ] })
1385
- ] });
1386
- }
1387
-
1388
- // src/blocks/ImageBlockEditor.tsx
1389
- import { useEffect as useEffect3, useRef as useRef3 } from "react";
1390
-
1391
- // src/upload/UploadField.tsx
1392
- import * as React10 from "react";
1393
- import { jsx as jsx15, jsxs as jsxs10 } from "react/jsx-runtime";
1394
- function UploadField({ category, label, accept, onUploaded }) {
1395
- const { upload, onError } = useQuoin();
1396
- const [busy, setBusy] = React10.useState(false);
1397
- const [error, setError] = React10.useState(null);
1398
- async function run(file) {
1399
- if (!upload) return;
1400
- setBusy(true);
1401
- setError(null);
1402
- let result;
1403
- try {
1404
- result = await upload(file, category);
1405
- } catch (cause) {
1406
- setError(cause instanceof Error && cause.message || "Upload failed.");
1407
- setBusy(false);
1408
- return;
1409
- }
1410
- try {
1411
- await onUploaded(result);
1412
- } catch (cause) {
1413
- onError(cause instanceof Error ? cause : new Error(String(cause)));
1414
- } finally {
1415
- setBusy(false);
1416
- }
1417
- }
1418
- return /* @__PURE__ */ jsxs10("div", { className: "flex flex-col items-start gap-1.5", children: [
1419
- /* @__PURE__ */ jsx15(
1420
- FilePickerButton,
1421
- {
1422
- label,
1423
- accept,
1424
- disabled: busy || !upload,
1425
- onPick: (file) => void run(file)
1426
- }
1427
- ),
1428
- !upload && /* @__PURE__ */ jsx15("span", { role: "note", className: "text-xs text-muted-foreground", children: "Uploads are not configured for this editor." }),
1429
- busy && /* @__PURE__ */ jsx15("span", { role: "status", className: "text-xs text-muted-foreground", children: "Uploading..." }),
1430
- error && /* @__PURE__ */ jsx15("span", { role: "alert", className: "text-xs text-destructive", children: error })
1431
- ] });
1432
- }
1433
-
1434
- // src/blocks/intrinsic-size.ts
1435
- function intrinsicSize(src) {
1436
- return new Promise((resolve) => {
1437
- if (typeof window === "undefined") return resolve(null);
1438
- const img = new window.Image();
1439
- img.onload = () => resolve({ width: img.naturalWidth, height: img.naturalHeight });
1440
- img.onerror = () => resolve(null);
1441
- img.src = src;
1442
- });
1443
- }
1444
-
1445
- // src/blocks/ImageBlockEditor.tsx
1446
- import { jsx as jsx16, jsxs as jsxs11 } from "react/jsx-runtime";
1447
- function ImageBlockEditor({ data, onChange }) {
1448
- const { resolveAssetUrl } = useQuoin();
1449
- const latest = useRef3(data);
1450
- useEffect3(() => {
1451
- latest.current = data;
1452
- });
1453
- const set = (patch) => onChange({ ...latest.current, ...patch });
1454
- const newestKey = useRef3("");
1455
- const onUploaded = async ({ storageKey }) => {
1456
- newestKey.current = storageKey;
1457
- const { width: _width, height: _height, ...unsized } = latest.current;
1458
- onChange({ ...unsized, storageKey });
1459
- const src = resolveAssetUrl(storageKey);
1460
- const size = src ? await intrinsicSize(src) : null;
1461
- if (size && newestKey.current === storageKey) set({ storageKey, ...size });
1462
- };
1463
- return /* @__PURE__ */ jsxs11("div", { className: "flex flex-col gap-2", children: [
1464
- /* @__PURE__ */ jsx16(UploadField, { category: "image", label: "Upload image", onUploaded }),
1465
- /* @__PURE__ */ jsxs11("label", { className: "flex flex-col gap-1", children: [
1466
- /* @__PURE__ */ jsx16("span", { className: "text-sm font-medium leading-none", children: "Alt text" }),
1467
- /* @__PURE__ */ jsx16(
1468
- Input,
1469
- {
1470
- "aria-label": "Image alt text",
1471
- value: str(data.alt),
1472
- onChange: (e) => set({ alt: e.target.value }),
1473
- className: "text-sm"
1474
- }
1475
- )
1476
- ] }),
1477
- /* @__PURE__ */ jsxs11("label", { className: "flex flex-col gap-1", children: [
1478
- /* @__PURE__ */ jsx16("span", { className: "text-sm font-medium leading-none", children: "Caption (optional)" }),
1479
- /* @__PURE__ */ jsx16(
1480
- Input,
1481
- {
1482
- "aria-label": "Image caption",
1483
- value: str(data.caption),
1484
- onChange: (e) => set({ caption: e.target.value }),
1485
- className: "text-sm"
1486
- }
1487
- )
1488
- ] }),
1489
- str(data.storageKey) && /* @__PURE__ */ jsx16("span", { className: "text-xs text-muted-foreground", children: "Image attached" })
1490
- ] });
1491
- }
1492
-
1493
- // src/blocks/FileBlockEditor.tsx
1494
- import { jsx as jsx17, jsxs as jsxs12 } from "react/jsx-runtime";
1495
- function FileBlockEditor({ data, onChange }) {
1496
- const set = (patch) => onChange({ ...data, ...patch });
1497
- return /* @__PURE__ */ jsxs12("div", { className: "flex flex-col gap-2", children: [
1498
- /* @__PURE__ */ jsx17(
1499
- UploadField,
1500
- {
1501
- category: "file",
1502
- label: "Upload file",
1503
- onUploaded: (r) => set({ storageKey: r.storageKey, filename: r.filename, size: r.size })
1504
- }
1505
- ),
1506
- str(data.filename) && /* @__PURE__ */ jsxs12("span", { className: "text-xs text-muted-foreground", children: [
1507
- str(data.filename),
1508
- " \u2713"
1509
- ] })
1510
- ] });
1511
- }
1512
-
1513
- // src/blocks/previews.tsx
1514
- import { jsx as jsx18 } from "react/jsx-runtime";
1515
- var HINT = "text-sm text-muted-foreground";
1516
- var TONE = {
1517
- info: "border-info bg-info/15",
1518
- tip: "border-primary bg-primary/15",
1519
- warning: "border-warning bg-warning/15",
1520
- success: "border-success bg-success/15",
1521
- danger: "border-destructive bg-destructive/15"
1522
- };
1523
- function MarkdownPreview({ data }) {
1524
- const markdown = str(data.markdown);
1525
- if (markdown.trim() === "") return /* @__PURE__ */ jsx18("p", { className: HINT, children: "Empty markdown block" });
1526
- return /* @__PURE__ */ jsx18("div", { className: "whitespace-pre-wrap text-base leading-7", children: markdown });
1527
- }
1528
- function CodePreview({ data }) {
1529
- const code = str(data.code);
1530
- if (code.trim() === "") return /* @__PURE__ */ jsx18("p", { className: HINT, children: "Empty code block" });
1531
- return /* @__PURE__ */ jsx18("pre", { className: "overflow-x-auto rounded-md bg-muted p-3 text-sm", children: /* @__PURE__ */ jsx18("code", { children: code }) });
1532
- }
1533
- function ImagePreview({ data }) {
1534
- const { resolveAssetUrl } = useQuoin();
1535
- const key = str(data.storageKey);
1536
- if (key === "") return /* @__PURE__ */ jsx18("p", { className: HINT, children: "No image chosen" });
1537
- return /* @__PURE__ */ jsx18("img", { src: resolveAssetUrl(key), alt: str(data.alt), className: "max-w-full rounded-md" });
1538
- }
1539
- function FilePreview({ data }) {
1540
- const name = str(data.filename);
1541
- if (name === "") return /* @__PURE__ */ jsx18("p", { className: HINT, children: "No file chosen" });
1542
- return /* @__PURE__ */ jsx18("p", { className: "text-sm", children: name });
1543
- }
1544
- function CalloutPreview({ data }) {
1545
- const body = str(data.body);
1546
- if (body.trim() === "") return /* @__PURE__ */ jsx18("p", { className: HINT, children: "Empty callout" });
1547
- const tone = str(data.tone) || "info";
1548
- const paint = TONE[tone] ?? "border-border bg-muted/50";
1549
- return /* @__PURE__ */ jsx18("div", { "data-tone": tone, className: `rounded-md border-l-4 ${paint} p-3 text-sm`, children: body });
1550
- }
1551
-
1552
- // src/blocks/core.ts
1553
- var coreBlocks = [
1554
- defineBlock({
1555
- type: "MARKDOWN",
1556
- label: "Markdown",
1557
- icon: FileText,
1558
- editor: MarkdownBlockEditor,
1559
- preview: MarkdownPreview,
1560
- initialData: () => ({ markdown: "" })
1561
- }),
1562
- defineBlock({
1563
- type: "CALLOUT",
1564
- label: "Callout",
1565
- icon: MessageSquare,
1566
- editor: CalloutBlockEditor,
1567
- preview: CalloutPreview,
1568
- initialData: () => ({ tone: "info", body: "" })
1569
- }),
1570
- defineBlock({
1571
- type: "CODE",
1572
- label: "Code",
1573
- icon: Code2,
1574
- editor: CodeBlockEditor,
1575
- preview: CodePreview,
1576
- initialData: () => ({ language: "", code: "" })
1577
- }),
1578
- defineBlock({
1579
- type: "IMAGE",
1580
- label: "Image",
1581
- icon: Image,
1582
- editor: ImageBlockEditor,
1583
- preview: ImagePreview,
1584
- initialData: () => ({ storageKey: "", alt: "" })
1585
- }),
1586
- defineBlock({
1587
- type: "FILE",
1588
- label: "File",
1589
- icon: Paperclip,
1590
- editor: FileBlockEditor,
1591
- preview: FilePreview,
1592
- initialData: () => ({ storageKey: "", filename: "" })
1593
- })
1594
- ];
1595
-
1596
- // src/hooks/useBlockList.ts
1597
- import * as React11 from "react";
1598
-
1599
- // src/hooks/new-block-id.ts
1600
- function newBlockId() {
1601
- const webCrypto = globalThis.crypto;
1602
- if (webCrypto && typeof webCrypto.randomUUID === "function") return webCrypto.randomUUID();
1603
- const random = () => Math.random().toString(36).slice(2, 10);
1604
- return `blk-${Date.now().toString(36)}-${random()}${random()}`;
1605
- }
1606
-
1607
- // src/hooks/useBlockList.ts
1608
- var removalSeq = 0;
1609
- var UNDO_DEPTH = 20;
1610
- function reducer(state, action) {
1611
- switch (action.type) {
1612
- case "insertAt": {
1613
- const next = [...state.blocks];
1614
- next.splice(Math.max(0, Math.min(action.index, state.blocks.length)), 0, action.block);
1615
- return { ...state, blocks: next };
1616
- }
1617
- case "update": {
1618
- return {
1619
- ...state,
1620
- blocks: state.blocks.map((b) => b.id === action.id ? { ...b, data: action.data } : b)
1621
- };
1622
- }
1623
- case "remove": {
1624
- const index = state.blocks.findIndex((b) => b.id === action.id);
1625
- if (index === -1) return state;
1626
- const removals = new Map(state.removals);
1627
- removals.set(action.token, { block: state.blocks[index], index });
1628
- while (removals.size > UNDO_DEPTH) {
1629
- const oldest = removals.keys().next();
1630
- if (oldest.done) break;
1631
- removals.delete(oldest.value);
1632
- }
1633
- return { blocks: state.blocks.filter((b) => b.id !== action.id), removals };
1634
- }
1635
- case "undo": {
1636
- const removal = state.removals.get(action.token);
1637
- if (!removal) return state;
1638
- const next = [...state.blocks];
1639
- next.splice(Math.min(removal.index, state.blocks.length), 0, removal.block);
1640
- const removals = new Map(state.removals);
1641
- removals.delete(action.token);
1642
- return { blocks: next, removals };
1643
- }
1644
- case "move": {
1645
- const j = action.index + action.dir;
1646
- if (j < 0 || j >= state.blocks.length) return state;
1647
- const next = [...state.blocks];
1648
- [next[action.index], next[j]] = [next[j], next[action.index]];
1649
- return { ...state, blocks: next };
1650
- }
1651
- case "reorder": {
1652
- const { from, to } = action;
1653
- if (from < 0 || to < 0 || from >= state.blocks.length || to >= state.blocks.length) return state;
1654
- const next = [...state.blocks];
1655
- const [moved] = next.splice(from, 1);
1656
- next.splice(to, 0, moved);
1657
- return { ...state, blocks: next };
1658
- }
1659
- case "reset":
1660
- return { blocks: action.blocks, removals: /* @__PURE__ */ new Map() };
1661
- }
1662
- }
1663
- function useBlockList(initial) {
1664
- const [state, dispatch] = React11.useReducer(reducer, { blocks: initial, removals: /* @__PURE__ */ new Map() });
1665
- const insertAt = React11.useCallback(
1666
- (index, type, data = {}) => {
1667
- const id = newBlockId();
1668
- dispatch({ type: "insertAt", index, block: { id, type, data } });
1669
- return id;
1670
- },
1671
- []
1672
- );
1673
- const update = React11.useCallback((id, data) => {
1674
- dispatch({ type: "update", id, data });
1675
- }, []);
1676
- const remove = React11.useCallback((id) => {
1677
- removalSeq += 1;
1678
- const token = removalSeq;
1679
- dispatch({ type: "remove", id, token });
1680
- return token;
1681
- }, []);
1682
- const undo = React11.useCallback((token) => {
1683
- dispatch({ type: "undo", token });
1684
- }, []);
1685
- const move = React11.useCallback((index, dir) => {
1686
- dispatch({ type: "move", index, dir });
1687
- }, []);
1688
- const reorder = React11.useCallback((from, to) => {
1689
- dispatch({ type: "reorder", from, to });
1690
- }, []);
1691
- const reset = React11.useCallback((blocks) => {
1692
- dispatch({ type: "reset", blocks });
1693
- }, []);
1694
- return { blocks: state.blocks, insertAt, update, remove, undo, move, reorder, reset };
1695
- }
1696
-
1697
- // src/canvas/announcements.ts
1698
- function describeBlock(blocks, blockTypes, id) {
1699
- const index = blocks.findIndex((b) => b.id === id);
1700
- if (index === -1) return "block";
1701
- const { type } = blocks[index];
1702
- const label = blockByType(blockTypes, type)?.label ?? type;
1703
- return `${label} block, position ${index + 1} of ${blocks.length}`;
1704
- }
1705
- function buildAnnouncements(blocks, blockTypes) {
1706
- const describe = (id) => describeBlock(blocks, blockTypes, String(id));
1707
- return {
1708
- onDragStart({ active }) {
1709
- return `Picked up ${describe(active.id)}.`;
1710
- },
1711
- onDragOver({ active, over }) {
1712
- if (!over) return `${describe(active.id)} is no longer over a drop position.`;
1713
- return `${describe(active.id)} was moved over ${describe(over.id)}.`;
1714
- },
1715
- onDragEnd({ active, over }) {
1716
- if (!over) return `${describe(active.id)} was dropped.`;
1717
- return `${describe(active.id)} was dropped over ${describe(over.id)}.`;
1718
- },
1719
- onDragCancel({ active }) {
1720
- return `Dragging was cancelled. ${describe(active.id)} was dropped.`;
1721
- }
1722
- };
1723
- }
1724
-
1725
- // src/canvas/BlockActionsMenu.tsx
1726
- import { ArrowDown, ArrowUp, MoreHorizontal, Trash2 } from "lucide-react";
1727
-
1728
- // src/ui/dropdown-menu.tsx
1729
- import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
1730
- import { CheckIcon as CheckIcon2, ChevronRightIcon, CircleIcon } from "lucide-react";
1731
- import { jsx as jsx19, jsxs as jsxs13 } from "react/jsx-runtime";
1732
- function DropdownMenu(props) {
1733
- return /* @__PURE__ */ jsx19(DropdownMenuPrimitive.Root, { "data-slot": "dropdown-menu", ...props });
1734
- }
1735
- function DropdownMenuTrigger(props) {
1736
- return /* @__PURE__ */ jsx19(
1737
- DropdownMenuPrimitive.Trigger,
1738
- {
1739
- "data-slot": "dropdown-menu-trigger",
1740
- ...props
1741
- }
1742
- );
1743
- }
1744
- function DropdownMenuContent({
1745
- className,
1746
- sideOffset = 4,
1747
- ...props
1748
- }) {
1749
- return /* @__PURE__ */ jsx19(DropdownMenuPrimitive.Portal, { children: /* @__PURE__ */ jsx19(
1750
- DropdownMenuPrimitive.Content,
1751
- {
1752
- "data-slot": "dropdown-menu-content",
1753
- sideOffset,
1754
- className: cn(
1755
- "quoin bg-popover text-popover-foreground z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
1756
- className
1757
- ),
1758
- ...props
1759
- }
1760
- ) });
1761
- }
1762
- function DropdownMenuItem({
1763
- className,
1764
- inset,
1765
- ...props
1766
- }) {
1767
- return /* @__PURE__ */ jsx19(
1768
- DropdownMenuPrimitive.Item,
1769
- {
1770
- "data-slot": "dropdown-menu-item",
1771
- "data-inset": inset,
1772
- className: cn(
1773
- "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg:not([class*='size-'])]:size-4",
1774
- HIGHLIGHT_BAR,
1775
- TAP_TARGET_FLOOR,
1776
- className
1777
- ),
1778
- ...props
1779
- }
1780
- );
1781
- }
1782
- function DropdownMenuSeparator({
1783
- className,
1784
- ...props
1785
- }) {
1786
- return /* @__PURE__ */ jsx19(
1787
- DropdownMenuPrimitive.Separator,
1788
- {
1789
- "data-slot": "dropdown-menu-separator",
1790
- className: cn("bg-border -mx-1 my-1 h-px", className),
1791
- ...props
1792
- }
1793
- );
1794
- }
1795
-
1796
- // src/canvas/BlockActionsMenu.tsx
1797
- import { jsx as jsx20, jsxs as jsxs14 } from "react/jsx-runtime";
1798
- function BlockActionsMenu({
1799
- index,
1800
- count,
1801
- label,
1802
- onMove,
1803
- onDelete
1804
- }) {
1805
- const position = index + 1;
1806
- return /* @__PURE__ */ jsxs14(DropdownMenu, { children: [
1807
- /* @__PURE__ */ jsx20(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx20(Button, { type: "button", variant: "ghost", size: "icon-sm", "aria-label": `${label} block ${position} options`, children: /* @__PURE__ */ jsx20(MoreHorizontal, { "aria-hidden": "true" }) }) }),
1808
- /* @__PURE__ */ jsxs14(DropdownMenuContent, { align: "end", className: "w-48", children: [
1809
- /* @__PURE__ */ jsxs14(
1810
- DropdownMenuItem,
1811
- {
1812
- "aria-label": `Move block ${position} up`,
1813
- disabled: index === 0,
1814
- onSelect: () => onMove(index, -1),
1815
- children: [
1816
- /* @__PURE__ */ jsx20(ArrowUp, { "aria-hidden": "true" }),
1817
- /* @__PURE__ */ jsx20("span", { children: "Move up" })
1818
- ]
1819
- }
1820
- ),
1821
- /* @__PURE__ */ jsxs14(
1822
- DropdownMenuItem,
1823
- {
1824
- "aria-label": `Move block ${position} down`,
1825
- disabled: index === count - 1,
1826
- onSelect: () => onMove(index, 1),
1827
- children: [
1828
- /* @__PURE__ */ jsx20(ArrowDown, { "aria-hidden": "true" }),
1829
- /* @__PURE__ */ jsx20("span", { children: "Move down" })
1830
- ]
1831
- }
1832
- ),
1833
- /* @__PURE__ */ jsx20(DropdownMenuSeparator, {}),
1834
- /* @__PURE__ */ jsxs14(
1835
- DropdownMenuItem,
1836
- {
1837
- "aria-label": `Delete block ${position}`,
1838
- className: "text-destructive focus:text-destructive",
1839
- onSelect: onDelete,
1840
- children: [
1841
- /* @__PURE__ */ jsx20(Trash2, { "aria-hidden": "true" }),
1842
- /* @__PURE__ */ jsx20("span", { children: "Delete" })
1843
- ]
1844
- }
1845
- )
1846
- ] })
1847
- ] });
1848
- }
1849
-
1850
- // src/canvas/BlockCard.tsx
1851
- import * as React12 from "react";
1852
- import { Pencil } from "lucide-react";
1853
-
1854
- // src/canvas/BlockEditor.tsx
1855
- import { jsx as jsx21 } from "react/jsx-runtime";
1856
- function BlockEditor({
1857
- type,
1858
- data,
1859
- onChange,
1860
- onInsertBlock,
1861
- onDone
1862
- }) {
1863
- const { blockTypes } = useQuoin();
1864
- const Editor = blockByType(blockTypes, type)?.editor;
1865
- if (!Editor) return null;
1866
- return /* @__PURE__ */ jsx21(Editor, { data, onChange, onInsertBlock, onDone });
1867
- }
1868
-
1869
- // src/canvas/source-offset.ts
1870
- function caretPositionAt(x, y) {
1871
- const doc = document;
1872
- const position = doc.caretPositionFromPoint?.(x, y);
1873
- if (position) return position;
1874
- const range = doc.caretRangeFromPoint?.(x, y);
1875
- return range ? { offsetNode: range.startContainer, offset: range.startOffset } : null;
1876
- }
1877
- function renderedOffsetAt(container, x, y) {
1878
- const position = caretPositionAt(x, y);
1879
- if (!position || position.offsetNode.nodeType !== Node.TEXT_NODE) return null;
1880
- if (!container.contains(position.offsetNode)) return null;
1881
- const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT);
1882
- let before = 0;
1883
- while (walker.nextNode()) {
1884
- if (walker.currentNode === position.offsetNode) return before + position.offset;
1885
- before += walker.currentNode.textContent?.length ?? 0;
1886
- }
1887
- return null;
1888
- }
1889
- var PROBE_LENGTHS = [40, 20, 8];
1890
- function search(source, probe) {
1891
- const literal = probe.clickedText.trim();
1892
- if (literal === "" || literal === probe.rendered.trim()) return source.length;
1893
- const candidates = [literal, literal.replace(/\s+/g, " ")];
1894
- for (const text of candidates) {
1895
- const whole = source.indexOf(text);
1896
- if (whole !== -1) return whole;
1897
- }
1898
- for (const length of PROBE_LENGTHS) {
1899
- for (const text of candidates) {
1900
- if (text.length < length) continue;
1901
- const at = source.indexOf(text.slice(0, length));
1902
- if (at !== -1) return at;
1903
- }
1904
- }
1905
- return source.length;
1906
- }
1907
- function sourceOffsetFor(source, probe) {
1908
- if (probe.renderedOffset !== null && probe.rendered === source) {
1909
- return Math.max(0, Math.min(probe.renderedOffset, source.length));
1910
- }
1911
- return search(source, probe);
1912
- }
1913
-
1914
- // src/dev-warn.ts
1915
- var seen = /* @__PURE__ */ new Set();
1916
- function devWarn(message) {
1917
- if (typeof process !== "undefined" && process?.env?.NODE_ENV === "production") return;
1918
- if (seen.has(message)) return;
1919
- seen.add(message);
1920
- console.warn(`Quoin: ${message}`);
1921
- }
1922
-
1923
- // src/canvas/BlockCard.tsx
1924
- import { jsx as jsx22, jsxs as jsxs15 } from "react/jsx-runtime";
1925
- var INTERACTIVE_SELECTOR = 'a, button, input, textarea, select, [role="button"], [tabindex], iframe';
1926
- var PORTALLED_OVERLAY = '[data-radix-popper-content-wrapper], [role="dialog"]';
1927
- function hasSelectionWithin(container) {
1928
- const selection = window.getSelection();
1929
- if (!selection || selection.isCollapsed || selection.rangeCount === 0) return false;
1930
- return container.contains(selection.getRangeAt(0).commonAncestorContainer);
1931
- }
1932
- function BlockCardImpl({
1933
- block,
1934
- index,
1935
- editingByDefault = false,
1936
- readOnly = false,
1937
- initialSelection = null,
1938
- onUpdate,
1939
- onInsertBlock
1940
- }) {
1941
- const { blockTypes, onError } = useQuoin();
1942
- const definition = blockByType(blockTypes, block.type);
1943
- const Preview = definition?.preview;
1944
- const editable = Boolean(definition?.editor) && !readOnly;
1945
- const [editing, setEditing] = React12.useState(editingByDefault && editable);
1946
- const editButtonRef = React12.useRef(null);
1947
- const editorContainerRef = React12.useRef(null);
1948
- const returnFocusOnClose = React12.useRef(false);
1949
- const clickedRef = React12.useRef(null);
1950
- const scrollTopRef = React12.useRef(null);
1951
- const seedRef = React12.useRef(initialSelection);
1952
- React12.useEffect(() => {
1953
- seedRef.current = initialSelection;
1954
- });
1955
- const handleInsertBlock = React12.useCallback(
1956
- (type) => onInsertBlock?.(index + 1, type),
1957
- [onInsertBlock, index]
1958
- );
1959
- const leaveEditing = React12.useCallback(() => {
1960
- returnFocusOnClose.current = true;
1961
- setEditing(false);
1962
- }, []);
1963
- const settleRef = React12.useRef(null);
1964
- const clearSettle = () => {
1965
- if (settleRef.current === null) return;
1966
- window.clearTimeout(settleRef.current);
1967
- settleRef.current = null;
1968
- };
1969
- React12.useEffect(() => clearSettle, []);
1970
- function stillInside(node) {
1971
- if (!node) return false;
1972
- return Boolean(editorContainerRef.current?.contains(node)) || Boolean(node.closest(PORTALLED_OVERLAY));
1973
- }
1974
- React12.useEffect(() => {
1975
- if (!editing) return;
1976
- function onPointerDown(e) {
1977
- if (stillInside(e.target)) return;
1978
- setEditing(false);
1979
- }
1980
- document.addEventListener("pointerdown", onPointerDown, true);
1981
- return () => document.removeEventListener("pointerdown", onPointerDown, true);
1982
- }, [editing]);
1983
- function closeIfTabbedAway() {
1984
- clearSettle();
1985
- settleRef.current = window.setTimeout(() => {
1986
- settleRef.current = null;
1987
- if (stillInside(document.activeElement)) return;
1988
- setEditing(false);
1989
- }, 0);
1990
- }
1991
- React12.useEffect(() => {
1992
- if (!editing) return;
1993
- const field = editorContainerRef.current?.querySelector(
1994
- 'textarea, input, [role="combobox"]'
1995
- );
1996
- if (!field) return;
1997
- const clicked = clickedRef.current;
1998
- clickedRef.current = null;
1999
- if (clicked === null || !(field instanceof HTMLTextAreaElement)) {
2000
- field.focus();
2001
- if (field instanceof HTMLTextAreaElement) {
2002
- const at = seedRef.current ?? { start: field.value.length, end: field.value.length };
2003
- field.setSelectionRange(at.start, at.end);
2004
- }
2005
- return;
2006
- }
2007
- const offset = sourceOffsetFor(field.value, clicked);
2008
- field.focus({ preventScroll: true });
2009
- field.setSelectionRange(offset, offset);
2010
- }, [editing]);
2011
- useIsomorphicLayoutEffect(() => {
2012
- const scrollTop = scrollTopRef.current;
2013
- scrollTopRef.current = null;
2014
- if (!editing || scrollTop === null) return;
2015
- const scroller = document.scrollingElement ?? document.documentElement;
2016
- if (scroller.scrollTop !== scrollTop) scroller.scrollTop = scrollTop;
2017
- }, [editing]);
2018
- React12.useEffect(() => {
2019
- if (editing || !returnFocusOnClose.current) return;
2020
- returnFocusOnClose.current = false;
2021
- editButtonRef.current?.focus();
2022
- }, [editing]);
2023
- const reportedType = React12.useRef(null);
2024
- React12.useEffect(() => {
2025
- if (definition || reportedType.current === block.type) return;
2026
- reportedType.current = block.type;
2027
- onError(new Error(`No block type is registered for "${block.type}".`));
2028
- }, [definition, block.type, onError]);
2029
- React12.useEffect(() => {
2030
- if (!readOnly || !definition || definition.preview) return;
2031
- devWarn(
2032
- `the block type "${block.type}" has no \`preview\`, so a read-only canvas can draw only its label. Give its definition a \`preview\` to render the block itself.`
2033
- );
2034
- }, [readOnly, definition, block.type]);
2035
- if (definition?.alwaysEditing && editable) {
2036
- return /* @__PURE__ */ jsx22(
2037
- BlockEditor,
2038
- {
2039
- type: block.type,
2040
- data: block.data,
2041
- onChange: (data) => onUpdate(block.id, data),
2042
- onInsertBlock: handleInsertBlock
2043
- }
2044
- );
2045
- }
2046
- if (editing) {
2047
- return /* @__PURE__ */ jsx22(
2048
- "div",
2049
- {
2050
- ref: editorContainerRef,
2051
- onKeyDown: (e) => {
2052
- if (e.key === "Tab") {
2053
- closeIfTabbedAway();
2054
- return;
2055
- }
2056
- if (e.key !== "Escape") return;
2057
- if (!editorContainerRef.current?.contains(e.target)) return;
2058
- returnFocusOnClose.current = true;
2059
- setEditing(false);
2060
- },
2061
- children: /* @__PURE__ */ jsx22(
2062
- BlockEditor,
2063
- {
2064
- type: block.type,
2065
- data: block.data,
2066
- onChange: (data) => onUpdate(block.id, data),
2067
- onInsertBlock: handleInsertBlock,
2068
- onDone: leaveEditing
2069
- }
2070
- )
2071
- }
2072
- );
2073
- }
2074
- const rest = Preview ? /* @__PURE__ */ jsx22(Preview, { data: block.data, blockId: block.id }) : /* @__PURE__ */ jsx22("p", { className: "py-1 text-sm text-muted-foreground", children: definition?.label ?? block.type });
2075
- if (!editable) return /* @__PURE__ */ jsx22("div", { children: rest });
2076
- return /* @__PURE__ */ jsxs15("div", { className: "group/card relative", children: [
2077
- /* @__PURE__ */ jsx22(
2078
- "div",
2079
- {
2080
- onClick: (e) => {
2081
- const target = e.target;
2082
- if (target.closest(INTERACTIVE_SELECTOR)) return;
2083
- if (hasSelectionWithin(e.currentTarget)) return;
2084
- clickedRef.current = {
2085
- rendered: e.currentTarget.textContent ?? "",
2086
- clickedText: target.textContent ?? "",
2087
- renderedOffset: renderedOffsetAt(e.currentTarget, e.clientX, e.clientY)
2088
- };
2089
- scrollTopRef.current = (document.scrollingElement ?? document.documentElement).scrollTop;
2090
- setEditing(true);
2091
- },
2092
- children: rest
2093
- }
2094
- ),
2095
- /* @__PURE__ */ jsx22(
2096
- Button,
2097
- {
2098
- ref: editButtonRef,
2099
- type: "button",
2100
- variant: "ghost",
2101
- size: "icon-sm",
2102
- "aria-label": `Edit block ${index + 1}`,
2103
- onClick: () => setEditing(true),
2104
- className: "absolute right-0 top-0 bg-background/90 opacity-0 transition-opacity group-hover/card:opacity-100 group-focus-within/card:opacity-100 focus-visible:opacity-100",
2105
- children: /* @__PURE__ */ jsx22(Pencil, { "aria-hidden": "true" })
2106
- }
2107
- )
2108
- ] });
2109
- }
2110
- var BlockCard = React12.memo(BlockCardImpl);
2111
-
2112
- // src/canvas/BlockInserter.tsx
2113
- import * as React13 from "react";
2114
- import { Plus } from "lucide-react";
2115
- import { jsx as jsx23, jsxs as jsxs16 } from "react/jsx-runtime";
2116
- function BlockInserter({
2117
- index,
2118
- onInsert,
2119
- label,
2120
- canvasRef
2121
- }) {
2122
- const { blockTypes } = useQuoin();
2123
- const options = paletteBlocks(blockTypes);
2124
- const insertedAtRef = React13.useRef(null);
2125
- function handleSelect(type) {
2126
- insertedAtRef.current = index;
2127
- onInsert(index, type);
2128
- }
2129
- function handleCloseAutoFocus(event) {
2130
- const insertedAt = insertedAtRef.current;
2131
- insertedAtRef.current = null;
2132
- if (insertedAt === null) return;
2133
- event.preventDefault();
2134
- const card = canvasRef.current?.querySelector(`[data-block-index="${insertedAt}"]`);
2135
- if (!card) return;
2136
- if (typeof card.scrollIntoView === "function") {
2137
- card.scrollIntoView({ block: "center", behavior: "smooth" });
2138
- }
2139
- card.querySelector('textarea, input, [role="combobox"]')?.focus();
2140
- }
2141
- return /* @__PURE__ */ jsxs16("div", { className: "group/inserter relative flex h-6 items-center justify-center pointer-coarse:h-11", children: [
2142
- /* @__PURE__ */ jsx23(
2143
- "span",
2144
- {
2145
- "aria-hidden": "true",
2146
- className: "absolute inset-x-0 top-1/2 h-px bg-border opacity-0 transition-opacity group-hover/inserter:opacity-100 group-focus-within/inserter:opacity-100"
2147
- }
2148
- ),
2149
- /* @__PURE__ */ jsxs16(DropdownMenu, { children: [
2150
- /* @__PURE__ */ jsx23(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx23(
2151
- Button,
2152
- {
2153
- type: "button",
2154
- variant: "outline",
2155
- size: "icon-xs",
2156
- "aria-label": label,
2157
- className: "relative z-10 rounded-full bg-background opacity-0 transition-opacity group-hover/inserter:opacity-100 group-focus-within/inserter:opacity-100 focus-visible:opacity-100 pointer-coarse:opacity-100",
2158
- children: /* @__PURE__ */ jsx23(Plus, { "aria-hidden": "true" })
2159
- }
2160
- ) }),
2161
- /* @__PURE__ */ jsx23(DropdownMenuContent, { align: "center", className: "w-56", onCloseAutoFocus: handleCloseAutoFocus, children: options.map((opt) => /* @__PURE__ */ jsxs16(DropdownMenuItem, { onSelect: () => handleSelect(opt.type), children: [
2162
- /* @__PURE__ */ jsx23(opt.icon, { "aria-hidden": "true" }),
2163
- /* @__PURE__ */ jsx23("span", { children: opt.label })
2164
- ] }, opt.type)) })
2165
- ] })
2166
- ] });
2167
- }
2168
-
2169
- // src/canvas/DocumentStarter.tsx
2170
- import * as React14 from "react";
2171
- import { jsx as jsx24, jsxs as jsxs17 } from "react/jsx-runtime";
2172
- var LINE = [
2173
- "w-full resize-none rounded-md border-0 bg-transparent px-2 py-1",
2174
- "text-base leading-7 placeholder:text-muted-foreground",
2175
- "transition-[color,box-shadow]",
2176
- FOCUS_RING
2177
- ].join(" ");
2178
- function DocumentStarter({
2179
- autoFocus = false,
2180
- onStart,
2181
- onInsertBlock
2182
- }) {
2183
- const ref = React14.useRef(null);
2184
- const palette = useSlashPalette(ref);
2185
- const [shortcutsOpen, setShortcutsOpen] = React14.useState(false);
2186
- function handleChange(e) {
2187
- const value = e.target.value;
2188
- e.target.value = "";
2189
- if (value === "") return;
2190
- if (value === "/") palette.openAtCaret();
2191
- else onStart(value);
2192
- }
2193
- function startWith(command) {
2194
- palette.setOpen(false);
2195
- const edit = insertCommand(command, "", 0);
2196
- onStart(edit.value, { start: edit.selectionStart, end: edit.selectionEnd });
2197
- }
2198
- return /* @__PURE__ */ jsxs17("div", { className: "relative", children: [
2199
- /* @__PURE__ */ jsx24(
2200
- "textarea",
2201
- {
2202
- ref,
2203
- "aria-label": "Write something",
2204
- defaultValue: "",
2205
- onChange: handleChange,
2206
- onKeyDown: (e) => {
2207
- if (e.nativeEvent.isComposing || e.keyCode === 229) return;
2208
- if (e.key !== "/") return;
2209
- e.preventDefault();
2210
- palette.openAtCaret();
2211
- },
2212
- autoFocus,
2213
- rows: 1,
2214
- placeholder: "Write, or press / for commands",
2215
- className: LINE
2216
- }
2217
- ),
2218
- /* @__PURE__ */ jsx24(
2219
- SlashPalette,
2220
- {
2221
- open: palette.open,
2222
- caret: palette.caret,
2223
- onOpenChange: palette.setOpen,
2224
- onTextCommand: startWith,
2225
- onBlockCommand: (type) => {
2226
- palette.setOpen(false);
2227
- onInsertBlock(type);
2228
- },
2229
- onShortcuts: () => {
2230
- palette.setOpen(false);
2231
- setShortcutsOpen(true);
2232
- },
2233
- onDismiss: () => palette.setOpen(false)
2234
- }
2235
- ),
2236
- /* @__PURE__ */ jsx24(ShortcutsReference, { open: shortcutsOpen, onOpenChange: setShortcutsOpen })
2237
- ] });
2238
- }
2239
-
2240
- // src/canvas/ReadOnlyCanvas.tsx
2241
- import { jsx as jsx25 } from "react/jsx-runtime";
2242
- function ReadOnlyCanvas({
2243
- blocks,
2244
- onUpdate,
2245
- root
2246
- }) {
2247
- return /* @__PURE__ */ jsx25("div", { ...root, children: /* @__PURE__ */ jsx25("ul", { className: "flex flex-col", children: blocks.map((b, i) => /* @__PURE__ */ jsx25("li", { children: /* @__PURE__ */ jsx25("div", { "data-block-index": i, className: "rounded-lg px-2 py-1", children: /* @__PURE__ */ jsx25(BlockCard, { block: b, index: i, editingByDefault: false, readOnly: true, onUpdate }) }) }, b.id)) }) });
2248
- }
2249
-
2250
- // src/canvas/reorder-indices.ts
2251
- function reorderIndices(ids, activeId, overId) {
2252
- if (overId === null || activeId === overId) return null;
2253
- const from = ids.indexOf(activeId);
2254
- const to = ids.indexOf(overId);
2255
- if (from === -1 || to === -1) return null;
2256
- return { from, to };
2257
- }
2258
-
2259
- // src/canvas/same-blocks.ts
2260
- function sameData(a, b) {
2261
- if (a === b) return true;
2262
- const keys = Object.keys(a);
2263
- if (keys.length !== Object.keys(b).length) return false;
2264
- return keys.every((key) => Object.hasOwn(b, key) && Object.is(a[key], b[key]));
2265
- }
2266
- function sameBlocks(a, b) {
2267
- if (a === b) return true;
2268
- if (a.length !== b.length) return false;
2269
- return a.every((block, i) => {
2270
- const other = b[i];
2271
- return block.id === other.id && block.type === other.type && sameData(block.data, other.data);
2272
- });
2273
- }
2274
-
2275
- // src/canvas/SortableBlock.tsx
2276
- import { useSortable } from "@dnd-kit/sortable";
2277
- import { CSS } from "@dnd-kit/utilities";
2278
- import { GripVertical } from "lucide-react";
2279
- import { jsx as jsx26, jsxs as jsxs18 } from "react/jsx-runtime";
2280
- var GUTTER = [
2281
- "flex items-center justify-end gap-0.5 pb-1",
2282
- "lg:absolute lg:right-full lg:top-1 lg:justify-start lg:pb-0 lg:pr-1",
2283
- "lg:opacity-0 lg:transition-opacity",
2284
- "lg:group-hover/block:opacity-100 lg:group-focus-within/block:opacity-100"
2285
- ].join(" ");
2286
- function SortableBlock({
2287
- id,
2288
- index,
2289
- actions,
2290
- children
2291
- }) {
2292
- const { attributes, listeners, setNodeRef, setActivatorNodeRef, transform, transition, isDragging } = useSortable({ id });
2293
- return /* @__PURE__ */ jsxs18(
2294
- "div",
2295
- {
2296
- ref: setNodeRef,
2297
- style: { transform: CSS.Transform.toString(transform), transition },
2298
- className: cn("group/block relative", isDragging && "z-10 opacity-80"),
2299
- children: [
2300
- /* @__PURE__ */ jsxs18("div", { className: GUTTER, children: [
2301
- actions,
2302
- /* @__PURE__ */ jsx26(
2303
- Button,
2304
- {
2305
- type: "button",
2306
- ref: setActivatorNodeRef,
2307
- variant: "ghost",
2308
- size: "icon-sm",
2309
- className: "cursor-grab text-muted-foreground active:cursor-grabbing",
2310
- "aria-label": `Reorder block ${index + 1}`,
2311
- ...attributes,
2312
- ...listeners,
2313
- children: /* @__PURE__ */ jsx26(GripVertical, { "aria-hidden": "true" })
2314
- }
2315
- )
2316
- ] }),
2317
- /* @__PURE__ */ jsx26(
2318
- "div",
2319
- {
2320
- "data-block-index": index,
2321
- className: cn(
2322
- "rounded-lg px-2 py-1 transition-colors",
2323
- isDragging ? "bg-popover shadow-md" : "group-hover/block:bg-muted/40"
2324
- ),
2325
- children
2326
- }
2327
- )
2328
- ]
2329
- }
2330
- );
2331
- }
2332
-
2333
- // src/canvas/unacknowledged.ts
2334
- var STRUCTURAL_ECHO_LIMIT = 64;
2335
- function createUnacknowledged() {
2336
- return { sequences: /* @__PURE__ */ new WeakMap(), emitted: 0, acknowledged: 0, newest: [] };
2337
- }
2338
- function keepNewest(outstanding) {
2339
- const kept = Math.min(STRUCTURAL_ECHO_LIMIT, outstanding.emitted - outstanding.acknowledged);
2340
- outstanding.newest.splice(0, outstanding.newest.length - kept);
2341
- }
2342
- function remember(outstanding, blocks) {
2343
- outstanding.emitted += 1;
2344
- outstanding.sequences.set(blocks, outstanding.emitted);
2345
- outstanding.newest.push(blocks);
2346
- keepNewest(outstanding);
2347
- }
2348
- function copiedSequence(outstanding, blocks) {
2349
- for (const emitted of outstanding.newest) {
2350
- if (sameBlocks(emitted, blocks)) return outstanding.sequences.get(emitted);
2351
- }
2352
- return void 0;
2353
- }
2354
- function acknowledge(outstanding, blocks) {
2355
- const echoed = outstanding.sequences.get(blocks) ?? copiedSequence(outstanding, blocks);
2356
- if (echoed === void 0 || echoed <= outstanding.acknowledged) return false;
2357
- outstanding.acknowledged = echoed;
2358
- keepNewest(outstanding);
2359
- return true;
2360
- }
2361
- function forgetAll(outstanding) {
2362
- outstanding.acknowledged = outstanding.emitted;
2363
- keepNewest(outstanding);
2364
- }
2365
-
2366
- // src/canvas/BlockCanvas.tsx
2367
- import { jsx as jsx27, jsxs as jsxs19 } from "react/jsx-runtime";
2368
- function Canvas({
2369
- blocks,
2370
- onChange,
2371
- onDelete,
2372
- blockTypes,
2373
- readOnly,
2374
- className,
2375
- id,
2376
- "aria-label": ariaLabel
2377
- }) {
2378
- const list = useBlockList(blocks);
2379
- const { insertAt, update, remove, move, reorder, reset } = list;
2380
- const sensors = useSensors(
2381
- useSensor(PointerSensor, { activationConstraint: { distance: 4 } }),
2382
- useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })
2383
- );
2384
- const dndId = React15.useId();
2385
- const [newBlockId2, setNewBlockId] = React15.useState(null);
2386
- const [seedSelection, setSeedSelection] = React15.useState(null);
2387
- const listRef = React15.useRef(null);
2388
- const pendingDeleteIndexRef = React15.useRef(null);
2389
- const removedRef = React15.useRef(null);
2390
- const announcements = React15.useMemo(
2391
- () => buildAnnouncements(list.blocks, blockTypes),
2392
- [list.blocks, blockTypes]
2393
- );
2394
- const lastEmitted = React15.useRef(blocks);
2395
- const lastSupplied = React15.useRef(blocks);
2396
- const unacknowledged = React15.useRef(createUnacknowledged());
2397
- React15.useEffect(() => {
2398
- if (list.blocks === lastEmitted.current) return;
2399
- lastEmitted.current = list.blocks;
2400
- remember(unacknowledged.current, list.blocks);
2401
- onChange(list.blocks);
2402
- const removed = removedRef.current;
2403
- removedRef.current = null;
2404
- if (removed) onDelete?.(removed.block, removed.index);
2405
- }, [list.blocks, onChange, onDelete]);
2406
- React15.useEffect(() => {
2407
- const previous = lastSupplied.current;
2408
- lastSupplied.current = blocks;
2409
- if (acknowledge(unacknowledged.current, blocks)) return;
2410
- if (sameBlocks(blocks, previous) || sameBlocks(blocks, list.blocks)) return;
2411
- forgetAll(unacknowledged.current);
2412
- lastEmitted.current = blocks;
2413
- reset(blocks);
2414
- }, [blocks, reset]);
2415
- React15.useEffect(() => {
2416
- if (newBlockId2 === null) return;
2417
- setNewBlockId(null);
2418
- setSeedSelection(null);
2419
- }, [newBlockId2]);
2420
- const insertPlain = React15.useCallback(
2421
- (index, type, data) => {
2422
- const seed = data ?? blockByType(blockTypes, type)?.initialData?.() ?? {};
2423
- setNewBlockId(insertAt(index, type, seed));
2424
- },
2425
- [insertAt, blockTypes]
2426
- );
2427
- function handleDelete(index, id2) {
2428
- const block = list.blocks[index];
2429
- pendingDeleteIndexRef.current = index;
2430
- if (block) removedRef.current = { block, index };
2431
- remove(id2);
2432
- }
2433
- React15.useEffect(() => {
2434
- const index = pendingDeleteIndexRef.current;
2435
- pendingDeleteIndexRef.current = null;
2436
- if (index === null || !listRef.current) return;
2437
- const landingIndex = Math.min(index, list.blocks.length - 1);
2438
- const card = landingIndex >= 0 ? listRef.current.querySelector(`[data-block-index="${landingIndex}"]`) : null;
2439
- (card?.querySelector("button") ?? listRef.current.querySelector("button"))?.focus();
2440
- }, [list.blocks]);
2441
- function handleDragEnd(event) {
2442
- const { active, over } = event;
2443
- const result = reorderIndices(
2444
- list.blocks.map((b) => b.id),
2445
- String(active.id),
2446
- over ? String(over.id) : null
2447
- );
2448
- if (result) reorder(result.from, result.to);
2449
- }
2450
- const rootProps = {
2451
- ref: listRef,
2452
- id,
2453
- className: cn("quoin flex flex-col", className),
2454
- role: ariaLabel ? "group" : void 0,
2455
- "aria-label": ariaLabel
2456
- };
2457
- if (readOnly) return /* @__PURE__ */ jsx27(ReadOnlyCanvas, { blocks: list.blocks, onUpdate: update, root: rootProps });
2458
- return /* @__PURE__ */ jsxs19("div", { ...rootProps, children: [
2459
- /* @__PURE__ */ jsx27(BlockInserter, { index: 0, onInsert: insertPlain, label: "Insert a block at the start", canvasRef: listRef }),
2460
- /* @__PURE__ */ jsx27(
2461
- DndContext,
2462
- {
2463
- id: dndId,
2464
- sensors,
2465
- collisionDetection: closestCenter,
2466
- modifiers: [restrictToVerticalAxis],
2467
- accessibility: { announcements },
2468
- onDragEnd: handleDragEnd,
2469
- children: /* @__PURE__ */ jsx27(SortableContext, { items: list.blocks.map((b) => b.id), strategy: verticalListSortingStrategy, children: /* @__PURE__ */ jsx27("ul", { className: "flex flex-col", children: list.blocks.map((b, i) => /* @__PURE__ */ jsxs19("li", { children: [
2470
- /* @__PURE__ */ jsx27(
2471
- SortableBlock,
2472
- {
2473
- id: b.id,
2474
- index: i,
2475
- actions: /* @__PURE__ */ jsx27(
2476
- BlockActionsMenu,
2477
- {
2478
- index: i,
2479
- count: list.blocks.length,
2480
- label: blockByType(blockTypes, b.type)?.label ?? b.type,
2481
- onMove: move,
2482
- onDelete: () => handleDelete(i, b.id)
2483
- }
2484
- ),
2485
- children: /* @__PURE__ */ jsx27(
2486
- BlockCard,
2487
- {
2488
- block: b,
2489
- index: i,
2490
- editingByDefault: b.id === newBlockId2,
2491
- initialSelection: b.id === newBlockId2 ? seedSelection : null,
2492
- onUpdate: update,
2493
- onInsertBlock: insertPlain
2494
- }
2495
- )
2496
- }
2497
- ),
2498
- /* @__PURE__ */ jsx27(
2499
- BlockInserter,
2500
- {
2501
- index: i + 1,
2502
- onInsert: insertPlain,
2503
- label: `Insert a block after block ${i + 1}`,
2504
- canvasRef: listRef
2505
- }
2506
- )
2507
- ] }, b.id)) }) })
2508
- }
2509
- ),
2510
- /* @__PURE__ */ jsx27(
2511
- DocumentStarter,
2512
- {
2513
- autoFocus: list.blocks.length === 0,
2514
- onStart: (markdown, selection) => {
2515
- setSeedSelection(selection ?? null);
2516
- setNewBlockId(insertAt(list.blocks.length, "MARKDOWN", { markdown }));
2517
- },
2518
- onInsertBlock: (type) => insertPlain(list.blocks.length, type)
2519
- }
2520
- )
2521
- ] });
2522
- }
2523
- function BlockCanvas({
2524
- blocks,
2525
- onChange,
2526
- onDelete,
2527
- blockTypes = coreBlocks,
2528
- upload,
2529
- resolveAssetUrl,
2530
- onError,
2531
- readOnly = false,
2532
- className,
2533
- id,
2534
- "aria-label": ariaLabel
2535
- }) {
2536
- const config = React15.useMemo(
2537
- () => ({ blockTypes, upload, resolveAssetUrl, onError }),
2538
- [blockTypes, upload, resolveAssetUrl, onError]
2539
- );
2540
- return /* @__PURE__ */ jsx27(QuoinProvider, { value: config, children: /* @__PURE__ */ jsx27(
2541
- Canvas,
2542
- {
2543
- blocks,
2544
- onChange,
2545
- onDelete,
2546
- blockTypes,
2547
- readOnly,
2548
- className,
2549
- id,
2550
- "aria-label": ariaLabel
2551
- }
2552
- ) });
2553
- }
2554
-
2555
- // src/hooks/useAutosave.ts
2556
- import * as React16 from "react";
2557
-
2558
- // src/hooks/autosave-backoff.ts
2559
- function clearTimer(ref) {
2560
- if (ref.current !== null) clearTimeout(ref.current);
2561
- ref.current = null;
2562
- }
2563
- var alwaysRetry = () => true;
2564
- function reportedFailure(result) {
2565
- return typeof result === "object" && result !== null && "ok" in result && result.ok === false;
2566
- }
2567
- async function attemptSave(run) {
2568
- try {
2569
- const result = await run();
2570
- return reportedFailure(result) ? { error: result } : null;
2571
- } catch (error) {
2572
- return { error };
2573
- }
2574
- }
2575
- function nextRetryDelay(error, retriesSoFar, { debounceMs, maxBackoffMs, maxRetries, isRetryable }) {
2576
- if (retriesSoFar >= maxRetries || !isRetryable(error)) return null;
2577
- return Math.min(debounceMs * 2 ** (retriesSoFar + 1), maxBackoffMs);
2578
- }
2579
-
2580
- // src/hooks/useAutosave.ts
2581
- function useAutosave({
2582
- deps,
2583
- build,
2584
- save,
2585
- debounceMs = 2e3,
2586
- maxBackoffMs = 3e4,
2587
- maxRetries = 8,
2588
- isRetryable = alwaysRetry,
2589
- flushOnHide = true
2590
- }) {
2591
- const [status, setStatus] = React16.useState("idle");
2592
- const [error, setError] = React16.useState(null);
2593
- const dirtyRef = React16.useRef(false);
2594
- const inFlightRef = React16.useRef(false);
2595
- const disposedRef = React16.useRef(false);
2596
- const retriesRef = React16.useRef(0);
2597
- const debounceRef = React16.useRef(null);
2598
- const backoffRef = React16.useRef(null);
2599
- const buildRef = React16.useRef(build);
2600
- const saveRef = React16.useRef(save);
2601
- const runRef = React16.useRef(() => {
2602
- });
2603
- React16.useEffect(() => {
2604
- buildRef.current = build;
2605
- saveRef.current = save;
2606
- const policy = { debounceMs, maxBackoffMs, maxRetries, isRetryable };
2607
- runRef.current = () => {
2608
- clearTimer(debounceRef);
2609
- clearTimer(backoffRef);
2610
- if (inFlightRef.current) return;
2611
- inFlightRef.current = true;
2612
- dirtyRef.current = false;
2613
- if (!disposedRef.current) setStatus("saving");
2614
- void (async () => {
2615
- const failure = await attemptSave(() => saveRef.current(buildRef.current()));
2616
- inFlightRef.current = false;
2617
- if (disposedRef.current) {
2618
- if (dirtyRef.current) runRef.current();
2619
- return;
2620
- }
2621
- if (!failure) {
2622
- retriesRef.current = 0;
2623
- setError(null);
2624
- if (dirtyRef.current) runRef.current();
2625
- else setStatus("saved");
2626
- return;
2627
- }
2628
- dirtyRef.current = true;
2629
- setError(failure.error);
2630
- const delay = nextRetryDelay(failure.error, retriesRef.current, policy);
2631
- if (delay === null) {
2632
- setStatus("failed");
2633
- return;
2634
- }
2635
- retriesRef.current += 1;
2636
- setStatus("retrying");
2637
- backoffRef.current = setTimeout(() => runRef.current(), delay);
2638
- })();
2639
- };
2640
- });
2641
- const seenRef = React16.useRef(null);
2642
- React16.useEffect(() => {
2643
- const seen2 = seenRef.current;
2644
- seenRef.current = deps;
2645
- if (seen2 === null) return;
2646
- const unchanged = seen2.length === deps.length && seen2.every((v, i) => v === deps[i]);
2647
- if (!unchanged) dirtyRef.current = true;
2648
- }, deps);
2649
- React16.useEffect(() => {
2650
- if (!dirtyRef.current) return;
2651
- clearTimer(debounceRef);
2652
- debounceRef.current = setTimeout(() => runRef.current(), debounceMs);
2653
- return () => clearTimer(debounceRef);
2654
- }, deps);
2655
- React16.useEffect(() => {
2656
- disposedRef.current = false;
2657
- return () => {
2658
- disposedRef.current = true;
2659
- clearTimer(debounceRef);
2660
- clearTimer(backoffRef);
2661
- if (dirtyRef.current) runRef.current();
2662
- };
2663
- }, []);
2664
- const retry = React16.useCallback(() => {
2665
- retriesRef.current = 0;
2666
- runRef.current();
2667
- }, []);
2668
- const flush = React16.useCallback(() => {
2669
- if (dirtyRef.current) retry();
2670
- }, [retry]);
2671
- React16.useEffect(() => {
2672
- if (!flushOnHide) return;
2673
- const onVisibility = () => {
2674
- if (document.visibilityState === "hidden") flush();
2675
- };
2676
- window.addEventListener("pagehide", flush);
2677
- document.addEventListener("visibilitychange", onVisibility);
2678
- return () => {
2679
- window.removeEventListener("pagehide", flush);
2680
- document.removeEventListener("visibilitychange", onVisibility);
2681
- };
2682
- }, [flushOnHide, flush]);
2683
- return { status, error, retry, flush };
2684
- }
16
+ BlockShell,
17
+ EditableRegion,
18
+ EditorView,
19
+ SelectionSink,
20
+ ViewController,
21
+ VoidBlockFallback,
22
+ caretFromPoint,
23
+ defaultRenderers,
24
+ domMapOf,
25
+ hostKey,
26
+ isCrossSelection,
27
+ renderInline,
28
+ useEditorState,
29
+ useQuoinEditor,
30
+ useViewController
31
+ } from "./chunk-ZS2WA357.js";
32
+ import {
33
+ blockByType,
34
+ defineBlock,
35
+ paletteBlocks
36
+ } from "./chunk-XEBWC7ZL.js";
37
+ import {
38
+ Transaction,
39
+ addMarkStep,
40
+ applyTransaction,
41
+ blockInputRules,
42
+ blockRangeSelection,
43
+ coreExtensions,
44
+ countDoc,
45
+ countText,
46
+ createEditor,
47
+ createPluginKey,
48
+ createState,
49
+ defineExtension,
50
+ fromHTML,
51
+ getPluginState,
52
+ insertBlockAfter,
53
+ insertBlocksStep,
54
+ insertText,
55
+ insertTextWithRules,
56
+ joinBlocksStep,
57
+ keyName,
58
+ markInputRules,
59
+ moveBlocksStep,
60
+ nodeSelection,
61
+ normalizeKey,
62
+ redo,
63
+ removeBlocksStep,
64
+ removeMarkStep,
65
+ replaceBlockStep,
66
+ replaceDocStep,
67
+ replaceInlineStep,
68
+ setAttrsStep,
69
+ setBlockType,
70
+ setIndentStep,
71
+ splitBlockStep,
72
+ stepFromJSON,
73
+ textSelection,
74
+ toHTML,
75
+ toMarkdown,
76
+ toPlainText,
77
+ toggleMark,
78
+ typographyRules,
79
+ undo
80
+ } from "./chunk-HJIQT3S5.js";
81
+ import {
82
+ diffBlocks,
83
+ diffInline
84
+ } from "./chunk-NOKWNK2O.js";
85
+ import {
86
+ PALETTE,
87
+ atom,
88
+ coreBlockSpecs,
89
+ createBlockCodec,
90
+ createSchema,
91
+ fromMarkdown,
92
+ normalizeInline,
93
+ parseInline,
94
+ sanitizeHref,
95
+ serializeInline,
96
+ text
97
+ } from "./chunk-LCQSG4M6.js";
2685
98
  export {
2686
99
  BlockCanvas,
100
+ BlockShell,
101
+ EditableRegion,
102
+ EditorView,
2687
103
  FilePickerButton,
104
+ PALETTE,
2688
105
  QuoinProvider,
2689
- TEXT_COMMANDS,
106
+ SelectionSink,
107
+ Transaction,
2690
108
  UploadField,
2691
- applyEdit,
109
+ ViewController,
110
+ VoidBlockFallback,
111
+ addMarkStep,
112
+ applyTransaction,
113
+ atom,
2692
114
  blockByType,
2693
- caretRect,
2694
- continueList,
115
+ blockInputRules,
116
+ blockRangeSelection,
117
+ caretFromPoint,
118
+ coreBlockSpecs,
2695
119
  coreBlocks,
120
+ coreExtensions,
121
+ countDoc,
122
+ countText,
123
+ createBlockCodec,
124
+ createEditor,
125
+ createPluginKey,
126
+ createSchema,
127
+ createState,
128
+ defaultRenderers,
2696
129
  defineBlock,
130
+ defineExtension,
131
+ diffBlocks,
132
+ diffInline,
133
+ domMapOf,
134
+ englishLabels,
2697
135
  formatShortcut,
2698
- indentList,
2699
- insertCommand,
2700
- lineAt,
2701
- linkPaste,
136
+ fromHTML,
137
+ fromMarkdown,
138
+ getPluginState,
139
+ hostKey,
140
+ insertBlockAfter,
141
+ insertBlocksStep,
142
+ insertText,
143
+ insertTextWithRules,
144
+ isCrossSelection,
145
+ joinBlocksStep,
146
+ keyName,
147
+ markInputRules,
148
+ moveBlocksStep,
149
+ nodeSelection,
150
+ normalizeInline,
151
+ normalizeKey,
2702
152
  paletteBlocks,
2703
- shouldOpenPalette,
2704
- toggleLinePrefix,
2705
- useAutoGrow,
153
+ parseInline,
154
+ redo,
155
+ removeBlocksStep,
156
+ removeMarkStep,
157
+ renderInline,
158
+ replaceBlockStep,
159
+ replaceDocStep,
160
+ replaceInlineStep,
161
+ resolveLabels,
162
+ sanitizeHref,
163
+ serializeInline,
164
+ setAttrsStep,
165
+ setBlockType,
166
+ setIndentStep,
167
+ splitBlockStep,
168
+ stepFromJSON,
169
+ text,
170
+ textSelection,
171
+ toHTML,
172
+ toMarkdown,
173
+ toPlainText,
174
+ toggleMark,
175
+ typographyRules,
176
+ undo,
2706
177
  useAutosave,
2707
- useBlockList,
2708
- useMarkdownKeymap,
178
+ useEditorState,
179
+ useLabels,
2709
180
  useQuoin,
2710
- wrapSelection
181
+ useQuoinEditor,
182
+ useViewController
2711
183
  };
2712
184
  //# sourceMappingURL=index.js.map