autoblogger 0.1.4 → 0.1.5

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/ui.js CHANGED
@@ -41,6 +41,10 @@ __export(ui_exports, {
41
41
  });
42
42
  module.exports = __toCommonJS(ui_exports);
43
43
 
44
+ // src/ui/dashboard.tsx
45
+ var import_react19 = require("react");
46
+ var import_lucide_react13 = require("lucide-react");
47
+
44
48
  // src/ui/context.tsx
45
49
  var import_react = require("react");
46
50
  var import_jsx_runtime = require("react/jsx-runtime");
@@ -6798,8 +6802,9 @@ function AutoResizeTextarea({
6798
6802
  }
6799
6803
  );
6800
6804
  }
6801
- function EditorPage({ slug }) {
6802
- const { apiBasePath, styles, fields, navigate, basePath, onEditorStateChange, onRegisterEditHandler } = useDashboardContext();
6805
+ function EditorPage({ slug, onEditorStateChange: onEditorStateChangeProp }) {
6806
+ const { apiBasePath, styles, fields, navigate, basePath, onRegisterEditHandler } = useDashboardContext();
6807
+ const onEditorStateChange = onEditorStateChangeProp;
6803
6808
  const [post, setPost] = (0, import_react16.useState)({
6804
6809
  title: "",
6805
6810
  subtitle: "",
@@ -6905,7 +6910,7 @@ function EditorPage({ slug }) {
6905
6910
  return () => {
6906
6911
  onEditorStateChange(null);
6907
6912
  };
6908
- }, [hasUnsavedChanges, post.status, savingAs, post.title, post.subtitle, post.markdown, onEditorStateChange]);
6913
+ }, [hasUnsavedChanges, post.status, savingAs, post.title, post.subtitle, post.markdown, onEditorStateChange, savePost, handlePublish]);
6909
6914
  (0, import_react16.useEffect)(() => {
6910
6915
  if (!onRegisterEditHandler) return;
6911
6916
  const handleEdit = (edit) => {
@@ -7000,29 +7005,79 @@ function EditorPage({ slug }) {
7000
7005
  hasTriggeredGeneration.current = true;
7001
7006
  setGenerating(true);
7002
7007
  const wordCount = urlParams.length ? parseInt(urlParams.length) : 500;
7003
- fetch(`${apiBasePath}/ai/generate`, {
7004
- method: "POST",
7005
- headers: { "Content-Type": "application/json" },
7006
- body: JSON.stringify({
7007
- idea: urlParams.idea,
7008
- wordCount,
7009
- model: urlParams.model,
7010
- useWebSearch: urlParams.web === "1",
7011
- useThinking: urlParams.thinking === "1"
7012
- })
7013
- }).then((r) => r.json()).then((data) => {
7014
- if (data.data) {
7008
+ if (typeof window !== "undefined") {
7009
+ window.history.replaceState({}, "", `${basePath}/editor`);
7010
+ }
7011
+ const runGenerate = async () => {
7012
+ try {
7013
+ const res = await fetch(`${apiBasePath}/ai/generate`, {
7014
+ method: "POST",
7015
+ headers: { "Content-Type": "application/json" },
7016
+ body: JSON.stringify({
7017
+ prompt: urlParams.idea,
7018
+ wordCount,
7019
+ model: urlParams.model
7020
+ })
7021
+ });
7022
+ if (!res.ok) {
7023
+ const error = await res.json().catch(() => ({ error: "Generation failed" }));
7024
+ console.error("Generation failed:", error);
7025
+ return;
7026
+ }
7027
+ const reader = res.body?.getReader();
7028
+ if (!reader) return;
7029
+ const decoder = new TextDecoder();
7030
+ let fullContent = "";
7031
+ while (true) {
7032
+ const { done, value } = await reader.read();
7033
+ if (done) break;
7034
+ const chunk = decoder.decode(value, { stream: true });
7035
+ const lines2 = chunk.split("\n");
7036
+ for (const line of lines2) {
7037
+ if (line.startsWith("data: ")) {
7038
+ const data = line.slice(6);
7039
+ if (data === "[DONE]") continue;
7040
+ try {
7041
+ const parsed = JSON.parse(data);
7042
+ if (parsed.text) {
7043
+ fullContent += parsed.text;
7044
+ setPost((prev) => ({ ...prev, markdown: fullContent }));
7045
+ }
7046
+ } catch {
7047
+ }
7048
+ }
7049
+ }
7050
+ }
7051
+ const lines = fullContent.trim().split("\n");
7052
+ let title = "";
7053
+ let subtitle = "";
7054
+ let markdownStart = 0;
7055
+ if (lines[0]?.startsWith("# ")) {
7056
+ title = lines[0].slice(2).trim();
7057
+ markdownStart = 1;
7058
+ const nextLine = lines[1]?.trim();
7059
+ if (nextLine) {
7060
+ const italicMatch = nextLine.match(/^\*(.+)\*$/) || nextLine.match(/^_(.+)_$/);
7061
+ if (italicMatch) {
7062
+ subtitle = italicMatch[1];
7063
+ markdownStart = 2;
7064
+ }
7065
+ }
7066
+ }
7067
+ const remainingMarkdown = lines.slice(markdownStart).join("\n").trim();
7015
7068
  setPost((prev) => ({
7016
7069
  ...prev,
7017
- title: data.data.title || prev.title,
7018
- subtitle: data.data.subtitle || prev.subtitle,
7019
- markdown: data.data.markdown || prev.markdown
7070
+ title: title || prev.title,
7071
+ subtitle: subtitle || prev.subtitle,
7072
+ markdown: remainingMarkdown || fullContent
7020
7073
  }));
7074
+ } catch (err) {
7075
+ console.error("Generation error:", err);
7076
+ } finally {
7077
+ setGenerating(false);
7021
7078
  }
7022
- }).catch(console.error).finally(() => setGenerating(false));
7023
- if (typeof window !== "undefined") {
7024
- window.history.replaceState({}, "", `${basePath}/editor`);
7025
- }
7079
+ };
7080
+ runGenerate();
7026
7081
  }
7027
7082
  }, [urlParams, slug, loading, apiBasePath, basePath]);
7028
7083
  const fetchRevisions = (0, import_react16.useCallback)(async () => {
@@ -7065,7 +7120,7 @@ function EditorPage({ slug }) {
7065
7120
  setPreviewingRevision(null);
7066
7121
  await savePost();
7067
7122
  }, [previewingRevision]);
7068
- const savePost = async (silent = false) => {
7123
+ const savePost = (0, import_react16.useCallback)(async (silent = false) => {
7069
7124
  if (!silent) {
7070
7125
  setSaving(true);
7071
7126
  setSavingAs("draft");
@@ -7102,8 +7157,8 @@ function EditorPage({ slug }) {
7102
7157
  setSavingAs(null);
7103
7158
  }
7104
7159
  }
7105
- };
7106
- const handlePublish = async () => {
7160
+ }, [post.id, post.title, post.subtitle, post.slug, post.markdown, post.status, post.tags, apiBasePath, fields, navigate]);
7161
+ const handlePublish = (0, import_react16.useCallback)(async () => {
7107
7162
  if (!confirm("Publish this essay?")) return;
7108
7163
  setSaving(true);
7109
7164
  setSavingAs("published");
@@ -7127,7 +7182,7 @@ function EditorPage({ slug }) {
7127
7182
  setSaving(false);
7128
7183
  setSavingAs(null);
7129
7184
  }
7130
- };
7185
+ }, [post.id, post.title, apiBasePath, fields, navigate]);
7131
7186
  const handleUnpublish = async () => {
7132
7187
  if (!confirm("Unpublish this essay?")) return;
7133
7188
  await fetch(`${apiBasePath}/posts/${post.id}`, {
@@ -7230,7 +7285,7 @@ function EditorPage({ slug }) {
7230
7285
  className: `${styles.subtitle} w-full bg-transparent border-none outline-none placeholder-gray-300 dark:placeholder-gray-700 ${generating || previewingRevision ? "opacity-60 cursor-not-allowed" : ""}`
7231
7286
  }
7232
7287
  ),
7233
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "!mt-4", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: `${styles.byline} underline ${generating ? "opacity-60" : ""}`, children: "Author" }) })
7288
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "!mt-4", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: `${styles.byline} underline ${generating ? "opacity-60" : ""}`, children: session?.user?.name || session?.user?.email || "Author" }) })
7234
7289
  ] }),
7235
7290
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "mt-8", children: generating && !post.markdown ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "space-y-3", children: [
7236
7291
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Skeleton2, { className: "h-4 w-full" }),
@@ -9435,8 +9490,14 @@ function DashboardLayout({
9435
9490
  theme,
9436
9491
  navbarRightSlot
9437
9492
  }) {
9438
- const { basePath, currentPath, navigate } = useDashboardContext();
9493
+ const { basePath, currentPath, navigate, onEditorStateChange } = useDashboardContext();
9494
+ const [editorState, setEditorState] = (0, import_react19.useState)(null);
9439
9495
  const editorSlug = currentPath.startsWith("/editor/") ? currentPath.replace("/editor/", "") : currentPath === "/editor" ? void 0 : void 0;
9496
+ const isEditorPage = currentPath.startsWith("/editor");
9497
+ const handleEditorStateChange = (state) => {
9498
+ setEditorState(state);
9499
+ onEditorStateChange?.(state);
9500
+ };
9440
9501
  useDashboardKeyboard({
9441
9502
  basePath,
9442
9503
  onToggleView: onToggleView ? () => onToggleView(currentPath, editorSlug) : void 0,
@@ -9451,6 +9512,21 @@ function DashboardLayout({
9451
9512
  if (currentPath !== "/" && currentPath !== "") navigate("/");
9452
9513
  }
9453
9514
  });
9515
+ const rightSlotWithSave = /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
9516
+ isEditorPage && editorState && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
9517
+ "button",
9518
+ {
9519
+ type: "button",
9520
+ onClick: () => editorState.onSave("draft"),
9521
+ disabled: !editorState.hasUnsavedChanges || !!editorState.savingAs,
9522
+ className: "w-9 h-9 rounded-md border border-border hover:bg-accent text-muted-foreground flex items-center justify-center disabled:opacity-50 disabled:cursor-not-allowed",
9523
+ "aria-label": "Save",
9524
+ title: editorState.hasUnsavedChanges ? "Save changes (\u2318S)" : "No unsaved changes",
9525
+ children: editorState.savingAs ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react13.Loader2, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_lucide_react13.Save, { className: "h-4 w-4" })
9526
+ }
9527
+ ),
9528
+ navbarRightSlot
9529
+ ] });
9454
9530
  return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "min-h-screen bg-background flex flex-col", children: [
9455
9531
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
9456
9532
  Navbar,
@@ -9458,18 +9534,18 @@ function DashboardLayout({
9458
9534
  onSignOut,
9459
9535
  onThemeToggle,
9460
9536
  theme,
9461
- rightSlot: navbarRightSlot
9537
+ rightSlot: rightSlotWithSave
9462
9538
  }
9463
9539
  ),
9464
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("main", { className: "flex-1", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(DashboardRouter, { path: currentPath }) })
9540
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("main", { className: "flex-1", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(DashboardRouter, { path: currentPath, onEditorStateChange: handleEditorStateChange }) })
9465
9541
  ] });
9466
9542
  }
9467
- function DashboardRouter({ path }) {
9543
+ function DashboardRouter({ path, onEditorStateChange }) {
9468
9544
  const pathWithoutQuery = path.split("?")[0];
9469
9545
  if (pathWithoutQuery === "/" || pathWithoutQuery === "") return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(WriterDashboard, {});
9470
9546
  if (pathWithoutQuery.startsWith("/editor")) {
9471
9547
  const slug = pathWithoutQuery.replace("/editor/", "").replace("/editor", "");
9472
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(EditorPage, { slug: slug || void 0 }, path);
9548
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(EditorPage, { slug: slug || void 0, onEditorStateChange }, path);
9473
9549
  }
9474
9550
  if (pathWithoutQuery.startsWith("/settings")) return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(SettingsPage, { subPath: pathWithoutQuery.replace("/settings", "") });
9475
9551
  return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "max-w-4xl mx-auto px-6 py-8", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("p", { className: "text-muted-foreground", children: [