@ampless/admin 1.0.0-alpha.70 → 1.0.0-alpha.72
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/api/index.d.ts +1 -1
- package/dist/{chunk-YPZWY7DZ.js → chunk-7ATGSNSZ.js} +2 -2
- package/dist/{chunk-CZQYHUH7.js → chunk-7TAT4AHW.js} +48 -0
- package/dist/{chunk-FTAGUNNZ.js → chunk-DDAGBC4N.js} +5 -1
- package/dist/{chunk-PIULVPF5.js → chunk-DSCNWIBN.js} +2 -2
- package/dist/{chunk-CAGGVZUP.js → chunk-DUSEHGSV.js} +1 -1
- package/dist/{chunk-WE2GQIMF.js → chunk-ECN2MBJN.js} +583 -135
- package/dist/{chunk-3CBZ5FIV.js → chunk-GNTGWI43.js} +2 -2
- package/dist/{chunk-UEX2BIGF.js → chunk-HOJJIABL.js} +2 -2
- package/dist/{chunk-JS4ULHOZ.js → chunk-IR4EB5C3.js} +1 -1
- package/dist/{chunk-WBE6K6WZ.js → chunk-LNX3I35F.js} +1 -1
- package/dist/{chunk-TM7WHB5Q.js → chunk-MPZ3GVZC.js} +1 -1
- package/dist/{chunk-L6U3X7GJ.js → chunk-NI4JSVXL.js} +1 -1
- package/dist/{chunk-DDKBL6N4.js → chunk-O7VJHAYM.js} +43 -3
- package/dist/{chunk-SIQ3B55P.js → chunk-PHV2FQFV.js} +1 -1
- package/dist/{chunk-PAATFYUJ.js → chunk-TBNETGQL.js} +1 -1
- package/dist/components/admin-dashboard.js +3 -3
- package/dist/components/edit-post-view.js +5 -5
- package/dist/components/index.d.ts +1 -1
- package/dist/components/index.js +6 -6
- package/dist/components/login-view.js +3 -3
- package/dist/components/mcp-tokens-view.js +3 -3
- package/dist/components/media-view.js +5 -5
- package/dist/components/new-post-view.js +5 -5
- package/dist/components/plugin-settings-form.js +3 -3
- package/dist/components/posts-list-view.js +3 -3
- package/dist/components/users-list-view.js +3 -3
- package/dist/{i18n-DTN5tkNf.d.ts → i18n-DBNV_Fgf.d.ts} +72 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/metafile-esm.json +1 -1
- package/dist/pages/index.d.ts +1 -1
- package/dist/pages/index.js +15 -15
- package/package.json +4 -4
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import {
|
|
3
3
|
ImageUploadDialog,
|
|
4
|
+
getAdminCmsConfig,
|
|
4
5
|
getMediaProcessingDefaults,
|
|
5
6
|
uploadProcessedImage
|
|
6
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-DDAGBC4N.js";
|
|
7
8
|
import {
|
|
8
9
|
publicMediaUrl
|
|
9
10
|
} from "./chunk-2ITWLRYF.js";
|
|
10
11
|
import {
|
|
11
12
|
useT
|
|
12
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-DUSEHGSV.js";
|
|
13
14
|
|
|
14
15
|
// src/components/media-picker.tsx
|
|
15
16
|
import { useEffect, useRef, useState } from "react";
|
|
@@ -154,7 +155,7 @@ function MediaPicker({ trigger, onSelect }) {
|
|
|
154
155
|
}
|
|
155
156
|
|
|
156
157
|
// src/components/post-form.tsx
|
|
157
|
-
import { useRef as useRef3, useState as
|
|
158
|
+
import { useEffect as useEffect4, useRef as useRef3, useState as useState5 } from "react";
|
|
158
159
|
import { useRouter } from "next/navigation";
|
|
159
160
|
import { Image as ImageIcon3 } from "lucide-react";
|
|
160
161
|
import {
|
|
@@ -164,13 +165,13 @@ import {
|
|
|
164
165
|
formatDate
|
|
165
166
|
} from "ampless";
|
|
166
167
|
import {
|
|
167
|
-
renderBody,
|
|
168
|
+
renderBody as renderBody2,
|
|
168
169
|
tiptapToHtml,
|
|
169
170
|
tiptapToMarkdown,
|
|
170
171
|
markdownToHtml,
|
|
171
172
|
htmlToMarkdown
|
|
172
173
|
} from "@ampless/runtime";
|
|
173
|
-
import { Button as
|
|
174
|
+
import { Button as Button7, Input, Label, Textarea } from "@ampless/runtime/ui";
|
|
174
175
|
|
|
175
176
|
// src/editor/tiptap-editor.tsx
|
|
176
177
|
import { useEditor, EditorContent } from "@tiptap/react";
|
|
@@ -579,7 +580,7 @@ var EDITOR_STYLES = `
|
|
|
579
580
|
border-radius: 2px;
|
|
580
581
|
}
|
|
581
582
|
`;
|
|
582
|
-
function TiptapEditor({ initialContent, onChange }) {
|
|
583
|
+
function TiptapEditor({ initialContent, onChange, onUserEdit }) {
|
|
583
584
|
const editor = useEditor({
|
|
584
585
|
extensions: [
|
|
585
586
|
StarterKit,
|
|
@@ -607,6 +608,7 @@ function TiptapEditor({ initialContent, onChange }) {
|
|
|
607
608
|
},
|
|
608
609
|
onUpdate: ({ editor: editor2 }) => {
|
|
609
610
|
onChange?.(editor2.getJSON());
|
|
611
|
+
onUserEdit?.();
|
|
610
612
|
}
|
|
611
613
|
});
|
|
612
614
|
return /* @__PURE__ */ jsxs5("div", { className: "rounded-md border", children: [
|
|
@@ -617,10 +619,187 @@ function TiptapEditor({ initialContent, onChange }) {
|
|
|
617
619
|
] });
|
|
618
620
|
}
|
|
619
621
|
|
|
622
|
+
// src/components/post-history-panel.tsx
|
|
623
|
+
import { useCallback, useEffect as useEffect3, useState as useState3 } from "react";
|
|
624
|
+
import { listPostHistory } from "ampless";
|
|
625
|
+
import { renderBody } from "@ampless/runtime";
|
|
626
|
+
import { Button as Button5 } from "@ampless/runtime/ui";
|
|
627
|
+
import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
628
|
+
var PAGE_SIZE = 20;
|
|
629
|
+
function formatRevisedAt(iso, timezone, locale) {
|
|
630
|
+
const d = new Date(iso);
|
|
631
|
+
if (Number.isNaN(d.getTime())) return iso;
|
|
632
|
+
try {
|
|
633
|
+
return new Intl.DateTimeFormat(locale || void 0, {
|
|
634
|
+
year: "numeric",
|
|
635
|
+
month: "short",
|
|
636
|
+
day: "2-digit",
|
|
637
|
+
hour: "2-digit",
|
|
638
|
+
minute: "2-digit",
|
|
639
|
+
timeZone: timezone
|
|
640
|
+
}).format(d);
|
|
641
|
+
} catch {
|
|
642
|
+
return iso;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
function revisionAsPost(rev) {
|
|
646
|
+
return {
|
|
647
|
+
postId: rev.postId,
|
|
648
|
+
slug: rev.slug ?? "",
|
|
649
|
+
title: rev.title ?? "",
|
|
650
|
+
excerpt: rev.excerpt,
|
|
651
|
+
format: rev.format ?? "markdown",
|
|
652
|
+
body: rev.body,
|
|
653
|
+
status: rev.status ?? "draft",
|
|
654
|
+
publishedAt: rev.publishedAt,
|
|
655
|
+
tags: rev.tags ?? [],
|
|
656
|
+
metadata: rev.metadata
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
function PostHistoryPanel({ postId, onRestore }) {
|
|
660
|
+
const t = useT();
|
|
661
|
+
const config = getAdminCmsConfig();
|
|
662
|
+
const timezone = config?.timezone ?? "UTC";
|
|
663
|
+
const locale = config?.locale ?? "en";
|
|
664
|
+
const [open, setOpen] = useState3(false);
|
|
665
|
+
const [loaded, setLoaded] = useState3(false);
|
|
666
|
+
const [loading, setLoading] = useState3(false);
|
|
667
|
+
const [error, setError] = useState3(null);
|
|
668
|
+
const [revisions, setRevisions] = useState3([]);
|
|
669
|
+
const [nextToken, setNextToken] = useState3(void 0);
|
|
670
|
+
const [selected, setSelected] = useState3(null);
|
|
671
|
+
const loadPage = useCallback(
|
|
672
|
+
async (token) => {
|
|
673
|
+
setLoading(true);
|
|
674
|
+
setError(null);
|
|
675
|
+
try {
|
|
676
|
+
const conn = await listPostHistory(postId, { limit: PAGE_SIZE, nextToken: token });
|
|
677
|
+
setRevisions((prev) => token ? [...prev, ...conn.items] : conn.items);
|
|
678
|
+
setNextToken(conn.nextToken);
|
|
679
|
+
} catch (err) {
|
|
680
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
681
|
+
} finally {
|
|
682
|
+
setLoading(false);
|
|
683
|
+
}
|
|
684
|
+
},
|
|
685
|
+
[postId]
|
|
686
|
+
);
|
|
687
|
+
useEffect3(() => {
|
|
688
|
+
if (open && !loaded) {
|
|
689
|
+
setLoaded(true);
|
|
690
|
+
void loadPage();
|
|
691
|
+
}
|
|
692
|
+
}, [open, loaded, loadPage]);
|
|
693
|
+
return /* @__PURE__ */ jsxs6("section", { className: "rounded-md border", children: [
|
|
694
|
+
/* @__PURE__ */ jsxs6(
|
|
695
|
+
"button",
|
|
696
|
+
{
|
|
697
|
+
type: "button",
|
|
698
|
+
onClick: () => setOpen((v) => !v),
|
|
699
|
+
"aria-expanded": open,
|
|
700
|
+
className: "flex w-full items-center justify-between px-4 py-3 text-left text-sm font-medium",
|
|
701
|
+
children: [
|
|
702
|
+
/* @__PURE__ */ jsx6("span", { children: t("posts.history.title") }),
|
|
703
|
+
/* @__PURE__ */ jsx6("span", { className: "text-muted-foreground", "aria-hidden": "true", children: open ? "\u2212" : "+" })
|
|
704
|
+
]
|
|
705
|
+
}
|
|
706
|
+
),
|
|
707
|
+
open && /* @__PURE__ */ jsxs6("div", { className: "space-y-4 border-t px-4 py-4", children: [
|
|
708
|
+
error && /* @__PURE__ */ jsx6("p", { className: "text-sm text-destructive", children: error }),
|
|
709
|
+
loading && revisions.length === 0 ? /* @__PURE__ */ jsx6("p", { className: "text-sm text-muted-foreground", children: t("common.loading") }) : revisions.length === 0 ? /* @__PURE__ */ jsx6("p", { className: "text-sm text-muted-foreground", children: t("posts.history.empty") }) : /* @__PURE__ */ jsx6("div", { className: "overflow-x-auto rounded-md border", children: /* @__PURE__ */ jsxs6("table", { className: "w-full text-sm", children: [
|
|
710
|
+
/* @__PURE__ */ jsx6("thead", { children: /* @__PURE__ */ jsxs6("tr", { className: "border-b text-left text-xs text-muted-foreground", children: [
|
|
711
|
+
/* @__PURE__ */ jsx6("th", { className: "px-3 py-2 font-medium", children: t("posts.history.columnRevisedAt") }),
|
|
712
|
+
/* @__PURE__ */ jsx6("th", { className: "px-3 py-2 font-medium", children: t("posts.history.columnStatus") }),
|
|
713
|
+
/* @__PURE__ */ jsx6("th", { className: "px-3 py-2 font-medium", children: t("posts.history.columnTitle") }),
|
|
714
|
+
/* @__PURE__ */ jsx6("th", { className: "px-3 py-2 font-medium text-right", children: t("common.actions") })
|
|
715
|
+
] }) }),
|
|
716
|
+
/* @__PURE__ */ jsx6("tbody", { children: revisions.map((rev) => /* @__PURE__ */ jsxs6(
|
|
717
|
+
"tr",
|
|
718
|
+
{
|
|
719
|
+
className: selected?.postHistoryId === rev.postHistoryId ? "border-b bg-muted/50" : "border-b",
|
|
720
|
+
children: [
|
|
721
|
+
/* @__PURE__ */ jsx6("td", { className: "whitespace-nowrap px-3 py-2 font-mono text-xs", children: formatRevisedAt(rev.revisedAt, timezone, locale) }),
|
|
722
|
+
/* @__PURE__ */ jsx6("td", { className: "px-3 py-2", children: /* @__PURE__ */ jsx6("span", { className: "text-xs text-muted-foreground", children: rev.status ? t(`common.${rev.status}`) : "\u2014" }) }),
|
|
723
|
+
/* @__PURE__ */ jsx6("td", { className: "px-3 py-2", children: rev.title || "\u2014" }),
|
|
724
|
+
/* @__PURE__ */ jsx6("td", { className: "px-3 py-2 text-right", children: /* @__PURE__ */ jsxs6("div", { className: "flex justify-end gap-2", children: [
|
|
725
|
+
/* @__PURE__ */ jsx6(
|
|
726
|
+
Button5,
|
|
727
|
+
{
|
|
728
|
+
type: "button",
|
|
729
|
+
variant: "outline",
|
|
730
|
+
size: "sm",
|
|
731
|
+
onClick: () => setSelected(rev),
|
|
732
|
+
children: t("posts.history.view")
|
|
733
|
+
}
|
|
734
|
+
),
|
|
735
|
+
/* @__PURE__ */ jsx6(
|
|
736
|
+
Button5,
|
|
737
|
+
{
|
|
738
|
+
type: "button",
|
|
739
|
+
variant: "outline",
|
|
740
|
+
size: "sm",
|
|
741
|
+
onClick: () => {
|
|
742
|
+
if (confirm(t("posts.history.restoreConfirm"))) onRestore(rev);
|
|
743
|
+
},
|
|
744
|
+
children: t("posts.history.restore")
|
|
745
|
+
}
|
|
746
|
+
)
|
|
747
|
+
] }) })
|
|
748
|
+
]
|
|
749
|
+
},
|
|
750
|
+
rev.postHistoryId
|
|
751
|
+
)) })
|
|
752
|
+
] }) }),
|
|
753
|
+
nextToken && /* @__PURE__ */ jsx6("div", { children: /* @__PURE__ */ jsx6(
|
|
754
|
+
Button5,
|
|
755
|
+
{
|
|
756
|
+
type: "button",
|
|
757
|
+
variant: "outline",
|
|
758
|
+
size: "sm",
|
|
759
|
+
disabled: loading,
|
|
760
|
+
onClick: () => void loadPage(nextToken),
|
|
761
|
+
children: loading ? t("common.loading") : t("posts.history.loadMore")
|
|
762
|
+
}
|
|
763
|
+
) }),
|
|
764
|
+
selected && /* @__PURE__ */ jsxs6("div", { className: "space-y-3 rounded-md border p-4", children: [
|
|
765
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-between", children: [
|
|
766
|
+
/* @__PURE__ */ jsx6("h4", { className: "text-sm font-semibold", children: t("posts.history.viewing", {
|
|
767
|
+
date: formatRevisedAt(selected.revisedAt, timezone, locale)
|
|
768
|
+
}) }),
|
|
769
|
+
/* @__PURE__ */ jsx6(
|
|
770
|
+
Button5,
|
|
771
|
+
{
|
|
772
|
+
type: "button",
|
|
773
|
+
variant: "ghost",
|
|
774
|
+
size: "sm",
|
|
775
|
+
onClick: () => setSelected(null),
|
|
776
|
+
children: t("common.cancel")
|
|
777
|
+
}
|
|
778
|
+
)
|
|
779
|
+
] }),
|
|
780
|
+
/* @__PURE__ */ jsxs6("p", { className: "text-xs text-muted-foreground", children: [
|
|
781
|
+
/* @__PURE__ */ jsx6("span", { className: "font-mono uppercase", children: selected.format ?? "\u2014" }),
|
|
782
|
+
selected.slug && /* @__PURE__ */ jsxs6(Fragment2, { children: [
|
|
783
|
+
/* @__PURE__ */ jsx6("span", { className: "mx-2", children: "\xB7" }),
|
|
784
|
+
/* @__PURE__ */ jsx6("span", { className: "font-mono", children: selected.slug })
|
|
785
|
+
] })
|
|
786
|
+
] }),
|
|
787
|
+
selected.format === "static" ? /* @__PURE__ */ jsx6("p", { className: "rounded-md bg-amber-50 p-2 text-xs text-amber-700 dark:bg-amber-950 dark:text-amber-400", children: t("posts.history.staticCaveat") }) : /* @__PURE__ */ jsx6(
|
|
788
|
+
"div",
|
|
789
|
+
{
|
|
790
|
+
className: "prose prose-neutral dark:prose-invert max-w-none text-sm",
|
|
791
|
+
dangerouslySetInnerHTML: { __html: renderBody(revisionAsPost(selected)) }
|
|
792
|
+
}
|
|
793
|
+
)
|
|
794
|
+
] })
|
|
795
|
+
] })
|
|
796
|
+
] });
|
|
797
|
+
}
|
|
798
|
+
|
|
620
799
|
// src/components/static-uploader.tsx
|
|
621
|
-
import { useState as
|
|
800
|
+
import { useState as useState4 } from "react";
|
|
622
801
|
import { FileText, AlertTriangle, FileArchive, X } from "lucide-react";
|
|
623
|
-
import { Button as
|
|
802
|
+
import { Button as Button6 } from "@ampless/runtime/ui";
|
|
624
803
|
|
|
625
804
|
// src/lib/static-bundle.ts
|
|
626
805
|
import JSZip from "jszip";
|
|
@@ -706,13 +885,13 @@ async function deleteBundle(slug) {
|
|
|
706
885
|
}
|
|
707
886
|
|
|
708
887
|
// src/components/static-uploader.tsx
|
|
709
|
-
import { jsx as
|
|
888
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
710
889
|
function StaticUploader({ initial, onFilesReady, onClear }) {
|
|
711
890
|
const t = useT();
|
|
712
|
-
const [pending, setPending] =
|
|
713
|
-
const [issues, setIssues] =
|
|
714
|
-
const [busy, setBusy] =
|
|
715
|
-
const [error, setError] =
|
|
891
|
+
const [pending, setPending] = useState4(null);
|
|
892
|
+
const [issues, setIssues] = useState4([]);
|
|
893
|
+
const [busy, setBusy] = useState4(false);
|
|
894
|
+
const [error, setError] = useState4(null);
|
|
716
895
|
async function handleZip(file) {
|
|
717
896
|
setBusy(true);
|
|
718
897
|
setError(null);
|
|
@@ -786,11 +965,11 @@ function StaticUploader({ initial, onFilesReady, onClear }) {
|
|
|
786
965
|
onClear();
|
|
787
966
|
}
|
|
788
967
|
const showCurrent = !pending && initial && initial.files.length > 0;
|
|
789
|
-
return /* @__PURE__ */
|
|
790
|
-
/* @__PURE__ */
|
|
791
|
-
/* @__PURE__ */
|
|
792
|
-
/* @__PURE__ */
|
|
793
|
-
/* @__PURE__ */
|
|
968
|
+
return /* @__PURE__ */ jsxs7("div", { className: "space-y-4", children: [
|
|
969
|
+
/* @__PURE__ */ jsxs7("div", { className: "rounded-md border border-dashed p-4", children: [
|
|
970
|
+
/* @__PURE__ */ jsxs7("label", { className: "flex flex-col items-start gap-2 text-sm", children: [
|
|
971
|
+
/* @__PURE__ */ jsx7("span", { className: "font-medium", children: t("posts.form.static.pick") }),
|
|
972
|
+
/* @__PURE__ */ jsx7(
|
|
794
973
|
"input",
|
|
795
974
|
{
|
|
796
975
|
type: "file",
|
|
@@ -800,13 +979,13 @@ function StaticUploader({ initial, onFilesReady, onClear }) {
|
|
|
800
979
|
disabled: busy
|
|
801
980
|
}
|
|
802
981
|
),
|
|
803
|
-
/* @__PURE__ */
|
|
982
|
+
/* @__PURE__ */ jsx7("span", { className: "text-xs text-muted-foreground", children: t("posts.form.static.pickHint") })
|
|
804
983
|
] }),
|
|
805
|
-
busy && /* @__PURE__ */
|
|
806
|
-
error && /* @__PURE__ */
|
|
984
|
+
busy && /* @__PURE__ */ jsx7("p", { className: "mt-2 text-sm text-muted-foreground", children: t("common.loading") }),
|
|
985
|
+
error && /* @__PURE__ */ jsx7("p", { className: "mt-2 text-sm text-destructive", children: error })
|
|
807
986
|
] }),
|
|
808
|
-
showCurrent && /* @__PURE__ */
|
|
809
|
-
pending && /* @__PURE__ */
|
|
987
|
+
showCurrent && /* @__PURE__ */ jsx7(CurrentBundle, { body: initial }),
|
|
988
|
+
pending && /* @__PURE__ */ jsx7(
|
|
810
989
|
PendingBundle,
|
|
811
990
|
{
|
|
812
991
|
files: pending,
|
|
@@ -818,15 +997,15 @@ function StaticUploader({ initial, onFilesReady, onClear }) {
|
|
|
818
997
|
}
|
|
819
998
|
function CurrentBundle({ body }) {
|
|
820
999
|
const t = useT();
|
|
821
|
-
return /* @__PURE__ */
|
|
822
|
-
/* @__PURE__ */
|
|
823
|
-
/* @__PURE__ */
|
|
1000
|
+
return /* @__PURE__ */ jsxs7("div", { className: "rounded-md border bg-muted/30 p-3", children: [
|
|
1001
|
+
/* @__PURE__ */ jsxs7("div", { className: "mb-2 flex items-center gap-2 text-sm font-medium", children: [
|
|
1002
|
+
/* @__PURE__ */ jsx7(FileArchive, { className: "h-4 w-4" }),
|
|
824
1003
|
t("posts.form.static.currentBundle", {
|
|
825
1004
|
count: body.files.length,
|
|
826
1005
|
entrypoint: body.entrypoint
|
|
827
1006
|
})
|
|
828
1007
|
] }),
|
|
829
|
-
/* @__PURE__ */
|
|
1008
|
+
/* @__PURE__ */ jsx7(FileList, { files: body.files })
|
|
830
1009
|
] });
|
|
831
1010
|
}
|
|
832
1011
|
function PendingBundle({
|
|
@@ -836,49 +1015,49 @@ function PendingBundle({
|
|
|
836
1015
|
}) {
|
|
837
1016
|
const t = useT();
|
|
838
1017
|
const totalBytes = files.reduce((sum, f) => sum + f.data.byteLength, 0);
|
|
839
|
-
return /* @__PURE__ */
|
|
840
|
-
/* @__PURE__ */
|
|
841
|
-
/* @__PURE__ */
|
|
842
|
-
/* @__PURE__ */
|
|
1018
|
+
return /* @__PURE__ */ jsxs7("div", { className: "rounded-md border p-3", children: [
|
|
1019
|
+
/* @__PURE__ */ jsxs7("div", { className: "mb-2 flex items-center justify-between gap-2 text-sm font-medium", children: [
|
|
1020
|
+
/* @__PURE__ */ jsxs7("span", { className: "flex items-center gap-2", children: [
|
|
1021
|
+
/* @__PURE__ */ jsx7(FileArchive, { className: "h-4 w-4" }),
|
|
843
1022
|
t("posts.form.static.pendingBundle", {
|
|
844
1023
|
count: files.length,
|
|
845
1024
|
size: formatBytes(totalBytes)
|
|
846
1025
|
})
|
|
847
1026
|
] }),
|
|
848
|
-
/* @__PURE__ */
|
|
849
|
-
/* @__PURE__ */
|
|
1027
|
+
/* @__PURE__ */ jsxs7(Button6, { type: "button", variant: "ghost", size: "sm", onClick: onClear, children: [
|
|
1028
|
+
/* @__PURE__ */ jsx7(X, { className: "mr-1 h-3 w-3" }),
|
|
850
1029
|
t("common.cancel")
|
|
851
1030
|
] })
|
|
852
1031
|
] }),
|
|
853
|
-
issues.length > 0 && /* @__PURE__ */
|
|
854
|
-
/* @__PURE__ */
|
|
855
|
-
/* @__PURE__ */
|
|
1032
|
+
issues.length > 0 && /* @__PURE__ */ jsxs7("div", { className: "mb-3 rounded-md border border-destructive/40 bg-destructive/5 p-2 text-sm", children: [
|
|
1033
|
+
/* @__PURE__ */ jsxs7("div", { className: "mb-1 flex items-center gap-2 font-medium text-destructive", children: [
|
|
1034
|
+
/* @__PURE__ */ jsx7(AlertTriangle, { className: "h-4 w-4" }),
|
|
856
1035
|
t("posts.form.static.issuesTitle", { count: issues.length })
|
|
857
1036
|
] }),
|
|
858
|
-
/* @__PURE__ */
|
|
859
|
-
issues.slice(0, 20).map((issue, idx) => /* @__PURE__ */
|
|
1037
|
+
/* @__PURE__ */ jsxs7("ul", { className: "space-y-0.5 text-xs", children: [
|
|
1038
|
+
issues.slice(0, 20).map((issue, idx) => /* @__PURE__ */ jsxs7("li", { className: "font-mono", children: [
|
|
860
1039
|
issue.path,
|
|
861
1040
|
": ",
|
|
862
1041
|
issue.reason
|
|
863
1042
|
] }, `${issue.path}-${idx}`)),
|
|
864
|
-
issues.length > 20 && /* @__PURE__ */
|
|
1043
|
+
issues.length > 20 && /* @__PURE__ */ jsxs7("li", { className: "font-mono text-muted-foreground", children: [
|
|
865
1044
|
"\u2026 ",
|
|
866
1045
|
issues.length - 20,
|
|
867
1046
|
" more"
|
|
868
1047
|
] })
|
|
869
1048
|
] }),
|
|
870
|
-
/* @__PURE__ */
|
|
1049
|
+
/* @__PURE__ */ jsx7("p", { className: "mt-2 text-xs text-muted-foreground", children: t("posts.form.static.issuesHint") })
|
|
871
1050
|
] }),
|
|
872
|
-
/* @__PURE__ */
|
|
1051
|
+
/* @__PURE__ */ jsx7(FileList, { files: files.map((f) => f.path) })
|
|
873
1052
|
] });
|
|
874
1053
|
}
|
|
875
1054
|
function FileList({ files }) {
|
|
876
|
-
return /* @__PURE__ */
|
|
877
|
-
files.slice(0, 40).map((path) => /* @__PURE__ */
|
|
878
|
-
/* @__PURE__ */
|
|
1055
|
+
return /* @__PURE__ */ jsxs7("ul", { className: "space-y-0.5 text-xs", children: [
|
|
1056
|
+
files.slice(0, 40).map((path) => /* @__PURE__ */ jsxs7("li", { className: "flex items-center gap-1.5 font-mono text-muted-foreground", children: [
|
|
1057
|
+
/* @__PURE__ */ jsx7(FileText, { className: "h-3 w-3 shrink-0" }),
|
|
879
1058
|
path
|
|
880
1059
|
] }, path)),
|
|
881
|
-
files.length > 40 && /* @__PURE__ */
|
|
1060
|
+
files.length > 40 && /* @__PURE__ */ jsxs7("li", { className: "font-mono text-xs text-muted-foreground", children: [
|
|
882
1061
|
"\u2026 ",
|
|
883
1062
|
files.length - 40,
|
|
884
1063
|
" more"
|
|
@@ -951,8 +1130,98 @@ function isFuture(iso) {
|
|
|
951
1130
|
return t > Date.now();
|
|
952
1131
|
}
|
|
953
1132
|
|
|
1133
|
+
// src/lib/post-draft.ts
|
|
1134
|
+
var DRAFT_KEY_PREFIX = "ampless:draft:";
|
|
1135
|
+
var NEW_POST_DRAFT_ID = "new";
|
|
1136
|
+
function draftKey(postId) {
|
|
1137
|
+
return `${DRAFT_KEY_PREFIX}${postId ?? NEW_POST_DRAFT_ID}`;
|
|
1138
|
+
}
|
|
1139
|
+
function hasStorage() {
|
|
1140
|
+
return typeof window !== "undefined" && !!window.localStorage;
|
|
1141
|
+
}
|
|
1142
|
+
function readDraft(postId) {
|
|
1143
|
+
if (!hasStorage()) return null;
|
|
1144
|
+
try {
|
|
1145
|
+
const raw = window.localStorage.getItem(draftKey(postId));
|
|
1146
|
+
if (!raw) return null;
|
|
1147
|
+
const parsed = JSON.parse(raw);
|
|
1148
|
+
if (!parsed || typeof parsed !== "object" || typeof parsed.format !== "string") {
|
|
1149
|
+
return null;
|
|
1150
|
+
}
|
|
1151
|
+
return parsed;
|
|
1152
|
+
} catch {
|
|
1153
|
+
return null;
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
function writeDraft(postId, draft) {
|
|
1157
|
+
if (!hasStorage()) return;
|
|
1158
|
+
try {
|
|
1159
|
+
window.localStorage.setItem(draftKey(postId), JSON.stringify(draft));
|
|
1160
|
+
} catch {
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
function clearDraft(postId) {
|
|
1164
|
+
if (!hasStorage()) return;
|
|
1165
|
+
try {
|
|
1166
|
+
window.localStorage.removeItem(draftKey(postId));
|
|
1167
|
+
} catch {
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
function clearAllDrafts() {
|
|
1171
|
+
if (!hasStorage()) return;
|
|
1172
|
+
try {
|
|
1173
|
+
const keys = [];
|
|
1174
|
+
for (let i = 0; i < window.localStorage.length; i++) {
|
|
1175
|
+
const k = window.localStorage.key(i);
|
|
1176
|
+
if (k && k.startsWith(DRAFT_KEY_PREFIX)) keys.push(k);
|
|
1177
|
+
}
|
|
1178
|
+
for (const k of keys) window.localStorage.removeItem(k);
|
|
1179
|
+
} catch {
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
function draftDiffersFromLoaded(draft, loaded) {
|
|
1183
|
+
if (draft.title !== loaded.title) return true;
|
|
1184
|
+
if (draft.slug !== loaded.slug) return true;
|
|
1185
|
+
if (draft.excerpt !== loaded.excerpt) return true;
|
|
1186
|
+
if (draft.format !== loaded.format) return true;
|
|
1187
|
+
if (draft.status !== loaded.status) return true;
|
|
1188
|
+
if (draft.tags !== loaded.tags) return true;
|
|
1189
|
+
if (draft.publishedAtInput !== loaded.publishedAtInput) return true;
|
|
1190
|
+
if (draft.noLayout !== loaded.noLayout) return true;
|
|
1191
|
+
return !deepEqual(draft.body, loaded.body);
|
|
1192
|
+
}
|
|
1193
|
+
function reconcileDraft(draft, loaded) {
|
|
1194
|
+
if (!draftDiffersFromLoaded(draft, loaded)) return "discard";
|
|
1195
|
+
if (draft.baseUpdatedAt !== loaded.updatedAt) return "stale";
|
|
1196
|
+
return "recover";
|
|
1197
|
+
}
|
|
1198
|
+
function deepEqual(a, b) {
|
|
1199
|
+
if (a === b) return true;
|
|
1200
|
+
if (typeof a !== typeof b) return false;
|
|
1201
|
+
if (a === null || b === null) return a === b;
|
|
1202
|
+
if (typeof a !== "object") return false;
|
|
1203
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
1204
|
+
if (!Array.isArray(a) || !Array.isArray(b)) return false;
|
|
1205
|
+
if (a.length !== b.length) return false;
|
|
1206
|
+
for (let i = 0; i < a.length; i++) {
|
|
1207
|
+
if (!deepEqual(a[i], b[i])) return false;
|
|
1208
|
+
}
|
|
1209
|
+
return true;
|
|
1210
|
+
}
|
|
1211
|
+
const ao = a;
|
|
1212
|
+
const bo = b;
|
|
1213
|
+
const ak = Object.keys(ao);
|
|
1214
|
+
const bk = Object.keys(bo);
|
|
1215
|
+
if (ak.length !== bk.length) return false;
|
|
1216
|
+
for (const k of ak) {
|
|
1217
|
+
if (!Object.prototype.hasOwnProperty.call(bo, k)) return false;
|
|
1218
|
+
if (!deepEqual(ao[k], bo[k])) return false;
|
|
1219
|
+
}
|
|
1220
|
+
return true;
|
|
1221
|
+
}
|
|
1222
|
+
|
|
954
1223
|
// src/components/post-form.tsx
|
|
955
|
-
import { jsx as
|
|
1224
|
+
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
956
1225
|
var EMPTY_TIPTAP_DOC = { type: "doc", content: [{ type: "paragraph" }] };
|
|
957
1226
|
var IMAGE_URL_RE = /\.(jpe?g|png|gif|webp|avif|svg|bmp|tiff?)(\?|$)/i;
|
|
958
1227
|
var STYLESHEET_URL_RE = /\.css(\?|$)/i;
|
|
@@ -978,23 +1247,51 @@ function defaultBodyForFormat(format) {
|
|
|
978
1247
|
function isStaticBody(value) {
|
|
979
1248
|
return !!value && typeof value === "object" && "entrypoint" in value && "files" in value && Array.isArray(value.files);
|
|
980
1249
|
}
|
|
1250
|
+
function formatDraftTime(epochMs, timezone, locale) {
|
|
1251
|
+
const d = new Date(epochMs);
|
|
1252
|
+
if (Number.isNaN(d.getTime())) return "";
|
|
1253
|
+
try {
|
|
1254
|
+
return new Intl.DateTimeFormat(locale || void 0, {
|
|
1255
|
+
year: "numeric",
|
|
1256
|
+
month: "short",
|
|
1257
|
+
day: "2-digit",
|
|
1258
|
+
hour: "2-digit",
|
|
1259
|
+
minute: "2-digit",
|
|
1260
|
+
timeZone: timezone
|
|
1261
|
+
}).format(d);
|
|
1262
|
+
} catch {
|
|
1263
|
+
return d.toLocaleString();
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
981
1266
|
function PostForm({ post }) {
|
|
982
1267
|
const router = useRouter();
|
|
983
1268
|
const t = useT();
|
|
984
1269
|
const isEdit = !!post;
|
|
985
1270
|
const bodyTextareaRef = useRef3(null);
|
|
986
|
-
const [title, setTitle] =
|
|
987
|
-
const [slug, setSlug] =
|
|
988
|
-
const [excerpt, setExcerpt] =
|
|
989
|
-
const [format, setFormat] =
|
|
990
|
-
const [body, setBody] =
|
|
991
|
-
const [status, setStatus] =
|
|
992
|
-
const [tagsInput, setTagsInput] =
|
|
993
|
-
const [noLayout, setNoLayout] =
|
|
994
|
-
const [publishedAtInput, setPublishedAtInput] =
|
|
995
|
-
const [saving, setSaving] =
|
|
996
|
-
const [error, setError] =
|
|
997
|
-
const [view, setView] =
|
|
1271
|
+
const [title, setTitle] = useState5(post?.title ?? "");
|
|
1272
|
+
const [slug, setSlug] = useState5(post?.slug ?? "");
|
|
1273
|
+
const [excerpt, setExcerpt] = useState5(post?.excerpt ?? "");
|
|
1274
|
+
const [format, setFormat] = useState5(post?.format ?? "tiptap");
|
|
1275
|
+
const [body, setBody] = useState5(post?.body ?? EMPTY_TIPTAP_DOC);
|
|
1276
|
+
const [status, setStatus] = useState5(post?.status ?? "draft");
|
|
1277
|
+
const [tagsInput, setTagsInput] = useState5((post?.tags ?? []).join(", "));
|
|
1278
|
+
const [noLayout, setNoLayout] = useState5(post?.metadata?.no_layout === true);
|
|
1279
|
+
const [publishedAtInput, setPublishedAtInput] = useState5(isoToLocalInput(post?.publishedAt));
|
|
1280
|
+
const [saving, setSaving] = useState5(false);
|
|
1281
|
+
const [error, setError] = useState5(null);
|
|
1282
|
+
const [view, setView] = useState5("edit");
|
|
1283
|
+
const [editorEpoch, setEditorEpoch] = useState5(0);
|
|
1284
|
+
const dirtyRef = useRef3(false);
|
|
1285
|
+
function markDirty() {
|
|
1286
|
+
dirtyRef.current = true;
|
|
1287
|
+
}
|
|
1288
|
+
const [recovery, setRecovery] = useState5(null);
|
|
1289
|
+
const [restoredHint, setRestoredHint] = useState5(false);
|
|
1290
|
+
const draftId = post?.postId ?? NEW_POST_DRAFT_ID;
|
|
1291
|
+
const isStaticFormat = format === "static";
|
|
1292
|
+
const cmsConfig = getAdminCmsConfig();
|
|
1293
|
+
const draftTimezone = cmsConfig?.timezone ?? "UTC";
|
|
1294
|
+
const draftLocale = cmsConfig?.locale ?? "en";
|
|
998
1295
|
const initialPublishedAtInput = isoToLocalInput(post?.publishedAt);
|
|
999
1296
|
const resolvedPublishedAt = resolvePublishedAtForSave({
|
|
1000
1297
|
status,
|
|
@@ -1002,7 +1299,7 @@ function PostForm({ post }) {
|
|
|
1002
1299
|
initialInput: initialPublishedAtInput,
|
|
1003
1300
|
existing: post?.publishedAt
|
|
1004
1301
|
});
|
|
1005
|
-
const [pendingBundle, setPendingBundle] =
|
|
1302
|
+
const [pendingBundle, setPendingBundle] = useState5(null);
|
|
1006
1303
|
const initialStaticBody = isStaticBody(post?.body) ? post.body : null;
|
|
1007
1304
|
function buildMetadata() {
|
|
1008
1305
|
const next = { ...post?.metadata ?? {} };
|
|
@@ -1017,9 +1314,87 @@ function PostForm({ post }) {
|
|
|
1017
1314
|
)
|
|
1018
1315
|
);
|
|
1019
1316
|
}
|
|
1317
|
+
const loadedState = {
|
|
1318
|
+
title: post?.title ?? "",
|
|
1319
|
+
slug: post?.slug ?? "",
|
|
1320
|
+
excerpt: post?.excerpt ?? "",
|
|
1321
|
+
format: post?.format ?? "tiptap",
|
|
1322
|
+
body: post?.body ?? EMPTY_TIPTAP_DOC,
|
|
1323
|
+
status: post?.status ?? "draft",
|
|
1324
|
+
tags: (post?.tags ?? []).join(", "),
|
|
1325
|
+
publishedAtInput: isoToLocalInput(post?.publishedAt),
|
|
1326
|
+
noLayout: post?.metadata?.no_layout === true,
|
|
1327
|
+
updatedAt: post?.updatedAt ?? null
|
|
1328
|
+
};
|
|
1329
|
+
function buildCurrentDraft() {
|
|
1330
|
+
return {
|
|
1331
|
+
title,
|
|
1332
|
+
slug,
|
|
1333
|
+
excerpt,
|
|
1334
|
+
format,
|
|
1335
|
+
body,
|
|
1336
|
+
status,
|
|
1337
|
+
tags: tagsInput,
|
|
1338
|
+
publishedAtInput,
|
|
1339
|
+
noLayout,
|
|
1340
|
+
baseUpdatedAt: post?.updatedAt ?? null,
|
|
1341
|
+
savedAt: Date.now()
|
|
1342
|
+
};
|
|
1343
|
+
}
|
|
1344
|
+
function applyDraft(draft) {
|
|
1345
|
+
setTitle(draft.title);
|
|
1346
|
+
setSlug(draft.slug);
|
|
1347
|
+
setExcerpt(draft.excerpt);
|
|
1348
|
+
setFormat(draft.format);
|
|
1349
|
+
setBody(draft.body);
|
|
1350
|
+
setStatus(draft.status);
|
|
1351
|
+
setTagsInput(draft.tags);
|
|
1352
|
+
setPublishedAtInput(draft.publishedAtInput);
|
|
1353
|
+
setNoLayout(draft.noLayout && draft.format === "html");
|
|
1354
|
+
setPendingBundle(null);
|
|
1355
|
+
setEditorEpoch((e) => e + 1);
|
|
1356
|
+
setView("edit");
|
|
1357
|
+
markDirty();
|
|
1358
|
+
setRestoredHint(true);
|
|
1359
|
+
}
|
|
1360
|
+
function acceptRecovery() {
|
|
1361
|
+
if (!recovery) return;
|
|
1362
|
+
applyDraft(recovery.draft);
|
|
1363
|
+
setRecovery(null);
|
|
1364
|
+
}
|
|
1365
|
+
function discardRecovery() {
|
|
1366
|
+
clearDraft(draftId);
|
|
1367
|
+
dirtyRef.current = false;
|
|
1368
|
+
setRecovery(null);
|
|
1369
|
+
}
|
|
1370
|
+
useEffect4(() => {
|
|
1371
|
+
if (loadedState.format === "static") return;
|
|
1372
|
+
const draft = readDraft(draftId);
|
|
1373
|
+
if (!draft) return;
|
|
1374
|
+
if (draft.format === "static") {
|
|
1375
|
+
clearDraft(draftId);
|
|
1376
|
+
return;
|
|
1377
|
+
}
|
|
1378
|
+
const decision = reconcileDraft(draft, loadedState);
|
|
1379
|
+
if (decision === "discard") {
|
|
1380
|
+
clearDraft(draftId);
|
|
1381
|
+
return;
|
|
1382
|
+
}
|
|
1383
|
+
setRecovery({ draft, decision });
|
|
1384
|
+
}, []);
|
|
1385
|
+
useEffect4(() => {
|
|
1386
|
+
if (isStaticFormat) return;
|
|
1387
|
+
if (!dirtyRef.current) return;
|
|
1388
|
+
const handle = setTimeout(() => {
|
|
1389
|
+
if (!dirtyRef.current) return;
|
|
1390
|
+
writeDraft(draftId, buildCurrentDraft());
|
|
1391
|
+
}, 1200);
|
|
1392
|
+
return () => clearTimeout(handle);
|
|
1393
|
+
}, [title, slug, excerpt, format, body, status, tagsInput, publishedAtInput, noLayout]);
|
|
1020
1394
|
function insertMediaSnippet(url) {
|
|
1021
1395
|
if (format === "tiptap") return;
|
|
1022
1396
|
const snippet = snippetFor(url, format);
|
|
1397
|
+
markDirty();
|
|
1023
1398
|
const ta = bodyTextareaRef.current;
|
|
1024
1399
|
const current = typeof body === "string" ? body : "";
|
|
1025
1400
|
if (!ta) {
|
|
@@ -1072,6 +1447,23 @@ function PostForm({ post }) {
|
|
|
1072
1447
|
setBody(nextBody);
|
|
1073
1448
|
setPendingBundle(null);
|
|
1074
1449
|
if (next !== "html") setNoLayout(false);
|
|
1450
|
+
markDirty();
|
|
1451
|
+
}
|
|
1452
|
+
function restoreRevision(rev) {
|
|
1453
|
+
const fmt = rev.format ?? "markdown";
|
|
1454
|
+
setTitle(rev.title ?? "");
|
|
1455
|
+
setSlug(rev.slug ?? "");
|
|
1456
|
+
setExcerpt(rev.excerpt ?? "");
|
|
1457
|
+
setFormat(fmt);
|
|
1458
|
+
setBody(rev.body);
|
|
1459
|
+
setStatus(rev.status ?? "draft");
|
|
1460
|
+
setTagsInput((rev.tags ?? []).join(", "));
|
|
1461
|
+
setPublishedAtInput(isoToLocalInput(rev.publishedAt));
|
|
1462
|
+
setNoLayout(rev.metadata?.no_layout === true && fmt === "html");
|
|
1463
|
+
setPendingBundle(null);
|
|
1464
|
+
setEditorEpoch((e) => e + 1);
|
|
1465
|
+
setView("edit");
|
|
1466
|
+
markDirty();
|
|
1075
1467
|
}
|
|
1076
1468
|
async function save(e) {
|
|
1077
1469
|
e.preventDefault();
|
|
@@ -1097,10 +1489,10 @@ function PostForm({ post }) {
|
|
|
1097
1489
|
});
|
|
1098
1490
|
nextBody = result.body;
|
|
1099
1491
|
metadata = { ...metadata ?? {}, files: result.filesMeta };
|
|
1100
|
-
} else if (initialStaticBody) {
|
|
1101
|
-
nextBody = initialStaticBody;
|
|
1102
1492
|
} else {
|
|
1103
|
-
|
|
1493
|
+
const fallbackManifest = isStaticBody(body) ? body : initialStaticBody;
|
|
1494
|
+
if (!fallbackManifest) throw new Error(t("posts.form.static.noBundle"));
|
|
1495
|
+
nextBody = fallbackManifest;
|
|
1104
1496
|
}
|
|
1105
1497
|
}
|
|
1106
1498
|
if (isEdit) {
|
|
@@ -1128,6 +1520,9 @@ function PostForm({ post }) {
|
|
|
1128
1520
|
metadata
|
|
1129
1521
|
});
|
|
1130
1522
|
}
|
|
1523
|
+
dirtyRef.current = false;
|
|
1524
|
+
clearDraft(draftId);
|
|
1525
|
+
if (!isEdit) clearDraft(NEW_POST_DRAFT_ID);
|
|
1131
1526
|
router.push("/admin/posts");
|
|
1132
1527
|
router.refresh();
|
|
1133
1528
|
} catch (err) {
|
|
@@ -1163,9 +1558,9 @@ function PostForm({ post }) {
|
|
|
1163
1558
|
publishedAt: resolvedPublishedAt,
|
|
1164
1559
|
tags: parseTags(tagsInput)
|
|
1165
1560
|
};
|
|
1166
|
-
return /* @__PURE__ */
|
|
1167
|
-
/* @__PURE__ */
|
|
1168
|
-
/* @__PURE__ */
|
|
1561
|
+
return /* @__PURE__ */ jsxs8("form", { onSubmit: save, className: "space-y-6", children: [
|
|
1562
|
+
/* @__PURE__ */ jsxs8("div", { className: "flex gap-1 border-b", children: [
|
|
1563
|
+
/* @__PURE__ */ jsx8(
|
|
1169
1564
|
"button",
|
|
1170
1565
|
{
|
|
1171
1566
|
type: "button",
|
|
@@ -1175,7 +1570,7 @@ function PostForm({ post }) {
|
|
|
1175
1570
|
children: t("posts.form.tabEdit")
|
|
1176
1571
|
}
|
|
1177
1572
|
),
|
|
1178
|
-
/* @__PURE__ */
|
|
1573
|
+
/* @__PURE__ */ jsx8(
|
|
1179
1574
|
"button",
|
|
1180
1575
|
{
|
|
1181
1576
|
type: "button",
|
|
@@ -1186,24 +1581,24 @@ function PostForm({ post }) {
|
|
|
1186
1581
|
}
|
|
1187
1582
|
)
|
|
1188
1583
|
] }),
|
|
1189
|
-
view === "preview" && /* @__PURE__ */
|
|
1190
|
-
/* @__PURE__ */
|
|
1191
|
-
/* @__PURE__ */
|
|
1192
|
-
/* @__PURE__ */
|
|
1193
|
-
previewPost.publishedAt ? /* @__PURE__ */
|
|
1194
|
-
/* @__PURE__ */
|
|
1195
|
-
/* @__PURE__ */
|
|
1584
|
+
view === "preview" && /* @__PURE__ */ jsxs8("article", { className: "space-y-4", children: [
|
|
1585
|
+
/* @__PURE__ */ jsxs8("header", { className: "border-b pb-4", children: [
|
|
1586
|
+
/* @__PURE__ */ jsx8("h1", { className: "text-3xl font-bold tracking-tight", children: title || /* @__PURE__ */ jsx8("span", { className: "text-muted-foreground italic", children: t("posts.form.previewNoTitle") }) }),
|
|
1587
|
+
/* @__PURE__ */ jsxs8("p", { className: "mt-2 text-sm text-muted-foreground", children: [
|
|
1588
|
+
previewPost.publishedAt ? /* @__PURE__ */ jsx8("time", { dateTime: previewPost.publishedAt, children: formatDate(previewPost.publishedAt) }) : /* @__PURE__ */ jsx8("span", { children: t("common.draft") }),
|
|
1589
|
+
/* @__PURE__ */ jsx8("span", { className: "mx-2", children: "\xB7" }),
|
|
1590
|
+
/* @__PURE__ */ jsx8("span", { className: "font-mono text-xs uppercase", children: format })
|
|
1196
1591
|
] }),
|
|
1197
|
-
excerpt && /* @__PURE__ */
|
|
1592
|
+
excerpt && /* @__PURE__ */ jsx8("p", { className: "mt-3 text-base text-muted-foreground", children: excerpt })
|
|
1198
1593
|
] }),
|
|
1199
|
-
format === "static" ? /* @__PURE__ */
|
|
1594
|
+
format === "static" ? /* @__PURE__ */ jsx8("p", { className: "text-sm text-muted-foreground", children: t("posts.form.static.previewHint") }) : /* @__PURE__ */ jsx8(
|
|
1200
1595
|
"div",
|
|
1201
1596
|
{
|
|
1202
1597
|
className: "prose prose-neutral dark:prose-invert max-w-none",
|
|
1203
|
-
dangerouslySetInnerHTML: { __html:
|
|
1598
|
+
dangerouslySetInnerHTML: { __html: renderBody2(previewPost) }
|
|
1204
1599
|
}
|
|
1205
1600
|
),
|
|
1206
|
-
previewPost.tags && previewPost.tags.length > 0 && /* @__PURE__ */
|
|
1601
|
+
previewPost.tags && previewPost.tags.length > 0 && /* @__PURE__ */ jsx8("div", { className: "flex flex-wrap gap-2 border-t pt-4 text-sm", children: previewPost.tags.map((tag) => /* @__PURE__ */ jsxs8(
|
|
1207
1602
|
"span",
|
|
1208
1603
|
{
|
|
1209
1604
|
className: "rounded-full border px-2 py-0.5 text-xs text-muted-foreground",
|
|
@@ -1214,12 +1609,33 @@ function PostForm({ post }) {
|
|
|
1214
1609
|
},
|
|
1215
1610
|
tag
|
|
1216
1611
|
)) }),
|
|
1217
|
-
/* @__PURE__ */
|
|
1612
|
+
/* @__PURE__ */ jsx8("p", { className: "text-xs text-muted-foreground", children: t("posts.form.previewHint") })
|
|
1218
1613
|
] }),
|
|
1219
|
-
/* @__PURE__ */
|
|
1220
|
-
/* @__PURE__ */
|
|
1221
|
-
|
|
1222
|
-
|
|
1614
|
+
/* @__PURE__ */ jsxs8("div", { className: view === "edit" ? "space-y-6" : "hidden", children: [
|
|
1615
|
+
recovery && /* @__PURE__ */ jsxs8(
|
|
1616
|
+
"div",
|
|
1617
|
+
{
|
|
1618
|
+
role: "alert",
|
|
1619
|
+
className: recovery.decision === "stale" ? "space-y-2 rounded-md border border-amber-300 bg-amber-50 p-4 text-sm dark:border-amber-800 dark:bg-amber-950" : "space-y-2 rounded-md border border-[var(--primary)]/40 bg-[var(--primary)]/5 p-4 text-sm",
|
|
1620
|
+
children: [
|
|
1621
|
+
/* @__PURE__ */ jsx8("p", { className: "font-medium", children: recovery.decision === "stale" ? t("posts.draft.staleTitle") : t("posts.draft.recoverTitle") }),
|
|
1622
|
+
/* @__PURE__ */ jsx8("p", { className: "text-muted-foreground", children: recovery.decision === "stale" ? t("posts.draft.staleBody", {
|
|
1623
|
+
when: formatDraftTime(recovery.draft.savedAt, draftTimezone, draftLocale)
|
|
1624
|
+
}) : t("posts.draft.recoverBody", {
|
|
1625
|
+
when: formatDraftTime(recovery.draft.savedAt, draftTimezone, draftLocale)
|
|
1626
|
+
}) }),
|
|
1627
|
+
/* @__PURE__ */ jsxs8("div", { className: "flex gap-2", children: [
|
|
1628
|
+
/* @__PURE__ */ jsx8(Button7, { type: "button", size: "sm", onClick: acceptRecovery, children: recovery.decision === "stale" ? t("posts.draft.restoreAnyway") : t("posts.draft.restore") }),
|
|
1629
|
+
/* @__PURE__ */ jsx8(Button7, { type: "button", size: "sm", variant: "outline", onClick: discardRecovery, children: t("posts.draft.discard") })
|
|
1630
|
+
] })
|
|
1631
|
+
]
|
|
1632
|
+
}
|
|
1633
|
+
),
|
|
1634
|
+
restoredHint && !recovery && /* @__PURE__ */ jsx8("p", { className: "rounded-md border border-[var(--primary)]/40 bg-[var(--primary)]/5 px-3 py-2 text-xs text-muted-foreground", children: t("posts.draft.restored") }),
|
|
1635
|
+
isStaticFormat && /* @__PURE__ */ jsx8("p", { className: "rounded-md border bg-muted/40 px-3 py-2 text-xs text-muted-foreground", children: t("posts.draft.staticUnsupported") }),
|
|
1636
|
+
/* @__PURE__ */ jsxs8("div", { className: "space-y-2", children: [
|
|
1637
|
+
/* @__PURE__ */ jsx8(Label, { htmlFor: "title", children: t("posts.form.title") }),
|
|
1638
|
+
/* @__PURE__ */ jsx8(
|
|
1223
1639
|
Input,
|
|
1224
1640
|
{
|
|
1225
1641
|
id: "title",
|
|
@@ -1228,37 +1644,44 @@ function PostForm({ post }) {
|
|
|
1228
1644
|
onChange: (e) => {
|
|
1229
1645
|
setTitle(e.target.value);
|
|
1230
1646
|
if (!isEdit && !slug) setSlug(slugify(e.target.value));
|
|
1647
|
+
markDirty();
|
|
1231
1648
|
}
|
|
1232
1649
|
}
|
|
1233
1650
|
)
|
|
1234
1651
|
] }),
|
|
1235
|
-
/* @__PURE__ */
|
|
1236
|
-
/* @__PURE__ */
|
|
1237
|
-
/* @__PURE__ */
|
|
1652
|
+
/* @__PURE__ */ jsxs8("div", { className: "space-y-2", children: [
|
|
1653
|
+
/* @__PURE__ */ jsx8(Label, { htmlFor: "slug", children: t("posts.form.slug") }),
|
|
1654
|
+
/* @__PURE__ */ jsx8(
|
|
1238
1655
|
Input,
|
|
1239
1656
|
{
|
|
1240
1657
|
id: "slug",
|
|
1241
1658
|
value: slug,
|
|
1242
|
-
onChange: (e) =>
|
|
1659
|
+
onChange: (e) => {
|
|
1660
|
+
setSlug(e.target.value);
|
|
1661
|
+
markDirty();
|
|
1662
|
+
},
|
|
1243
1663
|
placeholder: slugify(title) || t("posts.form.slugPlaceholder")
|
|
1244
1664
|
}
|
|
1245
1665
|
)
|
|
1246
1666
|
] }),
|
|
1247
|
-
/* @__PURE__ */
|
|
1248
|
-
/* @__PURE__ */
|
|
1249
|
-
/* @__PURE__ */
|
|
1667
|
+
/* @__PURE__ */ jsxs8("div", { className: "space-y-2", children: [
|
|
1668
|
+
/* @__PURE__ */ jsx8(Label, { htmlFor: "excerpt", children: t("posts.form.excerpt") }),
|
|
1669
|
+
/* @__PURE__ */ jsx8(
|
|
1250
1670
|
Textarea,
|
|
1251
1671
|
{
|
|
1252
1672
|
id: "excerpt",
|
|
1253
1673
|
rows: 2,
|
|
1254
1674
|
value: excerpt,
|
|
1255
|
-
onChange: (e) =>
|
|
1675
|
+
onChange: (e) => {
|
|
1676
|
+
setExcerpt(e.target.value);
|
|
1677
|
+
markDirty();
|
|
1678
|
+
}
|
|
1256
1679
|
}
|
|
1257
1680
|
)
|
|
1258
1681
|
] }),
|
|
1259
|
-
/* @__PURE__ */
|
|
1260
|
-
/* @__PURE__ */
|
|
1261
|
-
/* @__PURE__ */
|
|
1682
|
+
/* @__PURE__ */ jsxs8("div", { className: "space-y-2", children: [
|
|
1683
|
+
/* @__PURE__ */ jsx8(Label, { htmlFor: "format", children: t("posts.form.format") }),
|
|
1684
|
+
/* @__PURE__ */ jsxs8(
|
|
1262
1685
|
"select",
|
|
1263
1686
|
{
|
|
1264
1687
|
id: "format",
|
|
@@ -1266,120 +1689,145 @@ function PostForm({ post }) {
|
|
|
1266
1689
|
onChange: (e) => changeFormat(e.target.value),
|
|
1267
1690
|
className: "flex h-9 w-full max-w-xs rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm",
|
|
1268
1691
|
children: [
|
|
1269
|
-
/* @__PURE__ */
|
|
1270
|
-
/* @__PURE__ */
|
|
1271
|
-
/* @__PURE__ */
|
|
1272
|
-
/* @__PURE__ */
|
|
1692
|
+
/* @__PURE__ */ jsx8("option", { value: "tiptap", children: "Tiptap (rich editor)" }),
|
|
1693
|
+
/* @__PURE__ */ jsx8("option", { value: "markdown", children: "Markdown" }),
|
|
1694
|
+
/* @__PURE__ */ jsx8("option", { value: "html", children: "HTML" }),
|
|
1695
|
+
/* @__PURE__ */ jsx8("option", { value: "static", children: t("posts.form.formatStaticLabel") })
|
|
1273
1696
|
]
|
|
1274
1697
|
}
|
|
1275
1698
|
),
|
|
1276
|
-
/* @__PURE__ */
|
|
1699
|
+
/* @__PURE__ */ jsx8("p", { className: "text-xs text-muted-foreground", children: t("posts.form.formatHint") })
|
|
1277
1700
|
] }),
|
|
1278
|
-
/* @__PURE__ */
|
|
1279
|
-
/* @__PURE__ */
|
|
1280
|
-
/* @__PURE__ */
|
|
1701
|
+
/* @__PURE__ */ jsxs8("div", { className: "space-y-2", children: [
|
|
1702
|
+
/* @__PURE__ */ jsxs8("div", { className: "flex items-center justify-between", children: [
|
|
1703
|
+
/* @__PURE__ */ jsx8(Label, { children: t("posts.form.body") }),
|
|
1281
1704
|
format !== "tiptap" && format !== "static" && // For textarea-based formats (markdown / html) there's no
|
|
1282
1705
|
// embedded toolbar, so we surface the MediaPicker as a
|
|
1283
1706
|
// standalone button. Selecting an asset inserts a
|
|
1284
1707
|
// format-aware snippet at the cursor.
|
|
1285
|
-
/* @__PURE__ */
|
|
1708
|
+
/* @__PURE__ */ jsx8(
|
|
1286
1709
|
MediaPicker,
|
|
1287
1710
|
{
|
|
1288
1711
|
onSelect: insertMediaSnippet,
|
|
1289
|
-
trigger: /* @__PURE__ */
|
|
1290
|
-
/* @__PURE__ */
|
|
1712
|
+
trigger: /* @__PURE__ */ jsxs8(Button7, { type: "button", variant: "outline", size: "sm", children: [
|
|
1713
|
+
/* @__PURE__ */ jsx8(ImageIcon3, { className: "mr-2 h-3 w-3" }),
|
|
1291
1714
|
t("posts.form.insertMedia")
|
|
1292
1715
|
] })
|
|
1293
1716
|
}
|
|
1294
1717
|
)
|
|
1295
1718
|
] }),
|
|
1296
|
-
format === "tiptap" ? /* @__PURE__ */
|
|
1719
|
+
format === "tiptap" ? /* @__PURE__ */ jsx8(
|
|
1720
|
+
TiptapEditor,
|
|
1721
|
+
{
|
|
1722
|
+
initialContent: body,
|
|
1723
|
+
onChange: setBody,
|
|
1724
|
+
onUserEdit: markDirty
|
|
1725
|
+
},
|
|
1726
|
+
editorEpoch
|
|
1727
|
+
) : format === "static" ? /* @__PURE__ */ jsx8(
|
|
1297
1728
|
StaticUploader,
|
|
1298
1729
|
{
|
|
1299
1730
|
initial: initialStaticBody,
|
|
1300
1731
|
onFilesReady: (files, entrypoint) => setPendingBundle({ files, entrypoint }),
|
|
1301
1732
|
onClear: () => setPendingBundle(null)
|
|
1302
1733
|
}
|
|
1303
|
-
) : /* @__PURE__ */
|
|
1734
|
+
) : /* @__PURE__ */ jsx8(
|
|
1304
1735
|
Textarea,
|
|
1305
1736
|
{
|
|
1306
1737
|
ref: bodyTextareaRef,
|
|
1307
1738
|
rows: 20,
|
|
1308
1739
|
value: typeof body === "string" ? body : "",
|
|
1309
|
-
onChange: (e) =>
|
|
1740
|
+
onChange: (e) => {
|
|
1741
|
+
setBody(e.target.value);
|
|
1742
|
+
markDirty();
|
|
1743
|
+
},
|
|
1310
1744
|
className: "font-mono text-xs"
|
|
1311
1745
|
}
|
|
1312
1746
|
)
|
|
1313
1747
|
] }),
|
|
1314
|
-
/* @__PURE__ */
|
|
1315
|
-
/* @__PURE__ */
|
|
1316
|
-
/* @__PURE__ */
|
|
1748
|
+
/* @__PURE__ */ jsxs8("div", { className: "space-y-2", children: [
|
|
1749
|
+
/* @__PURE__ */ jsx8(Label, { htmlFor: "tags", children: t("posts.form.tags") }),
|
|
1750
|
+
/* @__PURE__ */ jsx8(
|
|
1317
1751
|
Input,
|
|
1318
1752
|
{
|
|
1319
1753
|
id: "tags",
|
|
1320
1754
|
value: tagsInput,
|
|
1321
|
-
onChange: (e) =>
|
|
1755
|
+
onChange: (e) => {
|
|
1756
|
+
setTagsInput(e.target.value);
|
|
1757
|
+
markDirty();
|
|
1758
|
+
},
|
|
1322
1759
|
placeholder: t("posts.form.tagsPlaceholder")
|
|
1323
1760
|
}
|
|
1324
1761
|
),
|
|
1325
|
-
/* @__PURE__ */
|
|
1762
|
+
/* @__PURE__ */ jsx8("p", { className: "text-xs text-muted-foreground", children: t("posts.form.tagsHint") })
|
|
1326
1763
|
] }),
|
|
1327
|
-
/* @__PURE__ */
|
|
1328
|
-
/* @__PURE__ */
|
|
1329
|
-
/* @__PURE__ */
|
|
1764
|
+
/* @__PURE__ */ jsxs8("div", { className: "space-y-2", children: [
|
|
1765
|
+
/* @__PURE__ */ jsx8(Label, { htmlFor: "status", children: t("posts.form.status") }),
|
|
1766
|
+
/* @__PURE__ */ jsxs8(
|
|
1330
1767
|
"select",
|
|
1331
1768
|
{
|
|
1332
1769
|
id: "status",
|
|
1333
1770
|
value: status,
|
|
1334
|
-
onChange: (e) =>
|
|
1771
|
+
onChange: (e) => {
|
|
1772
|
+
setStatus(e.target.value);
|
|
1773
|
+
markDirty();
|
|
1774
|
+
},
|
|
1335
1775
|
className: "flex h-9 w-full max-w-xs rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm",
|
|
1336
1776
|
children: [
|
|
1337
|
-
/* @__PURE__ */
|
|
1338
|
-
/* @__PURE__ */
|
|
1777
|
+
/* @__PURE__ */ jsx8("option", { value: "draft", children: t("common.draft") }),
|
|
1778
|
+
/* @__PURE__ */ jsx8("option", { value: "published", children: t("common.published") })
|
|
1339
1779
|
]
|
|
1340
1780
|
}
|
|
1341
1781
|
)
|
|
1342
1782
|
] }),
|
|
1343
|
-
/* @__PURE__ */
|
|
1344
|
-
/* @__PURE__ */
|
|
1345
|
-
/* @__PURE__ */
|
|
1783
|
+
/* @__PURE__ */ jsxs8("div", { className: "space-y-2", children: [
|
|
1784
|
+
/* @__PURE__ */ jsx8(Label, { htmlFor: "publishedAt", children: t("posts.form.publishedAt") }),
|
|
1785
|
+
/* @__PURE__ */ jsx8(
|
|
1346
1786
|
"input",
|
|
1347
1787
|
{
|
|
1348
1788
|
id: "publishedAt",
|
|
1349
1789
|
type: "datetime-local",
|
|
1350
1790
|
value: publishedAtInput,
|
|
1351
|
-
onChange: (e) =>
|
|
1791
|
+
onChange: (e) => {
|
|
1792
|
+
setPublishedAtInput(e.target.value);
|
|
1793
|
+
markDirty();
|
|
1794
|
+
},
|
|
1352
1795
|
className: "flex h-9 w-full max-w-xs rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm"
|
|
1353
1796
|
}
|
|
1354
1797
|
),
|
|
1355
|
-
/* @__PURE__ */
|
|
1356
|
-
status === "published" && isFuture(resolvedPublishedAt) && /* @__PURE__ */
|
|
1798
|
+
/* @__PURE__ */ jsx8("p", { className: "text-xs text-muted-foreground", children: t("posts.form.publishedAtHint") }),
|
|
1799
|
+
status === "published" && isFuture(resolvedPublishedAt) && /* @__PURE__ */ jsx8("p", { className: "text-xs text-amber-600 dark:text-amber-400", children: t("posts.form.scheduledNotice", { date: publishedAtInput }) })
|
|
1357
1800
|
] }),
|
|
1358
|
-
format === "html" && /* @__PURE__ */
|
|
1359
|
-
/* @__PURE__ */
|
|
1801
|
+
format === "html" && /* @__PURE__ */ jsx8("div", { className: "space-y-2", children: /* @__PURE__ */ jsxs8("label", { className: "flex items-start gap-2 text-sm", children: [
|
|
1802
|
+
/* @__PURE__ */ jsx8(
|
|
1360
1803
|
"input",
|
|
1361
1804
|
{
|
|
1362
1805
|
type: "checkbox",
|
|
1363
1806
|
checked: noLayout,
|
|
1364
|
-
onChange: (e) =>
|
|
1807
|
+
onChange: (e) => {
|
|
1808
|
+
setNoLayout(e.target.checked);
|
|
1809
|
+
markDirty();
|
|
1810
|
+
},
|
|
1365
1811
|
className: "mt-1"
|
|
1366
1812
|
}
|
|
1367
1813
|
),
|
|
1368
|
-
/* @__PURE__ */
|
|
1369
|
-
/* @__PURE__ */
|
|
1370
|
-
/* @__PURE__ */
|
|
1814
|
+
/* @__PURE__ */ jsxs8("span", { children: [
|
|
1815
|
+
/* @__PURE__ */ jsx8("span", { className: "font-medium", children: t("posts.form.noLayout") }),
|
|
1816
|
+
/* @__PURE__ */ jsx8("span", { className: "block text-xs text-muted-foreground", children: t("posts.form.noLayoutHint") })
|
|
1371
1817
|
] })
|
|
1372
1818
|
] }) }),
|
|
1373
|
-
error && /* @__PURE__ */
|
|
1374
|
-
/* @__PURE__ */
|
|
1375
|
-
/* @__PURE__ */
|
|
1376
|
-
isEdit && /* @__PURE__ */
|
|
1377
|
-
] })
|
|
1819
|
+
error && /* @__PURE__ */ jsx8("p", { className: "text-sm text-destructive", children: error }),
|
|
1820
|
+
/* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-2", children: [
|
|
1821
|
+
/* @__PURE__ */ jsx8(Button7, { type: "submit", disabled: saving, children: saving ? t("common.saving") : isEdit ? t("posts.form.saveChanges") : t("posts.form.createPost") }),
|
|
1822
|
+
isEdit && /* @__PURE__ */ jsx8(Button7, { type: "button", variant: "destructive", onClick: handleDelete, disabled: saving, children: t("posts.form.delete") })
|
|
1823
|
+
] }),
|
|
1824
|
+
isEdit && post && /* @__PURE__ */ jsx8(PostHistoryPanel, { postId: post.postId, onRestore: restoreRevision })
|
|
1378
1825
|
] })
|
|
1379
1826
|
] });
|
|
1380
1827
|
}
|
|
1381
1828
|
|
|
1382
1829
|
export {
|
|
1383
1830
|
MediaPicker,
|
|
1831
|
+
clearAllDrafts,
|
|
1384
1832
|
PostForm
|
|
1385
1833
|
};
|