@hachej/boring-workspace 0.1.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 (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +94 -0
  3. package/dist/CodeEditor-DQqOn4xz.js +266 -0
  4. package/dist/CommandPalette-aM61U-b0.js +5229 -0
  5. package/dist/FileTree-DRq_bfue.js +245 -0
  6. package/dist/MarkdownEditor-DjiHxnRv.js +349 -0
  7. package/dist/WorkspaceLoadingState-By0dZoPD.js +568 -0
  8. package/dist/agent-tool-NvxKfist.d.ts +28 -0
  9. package/dist/app-front.d.ts +485 -0
  10. package/dist/app-front.js +452 -0
  11. package/dist/app-server.d.ts +53 -0
  12. package/dist/app-server.js +769 -0
  13. package/dist/bootstrapServer-BRUqUpVW.d.ts +66 -0
  14. package/dist/boring-workspace.css +1 -0
  15. package/dist/charts.d.ts +114 -0
  16. package/dist/charts.js +143 -0
  17. package/dist/events.d.ts +178 -0
  18. package/dist/events.js +88 -0
  19. package/dist/explorer-DtLUnuah.d.ts +129 -0
  20. package/dist/panel-DnvDNQac.js +6 -0
  21. package/dist/server.d.ts +84 -0
  22. package/dist/server.js +811 -0
  23. package/dist/shared.d.ts +113 -0
  24. package/dist/shared.js +11 -0
  25. package/dist/testing-e2e.d.ts +68 -0
  26. package/dist/testing-e2e.js +45 -0
  27. package/dist/testing.d.ts +464 -0
  28. package/dist/testing.js +10984 -0
  29. package/dist/utils-B6yFEsav.js +8 -0
  30. package/dist/workspace.css +5780 -0
  31. package/dist/workspace.d.ts +2119 -0
  32. package/dist/workspace.js +1884 -0
  33. package/docs/INTERFACES.md +58 -0
  34. package/docs/PLUGIN_STRUCTURE.md +162 -0
  35. package/docs/README.md +19 -0
  36. package/docs/bridge.md +135 -0
  37. package/docs/panels.md +102 -0
  38. package/docs/plans/GENERIC_EXPLORER_PLUGIN_PLAN.md +455 -0
  39. package/docs/plans/MACRO_PLUGIN_GENERIC_HELPERS_AUDIT.md +962 -0
  40. package/docs/plans/PLUGIN_OUTPUTS_ISOLATION_PLAN.md +301 -0
  41. package/docs/plans/README.md +9 -0
  42. package/docs/plans/UI_BRIDGE_OWNERSHIP_REFACTOR.md +303 -0
  43. package/docs/plans/archive/CODE_OWNERSHIP_CLEANUP_PLAN.md +387 -0
  44. package/docs/plans/archive/COMMAND_PALETTE_REGISTRY.md +814 -0
  45. package/docs/plans/archive/DECLARATIVE_LAYOUT_MIGRATION.md +277 -0
  46. package/docs/plans/archive/PLUGIN_MODEL.md +3674 -0
  47. package/docs/plans/archive/SRC_FOLDER_REORG_PLAN.md +307 -0
  48. package/docs/plans/archive/UNIFIED_EVENT_BUS.md +647 -0
  49. package/docs/plans/archive/WORKSPACE_V2_PLAN.md +2489 -0
  50. package/docs/plugins.md +158 -0
  51. package/package.json +164 -0
@@ -0,0 +1,245 @@
1
+ import { jsx as i, jsxs as C } from "react/jsx-runtime";
2
+ import { useRef as w, useEffect as R, useMemo as P, useCallback as x, createContext as H, useContext as L } from "react";
3
+ import { Tree as q } from "react-arborist";
4
+ import { FolderOpenIcon as z, FolderIcon as B, ChevronRightIcon as K, Loader2Icon as E } from "lucide-react";
5
+ import { K as Y } from "./CommandPalette-aM61U-b0.js";
6
+ import { Input as G } from "@hachej/boring-ui-kit";
7
+ import { c as v } from "./utils-B6yFEsav.js";
8
+ const T = /* @__PURE__ */ new Set(), A = H({
9
+ onContextMenu: void 0,
10
+ editing: null,
11
+ pendingPaths: T,
12
+ onSubmitEdit: void 0,
13
+ onCancelEdit: void 0
14
+ });
15
+ function J({
16
+ initialValue: n,
17
+ onSubmit: p,
18
+ onCancel: h,
19
+ isDraft: c
20
+ }) {
21
+ const t = w(null), u = w(!1);
22
+ R(() => {
23
+ const e = t.current;
24
+ if (e)
25
+ if (e.focus(), !c && n.includes(".")) {
26
+ const o = n.lastIndexOf(".");
27
+ e.setSelectionRange(0, o);
28
+ } else
29
+ e.select();
30
+ }, [n, c]);
31
+ const l = () => {
32
+ var o;
33
+ if (u.current) return;
34
+ u.current = !0;
35
+ const e = ((o = t.current) == null ? void 0 : o.value.trim()) ?? "";
36
+ !e || e === n ? h() : p(e);
37
+ };
38
+ return /* @__PURE__ */ i(
39
+ G,
40
+ {
41
+ ref: t,
42
+ type: "text",
43
+ defaultValue: n,
44
+ "data-testid": "file-tree-edit-input",
45
+ "aria-label": c ? "Name" : "Rename",
46
+ onPointerDown: (e) => e.stopPropagation(),
47
+ onClick: (e) => e.stopPropagation(),
48
+ onKeyDown: (e) => {
49
+ e.stopPropagation(), e.key === "Enter" ? (e.preventDefault(), l()) : e.key === "Escape" && (e.preventDefault(), u.current = !0, h());
50
+ },
51
+ onBlur: l,
52
+ className: "h-5 min-w-0 flex-1 rounded-sm border-[color:var(--accent)]/60 px-1 text-[13px] leading-[1.2] focus-visible:ring-[color:var(--accent)]"
53
+ }
54
+ );
55
+ }
56
+ function Q({ node: n, style: p, dragHandle: h }) {
57
+ const { onContextMenu: c, editing: t, pendingPaths: u, onSubmitEdit: l, onCancelEdit: e } = L(A), o = n.data, f = o.kind === "dir", d = (t == null ? void 0 : t.path) === o.path, N = u.has(o.path), b = f ? n.isOpen ? z : B : Y(o.name || "untitled");
58
+ return /* @__PURE__ */ C(
59
+ "div",
60
+ {
61
+ ref: h,
62
+ style: p,
63
+ className: v(
64
+ "group relative mx-1 flex items-center gap-1.5 rounded-md px-2 py-0.5 text-[13px] leading-[1.4] cursor-pointer select-none text-foreground",
65
+ "transition-colors duration-150 ease-[cubic-bezier(0.22,1,0.36,1)]",
66
+ !d && "hover:bg-foreground/[0.04]",
67
+ n.isSelected && !d && "bg-[oklch(from_var(--accent)_l_c_h/0.10)] text-foreground font-medium",
68
+ n.willReceiveDrop && "bg-foreground/5 outline outline-1 outline-border"
69
+ ),
70
+ onClick: (s) => {
71
+ d || (s.stopPropagation(), f ? n.toggle() : (n.select(), n.activate()));
72
+ },
73
+ onContextMenu: (s) => {
74
+ d || o.isDraft || (s.preventDefault(), s.stopPropagation(), c == null || c(s, o));
75
+ },
76
+ children: [
77
+ f ? /* @__PURE__ */ i(
78
+ K,
79
+ {
80
+ className: v(
81
+ "h-3 w-3 shrink-0 text-muted-foreground/70 transition-transform duration-150 ease-[cubic-bezier(0.22,1,0.36,1)]",
82
+ n.isOpen && "rotate-90"
83
+ ),
84
+ strokeWidth: 2
85
+ }
86
+ ) : /* @__PURE__ */ i("span", { className: "w-3 shrink-0" }),
87
+ /* @__PURE__ */ i(
88
+ b,
89
+ {
90
+ className: v(
91
+ "h-4 w-4 shrink-0",
92
+ n.isSelected ? "text-[color:var(--accent)]" : "text-muted-foreground/80"
93
+ ),
94
+ strokeWidth: 1.5
95
+ }
96
+ ),
97
+ d ? /* @__PURE__ */ i(
98
+ J,
99
+ {
100
+ initialValue: (t == null ? void 0 : t.initialValue) ?? o.name ?? "",
101
+ isDraft: !!(t != null && t.isDraft),
102
+ onSubmit: (s) => l == null ? void 0 : l(o.path, s),
103
+ onCancel: () => e == null ? void 0 : e()
104
+ }
105
+ ) : /* @__PURE__ */ i(
106
+ "span",
107
+ {
108
+ className: v(
109
+ "truncate",
110
+ N && "text-muted-foreground italic"
111
+ ),
112
+ children: o.name
113
+ }
114
+ ),
115
+ N && !d && /* @__PURE__ */ i(
116
+ E,
117
+ {
118
+ "data-testid": "file-tree-pending-spinner",
119
+ "aria-label": "Pending",
120
+ className: "ml-auto h-3 w-3 shrink-0 animate-spin text-muted-foreground/70",
121
+ strokeWidth: 2
122
+ }
123
+ )
124
+ ]
125
+ }
126
+ );
127
+ }
128
+ function te({
129
+ files: n,
130
+ selectedPath: p,
131
+ searchQuery: h,
132
+ height: c = 400,
133
+ editing: t,
134
+ revealPath: u,
135
+ pendingPaths: l,
136
+ onSelect: e,
137
+ onExpand: o,
138
+ onCollapse: f,
139
+ onContextMenu: d,
140
+ onSubmitEdit: N,
141
+ onCancelEdit: b,
142
+ onDragDrop: s,
143
+ className: I
144
+ }) {
145
+ const k = w(null);
146
+ R(() => {
147
+ if (!(t != null && t.isDraft)) return;
148
+ const r = requestAnimationFrame(() => {
149
+ var a;
150
+ (a = k.current) == null || a.scrollTo(t.path);
151
+ });
152
+ return () => cancelAnimationFrame(r);
153
+ }, [t == null ? void 0 : t.isDraft, t == null ? void 0 : t.path]), R(() => {
154
+ if (!u) return;
155
+ const r = requestAnimationFrame(() => {
156
+ var a;
157
+ (a = k.current) == null || a.scrollTo(u);
158
+ });
159
+ return () => cancelAnimationFrame(r);
160
+ }, [n, u]);
161
+ const D = P(
162
+ () => p || void 0,
163
+ [p]
164
+ ), F = x(
165
+ (r) => {
166
+ r.data.kind === "file" && (e == null || e(r.data.path));
167
+ },
168
+ [e]
169
+ ), y = x(
170
+ (r) => {
171
+ var m;
172
+ const a = (m = k.current) == null ? void 0 : m.get(r);
173
+ a && (a.isOpen ? o == null || o(a.data.path) : f == null || f(a.data.path));
174
+ },
175
+ [o, f]
176
+ ), M = x(
177
+ (r) => {
178
+ if (!s) return;
179
+ const a = !r.parentNode || r.parentNode.isRoot, m = a ? "." : r.parentNode.data.path;
180
+ if (!(!a && r.parentNode.data.kind !== "dir"))
181
+ for (const j of r.dragNodes) {
182
+ const g = j.data.path;
183
+ if (m === g || m !== "." && m.startsWith(g + "/")) return;
184
+ s(g, m);
185
+ }
186
+ },
187
+ [s]
188
+ ), O = x(
189
+ (r) => {
190
+ if (!r.parentNode || r.parentNode.isRoot) return !1;
191
+ if (r.parentNode.data.kind !== "dir") return !0;
192
+ for (const a of r.dragNodes)
193
+ if (r.parentNode.data.path === a.data.path || r.parentNode.data.path.startsWith(a.data.path + "/")) return !0;
194
+ return !1;
195
+ },
196
+ []
197
+ ), W = x(
198
+ (r, a) => r.data.name.toLowerCase().includes(a.toLowerCase()),
199
+ []
200
+ ), _ = P(
201
+ () => ({
202
+ onContextMenu: d,
203
+ editing: t ?? null,
204
+ pendingPaths: l ?? T,
205
+ onSubmitEdit: N,
206
+ onCancelEdit: b
207
+ }),
208
+ [d, t, l, N, b]
209
+ );
210
+ return n.length === 0 ? /* @__PURE__ */ i(
211
+ "div",
212
+ {
213
+ className: v(
214
+ "flex h-full items-center justify-center text-sm text-muted-foreground",
215
+ I
216
+ ),
217
+ children: "No files"
218
+ }
219
+ ) : /* @__PURE__ */ i(A.Provider, { value: _, children: /* @__PURE__ */ i("div", { "data-boring-workspace-part": "file-tree", className: v("file-tree", I), children: /* @__PURE__ */ i(
220
+ q,
221
+ {
222
+ ref: k,
223
+ data: n,
224
+ idAccessor: "path",
225
+ childrenAccessor: "children",
226
+ openByDefault: !1,
227
+ width: "100%",
228
+ height: c,
229
+ rowHeight: 26,
230
+ indent: 14,
231
+ selection: D,
232
+ searchTerm: h ?? "",
233
+ searchMatch: W,
234
+ onActivate: F,
235
+ onToggle: y,
236
+ onMove: M,
237
+ disableDrop: O,
238
+ disableEdit: !0,
239
+ children: Q
240
+ }
241
+ ) }) });
242
+ }
243
+ export {
244
+ te as FileTree
245
+ };
@@ -0,0 +1,349 @@
1
+ import { jsxs as A, jsx as t } from "react/jsx-runtime";
2
+ import { useRef as k, useState as y, useEffect as L } from "react";
3
+ import { ReactNodeViewRenderer as S, NodeViewWrapper as B, useEditor as E, EditorContent as U, useEditorState as P } from "@tiptap/react";
4
+ import z from "@tiptap/starter-kit";
5
+ import D from "@tiptap/extension-underline";
6
+ import W from "@tiptap/extension-link";
7
+ import T from "@tiptap/extension-placeholder";
8
+ import F from "@tiptap/extension-task-list";
9
+ import j from "@tiptap/extension-task-item";
10
+ import q from "@tiptap/extension-text-align";
11
+ import V from "@tiptap/extension-highlight";
12
+ import { Table as _ } from "@tiptap/extension-table";
13
+ import { TableRow as $ } from "@tiptap/extension-table-row";
14
+ import { TableHeader as X } from "@tiptap/extension-table-header";
15
+ import { TableCell as K } from "@tiptap/extension-table-cell";
16
+ import O from "@tiptap/extension-image";
17
+ import { c as v } from "./utils-B6yFEsav.js";
18
+ import Q from "@tiptap/extension-code-block-lowlight";
19
+ import { createLowlight as Z, common as G } from "lowlight";
20
+ import { Markdown as J } from "tiptap-markdown";
21
+ import { BoldIcon as Y, ItalicIcon as tt, UnderlineIcon as et, StrikethroughIcon as it, Heading1Icon as rt, Heading2Icon as nt, Heading3Icon as at, ListIcon as ot, ListOrderedIcon as lt, ListChecksIcon as st, QuoteIcon as ct, CodeIcon as ut, LinkIcon as gt, ImageIcon as dt, HighlighterIcon as mt, AlignLeftIcon as ft, AlignCenterIcon as ht, AlignRightIcon as pt, MinusIcon as wt } from "lucide-react";
22
+ import { Toolbar as kt, Input as vt, ToolbarButton as bt, ToolbarSeparator as Lt } from "@hachej/boring-ui-kit";
23
+ const I = 64, x = 2e3, At = O.extend({
24
+ name: "image",
25
+ draggable: !0,
26
+ addAttributes() {
27
+ var a;
28
+ return {
29
+ ...((a = this.parent) == null ? void 0 : a.call(this)) ?? {},
30
+ width: {
31
+ default: null,
32
+ parseHTML: (n) => {
33
+ const r = n.getAttribute("width");
34
+ if (r) {
35
+ const s = parseInt(r, 10);
36
+ return Number.isFinite(s) ? s : null;
37
+ }
38
+ const u = n.style.width;
39
+ if (u && u.endsWith("px")) {
40
+ const s = parseInt(u, 10);
41
+ return Number.isFinite(s) ? s : null;
42
+ }
43
+ return null;
44
+ },
45
+ renderHTML: (n) => n.width ? { width: String(n.width) } : {}
46
+ },
47
+ align: {
48
+ default: "left",
49
+ parseHTML: (n) => {
50
+ const r = n.getAttribute("data-align");
51
+ return r === "left" || r === "center" || r === "right" ? r : n.style.marginLeft === "auto" && n.style.marginRight === "auto" ? "center" : n.style.marginLeft === "auto" ? "right" : "left";
52
+ },
53
+ renderHTML: (n) => ({ "data-align": n.align ?? "left" })
54
+ }
55
+ };
56
+ },
57
+ addNodeView() {
58
+ return S(It);
59
+ }
60
+ });
61
+ function It({ node: i, updateAttributes: a, selected: n }) {
62
+ const r = k(null), u = k(null), [s, d] = y(!1), c = i.attrs.src ?? "", e = i.attrs.alt ?? "", o = i.attrs.title ?? void 0, g = i.attrs.width, m = i.attrs.align ?? "left";
63
+ L(() => {
64
+ if (!s) return;
65
+ const h = (b) => {
66
+ const p = u.current;
67
+ if (!p) return;
68
+ const R = b.clientX - p.x, M = Math.max(I, Math.min(x, p.width + R));
69
+ a({ width: Math.round(M) });
70
+ }, f = () => {
71
+ u.current = null, d(!1);
72
+ };
73
+ return window.addEventListener("pointermove", h), window.addEventListener("pointerup", f), window.addEventListener("pointercancel", f), () => {
74
+ window.removeEventListener("pointermove", h), window.removeEventListener("pointerup", f), window.removeEventListener("pointercancel", f);
75
+ };
76
+ }, [s, a]);
77
+ const H = (h) => {
78
+ var p;
79
+ h.preventDefault(), h.stopPropagation();
80
+ const f = (p = r.current) == null ? void 0 : p.querySelector("img"), b = g ?? (f == null ? void 0 : f.getBoundingClientRect().width) ?? 320;
81
+ u.current = { x: h.clientX, width: b }, d(!0);
82
+ };
83
+ return /* @__PURE__ */ A(
84
+ B,
85
+ {
86
+ ref: r,
87
+ "data-resizable-image": "",
88
+ "data-selected": n ? "true" : void 0,
89
+ className: v(
90
+ "relative block max-w-full align-baseline",
91
+ "[&>img]:block [&>img]:max-w-full [&>img]:h-auto [&>img]:rounded-md",
92
+ m === "left" && "mr-auto",
93
+ m === "center" && "mx-auto",
94
+ m === "right" && "ml-auto",
95
+ n && "ring-2 ring-[color:var(--accent)] ring-offset-1 ring-offset-background"
96
+ ),
97
+ style: { width: g ? `${g}px` : void 0 },
98
+ children: [
99
+ /* @__PURE__ */ t("img", { src: c, alt: e, title: o, draggable: !1 }),
100
+ /* @__PURE__ */ t(
101
+ "span",
102
+ {
103
+ role: "slider",
104
+ "aria-label": "Resize image",
105
+ "aria-valuenow": g ?? 0,
106
+ "aria-valuemin": I,
107
+ "aria-valuemax": x,
108
+ "data-testid": "resize-handle",
109
+ onPointerDown: H,
110
+ className: v(
111
+ "absolute right-0 bottom-0 h-3 w-3 translate-x-1/2 translate-y-1/2 cursor-nwse-resize",
112
+ "rounded-full border border-background bg-[color:var(--accent)] shadow",
113
+ "opacity-0 transition-opacity duration-150 ease-out",
114
+ (n || s) && "opacity-100"
115
+ )
116
+ }
117
+ )
118
+ ]
119
+ }
120
+ );
121
+ }
122
+ const xt = Z(G), Ct = [
123
+ /<script[\s>][\s\S]*?<\/script>/gi,
124
+ /<iframe[\s>][\s\S]*?<\/iframe>/gi,
125
+ /\s+on\w+\s*=\s*["'][^"']*["']/gi,
126
+ /\s+on\w+\s*=\s*\S+/gi,
127
+ /href\s*=\s*["']?\s*javascript:[^"'\s>]*/gi,
128
+ /src\s*=\s*["']?\s*javascript:[^"'\s>]*/gi
129
+ ];
130
+ function Nt(i) {
131
+ let a = i;
132
+ for (const n of Ct)
133
+ a = a.replace(n, "");
134
+ return a;
135
+ }
136
+ const C = [
137
+ z.configure({
138
+ codeBlock: !1,
139
+ link: !1,
140
+ underline: !1
141
+ }),
142
+ D,
143
+ W.configure({
144
+ openOnClick: !0,
145
+ autolink: !0,
146
+ linkOnPaste: !0,
147
+ defaultProtocol: "https",
148
+ HTMLAttributes: { rel: "noopener noreferrer nofollow", target: "_blank" }
149
+ }),
150
+ T.configure({
151
+ placeholder: "Start writing..."
152
+ }),
153
+ F,
154
+ j.configure({ nested: !0 }),
155
+ q.configure({
156
+ types: ["heading", "paragraph"]
157
+ }),
158
+ V,
159
+ _.configure({
160
+ resizable: !0
161
+ }),
162
+ $,
163
+ X,
164
+ K,
165
+ At.configure({
166
+ inline: !1,
167
+ allowBase64: !0
168
+ }),
169
+ Q.configure({ lowlight: xt }),
170
+ J.configure({
171
+ html: !0,
172
+ transformPastedText: !0,
173
+ transformCopiedText: !0
174
+ })
175
+ ];
176
+ function l({ onClick: i, active: a, disabled: n, title: r, children: u }) {
177
+ return /* @__PURE__ */ t(
178
+ bt,
179
+ {
180
+ type: "button",
181
+ size: "icon-xs",
182
+ onClick: i,
183
+ disabled: n,
184
+ title: r,
185
+ "aria-pressed": a,
186
+ className: v(
187
+ "text-muted-foreground/70",
188
+ a && "bg-[color:var(--accent-soft)] text-[color:var(--accent)]"
189
+ ),
190
+ children: u
191
+ }
192
+ );
193
+ }
194
+ function w() {
195
+ return /* @__PURE__ */ t(Lt, { className: "mx-2 h-3.5" });
196
+ }
197
+ function N(i) {
198
+ const a = i.trim().toLowerCase();
199
+ return !a.startsWith("javascript:") && !a.startsWith("data:text/html");
200
+ }
201
+ function Tt(i) {
202
+ return new Promise((a, n) => {
203
+ const r = new FileReader();
204
+ r.onload = () => a(r.result), r.onerror = () => n(r.error ?? new Error("Read failed")), r.readAsDataURL(i);
205
+ });
206
+ }
207
+ function Ht({ editor: i }) {
208
+ const a = (e) => {
209
+ if (i) {
210
+ if (i.isActive("image")) {
211
+ i.chain().focus().updateAttributes("image", { align: e }).run();
212
+ return;
213
+ }
214
+ i.chain().focus().setTextAlign(e).run();
215
+ }
216
+ }, n = k(null), r = P({
217
+ editor: i,
218
+ selector: ({ editor: e }) => e ? {
219
+ bold: e.isActive("bold"),
220
+ italic: e.isActive("italic"),
221
+ underline: e.isActive("underline"),
222
+ strike: e.isActive("strike"),
223
+ h1: e.isActive("heading", { level: 1 }),
224
+ h2: e.isActive("heading", { level: 2 }),
225
+ h3: e.isActive("heading", { level: 3 }),
226
+ bulletList: e.isActive("bulletList"),
227
+ orderedList: e.isActive("orderedList"),
228
+ taskList: e.isActive("taskList"),
229
+ blockquote: e.isActive("blockquote"),
230
+ codeBlock: e.isActive("codeBlock"),
231
+ link: e.isActive("link"),
232
+ highlight: e.isActive("highlight"),
233
+ alignLeft: e.isActive("image") ? (e.getAttributes("image").align ?? "left") === "left" : e.isActive({ textAlign: "left" }),
234
+ alignCenter: e.isActive("image") ? e.getAttributes("image").align === "center" : e.isActive({ textAlign: "center" }),
235
+ alignRight: e.isActive("image") ? e.getAttributes("image").align === "right" : e.isActive({ textAlign: "right" })
236
+ } : null
237
+ });
238
+ if (!i || !r) return null;
239
+ const u = () => {
240
+ const e = window.prompt("URL:");
241
+ e && N(e) && i.chain().focus().extendMarkRange("link").setLink({ href: e }).run();
242
+ }, s = () => {
243
+ const e = window.prompt("Image URL:");
244
+ e && N(e) && i.chain().focus().setImage({ src: e }).run();
245
+ }, d = (e) => {
246
+ var o;
247
+ if (e != null && e.shiftKey) {
248
+ s();
249
+ return;
250
+ }
251
+ (o = n.current) == null || o.click();
252
+ }, c = async (e) => {
253
+ var g;
254
+ const o = (g = e.target.files) == null ? void 0 : g[0];
255
+ if (e.target.value = "", !(!o || !o.type.startsWith("image/")))
256
+ try {
257
+ const m = await Tt(o);
258
+ i.chain().focus().setImage({ src: m, alt: o.name }).run();
259
+ } catch {
260
+ }
261
+ };
262
+ return /* @__PURE__ */ A(kt, { className: "border-b border-border/60 bg-background px-3 py-1.5", "aria-label": "Formatting toolbar", children: [
263
+ /* @__PURE__ */ t(l, { onClick: () => i.chain().focus().toggleBold().run(), active: r.bold, title: "Bold", children: /* @__PURE__ */ t(Y, { className: "h-4 w-4" }) }),
264
+ /* @__PURE__ */ t(l, { onClick: () => i.chain().focus().toggleItalic().run(), active: r.italic, title: "Italic", children: /* @__PURE__ */ t(tt, { className: "h-4 w-4" }) }),
265
+ /* @__PURE__ */ t(l, { onClick: () => i.chain().focus().toggleUnderline().run(), active: r.underline, title: "Underline", children: /* @__PURE__ */ t(et, { className: "h-4 w-4" }) }),
266
+ /* @__PURE__ */ t(l, { onClick: () => i.chain().focus().toggleStrike().run(), active: r.strike, title: "Strikethrough", children: /* @__PURE__ */ t(it, { className: "h-4 w-4" }) }),
267
+ /* @__PURE__ */ t(w, {}),
268
+ /* @__PURE__ */ t(l, { onClick: () => i.chain().focus().toggleHeading({ level: 1 }).run(), active: r.h1, title: "Heading 1", children: /* @__PURE__ */ t(rt, { className: "h-4 w-4" }) }),
269
+ /* @__PURE__ */ t(l, { onClick: () => i.chain().focus().toggleHeading({ level: 2 }).run(), active: r.h2, title: "Heading 2", children: /* @__PURE__ */ t(nt, { className: "h-4 w-4" }) }),
270
+ /* @__PURE__ */ t(l, { onClick: () => i.chain().focus().toggleHeading({ level: 3 }).run(), active: r.h3, title: "Heading 3", children: /* @__PURE__ */ t(at, { className: "h-4 w-4" }) }),
271
+ /* @__PURE__ */ t(w, {}),
272
+ /* @__PURE__ */ t(l, { onClick: () => i.chain().focus().toggleBulletList().run(), active: r.bulletList, title: "Bullet list", children: /* @__PURE__ */ t(ot, { className: "h-4 w-4" }) }),
273
+ /* @__PURE__ */ t(l, { onClick: () => i.chain().focus().toggleOrderedList().run(), active: r.orderedList, title: "Ordered list", children: /* @__PURE__ */ t(lt, { className: "h-4 w-4" }) }),
274
+ /* @__PURE__ */ t(l, { onClick: () => i.chain().focus().toggleTaskList().run(), active: r.taskList, title: "Task list", children: /* @__PURE__ */ t(st, { className: "h-4 w-4" }) }),
275
+ /* @__PURE__ */ t(w, {}),
276
+ /* @__PURE__ */ t(l, { onClick: () => i.chain().focus().toggleBlockquote().run(), active: r.blockquote, title: "Quote", children: /* @__PURE__ */ t(ct, { className: "h-4 w-4" }) }),
277
+ /* @__PURE__ */ t(l, { onClick: () => i.chain().focus().toggleCodeBlock().run(), active: r.codeBlock, title: "Code block", children: /* @__PURE__ */ t(ut, { className: "h-4 w-4" }) }),
278
+ /* @__PURE__ */ t(l, { onClick: u, active: r.link, title: "Link", children: /* @__PURE__ */ t(gt, { className: "h-4 w-4" }) }),
279
+ /* @__PURE__ */ t(
280
+ l,
281
+ {
282
+ onClick: d,
283
+ title: "Image (click to upload, Shift+click for URL)",
284
+ children: /* @__PURE__ */ t(dt, { className: "h-4 w-4" })
285
+ }
286
+ ),
287
+ /* @__PURE__ */ t(
288
+ vt,
289
+ {
290
+ ref: n,
291
+ "data-testid": "image-file-input",
292
+ type: "file",
293
+ accept: "image/*",
294
+ className: "hidden",
295
+ onChange: c
296
+ }
297
+ ),
298
+ /* @__PURE__ */ t(l, { onClick: () => i.chain().focus().toggleHighlight().run(), active: r.highlight, title: "Highlight", children: /* @__PURE__ */ t(mt, { className: "h-4 w-4" }) }),
299
+ /* @__PURE__ */ t(w, {}),
300
+ /* @__PURE__ */ t(l, { onClick: () => a("left"), active: r.alignLeft, title: "Align left", children: /* @__PURE__ */ t(ft, { className: "h-4 w-4" }) }),
301
+ /* @__PURE__ */ t(l, { onClick: () => a("center"), active: r.alignCenter, title: "Center align", children: /* @__PURE__ */ t(ht, { className: "h-4 w-4" }) }),
302
+ /* @__PURE__ */ t(l, { onClick: () => a("right"), active: r.alignRight, title: "Align right", children: /* @__PURE__ */ t(pt, { className: "h-4 w-4" }) }),
303
+ /* @__PURE__ */ t(w, {}),
304
+ /* @__PURE__ */ t(l, { onClick: () => i.chain().focus().setHorizontalRule().run(), title: "Horizontal rule", children: /* @__PURE__ */ t(wt, { className: "h-4 w-4" }) })
305
+ ] });
306
+ }
307
+ function Gt({
308
+ content: i,
309
+ onChange: a,
310
+ readOnly: n = !1,
311
+ placeholder: r,
312
+ className: u
313
+ }) {
314
+ const s = k(a);
315
+ s.current = a;
316
+ const d = k(!1), c = E({
317
+ extensions: r ? [
318
+ ...C.filter((e) => e.name !== "placeholder"),
319
+ T.configure({ placeholder: r })
320
+ ] : C,
321
+ content: i,
322
+ editable: !n,
323
+ editorProps: {
324
+ attributes: {
325
+ class: "tiptap-prose max-w-[68ch] px-8 py-6 focus:outline-none min-h-[200px]"
326
+ },
327
+ transformPastedHTML: Nt
328
+ },
329
+ onUpdate: ({ editor: e }) => {
330
+ var o, g, m;
331
+ d.current || (m = s.current) == null || m.call(s, ((g = (o = e.storage.markdown) == null ? void 0 : o.getMarkdown) == null ? void 0 : g.call(o)) ?? e.getHTML());
332
+ }
333
+ });
334
+ return L(() => {
335
+ !c || c.isDestroyed || c.setEditable(!n);
336
+ }, [c, n]), L(() => {
337
+ var o, g;
338
+ !c || c.isDestroyed || (((g = (o = c.storage.markdown) == null ? void 0 : o.getMarkdown) == null ? void 0 : g.call(o)) ?? c.getHTML()) === i || (d.current = !0, c.commands.setContent(i), d.current = !1);
339
+ }, [c, i]), /* @__PURE__ */ A("div", { className: v("flex h-full flex-col overflow-hidden", u), children: [
340
+ !n && /* @__PURE__ */ t(Ht, { editor: c }),
341
+ /* @__PURE__ */ t("div", { className: "flex-1 overflow-auto", children: /* @__PURE__ */ t(U, { editor: c }) })
342
+ ] });
343
+ }
344
+ export {
345
+ Gt as MarkdownEditor,
346
+ N as isSafeUrl,
347
+ Tt as readFileAsDataUrl,
348
+ Nt as sanitizeHtml
349
+ };