@hitch42/applet 0.1.1-beta.14 → 0.1.1-beta.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_internal/server.d.ts +10 -1
- package/dist/_internal/server.js +16 -2
- package/dist/applet.js +12 -12
- package/dist/auth-D3CrCtZq.js +21 -0
- package/dist/{build-CwOM9C0W.js → build-XHoa_sJL.js} +1 -1
- package/dist/build-ueR7XjZT.js +2 -0
- package/dist/{ci-BKw14X2h.js → ci-B5-mH-YW.js} +1 -1
- package/dist/client.d.ts +3 -3
- package/dist/contract-BbOj5EB7.js +23 -0
- package/dist/{contract-CZdT22tB.d.ts → contract-D9wEvxGI.d.ts} +1 -1
- package/dist/{deploy-CaGPhe2h.js → deploy-C5JDIn2Y.js} +4 -3
- package/dist/device-BgIfytaR.js +135 -0
- package/dist/email-protocol-B9u_dh2p.d.ts +202 -0
- package/dist/{generate-CORu1X2X.js → generate-BV_emZgy.js} +39 -13
- package/dist/generate-C9bWqdaB.js +2 -0
- package/dist/hitch-file-url-BPKj9zVG.js +25 -0
- package/dist/index.d.ts +15 -25
- package/dist/index.js +80 -192
- package/dist/{init-CD-xOYWV.js → init-BGQ_bP44.js} +5 -5
- package/dist/{manifest-CuY3ICpN.js → load-config-Dkr2VxZN.js} +37 -2
- package/dist/{login-CUx3TmFQ.js → login-CceGvlMi.js} +3 -1
- package/dist/{logs-BySh9fnH.js → logs-DOkT5ots.js} +3 -3
- package/dist/{manifest-ARAy2HP7.d.ts → manifest-BSUtHbu-.d.ts} +1 -4
- package/dist/markdown-CwlqzBtP.js +88 -0
- package/dist/markdown-DSHOQIOb.d.ts +14 -0
- package/dist/markdown-editor-BmEaBzd7.d.ts +27 -0
- package/dist/markdown-editor-C5mLHGqu.js +428 -0
- package/dist/{project-z7x0omdw.js → project-CCVqCCgU.js} +5 -4
- package/dist/react/markdown-editor.d.ts +2 -0
- package/dist/react/markdown-editor.js +2 -0
- package/dist/react/markdown.d.ts +2 -0
- package/dist/react/markdown.js +2 -0
- package/dist/react.d.ts +2 -37
- package/dist/react.js +2 -534
- package/dist/{schedules-B_p9U-vM.js → schedules-D6-3v-Az.js} +1 -1
- package/dist/server.d.ts +3 -3
- package/dist/store-B8N_8JIN.js +196 -0
- package/dist/{upload-DKZzz5kW.d.ts → upload-D_kkmne_.d.ts} +5 -9
- package/dist/{variables-D9nEFyOK.js → variables-BSFZnLxY.js} +2 -2
- package/dist/vite.d.ts +1 -1
- package/dist/vite.js +165 -35
- package/dist/workspace-8yqXsvce.js +31 -0
- package/fonts/inter/LICENSE.txt +92 -0
- package/fonts/inter/inter-cyrillic-ext.woff2 +0 -0
- package/fonts/inter/inter-cyrillic.woff2 +0 -0
- package/fonts/inter/inter-greek-ext.woff2 +0 -0
- package/fonts/inter/inter-greek.woff2 +0 -0
- package/fonts/inter/inter-latin-ext.woff2 +0 -0
- package/fonts/inter/inter-latin.woff2 +0 -0
- package/fonts/inter/inter-vietnamese.woff2 +0 -0
- package/fonts/inter.css +81 -0
- package/package.json +15 -1
- package/scaffold/.agents/skills/hitch-applets/references/markdown.md +20 -2
- package/scaffold/applet/src/client/styles/globals.css +6 -1
- package/dist/build-CeRZYhM9.js +0 -2
- package/dist/email-WA4jnzZ0.js +0 -211
- package/dist/email-protocol-DqJ6YCiC.d.ts +0 -230
- package/dist/generate-CtbF9Zxd.js +0 -2
- package/dist/load-config-BeQZ2yOa.js +0 -27
- package/dist/me-BW7Paary.js +0 -32
- package/dist/workspace-D0dnnlxL.js +0 -385
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { n as cachedDownloadUrl, r as invalidateDownloadUrl, t as HITCH_FILE_PREFIX } from "./hitch-file-url-BPKj9zVG.js";
|
|
2
|
+
import { useEffect, useMemo, useState } from "react";
|
|
3
|
+
import { jsx } from "react/jsx-runtime";
|
|
4
|
+
import { Streamdown, defaultRehypePlugins, defaultUrlTransform } from "streamdown";
|
|
5
|
+
//#region src/react/markdown.tsx
|
|
6
|
+
function withHitchFileProtocol() {
|
|
7
|
+
const sanitize = defaultRehypePlugins.sanitize;
|
|
8
|
+
if (!Array.isArray(sanitize)) throw new Error("streamdown's default sanitize plugin is no longer [plugin, schema].");
|
|
9
|
+
const [plugin, schema] = sanitize;
|
|
10
|
+
if (!Array.isArray(schema.protocols?.src)) throw new Error("streamdown's sanitize schema no longer lists src protocols.");
|
|
11
|
+
return Object.values({
|
|
12
|
+
...defaultRehypePlugins,
|
|
13
|
+
sanitize: [plugin, {
|
|
14
|
+
...schema,
|
|
15
|
+
protocols: {
|
|
16
|
+
...schema.protocols,
|
|
17
|
+
src: [...schema.protocols.src, "hitch-file"]
|
|
18
|
+
}
|
|
19
|
+
}]
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
function HitchImage({ src, alt, files, node: _node, ...props }) {
|
|
23
|
+
const id = typeof src === "string" && src.startsWith("hitch-file://") ? src.slice(HITCH_FILE_PREFIX.length) : void 0;
|
|
24
|
+
const [state, setState] = useState({ id });
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (id === void 0 || files === void 0) return;
|
|
27
|
+
let active = true;
|
|
28
|
+
cachedDownloadUrl(files, id).then((url) => {
|
|
29
|
+
if (active) setState({
|
|
30
|
+
id,
|
|
31
|
+
url
|
|
32
|
+
});
|
|
33
|
+
}, (error) => {
|
|
34
|
+
invalidateDownloadUrl(id);
|
|
35
|
+
console.error(`Failed to download Hitch file "${id}".`, error);
|
|
36
|
+
if (active) setState({
|
|
37
|
+
id,
|
|
38
|
+
error
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
return () => {
|
|
42
|
+
active = false;
|
|
43
|
+
};
|
|
44
|
+
}, [files, id]);
|
|
45
|
+
if (id === void 0) return /* @__PURE__ */ jsx("img", {
|
|
46
|
+
src,
|
|
47
|
+
alt,
|
|
48
|
+
...props
|
|
49
|
+
});
|
|
50
|
+
if (files === void 0 || state.error !== void 0) return /* @__PURE__ */ jsx("span", {
|
|
51
|
+
className: "text-muted-foreground text-sm",
|
|
52
|
+
children: alt ?? "Image unavailable"
|
|
53
|
+
});
|
|
54
|
+
if (state.id !== id || state.url === void 0) return /* @__PURE__ */ jsx("span", {
|
|
55
|
+
className: "inline-block min-h-16 min-w-32 animate-pulse rounded-md bg-muted p-3 text-muted-foreground text-sm",
|
|
56
|
+
role: "img",
|
|
57
|
+
"aria-label": alt ?? "Loading image",
|
|
58
|
+
children: alt ?? "Loading image"
|
|
59
|
+
});
|
|
60
|
+
return /* @__PURE__ */ jsx("img", {
|
|
61
|
+
src: state.url,
|
|
62
|
+
alt,
|
|
63
|
+
...props,
|
|
64
|
+
onError: () => {
|
|
65
|
+
invalidateDownloadUrl(id);
|
|
66
|
+
setState({
|
|
67
|
+
id,
|
|
68
|
+
error: /* @__PURE__ */ new Error("image failed to load")
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
function Markdown({ children, files, className }) {
|
|
74
|
+
const rehypePlugins = useMemo(withHitchFileProtocol, []);
|
|
75
|
+
const components = useMemo(() => ({ img: (props) => /* @__PURE__ */ jsx(HitchImage, {
|
|
76
|
+
...props,
|
|
77
|
+
files
|
|
78
|
+
}) }), [files]);
|
|
79
|
+
return /* @__PURE__ */ jsx(Streamdown, {
|
|
80
|
+
className,
|
|
81
|
+
components,
|
|
82
|
+
rehypePlugins,
|
|
83
|
+
urlTransform: (url, key, node) => url.startsWith("hitch-file://") ? url : defaultUrlTransform(url, key, node),
|
|
84
|
+
children
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
//#endregion
|
|
88
|
+
export { Markdown as t };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { a as UploadedHitchFile } from "./upload-D_kkmne_.js";
|
|
2
|
+
import { ReactElement } from "react";
|
|
3
|
+
//#region src/react/markdown.d.ts
|
|
4
|
+
type MarkdownFiles = {
|
|
5
|
+
download: (id: string) => Promise<Pick<UploadedHitchFile, "downloadUrl">>;
|
|
6
|
+
};
|
|
7
|
+
type MarkdownProps = {
|
|
8
|
+
children: string;
|
|
9
|
+
files?: MarkdownFiles;
|
|
10
|
+
className?: string;
|
|
11
|
+
};
|
|
12
|
+
declare function Markdown({ children, files, className }: MarkdownProps): ReactElement;
|
|
13
|
+
//#endregion
|
|
14
|
+
export { MarkdownFiles as n, MarkdownProps as r, Markdown as t };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { a as UploadedHitchFile } from "./upload-D_kkmne_.js";
|
|
2
|
+
import { ReactElement } from "react";
|
|
3
|
+
import "@tiptap/core";
|
|
4
|
+
//#region src/react/markdown-editor-extensions.d.ts
|
|
5
|
+
type MarkdownEditorFiles = {
|
|
6
|
+
upload: (file: File) => Promise<Pick<UploadedHitchFile, "id" | "downloadUrl">>;
|
|
7
|
+
download: (id: string) => Promise<Pick<UploadedHitchFile, "downloadUrl">>;
|
|
8
|
+
};
|
|
9
|
+
//#endregion
|
|
10
|
+
//#region src/react/markdown-editor.d.ts
|
|
11
|
+
type MarkdownEditorProps = {
|
|
12
|
+
value: string;
|
|
13
|
+
onChange: (value: string) => void;
|
|
14
|
+
files?: MarkdownEditorFiles;
|
|
15
|
+
id?: string;
|
|
16
|
+
placeholder?: string;
|
|
17
|
+
required?: boolean;
|
|
18
|
+
minLength?: number;
|
|
19
|
+
maxLength?: number;
|
|
20
|
+
disabled?: boolean;
|
|
21
|
+
autoFocus?: boolean;
|
|
22
|
+
className?: string;
|
|
23
|
+
onUploadError?: (error: unknown, file: File) => void;
|
|
24
|
+
};
|
|
25
|
+
declare function MarkdownEditor({ value, onChange, files, id, placeholder, required, minLength, maxLength, disabled, autoFocus, className, onUploadError }: MarkdownEditorProps): ReactElement;
|
|
26
|
+
//#endregion
|
|
27
|
+
export { MarkdownEditorProps as n, MarkdownEditorFiles as r, MarkdownEditor as t };
|
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
import { i as seedDownloadUrl, n as cachedDownloadUrl, r as invalidateDownloadUrl, t as HITCH_FILE_PREFIX } from "./hitch-file-url-BPKj9zVG.js";
|
|
2
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
3
|
+
import { BetweenHorizontalEnd, BetweenVerticalEnd, Bold, Code, Heading, Italic, Link, List, ListMinus, ListOrdered, ListTodo, Quote, Strikethrough, Table, Trash2, X } from "lucide-react";
|
|
4
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
import { GapCursor } from "@tiptap/pm/gapcursor";
|
|
6
|
+
import { NodeSelection } from "@tiptap/pm/state";
|
|
7
|
+
import { EditorContent, NodeViewWrapper, ReactNodeViewRenderer, useEditor, useEditorState } from "@tiptap/react";
|
|
8
|
+
import { Button, Toolbar } from "react-aria-components";
|
|
9
|
+
import { twMerge } from "cn";
|
|
10
|
+
import { generateHTML } from "@tiptap/core";
|
|
11
|
+
import Image from "@tiptap/extension-image";
|
|
12
|
+
import { TaskItem, TaskList } from "@tiptap/extension-list";
|
|
13
|
+
import { Table as Table$1, TableCell, TableHeader, TableRow, renderTableToMarkdown } from "@tiptap/extension-table";
|
|
14
|
+
import { Placeholder } from "@tiptap/extensions";
|
|
15
|
+
import { Markdown } from "@tiptap/markdown";
|
|
16
|
+
import StarterKit from "@tiptap/starter-kit";
|
|
17
|
+
//#region src/react/markdown-editor-extensions.tsx
|
|
18
|
+
function sanitizeAltText(text) {
|
|
19
|
+
return text.replaceAll(/[[\]()\\]/g, "").replaceAll(/\s+/g, " ");
|
|
20
|
+
}
|
|
21
|
+
function editorMarkdown(editor) {
|
|
22
|
+
return editor.getMarkdown().trimEnd();
|
|
23
|
+
}
|
|
24
|
+
function tableHasBlockCells(node) {
|
|
25
|
+
return (node.content ?? []).some((row) => (row.content ?? []).some((cell) => cell.content?.length !== 1 || cell.content[0]?.type !== "paragraph"));
|
|
26
|
+
}
|
|
27
|
+
function hitchImageNode(files) {
|
|
28
|
+
return function HitchImageNode({ node, selected, editor, deleteNode }) {
|
|
29
|
+
const src = String(node.attrs.src);
|
|
30
|
+
const alt = typeof node.attrs.alt === "string" ? node.attrs.alt : void 0;
|
|
31
|
+
const id = src.startsWith("hitch-file://") ? src.slice(HITCH_FILE_PREFIX.length) : void 0;
|
|
32
|
+
const [state, setState] = useState({ id });
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (id === void 0 || files === void 0) return;
|
|
35
|
+
let active = true;
|
|
36
|
+
cachedDownloadUrl(files, id).then((url) => {
|
|
37
|
+
if (active) setState({
|
|
38
|
+
id,
|
|
39
|
+
url
|
|
40
|
+
});
|
|
41
|
+
}, (error) => {
|
|
42
|
+
invalidateDownloadUrl(id);
|
|
43
|
+
console.error(`Failed to download Hitch file "${id}".`, error);
|
|
44
|
+
if (active) setState({
|
|
45
|
+
id,
|
|
46
|
+
error
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
return () => {
|
|
50
|
+
active = false;
|
|
51
|
+
};
|
|
52
|
+
}, [files, id]);
|
|
53
|
+
const content = id === void 0 ? /* @__PURE__ */ jsx("img", {
|
|
54
|
+
src,
|
|
55
|
+
alt,
|
|
56
|
+
className: "max-w-full rounded-lg",
|
|
57
|
+
"data-drag-handle": true
|
|
58
|
+
}) : files === void 0 || state.error !== void 0 ? /* @__PURE__ */ jsx("span", {
|
|
59
|
+
className: "text-muted-foreground text-sm",
|
|
60
|
+
children: alt ?? "Image unavailable"
|
|
61
|
+
}) : state.id !== id || state.url === void 0 ? /* @__PURE__ */ jsx("span", {
|
|
62
|
+
className: "inline-block min-h-16 min-w-32 animate-pulse rounded-md bg-muted p-3 text-muted-foreground text-sm",
|
|
63
|
+
role: "img",
|
|
64
|
+
"aria-label": alt ?? "Loading image",
|
|
65
|
+
children: alt ?? "Loading image"
|
|
66
|
+
}) : /* @__PURE__ */ jsx("img", {
|
|
67
|
+
src: state.url,
|
|
68
|
+
alt,
|
|
69
|
+
className: "max-w-full rounded-lg",
|
|
70
|
+
"data-drag-handle": true,
|
|
71
|
+
onError: () => {
|
|
72
|
+
invalidateDownloadUrl(id);
|
|
73
|
+
setState({
|
|
74
|
+
id,
|
|
75
|
+
error: /* @__PURE__ */ new Error("image failed to load")
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
return /* @__PURE__ */ jsxs(NodeViewWrapper, {
|
|
80
|
+
as: "span",
|
|
81
|
+
className: twMerge("relative inline-block max-w-full rounded-lg", selected && "in-[.ProseMirror[contenteditable=true]:focus-within]:ring-2 in-[.ProseMirror[contenteditable=true]:focus-within]:ring-ring"),
|
|
82
|
+
children: [content, selected && /* @__PURE__ */ jsx("button", {
|
|
83
|
+
type: "button",
|
|
84
|
+
contentEditable: false,
|
|
85
|
+
"aria-label": "Remove image",
|
|
86
|
+
className: "absolute top-1.5 right-1.5 hidden size-6 in-[.ProseMirror[contenteditable=true]:focus-within]:inline-flex items-center justify-center rounded-full bg-background/90 text-foreground shadow-sm outline-none hover:bg-muted focus-visible:ring-3 focus-visible:ring-ring/30 [&_svg]:size-3.5",
|
|
87
|
+
onClick: () => {
|
|
88
|
+
deleteNode();
|
|
89
|
+
editor.commands.focus();
|
|
90
|
+
},
|
|
91
|
+
children: /* @__PURE__ */ jsx(X, {})
|
|
92
|
+
})]
|
|
93
|
+
});
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function markdownEditorExtensions(files, placeholder = "") {
|
|
97
|
+
const extensions = [];
|
|
98
|
+
extensions.push(StarterKit.configure({ link: { openOnClick: false } }), Table$1.extend({ renderMarkdown(node, helpers) {
|
|
99
|
+
if (!tableHasBlockCells(node)) return renderTableToMarkdown(node, helpers);
|
|
100
|
+
return `\n${generateHTML({
|
|
101
|
+
type: "doc",
|
|
102
|
+
content: [node]
|
|
103
|
+
}, extensions)}\n`;
|
|
104
|
+
} }), TableRow, TableHeader, TableCell, TaskList, TaskItem.configure({ nested: true }), Image.extend({ addNodeView() {
|
|
105
|
+
return ReactNodeViewRenderer(hitchImageNode(files));
|
|
106
|
+
} }), Markdown, Placeholder.configure({
|
|
107
|
+
placeholder,
|
|
108
|
+
showOnlyWhenEditable: false
|
|
109
|
+
}));
|
|
110
|
+
return extensions;
|
|
111
|
+
}
|
|
112
|
+
//#endregion
|
|
113
|
+
//#region src/react/markdown-editor.tsx
|
|
114
|
+
const buttonClassName = "inline-flex size-7 shrink-0 items-center justify-center rounded-2xl border border-transparent text-muted-foreground outline-none transition-all select-none hover:bg-muted hover:text-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/30 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0";
|
|
115
|
+
const editorClassName = "min-h-32 w-full px-2.5 py-2 text-base md:text-sm outline-none whitespace-pre-wrap space-y-4 [&>*:first-child]:mt-0 [&>*:last-child]:mb-0 [&_h1]:mt-6 [&_h1]:mb-2 [&_h1]:font-semibold [&_h1]:text-3xl [&_h2]:mt-6 [&_h2]:mb-2 [&_h2]:font-semibold [&_h2]:text-2xl [&_h3]:mt-6 [&_h3]:mb-2 [&_h3]:font-semibold [&_h3]:text-xl [&_h4]:mt-6 [&_h4]:mb-2 [&_h4]:font-semibold [&_h4]:text-lg [&_h5]:mt-6 [&_h5]:mb-2 [&_h5]:font-semibold [&_h5]:text-base [&_h6]:mt-6 [&_h6]:mb-2 [&_h6]:font-semibold [&_h6]:text-sm [&_ul]:list-disc [&_ul]:pl-5 [&_ol]:list-decimal [&_ol]:pl-5 [&_blockquote]:my-4 [&_blockquote]:border-l-4 [&_blockquote]:border-muted-foreground/30 [&_blockquote]:pl-4 [&_blockquote]:text-muted-foreground [&_blockquote]:italic [&_code]:rounded [&_code]:bg-muted [&_code]:px-1.5 [&_code]:py-0.5 [&_code]:font-mono [&_code]:text-sm [&_pre]:overflow-x-auto [&_pre]:rounded-md [&_pre]:border [&_pre]:border-border [&_pre]:bg-background [&_pre]:p-4 [&_pre]:text-sm [&_pre_code]:bg-transparent [&_pre_code]:p-0 [&_a]:font-medium [&_a]:text-primary [&_a]:underline [&_a]:wrap-anywhere [&_hr]:my-4 [&_hr]:border-border [&_table]:my-4 [&_table]:w-full [&_table]:border-collapse [&_th]:border [&_th]:border-border [&_th]:px-4 [&_th]:py-2 [&_th]:text-left [&_th]:font-semibold [&_th]:text-sm [&_td]:border [&_td]:border-border [&_td]:px-4 [&_td]:py-2 [&_td]:text-sm [&_ul[data-type=taskList]]:list-none [&_ul[data-type=taskList]]:pl-0 [&_li[data-type=taskItem]]:flex [&_li[data-type=taskItem]]:items-start [&_li[data-type=taskItem]]:gap-2 [&_li[data-type=taskItem]>div]:flex-1 [&_p.is-editor-empty:first-child]:before:content-[attr(data-placeholder)] [&_p.is-editor-empty:first-child]:before:float-left [&_p.is-editor-empty:first-child]:before:h-0 [&_p.is-editor-empty:first-child]:before:pointer-events-none [&_p.is-editor-empty:first-child]:before:text-muted-foreground";
|
|
116
|
+
function findImage(view, src) {
|
|
117
|
+
let found;
|
|
118
|
+
view.state.doc.descendants((node, pos) => {
|
|
119
|
+
if (found !== void 0) return false;
|
|
120
|
+
if (node.type.name === "image" && node.attrs.src === src) found = {
|
|
121
|
+
pos,
|
|
122
|
+
size: node.nodeSize
|
|
123
|
+
};
|
|
124
|
+
return found === void 0;
|
|
125
|
+
});
|
|
126
|
+
return found;
|
|
127
|
+
}
|
|
128
|
+
function MarkdownEditor({ value, onChange, files, id, placeholder, required, minLength, maxLength, disabled, autoFocus, className, onUploadError }) {
|
|
129
|
+
const validationRef = useRef(null);
|
|
130
|
+
const lastEmittedRef = useRef(value);
|
|
131
|
+
const filesRef = useRef(files);
|
|
132
|
+
const uploadErrorRef = useRef(onUploadError);
|
|
133
|
+
const pendingUploads = useRef(0);
|
|
134
|
+
useEffect(() => {
|
|
135
|
+
filesRef.current = files;
|
|
136
|
+
uploadErrorRef.current = onUploadError;
|
|
137
|
+
});
|
|
138
|
+
const hasFiles = files !== void 0;
|
|
139
|
+
const stableFiles = useMemo(() => {
|
|
140
|
+
if (!hasFiles) return void 0;
|
|
141
|
+
const current = () => {
|
|
142
|
+
const filesNow = filesRef.current;
|
|
143
|
+
if (filesNow === void 0) throw new Error("files was removed while the editor used it.");
|
|
144
|
+
return filesNow;
|
|
145
|
+
};
|
|
146
|
+
return {
|
|
147
|
+
upload: (file) => current().upload(file),
|
|
148
|
+
download: (fileId) => current().download(fileId)
|
|
149
|
+
};
|
|
150
|
+
}, [hasFiles]);
|
|
151
|
+
const extensions = useMemo(() => markdownEditorExtensions(stableFiles, placeholder), [stableFiles, placeholder]);
|
|
152
|
+
const applyValidity = () => {
|
|
153
|
+
validationRef.current?.setCustomValidity(pendingUploads.current > 0 ? "Wait for the image upload to finish." : minLength !== void 0 && value.length > 0 && value.length < minLength ? `Use at least ${minLength} characters.` : maxLength !== void 0 && value.length > maxLength ? `Use no more than ${maxLength} characters.` : "");
|
|
154
|
+
};
|
|
155
|
+
useEffect(applyValidity);
|
|
156
|
+
const markUploadPending = (delta) => {
|
|
157
|
+
pendingUploads.current += delta;
|
|
158
|
+
applyValidity();
|
|
159
|
+
};
|
|
160
|
+
const editor = useEditor({
|
|
161
|
+
extensions,
|
|
162
|
+
content: value,
|
|
163
|
+
contentType: "markdown",
|
|
164
|
+
editable: !disabled,
|
|
165
|
+
autofocus: autoFocus ? "end" : false,
|
|
166
|
+
editorProps: {
|
|
167
|
+
attributes: { class: editorClassName },
|
|
168
|
+
handlePaste: (view, event) => {
|
|
169
|
+
const imageFiles = Array.from(event.clipboardData?.files ?? []).filter((file) => file.type.startsWith("image/"));
|
|
170
|
+
if (filesRef.current === void 0 || imageFiles.length === 0) return false;
|
|
171
|
+
uploadImages(view, imageFiles, view.state.selection.from);
|
|
172
|
+
return true;
|
|
173
|
+
},
|
|
174
|
+
handleDrop: (view, event) => {
|
|
175
|
+
const imageFiles = Array.from(event.dataTransfer?.files ?? []).filter((file) => file.type.startsWith("image/"));
|
|
176
|
+
if (filesRef.current === void 0 || imageFiles.length === 0) return false;
|
|
177
|
+
const position = view.posAtCoords({
|
|
178
|
+
left: event.clientX,
|
|
179
|
+
top: event.clientY
|
|
180
|
+
})?.pos;
|
|
181
|
+
if (position === void 0) return false;
|
|
182
|
+
uploadImages(view, imageFiles, position);
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
onCreate: ({ editor: createdEditor }) => {
|
|
187
|
+
if (autoFocus) createdEditor.commands.focus("end");
|
|
188
|
+
else if (createdEditor.state.selection instanceof NodeSelection) {
|
|
189
|
+
const { state, view } = createdEditor;
|
|
190
|
+
view.dispatch(state.tr.setSelection(new GapCursor(state.doc.resolve(0))));
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
onUpdate: ({ editor: updatedEditor, transaction }) => {
|
|
194
|
+
if (!transaction.docChanged) return;
|
|
195
|
+
const markdown = editorMarkdown(updatedEditor);
|
|
196
|
+
if (markdown === lastEmittedRef.current) return;
|
|
197
|
+
lastEmittedRef.current = markdown;
|
|
198
|
+
onChange(markdown);
|
|
199
|
+
}
|
|
200
|
+
}, [extensions]);
|
|
201
|
+
async function uploadImages(view, imageFiles, position) {
|
|
202
|
+
const uploadFiles = filesRef.current;
|
|
203
|
+
if (uploadFiles === void 0) return;
|
|
204
|
+
markUploadPending(1);
|
|
205
|
+
try {
|
|
206
|
+
const pending = imageFiles.map((file) => ({
|
|
207
|
+
file,
|
|
208
|
+
url: URL.createObjectURL(file)
|
|
209
|
+
}));
|
|
210
|
+
const transaction = view.state.tr;
|
|
211
|
+
let insertPosition = position;
|
|
212
|
+
for (const { file, url } of pending) {
|
|
213
|
+
const node = view.state.schema.nodes.image.create({
|
|
214
|
+
src: url,
|
|
215
|
+
alt: sanitizeAltText(file.name)
|
|
216
|
+
});
|
|
217
|
+
transaction.insert(insertPosition, node);
|
|
218
|
+
insertPosition += node.nodeSize;
|
|
219
|
+
}
|
|
220
|
+
view.dispatch(transaction);
|
|
221
|
+
for (const { file, url } of pending) try {
|
|
222
|
+
const uploaded = await uploadFiles.upload(file);
|
|
223
|
+
seedDownloadUrl(uploaded.id, uploaded.downloadUrl);
|
|
224
|
+
const image = findImage(view, url);
|
|
225
|
+
if (image !== void 0) view.dispatch(view.state.tr.setNodeMarkup(image.pos, void 0, {
|
|
226
|
+
...view.state.doc.nodeAt(image.pos)?.attrs,
|
|
227
|
+
src: `${HITCH_FILE_PREFIX}${uploaded.id}`
|
|
228
|
+
}));
|
|
229
|
+
} catch (error) {
|
|
230
|
+
const image = findImage(view, url);
|
|
231
|
+
if (image !== void 0) view.dispatch(view.state.tr.delete(image.pos, image.pos + image.size));
|
|
232
|
+
if (uploadErrorRef.current !== void 0) uploadErrorRef.current(error, file);
|
|
233
|
+
else console.error(`Upload of "${file.name}" failed.`, error);
|
|
234
|
+
} finally {
|
|
235
|
+
URL.revokeObjectURL(url);
|
|
236
|
+
}
|
|
237
|
+
} finally {
|
|
238
|
+
markUploadPending(-1);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
useEffect(() => {
|
|
242
|
+
if (value !== lastEmittedRef.current && value !== editorMarkdown(editor)) editor.commands.setContent(value, {
|
|
243
|
+
contentType: "markdown",
|
|
244
|
+
emitUpdate: false
|
|
245
|
+
});
|
|
246
|
+
lastEmittedRef.current = value;
|
|
247
|
+
}, [editor, value]);
|
|
248
|
+
useEffect(() => editor.setEditable(!disabled, false), [disabled, editor]);
|
|
249
|
+
const active = useEditorState({
|
|
250
|
+
editor,
|
|
251
|
+
selector: ({ editor: current }) => ({
|
|
252
|
+
heading: current.isActive("heading"),
|
|
253
|
+
bold: current.isActive("bold"),
|
|
254
|
+
italic: current.isActive("italic"),
|
|
255
|
+
strike: current.isActive("strike"),
|
|
256
|
+
code: current.isActive("code"),
|
|
257
|
+
link: current.isActive("link"),
|
|
258
|
+
bulletList: current.isActive("bulletList"),
|
|
259
|
+
orderedList: current.isActive("orderedList"),
|
|
260
|
+
taskList: current.isActive("taskList"),
|
|
261
|
+
blockquote: current.isActive("blockquote"),
|
|
262
|
+
table: current.isActive("table")
|
|
263
|
+
})
|
|
264
|
+
});
|
|
265
|
+
const cycleHeading = () => {
|
|
266
|
+
const level = [
|
|
267
|
+
1,
|
|
268
|
+
2,
|
|
269
|
+
3
|
|
270
|
+
].find((item) => editor.isActive("heading", { level: item }));
|
|
271
|
+
if (level === void 0) editor.chain().focus().toggleHeading({ level: 1 }).run();
|
|
272
|
+
else if (level < 3) editor.chain().focus().toggleHeading({ level: level + 1 }).run();
|
|
273
|
+
else editor.chain().focus().setParagraph().run();
|
|
274
|
+
};
|
|
275
|
+
const toggleLink = () => {
|
|
276
|
+
if (editor.isActive("link")) return void editor.chain().focus().unsetLink().run();
|
|
277
|
+
const url = window.prompt("Link URL");
|
|
278
|
+
if (!url) return;
|
|
279
|
+
if (editor.state.selection.empty) editor.chain().focus().insertContent({
|
|
280
|
+
type: "text",
|
|
281
|
+
text: url,
|
|
282
|
+
marks: [{
|
|
283
|
+
type: "link",
|
|
284
|
+
attrs: { href: url }
|
|
285
|
+
}]
|
|
286
|
+
}).run();
|
|
287
|
+
else editor.chain().focus().extendMarkRange("link").setLink({ href: url }).run();
|
|
288
|
+
};
|
|
289
|
+
const items = [
|
|
290
|
+
{
|
|
291
|
+
title: "Heading",
|
|
292
|
+
active: active.heading,
|
|
293
|
+
icon: Heading,
|
|
294
|
+
command: cycleHeading
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
title: "Bold",
|
|
298
|
+
active: active.bold,
|
|
299
|
+
icon: Bold,
|
|
300
|
+
command: () => editor.chain().focus().toggleBold().run()
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
title: "Italic",
|
|
304
|
+
active: active.italic,
|
|
305
|
+
icon: Italic,
|
|
306
|
+
command: () => editor.chain().focus().toggleItalic().run()
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
title: "Strikethrough",
|
|
310
|
+
active: active.strike,
|
|
311
|
+
icon: Strikethrough,
|
|
312
|
+
command: () => editor.chain().focus().toggleStrike().run()
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
title: "Inline code",
|
|
316
|
+
active: active.code,
|
|
317
|
+
icon: Code,
|
|
318
|
+
command: () => editor.chain().focus().toggleCode().run()
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
title: "Link",
|
|
322
|
+
active: active.link,
|
|
323
|
+
icon: Link,
|
|
324
|
+
command: toggleLink
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
title: "Bulleted list",
|
|
328
|
+
active: active.bulletList,
|
|
329
|
+
icon: List,
|
|
330
|
+
command: () => editor.chain().focus().toggleBulletList().run()
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
title: "Numbered list",
|
|
334
|
+
active: active.orderedList,
|
|
335
|
+
icon: ListOrdered,
|
|
336
|
+
command: () => editor.chain().focus().toggleOrderedList().run()
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
title: "Task list",
|
|
340
|
+
active: active.taskList,
|
|
341
|
+
icon: ListTodo,
|
|
342
|
+
command: () => editor.chain().focus().toggleTaskList().run()
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
title: "Quote",
|
|
346
|
+
active: active.blockquote,
|
|
347
|
+
icon: Quote,
|
|
348
|
+
command: () => editor.chain().focus().toggleBlockquote().run()
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
title: "Table",
|
|
352
|
+
active: active.table,
|
|
353
|
+
icon: Table,
|
|
354
|
+
command: () => editor.chain().focus().insertTable({
|
|
355
|
+
rows: 3,
|
|
356
|
+
cols: 3,
|
|
357
|
+
withHeaderRow: true
|
|
358
|
+
}).run()
|
|
359
|
+
}
|
|
360
|
+
];
|
|
361
|
+
const tableItems = [
|
|
362
|
+
{
|
|
363
|
+
title: "Add row below",
|
|
364
|
+
icon: BetweenHorizontalEnd,
|
|
365
|
+
command: () => editor.chain().focus().addRowAfter().run()
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
title: "Add column right",
|
|
369
|
+
icon: BetweenVerticalEnd,
|
|
370
|
+
command: () => editor.chain().focus().addColumnAfter().run()
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
title: "Delete row",
|
|
374
|
+
icon: ListMinus,
|
|
375
|
+
command: () => editor.chain().focus().deleteRow().run()
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
title: "Delete column",
|
|
379
|
+
icon: ListMinus,
|
|
380
|
+
iconClassName: "rotate-90",
|
|
381
|
+
command: () => editor.chain().focus().deleteColumn().run()
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
title: "Delete table",
|
|
385
|
+
icon: Trash2,
|
|
386
|
+
command: () => editor.chain().focus().deleteTable().run()
|
|
387
|
+
}
|
|
388
|
+
];
|
|
389
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
390
|
+
className: twMerge("flex flex-col overflow-hidden rounded-2xl border border-input bg-background", className),
|
|
391
|
+
children: [
|
|
392
|
+
/* @__PURE__ */ jsx(EditorContent, { editor }),
|
|
393
|
+
/* @__PURE__ */ jsxs(Toolbar, {
|
|
394
|
+
className: "order-first flex flex-wrap gap-1 border-b border-input p-1",
|
|
395
|
+
"aria-label": "Markdown formatting",
|
|
396
|
+
children: [items.map(({ title, active: isActive, icon: Icon, command }) => /* @__PURE__ */ jsx(Button, {
|
|
397
|
+
className: twMerge(buttonClassName, isActive && "bg-muted text-foreground"),
|
|
398
|
+
"aria-label": title,
|
|
399
|
+
isDisabled: disabled,
|
|
400
|
+
onPress: command,
|
|
401
|
+
children: /* @__PURE__ */ jsx(Icon, {})
|
|
402
|
+
}, title)), active.table && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
|
|
403
|
+
"aria-hidden": true,
|
|
404
|
+
className: "mx-1 w-px self-stretch bg-border"
|
|
405
|
+
}), tableItems.map(({ title, icon: Icon, iconClassName, command }) => /* @__PURE__ */ jsx(Button, {
|
|
406
|
+
className: buttonClassName,
|
|
407
|
+
"aria-label": title,
|
|
408
|
+
isDisabled: disabled,
|
|
409
|
+
onPress: command,
|
|
410
|
+
children: /* @__PURE__ */ jsx(Icon, { className: iconClassName })
|
|
411
|
+
}, title))] })]
|
|
412
|
+
}),
|
|
413
|
+
/* @__PURE__ */ jsx("textarea", {
|
|
414
|
+
ref: validationRef,
|
|
415
|
+
id,
|
|
416
|
+
className: "sr-only",
|
|
417
|
+
tabIndex: -1,
|
|
418
|
+
value,
|
|
419
|
+
required,
|
|
420
|
+
disabled,
|
|
421
|
+
onChange: () => void 0,
|
|
422
|
+
onFocus: () => editor.commands.focus()
|
|
423
|
+
})
|
|
424
|
+
]
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
//#endregion
|
|
428
|
+
export { MarkdownEditor as t };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as
|
|
1
|
+
import { a as organizationInstanceUrl, n as DEFAULT_INSTANCE_URL } from "./store-B8N_8JIN.js";
|
|
2
|
+
import { n as readWorkspaceConfig } from "./workspace-8yqXsvce.js";
|
|
3
|
+
import { n as loadAppletSourceDefinition } from "./load-config-Dkr2VxZN.js";
|
|
3
4
|
import { z } from "zod";
|
|
4
5
|
import { existsSync } from "node:fs";
|
|
5
6
|
import { cp, mkdir, readFile, readdir, rm, symlink, writeFile } from "node:fs/promises";
|
|
@@ -140,7 +141,7 @@ function workspacePackage(org) {
|
|
|
140
141
|
scripts: {
|
|
141
142
|
lint: "oxlint",
|
|
142
143
|
fmt: "oxfmt",
|
|
143
|
-
check: "oxfmt --check &&
|
|
144
|
+
check: "oxfmt --check; s=$?; oxlint && exit $s"
|
|
144
145
|
},
|
|
145
146
|
devDependencies: {
|
|
146
147
|
"@hitch42/applet": "catalog:",
|
|
@@ -375,7 +376,7 @@ function appletPackage(definition, options) {
|
|
|
375
376
|
typecheck: "tsc --noEmit",
|
|
376
377
|
lint: "oxlint",
|
|
377
378
|
fmt: "oxfmt",
|
|
378
|
-
check: "oxfmt --check &&
|
|
379
|
+
check: "oxfmt --check; s=$?; oxlint && exit $s"
|
|
379
380
|
},
|
|
380
381
|
dependencies: sortedByName({
|
|
381
382
|
"@hitch42/applet": "catalog:",
|
package/dist/react.d.ts
CHANGED
|
@@ -1,38 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import "@tiptap/core";
|
|
4
|
-
//#region src/react/markdown.d.ts
|
|
5
|
-
type MarkdownFiles = {
|
|
6
|
-
download: (id: string) => Promise<Pick<UploadedHitchFile, "downloadUrl">>;
|
|
7
|
-
};
|
|
8
|
-
type MarkdownProps = {
|
|
9
|
-
children: string;
|
|
10
|
-
files?: MarkdownFiles;
|
|
11
|
-
className?: string;
|
|
12
|
-
};
|
|
13
|
-
declare function Markdown({ children, files, className }: MarkdownProps): ReactElement;
|
|
14
|
-
//#endregion
|
|
15
|
-
//#region src/react/markdown-editor-extensions.d.ts
|
|
16
|
-
type MarkdownEditorFiles = {
|
|
17
|
-
upload: (file: File) => Promise<Pick<UploadedHitchFile, "id" | "downloadUrl">>;
|
|
18
|
-
download: (id: string) => Promise<Pick<UploadedHitchFile, "downloadUrl">>;
|
|
19
|
-
};
|
|
20
|
-
//#endregion
|
|
21
|
-
//#region src/react/markdown-editor.d.ts
|
|
22
|
-
type MarkdownEditorProps = {
|
|
23
|
-
value: string;
|
|
24
|
-
onChange: (value: string) => void;
|
|
25
|
-
files?: MarkdownEditorFiles;
|
|
26
|
-
id?: string;
|
|
27
|
-
placeholder?: string;
|
|
28
|
-
required?: boolean;
|
|
29
|
-
minLength?: number;
|
|
30
|
-
maxLength?: number;
|
|
31
|
-
disabled?: boolean;
|
|
32
|
-
autoFocus?: boolean;
|
|
33
|
-
className?: string;
|
|
34
|
-
onUploadError?: (error: unknown, file: File) => void;
|
|
35
|
-
};
|
|
36
|
-
declare function MarkdownEditor({ value, onChange, files, id, placeholder, required, minLength, maxLength, disabled, autoFocus, className, onUploadError }: MarkdownEditorProps): ReactElement;
|
|
37
|
-
//#endregion
|
|
1
|
+
import { n as MarkdownFiles, r as MarkdownProps, t as Markdown } from "./markdown-DSHOQIOb.js";
|
|
2
|
+
import { n as MarkdownEditorProps, r as MarkdownEditorFiles, t as MarkdownEditor } from "./markdown-editor-BmEaBzd7.js";
|
|
38
3
|
export { Markdown, MarkdownEditor, type MarkdownEditorFiles, type MarkdownEditorProps, type MarkdownFiles, type MarkdownProps };
|