@hachej/boring-workspace 0.1.6 → 0.1.7

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.
@@ -1,458 +0,0 @@
1
- import { jsxs as T, jsx as i } from "react/jsx-runtime";
2
- import { useRef as v, useState as B, useEffect as L } from "react";
3
- import { ReactNodeViewRenderer as $, NodeViewWrapper as E, useEditor as z, EditorContent as P, useEditorState as D } from "@tiptap/react";
4
- import q from "@tiptap/starter-kit";
5
- import j from "@tiptap/extension-underline";
6
- import F from "@tiptap/extension-link";
7
- import S from "@tiptap/extension-placeholder";
8
- import W from "@tiptap/extension-task-list";
9
- import O from "@tiptap/extension-task-item";
10
- import V from "@tiptap/extension-text-align";
11
- import _ from "@tiptap/extension-highlight";
12
- import { Table as X } from "@tiptap/extension-table";
13
- import { TableRow as K } from "@tiptap/extension-table-row";
14
- import { TableHeader as Q } from "@tiptap/extension-table-header";
15
- import { TableCell as J } from "@tiptap/extension-table-cell";
16
- import Z from "@tiptap/extension-image";
17
- import { c as x } from "./utils-B6yFEsav.js";
18
- import { an as G, ao as Y } from "./CommandPalette-CdiVSPYz.js";
19
- import tt from "@tiptap/extension-code-block-lowlight";
20
- import { createLowlight as et, common as it } from "lowlight";
21
- import { Markdown as rt } from "@tiptap/markdown";
22
- import { BoldIcon as nt, ItalicIcon as at, UnderlineIcon as ot, StrikethroughIcon as lt, Heading1Icon as st, Heading2Icon as ct, Heading3Icon as ut, ListIcon as gt, ListOrderedIcon as dt, ListChecksIcon as ft, QuoteIcon as mt, CodeIcon as ht, LinkIcon as pt, ImageIcon as wt, HighlighterIcon as kt, AlignLeftIcon as bt, AlignCenterIcon as vt, AlignRightIcon as It, MinusIcon as xt } from "lucide-react";
23
- import { Toolbar as At, Input as Lt, ToolbarButton as Nt, ToolbarSeparator as Tt } from "@hachej/boring-ui-kit";
24
- const C = 64, y = 2e3;
25
- function Ct(t) {
26
- return t.replace(/\\/g, "\\\\").replace(/]/g, "\\]");
27
- }
28
- function A(t) {
29
- return t.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
30
- }
31
- function R(t) {
32
- if (typeof t == "number" && Number.isFinite(t) && t > 0) return t;
33
- if (typeof t == "string" && t.trim()) {
34
- const n = Number(t);
35
- if (Number.isFinite(n) && n > 0) return n;
36
- }
37
- return null;
38
- }
39
- const yt = Z.extend({
40
- name: "image",
41
- draggable: !0,
42
- parseMarkdown: (t, n) => n.createNode("image", {
43
- src: t.href,
44
- title: t.title,
45
- alt: t.text
46
- }),
47
- renderMarkdown: (t) => {
48
- var k, f, h, w, e, a, u;
49
- const n = typeof ((k = t.attrs) == null ? void 0 : k.src) == "string" ? t.attrs.src : "", r = typeof ((f = t.attrs) == null ? void 0 : f.alt) == "string" ? t.attrs.alt : "", o = typeof ((h = t.attrs) == null ? void 0 : h.title) == "string" ? t.attrs.title : "", c = R((w = t.attrs) == null ? void 0 : w.width), d = R((e = t.attrs) == null ? void 0 : e.height), m = ((a = t.attrs) == null ? void 0 : a.align) === "center" || ((u = t.attrs) == null ? void 0 : u.align) === "right" ? t.attrs.align : "left";
50
- if (!c && !d && m === "left") {
51
- const s = Ct(r);
52
- return o ? `![${s}](${n} "${o.replace(/"/g, '\\"')}")` : `![${s}](${n})`;
53
- }
54
- const l = `<img ${[
55
- `src="${A(n)}"`,
56
- r ? `alt="${A(r)}"` : null,
57
- o ? `title="${A(o)}"` : null,
58
- c ? `width="${c}"` : null,
59
- d ? `height="${d}"` : null
60
- ].filter(Boolean).join(" ")} />`;
61
- return m === "center" || m === "right" ? `<p align="${m}">${l}</p>` : l;
62
- },
63
- addAttributes() {
64
- var n;
65
- return {
66
- ...((n = this.parent) == null ? void 0 : n.call(this)) ?? {},
67
- width: {
68
- default: null,
69
- parseHTML: (r) => {
70
- const o = r.getAttribute("width");
71
- if (o) {
72
- const d = parseInt(o, 10);
73
- return Number.isFinite(d) ? d : null;
74
- }
75
- const c = r.style.width;
76
- if (c && c.endsWith("px")) {
77
- const d = parseInt(c, 10);
78
- return Number.isFinite(d) ? d : null;
79
- }
80
- return null;
81
- },
82
- renderHTML: (r) => r.width ? { width: String(r.width) } : {}
83
- },
84
- align: {
85
- default: "left",
86
- parseHTML: (r) => {
87
- const o = r.getAttribute("data-align");
88
- return o === "left" || o === "center" || o === "right" ? o : r.style.marginLeft === "auto" && r.style.marginRight === "auto" ? "center" : r.style.marginLeft === "auto" ? "right" : "left";
89
- },
90
- renderHTML: (r) => ({ "data-align": r.align ?? "left" })
91
- }
92
- };
93
- },
94
- addNodeView() {
95
- return $(Rt);
96
- }
97
- });
98
- function Rt({ node: t, updateAttributes: n, selected: r }) {
99
- const o = v(null), c = v(null), [d, m] = B(!1), p = t.attrs.src ?? "", l = t.attrs.alt ?? "", k = t.attrs.title ?? void 0, f = t.attrs.width, h = t.attrs.align ?? "left";
100
- L(() => {
101
- if (!d) return;
102
- const e = (u) => {
103
- const s = c.current;
104
- if (!s) return;
105
- const I = u.clientX - s.x, U = Math.max(C, Math.min(y, s.width + I));
106
- n({ width: Math.round(U) });
107
- }, a = () => {
108
- c.current = null, m(!1);
109
- };
110
- return window.addEventListener("pointermove", e), window.addEventListener("pointerup", a), window.addEventListener("pointercancel", a), () => {
111
- window.removeEventListener("pointermove", e), window.removeEventListener("pointerup", a), window.removeEventListener("pointercancel", a);
112
- };
113
- }, [d, n]);
114
- const w = (e) => {
115
- var s;
116
- e.preventDefault(), e.stopPropagation();
117
- const a = (s = o.current) == null ? void 0 : s.querySelector("img"), u = f ?? (a == null ? void 0 : a.getBoundingClientRect().width) ?? 320;
118
- c.current = { x: e.clientX, width: u }, m(!0);
119
- };
120
- return /* @__PURE__ */ T(
121
- E,
122
- {
123
- ref: o,
124
- "data-resizable-image": "",
125
- "data-selected": r ? "true" : void 0,
126
- className: x(
127
- "relative block max-w-full align-baseline",
128
- "[&>img]:block [&>img]:max-w-full [&>img]:h-auto [&>img]:rounded-md",
129
- h === "left" && "mr-auto",
130
- h === "center" && "mx-auto",
131
- h === "right" && "ml-auto",
132
- r && "ring-2 ring-[color:var(--accent)] ring-offset-1 ring-offset-background"
133
- ),
134
- style: { width: f ? `${f}px` : void 0 },
135
- children: [
136
- /* @__PURE__ */ i("img", { src: p, alt: l, title: k, draggable: !1 }),
137
- /* @__PURE__ */ i(
138
- "span",
139
- {
140
- role: "slider",
141
- "aria-label": "Resize image",
142
- "aria-valuenow": f ?? 0,
143
- "aria-valuemin": C,
144
- "aria-valuemax": y,
145
- "data-testid": "resize-handle",
146
- onPointerDown: w,
147
- className: x(
148
- "absolute right-0 bottom-0 h-3 w-3 translate-x-1/2 translate-y-1/2 cursor-nwse-resize",
149
- "rounded-full border border-background bg-[color:var(--accent)] shadow",
150
- "opacity-0 transition-opacity duration-150 ease-out",
151
- (r || d) && "opacity-100"
152
- )
153
- }
154
- )
155
- ]
156
- }
157
- );
158
- }
159
- const Ht = et(it), Mt = [
160
- /<script[\s>][\s\S]*?<\/script>/gi,
161
- /<iframe[\s>][\s\S]*?<\/iframe>/gi,
162
- /\s+on\w+\s*=\s*["'][^"']*["']/gi,
163
- /\s+on\w+\s*=\s*\S+/gi,
164
- /href\s*=\s*["']?\s*javascript:[^"'\s>]*/gi,
165
- /src\s*=\s*["']?\s*javascript:[^"'\s>]*/gi
166
- ];
167
- function Bt(t) {
168
- let n = t;
169
- for (const r of Mt)
170
- n = n.replace(r, "");
171
- return n;
172
- }
173
- const H = [
174
- q.configure({
175
- codeBlock: !1,
176
- link: !1,
177
- underline: !1
178
- }),
179
- j,
180
- F.configure({
181
- openOnClick: !0,
182
- autolink: !0,
183
- linkOnPaste: !0,
184
- defaultProtocol: "https",
185
- HTMLAttributes: { rel: "noopener noreferrer nofollow", target: "_blank" }
186
- }),
187
- S.configure({
188
- placeholder: "Start writing..."
189
- }),
190
- W,
191
- O.configure({ nested: !0 }),
192
- V.configure({
193
- types: ["heading", "paragraph"]
194
- }),
195
- _,
196
- X.configure({
197
- resizable: !0
198
- }),
199
- K,
200
- Q,
201
- J,
202
- yt.configure({
203
- inline: !1,
204
- allowBase64: !0
205
- }),
206
- tt.configure({ lowlight: Ht }),
207
- rt.configure({
208
- markedOptions: {
209
- gfm: !0,
210
- breaks: !1,
211
- pedantic: !1
212
- }
213
- })
214
- ];
215
- function g({ onClick: t, active: n, disabled: r, title: o, children: c }) {
216
- return /* @__PURE__ */ i(
217
- Nt,
218
- {
219
- type: "button",
220
- size: "icon-xs",
221
- onClick: t,
222
- disabled: r,
223
- title: o,
224
- "aria-pressed": n,
225
- className: x(
226
- "text-muted-foreground/70",
227
- n && "bg-[color:var(--accent-soft)] text-[color:var(--accent)]"
228
- ),
229
- children: c
230
- }
231
- );
232
- }
233
- function b() {
234
- return /* @__PURE__ */ i(Tt, { className: "mx-2 h-3.5" });
235
- }
236
- function M(t) {
237
- const n = t.trim().toLowerCase();
238
- return !n.startsWith("javascript:") && !n.startsWith("data:text/html");
239
- }
240
- function N(t) {
241
- return new Promise((n, r) => {
242
- const o = new FileReader();
243
- o.onload = () => n(o.result), o.onerror = () => r(o.error ?? new Error("Read failed")), o.readAsDataURL(t);
244
- });
245
- }
246
- function St(t, n) {
247
- return `${t.replace(/\/$/, "")}${n}`;
248
- }
249
- async function Ut(t, n, r = {}) {
250
- if (!n) return await N(t);
251
- const o = await N(t), c = o.indexOf(","), d = c >= 0 ? o.slice(c + 1) : "", m = { "Content-Type": "application/json" };
252
- r.workspaceRequestId && (m["x-boring-workspace-id"] = r.workspaceRequestId);
253
- const p = await fetch(St(r.apiBaseUrl ?? "", "/api/v1/files/upload"), {
254
- method: "POST",
255
- headers: m,
256
- credentials: "include",
257
- body: JSON.stringify({
258
- filename: t.name,
259
- contentType: t.type,
260
- contentBase64: d,
261
- sourcePath: n
262
- })
263
- });
264
- if (!p.ok) throw new Error(`Upload failed: ${p.status}`);
265
- const l = await p.json();
266
- return l.markdownUrl ?? l.path ?? o;
267
- }
268
- function $t({
269
- editor: t,
270
- documentPath: n,
271
- apiBaseUrl: r,
272
- workspaceRequestId: o,
273
- rawMode: c,
274
- onToggleRawMode: d
275
- }) {
276
- const m = (e) => {
277
- if (t) {
278
- if (t.isActive("image")) {
279
- t.chain().focus().updateAttributes("image", { align: e }).run();
280
- return;
281
- }
282
- t.chain().focus().setTextAlign(e).run();
283
- }
284
- }, p = v(null), l = D({
285
- editor: t,
286
- selector: ({ editor: e }) => e ? {
287
- bold: e.isActive("bold"),
288
- italic: e.isActive("italic"),
289
- underline: e.isActive("underline"),
290
- strike: e.isActive("strike"),
291
- h1: e.isActive("heading", { level: 1 }),
292
- h2: e.isActive("heading", { level: 2 }),
293
- h3: e.isActive("heading", { level: 3 }),
294
- bulletList: e.isActive("bulletList"),
295
- orderedList: e.isActive("orderedList"),
296
- taskList: e.isActive("taskList"),
297
- blockquote: e.isActive("blockquote"),
298
- codeBlock: e.isActive("codeBlock"),
299
- link: e.isActive("link"),
300
- highlight: e.isActive("highlight"),
301
- alignLeft: e.isActive("image") ? (e.getAttributes("image").align ?? "left") === "left" : e.isActive({ textAlign: "left" }),
302
- alignCenter: e.isActive("image") ? e.getAttributes("image").align === "center" : e.isActive({ textAlign: "center" }),
303
- alignRight: e.isActive("image") ? e.getAttributes("image").align === "right" : e.isActive({ textAlign: "right" })
304
- } : null
305
- });
306
- if (!t || !l) return null;
307
- const k = () => {
308
- const e = window.prompt("URL:");
309
- e && M(e) && t.chain().focus().extendMarkRange("link").setLink({ href: e }).run();
310
- }, f = () => {
311
- const e = window.prompt("Image URL:");
312
- e && M(e) && t.chain().focus().setImage({ src: e }).run();
313
- }, h = (e) => {
314
- var a;
315
- if (e != null && e.shiftKey) {
316
- f();
317
- return;
318
- }
319
- (a = p.current) == null || a.click();
320
- }, w = async (e) => {
321
- var u;
322
- const a = (u = e.target.files) == null ? void 0 : u[0];
323
- if (e.target.value = "", !(!a || !a.type.startsWith("image/")))
324
- try {
325
- const s = await Ut(a, n, { apiBaseUrl: r, workspaceRequestId: o });
326
- t.chain().focus().setImage({ src: s, alt: a.name }).run();
327
- } catch {
328
- try {
329
- const s = await N(a);
330
- t.chain().focus().setImage({ src: s, alt: a.name }).run();
331
- } catch {
332
- }
333
- }
334
- };
335
- return /* @__PURE__ */ T(At, { className: "border-b border-border/60 bg-background px-3 py-1.5", "aria-label": "Formatting toolbar", children: [
336
- /* @__PURE__ */ i(g, { onClick: () => t.chain().focus().toggleBold().run(), active: l.bold, title: "Bold", children: /* @__PURE__ */ i(nt, { className: "h-4 w-4" }) }),
337
- /* @__PURE__ */ i(g, { onClick: () => t.chain().focus().toggleItalic().run(), active: l.italic, title: "Italic", children: /* @__PURE__ */ i(at, { className: "h-4 w-4" }) }),
338
- /* @__PURE__ */ i(g, { onClick: () => t.chain().focus().toggleUnderline().run(), active: l.underline, title: "Underline", children: /* @__PURE__ */ i(ot, { className: "h-4 w-4" }) }),
339
- /* @__PURE__ */ i(g, { onClick: () => t.chain().focus().toggleStrike().run(), active: l.strike, title: "Strikethrough", children: /* @__PURE__ */ i(lt, { className: "h-4 w-4" }) }),
340
- /* @__PURE__ */ i(b, {}),
341
- /* @__PURE__ */ i(g, { onClick: () => t.chain().focus().toggleHeading({ level: 1 }).run(), active: l.h1, title: "Heading 1", children: /* @__PURE__ */ i(st, { className: "h-4 w-4" }) }),
342
- /* @__PURE__ */ i(g, { onClick: () => t.chain().focus().toggleHeading({ level: 2 }).run(), active: l.h2, title: "Heading 2", children: /* @__PURE__ */ i(ct, { className: "h-4 w-4" }) }),
343
- /* @__PURE__ */ i(g, { onClick: () => t.chain().focus().toggleHeading({ level: 3 }).run(), active: l.h3, title: "Heading 3", children: /* @__PURE__ */ i(ut, { className: "h-4 w-4" }) }),
344
- /* @__PURE__ */ i(b, {}),
345
- /* @__PURE__ */ i(g, { onClick: () => t.chain().focus().toggleBulletList().run(), active: l.bulletList, title: "Bullet list", children: /* @__PURE__ */ i(gt, { className: "h-4 w-4" }) }),
346
- /* @__PURE__ */ i(g, { onClick: () => t.chain().focus().toggleOrderedList().run(), active: l.orderedList, title: "Ordered list", children: /* @__PURE__ */ i(dt, { className: "h-4 w-4" }) }),
347
- /* @__PURE__ */ i(g, { onClick: () => t.chain().focus().toggleTaskList().run(), active: l.taskList, title: "Task list", children: /* @__PURE__ */ i(ft, { className: "h-4 w-4" }) }),
348
- /* @__PURE__ */ i(b, {}),
349
- /* @__PURE__ */ i(g, { onClick: () => t.chain().focus().toggleBlockquote().run(), active: l.blockquote, title: "Quote", children: /* @__PURE__ */ i(mt, { className: "h-4 w-4" }) }),
350
- /* @__PURE__ */ i(g, { onClick: () => t.chain().focus().toggleCodeBlock().run(), active: l.codeBlock, title: "Code block", children: /* @__PURE__ */ i(ht, { className: "h-4 w-4" }) }),
351
- /* @__PURE__ */ i(g, { onClick: k, active: l.link, title: "Link", children: /* @__PURE__ */ i(pt, { className: "h-4 w-4" }) }),
352
- /* @__PURE__ */ i(
353
- g,
354
- {
355
- onClick: h,
356
- title: "Image (click to upload, Shift+click for URL)",
357
- children: /* @__PURE__ */ i(wt, { className: "h-4 w-4" })
358
- }
359
- ),
360
- /* @__PURE__ */ i(
361
- Lt,
362
- {
363
- ref: p,
364
- "data-testid": "image-file-input",
365
- type: "file",
366
- accept: "image/*",
367
- className: "hidden",
368
- onChange: w
369
- }
370
- ),
371
- /* @__PURE__ */ i(g, { onClick: () => t.chain().focus().toggleHighlight().run(), active: l.highlight, title: "Highlight", children: /* @__PURE__ */ i(kt, { className: "h-4 w-4" }) }),
372
- /* @__PURE__ */ i(b, {}),
373
- /* @__PURE__ */ i(g, { onClick: () => m("left"), active: l.alignLeft, title: "Align left", children: /* @__PURE__ */ i(bt, { className: "h-4 w-4" }) }),
374
- /* @__PURE__ */ i(g, { onClick: () => m("center"), active: l.alignCenter, title: "Center align", children: /* @__PURE__ */ i(vt, { className: "h-4 w-4" }) }),
375
- /* @__PURE__ */ i(g, { onClick: () => m("right"), active: l.alignRight, title: "Align right", children: /* @__PURE__ */ i(It, { className: "h-4 w-4" }) }),
376
- /* @__PURE__ */ i(b, {}),
377
- /* @__PURE__ */ i(g, { onClick: () => t.chain().focus().setHorizontalRule().run(), title: "Horizontal rule", children: /* @__PURE__ */ i(xt, { className: "h-4 w-4" }) }),
378
- /* @__PURE__ */ i(b, {}),
379
- /* @__PURE__ */ i(
380
- g,
381
- {
382
- onClick: d,
383
- active: c,
384
- title: c ? "Rich text" : "Raw markdown",
385
- children: /* @__PURE__ */ i("span", { className: "font-mono text-[10px] font-semibold leading-none tracking-[-0.02em]", children: "MD" })
386
- }
387
- )
388
- ] });
389
- }
390
- function ae({
391
- content: t,
392
- onChange: n,
393
- readOnly: r = !1,
394
- placeholder: o,
395
- className: c,
396
- documentPath: d
397
- }) {
398
- const m = G(), p = Y(), [l, k] = B(!1), f = v(n);
399
- f.current = n;
400
- const h = v(!1), w = t || { type: "doc", content: [{ type: "paragraph" }] }, e = t ? "markdown" : "json", a = z({
401
- extensions: o ? [
402
- ...H.filter((u) => u.name !== "placeholder"),
403
- S.configure({ placeholder: o })
404
- ] : H,
405
- content: w,
406
- contentType: e,
407
- editable: !r,
408
- editorProps: {
409
- attributes: {
410
- class: "tiptap-prose max-w-[68ch] px-8 py-6 focus:outline-none min-h-[200px]"
411
- },
412
- transformPastedHTML: Bt
413
- },
414
- onUpdate: ({ editor: u }) => {
415
- var s, I;
416
- h.current || (I = f.current) == null || I.call(f, ((s = u.getMarkdown) == null ? void 0 : s.call(u)) ?? u.getHTML());
417
- }
418
- });
419
- return L(() => {
420
- !a || a.isDestroyed || a.setEditable(!r);
421
- }, [a, r]), L(() => {
422
- var s;
423
- !a || a.isDestroyed || (((s = a.getMarkdown) == null ? void 0 : s.call(a)) ?? a.getHTML()) === t || (h.current = !0, a.commands.setContent(w, { contentType: e }), h.current = !1);
424
- }, [a, t]), /* @__PURE__ */ T("div", { className: x("flex h-full flex-col overflow-hidden", c), children: [
425
- !r && /* @__PURE__ */ i(
426
- $t,
427
- {
428
- editor: a,
429
- documentPath: d,
430
- apiBaseUrl: m,
431
- workspaceRequestId: p,
432
- rawMode: l,
433
- onToggleRawMode: () => k((u) => !u)
434
- }
435
- ),
436
- /* @__PURE__ */ i("div", { className: "flex-1 overflow-auto", children: l && !r ? /* @__PURE__ */ i(
437
- "textarea",
438
- {
439
- "aria-label": "Raw markdown",
440
- "data-testid": "markdown-raw-editor",
441
- className: "h-full min-h-[200px] w-full resize-none bg-background px-8 py-6 font-mono text-[13px] leading-6 text-foreground outline-none placeholder:text-muted-foreground/70",
442
- value: t,
443
- placeholder: o,
444
- spellCheck: !1,
445
- onChange: (u) => {
446
- var s;
447
- return (s = f.current) == null ? void 0 : s.call(f, u.target.value);
448
- }
449
- }
450
- ) : /* @__PURE__ */ i(P, { editor: a }) })
451
- ] });
452
- }
453
- export {
454
- ae as MarkdownEditor,
455
- M as isSafeUrl,
456
- N as readFileAsDataUrl,
457
- Bt as sanitizeHtml
458
- };