@fydemy/cms 1.0.0 → 1.0.2
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/README.md +310 -0
- package/dist/bin.d.mts +1 -0
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +199 -0
- package/dist/bin.js.map +1 -0
- package/dist/bin.mjs +176 -0
- package/dist/bin.mjs.map +1 -0
- package/dist/index.d.mts +6 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +1196 -10
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1194 -10
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -1
package/dist/index.js
CHANGED
|
@@ -104,8 +104,10 @@ var init_rate_limit = __esm({
|
|
|
104
104
|
// src/index.ts
|
|
105
105
|
var index_exports = {};
|
|
106
106
|
__export(index_exports, {
|
|
107
|
+
AdminDashboard: () => AdminDashboard,
|
|
107
108
|
GitHubStorage: () => GitHubStorage,
|
|
108
109
|
LocalStorage: () => LocalStorage,
|
|
110
|
+
Login: () => Login,
|
|
109
111
|
MAX_FILE_SIZE: () => MAX_FILE_SIZE,
|
|
110
112
|
MAX_PASSWORD_LENGTH: () => MAX_PASSWORD_LENGTH,
|
|
111
113
|
MAX_USERNAME_LENGTH: () => MAX_USERNAME_LENGTH,
|
|
@@ -816,6 +818,1060 @@ async function handleUpload(request) {
|
|
|
816
818
|
}
|
|
817
819
|
}
|
|
818
820
|
|
|
821
|
+
// src/ui/AdminDashboard.tsx
|
|
822
|
+
var import_react = require("react");
|
|
823
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
824
|
+
function AdminDashboard() {
|
|
825
|
+
const [entries, setEntries] = (0, import_react.useState)([]);
|
|
826
|
+
const [currentPath, setCurrentPath] = (0, import_react.useState)("");
|
|
827
|
+
const [selectedFile, setSelectedFile] = (0, import_react.useState)(null);
|
|
828
|
+
const [fields, setFields] = (0, import_react.useState)({});
|
|
829
|
+
const [loading, setLoading] = (0, import_react.useState)(false);
|
|
830
|
+
const [message, setMessage] = (0, import_react.useState)("");
|
|
831
|
+
const [newFileName, setNewFileName] = (0, import_react.useState)("");
|
|
832
|
+
const [uploading, setUploading] = (0, import_react.useState)(false);
|
|
833
|
+
(0, import_react.useEffect)(() => {
|
|
834
|
+
loadDirectory(currentPath);
|
|
835
|
+
}, [currentPath]);
|
|
836
|
+
const loadDirectory = async (path4) => {
|
|
837
|
+
try {
|
|
838
|
+
const url = path4 ? `/api/cms/list/${path4}` : "/api/cms/list";
|
|
839
|
+
const response = await fetch(url);
|
|
840
|
+
const data = await response.json();
|
|
841
|
+
setEntries(data.entries || []);
|
|
842
|
+
} catch (error) {
|
|
843
|
+
console.error("Failed to load directory:", error);
|
|
844
|
+
}
|
|
845
|
+
};
|
|
846
|
+
const loadFile = async (filePath) => {
|
|
847
|
+
try {
|
|
848
|
+
const response = await fetch(`/api/cms/content/${filePath}`);
|
|
849
|
+
const data = await response.json();
|
|
850
|
+
setSelectedFile(filePath);
|
|
851
|
+
const loadedFields = {};
|
|
852
|
+
if (data.content !== void 0) {
|
|
853
|
+
loadedFields["content"] = { type: "markdown", value: data.content };
|
|
854
|
+
}
|
|
855
|
+
Object.entries(data.data || {}).forEach(([key, value]) => {
|
|
856
|
+
loadedFields[key] = {
|
|
857
|
+
type: detectFieldType(value),
|
|
858
|
+
value
|
|
859
|
+
};
|
|
860
|
+
});
|
|
861
|
+
setFields(loadedFields);
|
|
862
|
+
} catch (error) {
|
|
863
|
+
setMessage("Failed to load file");
|
|
864
|
+
}
|
|
865
|
+
};
|
|
866
|
+
const detectFieldType = (value) => {
|
|
867
|
+
if (typeof value === "number") return "number";
|
|
868
|
+
if (typeof value === "string") {
|
|
869
|
+
if (value.match(/^\d{4}-\d{2}-\d{2}/)) return "date";
|
|
870
|
+
if (value.startsWith("/uploads/") || value.startsWith("http"))
|
|
871
|
+
return "image";
|
|
872
|
+
if (value.length > 100) return "markdown";
|
|
873
|
+
}
|
|
874
|
+
return "text";
|
|
875
|
+
};
|
|
876
|
+
const saveFile = async () => {
|
|
877
|
+
if (!selectedFile) return;
|
|
878
|
+
setLoading(true);
|
|
879
|
+
setMessage("");
|
|
880
|
+
try {
|
|
881
|
+
const frontmatter = {};
|
|
882
|
+
let content = "";
|
|
883
|
+
Object.entries(fields).forEach(([key, field]) => {
|
|
884
|
+
if (key === "content") {
|
|
885
|
+
content = field.value;
|
|
886
|
+
} else {
|
|
887
|
+
frontmatter[key] = field.value;
|
|
888
|
+
}
|
|
889
|
+
});
|
|
890
|
+
const response = await fetch(`/api/cms/content/${selectedFile}`, {
|
|
891
|
+
method: "POST",
|
|
892
|
+
headers: { "Content-Type": "application/json" },
|
|
893
|
+
body: JSON.stringify({ data: frontmatter, content })
|
|
894
|
+
});
|
|
895
|
+
if (response.ok) {
|
|
896
|
+
setMessage("\u2705 File saved successfully!");
|
|
897
|
+
setTimeout(() => setMessage(""), 3e3);
|
|
898
|
+
} else {
|
|
899
|
+
const errorData = await response.json().catch(() => ({}));
|
|
900
|
+
setMessage(
|
|
901
|
+
`\u274C Failed to save file: ${errorData.error || response.statusText}`
|
|
902
|
+
);
|
|
903
|
+
}
|
|
904
|
+
} catch (error) {
|
|
905
|
+
setMessage(
|
|
906
|
+
`\u274C Error saving file: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
907
|
+
);
|
|
908
|
+
} finally {
|
|
909
|
+
setLoading(false);
|
|
910
|
+
}
|
|
911
|
+
};
|
|
912
|
+
const createFile = async () => {
|
|
913
|
+
if (!newFileName) return;
|
|
914
|
+
const fileName = newFileName.endsWith(".md") ? newFileName : `${newFileName}.md`;
|
|
915
|
+
const fullPath = currentPath ? `${currentPath}/${fileName}` : fileName;
|
|
916
|
+
setLoading(true);
|
|
917
|
+
setMessage("");
|
|
918
|
+
try {
|
|
919
|
+
const response = await fetch(`/api/cms/content/${fullPath}`, {
|
|
920
|
+
method: "POST",
|
|
921
|
+
headers: { "Content-Type": "application/json" },
|
|
922
|
+
body: JSON.stringify({
|
|
923
|
+
data: {},
|
|
924
|
+
content: ""
|
|
925
|
+
})
|
|
926
|
+
});
|
|
927
|
+
if (response.ok) {
|
|
928
|
+
setMessage("\u2705 File created!");
|
|
929
|
+
setNewFileName("");
|
|
930
|
+
loadDirectory(currentPath);
|
|
931
|
+
loadFile(fullPath);
|
|
932
|
+
} else {
|
|
933
|
+
setMessage("\u274C Failed to create file");
|
|
934
|
+
}
|
|
935
|
+
} catch (error) {
|
|
936
|
+
setMessage("\u274C Error creating file");
|
|
937
|
+
} finally {
|
|
938
|
+
setLoading(false);
|
|
939
|
+
}
|
|
940
|
+
};
|
|
941
|
+
const duplicateFile = async () => {
|
|
942
|
+
if (!selectedFile) return;
|
|
943
|
+
const newName = prompt("Enter new filename (without .md extension):");
|
|
944
|
+
if (!newName) return;
|
|
945
|
+
const duplicateName = currentPath ? `${currentPath}/${newName}.md` : `${newName}.md`;
|
|
946
|
+
setLoading(true);
|
|
947
|
+
setMessage("");
|
|
948
|
+
try {
|
|
949
|
+
const frontmatter = {};
|
|
950
|
+
let content = "";
|
|
951
|
+
Object.entries(fields).forEach(([key, field]) => {
|
|
952
|
+
if (key === "content") {
|
|
953
|
+
content = field.value;
|
|
954
|
+
} else {
|
|
955
|
+
frontmatter[key] = field.value;
|
|
956
|
+
}
|
|
957
|
+
});
|
|
958
|
+
const response = await fetch(`/api/cms/content/${duplicateName}`, {
|
|
959
|
+
method: "POST",
|
|
960
|
+
headers: { "Content-Type": "application/json" },
|
|
961
|
+
body: JSON.stringify({ data: frontmatter, content })
|
|
962
|
+
});
|
|
963
|
+
if (response.ok) {
|
|
964
|
+
setMessage("\u2705 File duplicated!");
|
|
965
|
+
loadDirectory(currentPath);
|
|
966
|
+
loadFile(duplicateName);
|
|
967
|
+
} else {
|
|
968
|
+
const errorData = await response.json().catch(() => ({}));
|
|
969
|
+
setMessage(
|
|
970
|
+
`\u274C Failed to duplicate file: ${errorData.error || "Unknown error"}`
|
|
971
|
+
);
|
|
972
|
+
}
|
|
973
|
+
} catch (error) {
|
|
974
|
+
setMessage("\u274C Error duplicating file");
|
|
975
|
+
} finally {
|
|
976
|
+
setLoading(false);
|
|
977
|
+
}
|
|
978
|
+
};
|
|
979
|
+
const deleteFile = async (filePath) => {
|
|
980
|
+
if (!confirm(`Delete ${filePath}?`)) return;
|
|
981
|
+
setLoading(true);
|
|
982
|
+
setMessage("");
|
|
983
|
+
try {
|
|
984
|
+
const response = await fetch(`/api/cms/content/${filePath}`, {
|
|
985
|
+
method: "DELETE"
|
|
986
|
+
});
|
|
987
|
+
if (response.ok) {
|
|
988
|
+
setMessage("\u2705 File deleted!");
|
|
989
|
+
if (selectedFile === filePath) {
|
|
990
|
+
setSelectedFile(null);
|
|
991
|
+
setFields({});
|
|
992
|
+
}
|
|
993
|
+
loadDirectory(currentPath);
|
|
994
|
+
} else {
|
|
995
|
+
setMessage("\u274C Failed to delete file");
|
|
996
|
+
}
|
|
997
|
+
} catch (error) {
|
|
998
|
+
setMessage("\u274C Error deleting file");
|
|
999
|
+
} finally {
|
|
1000
|
+
setLoading(false);
|
|
1001
|
+
}
|
|
1002
|
+
};
|
|
1003
|
+
const navigateToDir = (dirPath) => {
|
|
1004
|
+
setCurrentPath(dirPath);
|
|
1005
|
+
setSelectedFile(null);
|
|
1006
|
+
};
|
|
1007
|
+
const goUp = () => {
|
|
1008
|
+
const parts = currentPath.split("/");
|
|
1009
|
+
parts.pop();
|
|
1010
|
+
setCurrentPath(parts.join("/"));
|
|
1011
|
+
setSelectedFile(null);
|
|
1012
|
+
};
|
|
1013
|
+
const handleLogout2 = async () => {
|
|
1014
|
+
await fetch("/api/cms/logout", { method: "POST" });
|
|
1015
|
+
window.location.href = "/admin/login";
|
|
1016
|
+
};
|
|
1017
|
+
const updateField = (key, value) => {
|
|
1018
|
+
setFields((prev) => ({
|
|
1019
|
+
...prev,
|
|
1020
|
+
[key]: { ...prev[key], value }
|
|
1021
|
+
}));
|
|
1022
|
+
};
|
|
1023
|
+
const addField = () => {
|
|
1024
|
+
const fieldName = prompt("Enter field name:");
|
|
1025
|
+
if (!fieldName || fields[fieldName]) return;
|
|
1026
|
+
const fieldType = prompt(
|
|
1027
|
+
"Enter field type (text/date/number/markdown/image):",
|
|
1028
|
+
"text"
|
|
1029
|
+
);
|
|
1030
|
+
const validTypes = [
|
|
1031
|
+
"text",
|
|
1032
|
+
"date",
|
|
1033
|
+
"number",
|
|
1034
|
+
"markdown",
|
|
1035
|
+
"image"
|
|
1036
|
+
];
|
|
1037
|
+
if (!validTypes.includes(fieldType)) {
|
|
1038
|
+
alert("Invalid field type! Use: text, date, number, markdown, or image");
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
1041
|
+
const defaultValue = fieldType === "number" ? 0 : fieldType === "date" ? (/* @__PURE__ */ new Date()).toISOString().split("T")[0] : "";
|
|
1042
|
+
setFields((prev) => ({
|
|
1043
|
+
...prev,
|
|
1044
|
+
[fieldName]: { type: fieldType, value: defaultValue }
|
|
1045
|
+
}));
|
|
1046
|
+
};
|
|
1047
|
+
const removeField = (key) => {
|
|
1048
|
+
const newFields = { ...fields };
|
|
1049
|
+
delete newFields[key];
|
|
1050
|
+
setFields(newFields);
|
|
1051
|
+
};
|
|
1052
|
+
const handleImageUpload = async (key, event) => {
|
|
1053
|
+
const file = event.target.files?.[0];
|
|
1054
|
+
if (!file) return;
|
|
1055
|
+
setUploading(true);
|
|
1056
|
+
try {
|
|
1057
|
+
const formData = new FormData();
|
|
1058
|
+
formData.append("file", file);
|
|
1059
|
+
const response = await fetch("/api/cms/upload", {
|
|
1060
|
+
method: "POST",
|
|
1061
|
+
body: formData
|
|
1062
|
+
});
|
|
1063
|
+
const data = await response.json();
|
|
1064
|
+
if (response.ok) {
|
|
1065
|
+
updateField(key, data.url);
|
|
1066
|
+
setMessage(`\u2705 Image uploaded!`);
|
|
1067
|
+
setTimeout(() => setMessage(""), 3e3);
|
|
1068
|
+
} else {
|
|
1069
|
+
setMessage("\u274C Failed to upload image");
|
|
1070
|
+
}
|
|
1071
|
+
} catch (error) {
|
|
1072
|
+
setMessage("\u274C Error uploading image");
|
|
1073
|
+
} finally {
|
|
1074
|
+
setUploading(false);
|
|
1075
|
+
event.target.value = "";
|
|
1076
|
+
}
|
|
1077
|
+
};
|
|
1078
|
+
const handleFileUpload = async (event) => {
|
|
1079
|
+
const file = event.target.files?.[0];
|
|
1080
|
+
if (!file) return;
|
|
1081
|
+
setUploading(true);
|
|
1082
|
+
setMessage("");
|
|
1083
|
+
try {
|
|
1084
|
+
const formData = new FormData();
|
|
1085
|
+
formData.append("file", file);
|
|
1086
|
+
const response = await fetch("/api/cms/upload", {
|
|
1087
|
+
method: "POST",
|
|
1088
|
+
body: formData
|
|
1089
|
+
});
|
|
1090
|
+
const data = await response.json();
|
|
1091
|
+
if (response.ok) {
|
|
1092
|
+
setMessage(`\u2705 File uploaded! Path: ${data.url}`);
|
|
1093
|
+
setTimeout(() => setMessage(""), 5e3);
|
|
1094
|
+
} else {
|
|
1095
|
+
setMessage("\u274C Failed to upload file");
|
|
1096
|
+
}
|
|
1097
|
+
} catch (error) {
|
|
1098
|
+
setMessage("\u274C Error uploading file");
|
|
1099
|
+
} finally {
|
|
1100
|
+
setUploading(false);
|
|
1101
|
+
event.target.value = "";
|
|
1102
|
+
}
|
|
1103
|
+
};
|
|
1104
|
+
const renderField = (key, field) => {
|
|
1105
|
+
switch (field.type) {
|
|
1106
|
+
case "text":
|
|
1107
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1108
|
+
"input",
|
|
1109
|
+
{
|
|
1110
|
+
id: key,
|
|
1111
|
+
type: "text",
|
|
1112
|
+
value: field.value,
|
|
1113
|
+
onChange: (e) => updateField(key, e.target.value),
|
|
1114
|
+
style: {
|
|
1115
|
+
width: "100%",
|
|
1116
|
+
padding: "0.5rem",
|
|
1117
|
+
border: "1px solid #ddd",
|
|
1118
|
+
borderRadius: "4px"
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
);
|
|
1122
|
+
case "number":
|
|
1123
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1124
|
+
"input",
|
|
1125
|
+
{
|
|
1126
|
+
id: key,
|
|
1127
|
+
type: "number",
|
|
1128
|
+
value: field.value,
|
|
1129
|
+
onChange: (e) => updateField(key, parseFloat(e.target.value) || 0),
|
|
1130
|
+
style: {
|
|
1131
|
+
width: "100%",
|
|
1132
|
+
padding: "0.5rem",
|
|
1133
|
+
border: "1px solid #ddd",
|
|
1134
|
+
borderRadius: "4px"
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
);
|
|
1138
|
+
case "date":
|
|
1139
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1140
|
+
"input",
|
|
1141
|
+
{
|
|
1142
|
+
id: key,
|
|
1143
|
+
type: "date",
|
|
1144
|
+
value: field.value,
|
|
1145
|
+
onChange: (e) => updateField(key, e.target.value),
|
|
1146
|
+
style: {
|
|
1147
|
+
width: "100%",
|
|
1148
|
+
padding: "0.5rem",
|
|
1149
|
+
border: "1px solid #ddd",
|
|
1150
|
+
borderRadius: "4px"
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
);
|
|
1154
|
+
case "markdown":
|
|
1155
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1156
|
+
"textarea",
|
|
1157
|
+
{
|
|
1158
|
+
id: key,
|
|
1159
|
+
value: field.value,
|
|
1160
|
+
onChange: (e) => updateField(key, e.target.value),
|
|
1161
|
+
style: {
|
|
1162
|
+
width: "100%",
|
|
1163
|
+
minHeight: key === "content" ? "400px" : "150px",
|
|
1164
|
+
fontFamily: "monospace",
|
|
1165
|
+
padding: "0.5rem",
|
|
1166
|
+
border: "1px solid #ddd",
|
|
1167
|
+
borderRadius: "4px"
|
|
1168
|
+
},
|
|
1169
|
+
placeholder: key === "content" ? "Write your markdown content here..." : "Markdown text..."
|
|
1170
|
+
}
|
|
1171
|
+
);
|
|
1172
|
+
case "image":
|
|
1173
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
|
|
1174
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1175
|
+
"input",
|
|
1176
|
+
{
|
|
1177
|
+
id: key,
|
|
1178
|
+
type: "text",
|
|
1179
|
+
value: field.value,
|
|
1180
|
+
onChange: (e) => updateField(key, e.target.value),
|
|
1181
|
+
placeholder: "/uploads/image.jpg or https://...",
|
|
1182
|
+
style: {
|
|
1183
|
+
width: "100%",
|
|
1184
|
+
padding: "0.5rem",
|
|
1185
|
+
marginBottom: "0.5rem",
|
|
1186
|
+
border: "1px solid #ddd",
|
|
1187
|
+
borderRadius: "4px"
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
),
|
|
1191
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1192
|
+
"input",
|
|
1193
|
+
{
|
|
1194
|
+
type: "file",
|
|
1195
|
+
accept: "image/*",
|
|
1196
|
+
onChange: (e) => handleImageUpload(key, e),
|
|
1197
|
+
disabled: uploading,
|
|
1198
|
+
style: { marginBottom: "0.5rem" }
|
|
1199
|
+
}
|
|
1200
|
+
),
|
|
1201
|
+
field.value && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1202
|
+
"img",
|
|
1203
|
+
{
|
|
1204
|
+
src: field.value,
|
|
1205
|
+
alt: key,
|
|
1206
|
+
style: {
|
|
1207
|
+
maxWidth: "200px",
|
|
1208
|
+
display: "block",
|
|
1209
|
+
marginTop: "0.5rem",
|
|
1210
|
+
borderRadius: "4px",
|
|
1211
|
+
border: "1px solid #ddd"
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
)
|
|
1215
|
+
] });
|
|
1216
|
+
default:
|
|
1217
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1218
|
+
"input",
|
|
1219
|
+
{
|
|
1220
|
+
id: key,
|
|
1221
|
+
type: "text",
|
|
1222
|
+
value: field.value,
|
|
1223
|
+
onChange: (e) => updateField(key, e.target.value)
|
|
1224
|
+
}
|
|
1225
|
+
);
|
|
1226
|
+
}
|
|
1227
|
+
};
|
|
1228
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1229
|
+
"div",
|
|
1230
|
+
{
|
|
1231
|
+
className: "container",
|
|
1232
|
+
style: { maxWidth: "1200px", margin: "0 auto", padding: "2rem" },
|
|
1233
|
+
children: [
|
|
1234
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1235
|
+
"div",
|
|
1236
|
+
{
|
|
1237
|
+
style: {
|
|
1238
|
+
display: "flex",
|
|
1239
|
+
justifyContent: "space-between",
|
|
1240
|
+
alignItems: "center",
|
|
1241
|
+
marginBottom: "2rem"
|
|
1242
|
+
},
|
|
1243
|
+
children: [
|
|
1244
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { children: "\u{1F4DD} Admin Dashboard" }),
|
|
1245
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1246
|
+
"button",
|
|
1247
|
+
{
|
|
1248
|
+
onClick: handleLogout2,
|
|
1249
|
+
style: {
|
|
1250
|
+
padding: "0.5rem 1rem",
|
|
1251
|
+
background: "#f5f5f5",
|
|
1252
|
+
border: "1px solid #ddd",
|
|
1253
|
+
borderRadius: "4px",
|
|
1254
|
+
cursor: "pointer"
|
|
1255
|
+
},
|
|
1256
|
+
children: "Logout"
|
|
1257
|
+
}
|
|
1258
|
+
)
|
|
1259
|
+
]
|
|
1260
|
+
}
|
|
1261
|
+
),
|
|
1262
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1263
|
+
"div",
|
|
1264
|
+
{
|
|
1265
|
+
style: {
|
|
1266
|
+
display: "grid",
|
|
1267
|
+
gridTemplateColumns: "minmax(250px, 300px) 1fr",
|
|
1268
|
+
gap: "2rem"
|
|
1269
|
+
},
|
|
1270
|
+
children: [
|
|
1271
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1272
|
+
"div",
|
|
1273
|
+
{
|
|
1274
|
+
style: {
|
|
1275
|
+
background: "white",
|
|
1276
|
+
padding: "1.5rem",
|
|
1277
|
+
borderRadius: "8px",
|
|
1278
|
+
boxShadow: "0 2px 4px rgba(0,0,0,0.1)"
|
|
1279
|
+
},
|
|
1280
|
+
children: [
|
|
1281
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { style: { marginTop: 0, marginBottom: "1rem" }, children: "Files" }),
|
|
1282
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1283
|
+
"div",
|
|
1284
|
+
{
|
|
1285
|
+
style: {
|
|
1286
|
+
marginBottom: "1rem",
|
|
1287
|
+
fontSize: "0.875rem",
|
|
1288
|
+
color: "#666"
|
|
1289
|
+
},
|
|
1290
|
+
children: [
|
|
1291
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1292
|
+
"span",
|
|
1293
|
+
{
|
|
1294
|
+
onClick: () => setCurrentPath(""),
|
|
1295
|
+
style: {
|
|
1296
|
+
cursor: "pointer",
|
|
1297
|
+
color: "#0070f3",
|
|
1298
|
+
textDecoration: "underline"
|
|
1299
|
+
},
|
|
1300
|
+
children: "content"
|
|
1301
|
+
}
|
|
1302
|
+
),
|
|
1303
|
+
currentPath && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
1304
|
+
" / ",
|
|
1305
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontWeight: "bold" }, children: currentPath })
|
|
1306
|
+
] })
|
|
1307
|
+
]
|
|
1308
|
+
}
|
|
1309
|
+
),
|
|
1310
|
+
currentPath && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1311
|
+
"button",
|
|
1312
|
+
{
|
|
1313
|
+
onClick: goUp,
|
|
1314
|
+
style: {
|
|
1315
|
+
width: "100%",
|
|
1316
|
+
marginBottom: "1rem",
|
|
1317
|
+
padding: "0.5rem",
|
|
1318
|
+
background: "#f5f5f5",
|
|
1319
|
+
border: "1px solid #ddd",
|
|
1320
|
+
borderRadius: "4px",
|
|
1321
|
+
cursor: "pointer"
|
|
1322
|
+
},
|
|
1323
|
+
children: "\u2B06\uFE0F Go Up"
|
|
1324
|
+
}
|
|
1325
|
+
),
|
|
1326
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1327
|
+
"div",
|
|
1328
|
+
{
|
|
1329
|
+
style: {
|
|
1330
|
+
marginBottom: "1.5rem",
|
|
1331
|
+
paddingBottom: "1.5rem",
|
|
1332
|
+
borderBottom: "1px solid #eee"
|
|
1333
|
+
},
|
|
1334
|
+
children: [
|
|
1335
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1336
|
+
"input",
|
|
1337
|
+
{
|
|
1338
|
+
type: "text",
|
|
1339
|
+
placeholder: "new-file.md",
|
|
1340
|
+
value: newFileName,
|
|
1341
|
+
onChange: (e) => setNewFileName(e.target.value),
|
|
1342
|
+
style: {
|
|
1343
|
+
width: "100%",
|
|
1344
|
+
padding: "0.5rem",
|
|
1345
|
+
marginBottom: "0.5rem",
|
|
1346
|
+
border: "1px solid #ddd",
|
|
1347
|
+
borderRadius: "4px"
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
),
|
|
1351
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1352
|
+
"button",
|
|
1353
|
+
{
|
|
1354
|
+
onClick: createFile,
|
|
1355
|
+
disabled: loading || !newFileName,
|
|
1356
|
+
style: {
|
|
1357
|
+
width: "100%",
|
|
1358
|
+
padding: "0.5rem",
|
|
1359
|
+
background: "#0070f3",
|
|
1360
|
+
color: "white",
|
|
1361
|
+
border: "none",
|
|
1362
|
+
borderRadius: "4px",
|
|
1363
|
+
cursor: "pointer",
|
|
1364
|
+
opacity: loading ? 0.7 : 1
|
|
1365
|
+
},
|
|
1366
|
+
children: "+ New File"
|
|
1367
|
+
}
|
|
1368
|
+
)
|
|
1369
|
+
]
|
|
1370
|
+
}
|
|
1371
|
+
),
|
|
1372
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("ul", { style: { listStyle: "none", padding: 0, margin: 0 }, children: [
|
|
1373
|
+
entries.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { style: { color: "#999", fontStyle: "italic" }, children: "Empty directory" }),
|
|
1374
|
+
entries.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1375
|
+
"li",
|
|
1376
|
+
{
|
|
1377
|
+
style: {
|
|
1378
|
+
marginBottom: "0.5rem",
|
|
1379
|
+
display: "flex",
|
|
1380
|
+
flexDirection: "column"
|
|
1381
|
+
},
|
|
1382
|
+
children: entry.type === "directory" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1383
|
+
"button",
|
|
1384
|
+
{
|
|
1385
|
+
onClick: () => navigateToDir(entry.path),
|
|
1386
|
+
style: {
|
|
1387
|
+
background: "none",
|
|
1388
|
+
border: "none",
|
|
1389
|
+
color: "#0070f3",
|
|
1390
|
+
cursor: "pointer",
|
|
1391
|
+
textAlign: "left",
|
|
1392
|
+
fontSize: "1rem",
|
|
1393
|
+
padding: "0.25rem 0",
|
|
1394
|
+
fontWeight: "bold"
|
|
1395
|
+
},
|
|
1396
|
+
children: [
|
|
1397
|
+
"\u{1F4C1} ",
|
|
1398
|
+
entry.name
|
|
1399
|
+
]
|
|
1400
|
+
}
|
|
1401
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1402
|
+
"div",
|
|
1403
|
+
{
|
|
1404
|
+
style: {
|
|
1405
|
+
display: "flex",
|
|
1406
|
+
justifyContent: "space-between",
|
|
1407
|
+
alignItems: "center"
|
|
1408
|
+
},
|
|
1409
|
+
children: [
|
|
1410
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1411
|
+
"button",
|
|
1412
|
+
{
|
|
1413
|
+
onClick: () => loadFile(entry.path),
|
|
1414
|
+
style: {
|
|
1415
|
+
background: "none",
|
|
1416
|
+
border: "none",
|
|
1417
|
+
color: selectedFile === entry.path ? "#000" : "#444",
|
|
1418
|
+
cursor: "pointer",
|
|
1419
|
+
textAlign: "left",
|
|
1420
|
+
padding: "0.25rem 0",
|
|
1421
|
+
fontWeight: selectedFile === entry.path ? "bold" : "normal",
|
|
1422
|
+
textDecoration: selectedFile === entry.path ? "underline" : "none",
|
|
1423
|
+
flex: 1
|
|
1424
|
+
},
|
|
1425
|
+
children: [
|
|
1426
|
+
"\u{1F4C4} ",
|
|
1427
|
+
entry.name
|
|
1428
|
+
]
|
|
1429
|
+
}
|
|
1430
|
+
),
|
|
1431
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1432
|
+
"button",
|
|
1433
|
+
{
|
|
1434
|
+
onClick: () => deleteFile(entry.path),
|
|
1435
|
+
disabled: loading,
|
|
1436
|
+
style: {
|
|
1437
|
+
background: "none",
|
|
1438
|
+
border: "none",
|
|
1439
|
+
color: "#d32f2f",
|
|
1440
|
+
cursor: "pointer",
|
|
1441
|
+
fontSize: "0.8rem",
|
|
1442
|
+
opacity: 0.7
|
|
1443
|
+
},
|
|
1444
|
+
children: "\u2715"
|
|
1445
|
+
}
|
|
1446
|
+
)
|
|
1447
|
+
]
|
|
1448
|
+
}
|
|
1449
|
+
)
|
|
1450
|
+
},
|
|
1451
|
+
entry.path
|
|
1452
|
+
))
|
|
1453
|
+
] })
|
|
1454
|
+
]
|
|
1455
|
+
}
|
|
1456
|
+
),
|
|
1457
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1458
|
+
"div",
|
|
1459
|
+
{
|
|
1460
|
+
style: {
|
|
1461
|
+
background: "white",
|
|
1462
|
+
padding: "2rem",
|
|
1463
|
+
borderRadius: "8px",
|
|
1464
|
+
boxShadow: "0 2px 4px rgba(0,0,0,0.1)"
|
|
1465
|
+
},
|
|
1466
|
+
children: selectedFile ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
1467
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1468
|
+
"div",
|
|
1469
|
+
{
|
|
1470
|
+
style: {
|
|
1471
|
+
display: "flex",
|
|
1472
|
+
justifyContent: "space-between",
|
|
1473
|
+
alignItems: "center",
|
|
1474
|
+
marginBottom: "2rem",
|
|
1475
|
+
paddingBottom: "1rem",
|
|
1476
|
+
borderBottom: "1px solid #eee"
|
|
1477
|
+
},
|
|
1478
|
+
children: [
|
|
1479
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h2", { style: { margin: 0 }, children: [
|
|
1480
|
+
"Edit: ",
|
|
1481
|
+
selectedFile
|
|
1482
|
+
] }),
|
|
1483
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: "0.5rem" }, children: [
|
|
1484
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1485
|
+
"button",
|
|
1486
|
+
{
|
|
1487
|
+
onClick: duplicateFile,
|
|
1488
|
+
disabled: loading,
|
|
1489
|
+
style: {
|
|
1490
|
+
padding: "0.5rem 1rem",
|
|
1491
|
+
background: "#f5f5f5",
|
|
1492
|
+
border: "1px solid #ddd",
|
|
1493
|
+
borderRadius: "4px",
|
|
1494
|
+
cursor: "pointer"
|
|
1495
|
+
},
|
|
1496
|
+
children: "\u{1F4CB} Duplicate"
|
|
1497
|
+
}
|
|
1498
|
+
),
|
|
1499
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1500
|
+
"button",
|
|
1501
|
+
{
|
|
1502
|
+
onClick: saveFile,
|
|
1503
|
+
disabled: loading,
|
|
1504
|
+
style: {
|
|
1505
|
+
padding: "0.5rem 1rem",
|
|
1506
|
+
background: "#0070f3",
|
|
1507
|
+
color: "white",
|
|
1508
|
+
border: "none",
|
|
1509
|
+
borderRadius: "4px",
|
|
1510
|
+
cursor: "pointer",
|
|
1511
|
+
opacity: loading ? 0.7 : 1
|
|
1512
|
+
},
|
|
1513
|
+
children: loading ? "Saving..." : "\u{1F4BE} Save Changes"
|
|
1514
|
+
}
|
|
1515
|
+
)
|
|
1516
|
+
] })
|
|
1517
|
+
]
|
|
1518
|
+
}
|
|
1519
|
+
),
|
|
1520
|
+
message && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1521
|
+
"div",
|
|
1522
|
+
{
|
|
1523
|
+
style: {
|
|
1524
|
+
marginBottom: "1.5rem",
|
|
1525
|
+
padding: "0.75rem",
|
|
1526
|
+
background: message.includes("\u2705") ? "#e6ffe6" : "#ffe6e6",
|
|
1527
|
+
borderRadius: "4px",
|
|
1528
|
+
border: message.includes("\u2705") ? "1px solid #a5d6a7" : "1px solid #ef9a9a"
|
|
1529
|
+
},
|
|
1530
|
+
children: message
|
|
1531
|
+
}
|
|
1532
|
+
),
|
|
1533
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { marginBottom: "2rem" }, children: [
|
|
1534
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1535
|
+
"div",
|
|
1536
|
+
{
|
|
1537
|
+
style: {
|
|
1538
|
+
display: "flex",
|
|
1539
|
+
justifyContent: "space-between",
|
|
1540
|
+
alignItems: "center",
|
|
1541
|
+
marginBottom: "1rem"
|
|
1542
|
+
},
|
|
1543
|
+
children: [
|
|
1544
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { style: { fontSize: "1.1rem", margin: 0 }, children: "Fields" }),
|
|
1545
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1546
|
+
"button",
|
|
1547
|
+
{
|
|
1548
|
+
onClick: addField,
|
|
1549
|
+
style: {
|
|
1550
|
+
fontSize: "0.875rem",
|
|
1551
|
+
padding: "0.4rem 0.8rem",
|
|
1552
|
+
background: "#e1f5fe",
|
|
1553
|
+
color: "#0288d1",
|
|
1554
|
+
border: "none",
|
|
1555
|
+
borderRadius: "4px",
|
|
1556
|
+
cursor: "pointer"
|
|
1557
|
+
},
|
|
1558
|
+
children: "+ Add Field"
|
|
1559
|
+
}
|
|
1560
|
+
)
|
|
1561
|
+
]
|
|
1562
|
+
}
|
|
1563
|
+
),
|
|
1564
|
+
Object.keys(fields).length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1565
|
+
"div",
|
|
1566
|
+
{
|
|
1567
|
+
style: {
|
|
1568
|
+
padding: "2rem",
|
|
1569
|
+
textAlign: "center",
|
|
1570
|
+
background: "#f9f9f9",
|
|
1571
|
+
borderRadius: "4px",
|
|
1572
|
+
border: "1px dashed #ccc"
|
|
1573
|
+
},
|
|
1574
|
+
children: [
|
|
1575
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1576
|
+
"p",
|
|
1577
|
+
{
|
|
1578
|
+
style: { color: "#666", fontStyle: "italic", margin: 0 },
|
|
1579
|
+
children: 'No fields yet. Click "+ Add Field" to start.'
|
|
1580
|
+
}
|
|
1581
|
+
),
|
|
1582
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1583
|
+
"small",
|
|
1584
|
+
{
|
|
1585
|
+
style: {
|
|
1586
|
+
color: "#999",
|
|
1587
|
+
marginTop: "0.5rem",
|
|
1588
|
+
display: "block"
|
|
1589
|
+
},
|
|
1590
|
+
children: "Add 'content' field for the main body"
|
|
1591
|
+
}
|
|
1592
|
+
)
|
|
1593
|
+
]
|
|
1594
|
+
}
|
|
1595
|
+
) : Object.entries(fields).map(([key, field]) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1596
|
+
"div",
|
|
1597
|
+
{
|
|
1598
|
+
style: {
|
|
1599
|
+
marginBottom: "1.5rem",
|
|
1600
|
+
background: "#fff",
|
|
1601
|
+
border: "1px solid #eee",
|
|
1602
|
+
padding: "1rem",
|
|
1603
|
+
borderRadius: "6px"
|
|
1604
|
+
},
|
|
1605
|
+
children: [
|
|
1606
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1607
|
+
"div",
|
|
1608
|
+
{
|
|
1609
|
+
style: {
|
|
1610
|
+
display: "flex",
|
|
1611
|
+
justifyContent: "space-between",
|
|
1612
|
+
alignItems: "center",
|
|
1613
|
+
marginBottom: "0.5rem"
|
|
1614
|
+
},
|
|
1615
|
+
children: [
|
|
1616
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { htmlFor: key, style: { fontWeight: 500 }, children: [
|
|
1617
|
+
key,
|
|
1618
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1619
|
+
"span",
|
|
1620
|
+
{
|
|
1621
|
+
style: {
|
|
1622
|
+
marginLeft: "0.5rem",
|
|
1623
|
+
fontSize: "0.75rem",
|
|
1624
|
+
color: "#999",
|
|
1625
|
+
background: "#f0f0f0",
|
|
1626
|
+
padding: "0.1rem 0.4rem",
|
|
1627
|
+
borderRadius: "4px"
|
|
1628
|
+
},
|
|
1629
|
+
children: field.type
|
|
1630
|
+
}
|
|
1631
|
+
)
|
|
1632
|
+
] }),
|
|
1633
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1634
|
+
"button",
|
|
1635
|
+
{
|
|
1636
|
+
onClick: () => removeField(key),
|
|
1637
|
+
style: {
|
|
1638
|
+
background: "none",
|
|
1639
|
+
border: "none",
|
|
1640
|
+
color: "#e57373",
|
|
1641
|
+
cursor: "pointer",
|
|
1642
|
+
fontSize: "0.875rem"
|
|
1643
|
+
},
|
|
1644
|
+
children: "\u2715 Remove"
|
|
1645
|
+
}
|
|
1646
|
+
)
|
|
1647
|
+
]
|
|
1648
|
+
}
|
|
1649
|
+
),
|
|
1650
|
+
renderField(key, field)
|
|
1651
|
+
]
|
|
1652
|
+
},
|
|
1653
|
+
key
|
|
1654
|
+
))
|
|
1655
|
+
] }),
|
|
1656
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1657
|
+
"div",
|
|
1658
|
+
{
|
|
1659
|
+
style: {
|
|
1660
|
+
marginTop: "3rem",
|
|
1661
|
+
paddingTop: "1rem",
|
|
1662
|
+
borderTop: "1px solid #eee"
|
|
1663
|
+
},
|
|
1664
|
+
children: [
|
|
1665
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1666
|
+
"h3",
|
|
1667
|
+
{
|
|
1668
|
+
style: {
|
|
1669
|
+
fontSize: "1rem",
|
|
1670
|
+
marginBottom: "0.5rem",
|
|
1671
|
+
color: "#666"
|
|
1672
|
+
},
|
|
1673
|
+
children: "Quick File Upload"
|
|
1674
|
+
}
|
|
1675
|
+
),
|
|
1676
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: "0.5rem" }, children: [
|
|
1677
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1678
|
+
"input",
|
|
1679
|
+
{
|
|
1680
|
+
type: "file",
|
|
1681
|
+
onChange: handleFileUpload,
|
|
1682
|
+
disabled: uploading,
|
|
1683
|
+
style: { flex: 1 }
|
|
1684
|
+
}
|
|
1685
|
+
),
|
|
1686
|
+
uploading && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: "#666" }, children: "Uploading..." })
|
|
1687
|
+
] })
|
|
1688
|
+
]
|
|
1689
|
+
}
|
|
1690
|
+
)
|
|
1691
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1692
|
+
"div",
|
|
1693
|
+
{
|
|
1694
|
+
style: {
|
|
1695
|
+
textAlign: "center",
|
|
1696
|
+
padding: "5rem 2rem",
|
|
1697
|
+
color: "#999"
|
|
1698
|
+
},
|
|
1699
|
+
children: [
|
|
1700
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontSize: "3rem", marginBottom: "1rem" }, children: "\u{1F448}" }),
|
|
1701
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { fontSize: "1.1rem" }, children: "Select a file from the sidebar to edit" }),
|
|
1702
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: "or create a new file to get started" })
|
|
1703
|
+
]
|
|
1704
|
+
}
|
|
1705
|
+
)
|
|
1706
|
+
}
|
|
1707
|
+
)
|
|
1708
|
+
]
|
|
1709
|
+
}
|
|
1710
|
+
)
|
|
1711
|
+
]
|
|
1712
|
+
}
|
|
1713
|
+
);
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
// src/ui/Login.tsx
|
|
1717
|
+
var import_react2 = require("react");
|
|
1718
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
1719
|
+
function Login() {
|
|
1720
|
+
const [username, setUsername] = (0, import_react2.useState)("");
|
|
1721
|
+
const [password, setPassword] = (0, import_react2.useState)("");
|
|
1722
|
+
const [error, setError] = (0, import_react2.useState)("");
|
|
1723
|
+
const [loading, setLoading] = (0, import_react2.useState)(false);
|
|
1724
|
+
const handleSubmit = async (e) => {
|
|
1725
|
+
e.preventDefault();
|
|
1726
|
+
setError("");
|
|
1727
|
+
setLoading(true);
|
|
1728
|
+
try {
|
|
1729
|
+
const response = await fetch("/api/cms/login", {
|
|
1730
|
+
method: "POST",
|
|
1731
|
+
headers: { "Content-Type": "application/json" },
|
|
1732
|
+
body: JSON.stringify({ username, password })
|
|
1733
|
+
});
|
|
1734
|
+
const data = await response.json();
|
|
1735
|
+
if (response.ok) {
|
|
1736
|
+
window.location.href = "/admin";
|
|
1737
|
+
} else {
|
|
1738
|
+
setError(data.error || "Login failed");
|
|
1739
|
+
}
|
|
1740
|
+
} catch (err) {
|
|
1741
|
+
setError("An error occurred. Please try again.");
|
|
1742
|
+
} finally {
|
|
1743
|
+
setLoading(false);
|
|
1744
|
+
}
|
|
1745
|
+
};
|
|
1746
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1747
|
+
"div",
|
|
1748
|
+
{
|
|
1749
|
+
style: {
|
|
1750
|
+
display: "flex",
|
|
1751
|
+
justifyContent: "center",
|
|
1752
|
+
alignItems: "center",
|
|
1753
|
+
minHeight: "100vh",
|
|
1754
|
+
background: "#f5f5f5",
|
|
1755
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif'
|
|
1756
|
+
},
|
|
1757
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
1758
|
+
"div",
|
|
1759
|
+
{
|
|
1760
|
+
style: {
|
|
1761
|
+
width: "100%",
|
|
1762
|
+
maxWidth: "400px",
|
|
1763
|
+
padding: "2rem",
|
|
1764
|
+
background: "white",
|
|
1765
|
+
borderRadius: "8px",
|
|
1766
|
+
boxShadow: "0 4px 6px rgba(0,0,0,0.1)"
|
|
1767
|
+
},
|
|
1768
|
+
children: [
|
|
1769
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h1", { style: { marginBottom: "0.5rem", textAlign: "center" }, children: "Admin Login" }),
|
|
1770
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: { color: "#666", marginBottom: "2rem", textAlign: "center" }, children: "Enter your credentials to access the CMS" }),
|
|
1771
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("form", { onSubmit: handleSubmit, children: [
|
|
1772
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { marginBottom: "1rem" }, children: [
|
|
1773
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1774
|
+
"label",
|
|
1775
|
+
{
|
|
1776
|
+
htmlFor: "username",
|
|
1777
|
+
style: {
|
|
1778
|
+
display: "block",
|
|
1779
|
+
marginBottom: "0.5rem",
|
|
1780
|
+
fontWeight: 500
|
|
1781
|
+
},
|
|
1782
|
+
children: "Username"
|
|
1783
|
+
}
|
|
1784
|
+
),
|
|
1785
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1786
|
+
"input",
|
|
1787
|
+
{
|
|
1788
|
+
id: "username",
|
|
1789
|
+
type: "text",
|
|
1790
|
+
value: username,
|
|
1791
|
+
onChange: (e) => setUsername(e.target.value),
|
|
1792
|
+
required: true,
|
|
1793
|
+
autoFocus: true,
|
|
1794
|
+
style: {
|
|
1795
|
+
width: "100%",
|
|
1796
|
+
padding: "0.75rem",
|
|
1797
|
+
border: "1px solid #ddd",
|
|
1798
|
+
borderRadius: "4px"
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
)
|
|
1802
|
+
] }),
|
|
1803
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { marginBottom: "1.5rem" }, children: [
|
|
1804
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1805
|
+
"label",
|
|
1806
|
+
{
|
|
1807
|
+
htmlFor: "password",
|
|
1808
|
+
style: {
|
|
1809
|
+
display: "block",
|
|
1810
|
+
marginBottom: "0.5rem",
|
|
1811
|
+
fontWeight: 500
|
|
1812
|
+
},
|
|
1813
|
+
children: "Password"
|
|
1814
|
+
}
|
|
1815
|
+
),
|
|
1816
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1817
|
+
"input",
|
|
1818
|
+
{
|
|
1819
|
+
id: "password",
|
|
1820
|
+
type: "password",
|
|
1821
|
+
value: password,
|
|
1822
|
+
onChange: (e) => setPassword(e.target.value),
|
|
1823
|
+
required: true,
|
|
1824
|
+
style: {
|
|
1825
|
+
width: "100%",
|
|
1826
|
+
padding: "0.75rem",
|
|
1827
|
+
border: "1px solid #ddd",
|
|
1828
|
+
borderRadius: "4px"
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
)
|
|
1832
|
+
] }),
|
|
1833
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1834
|
+
"div",
|
|
1835
|
+
{
|
|
1836
|
+
style: {
|
|
1837
|
+
marginBottom: "1rem",
|
|
1838
|
+
padding: "0.75rem",
|
|
1839
|
+
background: "#ffe6e6",
|
|
1840
|
+
color: "#d32f2f",
|
|
1841
|
+
borderRadius: "4px",
|
|
1842
|
+
fontSize: "0.875rem"
|
|
1843
|
+
},
|
|
1844
|
+
children: error
|
|
1845
|
+
}
|
|
1846
|
+
),
|
|
1847
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1848
|
+
"button",
|
|
1849
|
+
{
|
|
1850
|
+
type: "submit",
|
|
1851
|
+
disabled: loading,
|
|
1852
|
+
style: {
|
|
1853
|
+
width: "100%",
|
|
1854
|
+
padding: "0.75rem",
|
|
1855
|
+
background: "#0070f3",
|
|
1856
|
+
color: "white",
|
|
1857
|
+
border: "none",
|
|
1858
|
+
borderRadius: "4px",
|
|
1859
|
+
fontSize: "1rem",
|
|
1860
|
+
fontWeight: 500,
|
|
1861
|
+
cursor: "pointer",
|
|
1862
|
+
opacity: loading ? 0.7 : 1
|
|
1863
|
+
},
|
|
1864
|
+
children: loading ? "Logging in..." : "Login"
|
|
1865
|
+
}
|
|
1866
|
+
)
|
|
1867
|
+
] })
|
|
1868
|
+
]
|
|
1869
|
+
}
|
|
1870
|
+
)
|
|
1871
|
+
}
|
|
1872
|
+
);
|
|
1873
|
+
}
|
|
1874
|
+
|
|
819
1875
|
// src/middleware/auth.ts
|
|
820
1876
|
var import_server3 = require("next/server");
|
|
821
1877
|
function createAuthMiddleware(options = {}) {
|
|
@@ -849,6 +1905,16 @@ var import_path3 = __toESM(require("path"));
|
|
|
849
1905
|
async function initCMS(config = {}) {
|
|
850
1906
|
const contentDir = config.contentDir || "public/content";
|
|
851
1907
|
const fullPath = import_path3.default.join(process.cwd(), contentDir);
|
|
1908
|
+
const appDir = import_path3.default.join(process.cwd(), "app");
|
|
1909
|
+
try {
|
|
1910
|
+
await import_promises2.default.access(appDir);
|
|
1911
|
+
} catch {
|
|
1912
|
+
console.error(
|
|
1913
|
+
'\u274C Error: "app" directory not found. This init script requires Next.js App Router.'
|
|
1914
|
+
);
|
|
1915
|
+
return;
|
|
1916
|
+
}
|
|
1917
|
+
console.log("\u{1F680} Initializing @fydemy/cms...");
|
|
852
1918
|
await import_promises2.default.mkdir(fullPath, { recursive: true });
|
|
853
1919
|
const exampleContent = `---
|
|
854
1920
|
title: Example Post
|
|
@@ -870,25 +1936,145 @@ This is an example markdown file. You can edit or delete it from the admin dashb
|
|
|
870
1936
|
`;
|
|
871
1937
|
const examplePath = import_path3.default.join(fullPath, "example.md");
|
|
872
1938
|
await import_promises2.default.writeFile(examplePath, exampleContent, "utf-8");
|
|
873
|
-
console.log("\u2705
|
|
874
|
-
|
|
875
|
-
|
|
1939
|
+
console.log("\u2705 Created content directory and example file");
|
|
1940
|
+
const adminDir = import_path3.default.join(appDir, "admin");
|
|
1941
|
+
const loginDir = import_path3.default.join(adminDir, "login");
|
|
1942
|
+
await import_promises2.default.mkdir(loginDir, { recursive: true });
|
|
1943
|
+
await import_promises2.default.writeFile(
|
|
1944
|
+
import_path3.default.join(adminDir, "page.tsx"),
|
|
1945
|
+
`import { AdminDashboard } from '@fydemy/cms';
|
|
1946
|
+
|
|
1947
|
+
export default AdminDashboard;
|
|
1948
|
+
`,
|
|
1949
|
+
"utf-8"
|
|
1950
|
+
);
|
|
1951
|
+
await import_promises2.default.writeFile(
|
|
1952
|
+
import_path3.default.join(loginDir, "page.tsx"),
|
|
1953
|
+
`import { Login } from '@fydemy/cms';
|
|
1954
|
+
|
|
1955
|
+
export default Login;
|
|
1956
|
+
`,
|
|
1957
|
+
"utf-8"
|
|
1958
|
+
);
|
|
1959
|
+
console.log("\u2705 Created Admin UI pages");
|
|
1960
|
+
const apiCmsDir = import_path3.default.join(appDir, "api", "cms");
|
|
1961
|
+
await import_promises2.default.mkdir(import_path3.default.join(apiCmsDir, "login"), { recursive: true });
|
|
1962
|
+
await import_promises2.default.writeFile(
|
|
1963
|
+
import_path3.default.join(apiCmsDir, "login", "route.ts"),
|
|
1964
|
+
`import { handleLogin } from '@fydemy/cms';
|
|
1965
|
+
export { handleLogin as POST };
|
|
1966
|
+
`,
|
|
1967
|
+
"utf-8"
|
|
1968
|
+
);
|
|
1969
|
+
await import_promises2.default.mkdir(import_path3.default.join(apiCmsDir, "logout"), { recursive: true });
|
|
1970
|
+
await import_promises2.default.writeFile(
|
|
1971
|
+
import_path3.default.join(apiCmsDir, "logout", "route.ts"),
|
|
1972
|
+
`import { handleLogout } from '@fydemy/cms';
|
|
1973
|
+
export { handleLogout as POST };
|
|
1974
|
+
`,
|
|
1975
|
+
"utf-8"
|
|
1976
|
+
);
|
|
1977
|
+
await import_promises2.default.mkdir(import_path3.default.join(apiCmsDir, "upload"), { recursive: true });
|
|
1978
|
+
await import_promises2.default.writeFile(
|
|
1979
|
+
import_path3.default.join(apiCmsDir, "upload", "route.ts"),
|
|
1980
|
+
`import { handleUpload } from '@fydemy/cms';
|
|
1981
|
+
export { handleUpload as POST };
|
|
1982
|
+
`,
|
|
1983
|
+
"utf-8"
|
|
1984
|
+
);
|
|
1985
|
+
await import_promises2.default.mkdir(import_path3.default.join(apiCmsDir, "list", "[[...path]]"), {
|
|
1986
|
+
recursive: true
|
|
1987
|
+
});
|
|
1988
|
+
await import_promises2.default.writeFile(
|
|
1989
|
+
import_path3.default.join(apiCmsDir, "list", "[[...path]]", "route.ts"),
|
|
1990
|
+
`import { createListApiHandlers } from '@fydemy/cms';
|
|
1991
|
+
|
|
1992
|
+
const handlers = createListApiHandlers();
|
|
1993
|
+
export const GET = handlers.GET;
|
|
1994
|
+
`,
|
|
1995
|
+
"utf-8"
|
|
1996
|
+
);
|
|
1997
|
+
await import_promises2.default.mkdir(import_path3.default.join(apiCmsDir, "content", "[...path]"), {
|
|
1998
|
+
recursive: true
|
|
1999
|
+
});
|
|
2000
|
+
await import_promises2.default.writeFile(
|
|
2001
|
+
import_path3.default.join(apiCmsDir, "content", "[...path]", "route.ts"),
|
|
2002
|
+
`import { createContentApiHandlers } from '@fydemy/cms';
|
|
2003
|
+
|
|
2004
|
+
const handlers = createContentApiHandlers();
|
|
2005
|
+
export const GET = handlers.GET;
|
|
2006
|
+
export const POST = handlers.POST;
|
|
2007
|
+
export const DELETE = handlers.DELETE;
|
|
2008
|
+
`,
|
|
2009
|
+
"utf-8"
|
|
2010
|
+
);
|
|
2011
|
+
console.log("\u2705 Created API routes");
|
|
2012
|
+
const middlewarePath = import_path3.default.join(process.cwd(), "middleware.ts");
|
|
2013
|
+
try {
|
|
2014
|
+
await import_promises2.default.access(middlewarePath);
|
|
2015
|
+
console.log(
|
|
2016
|
+
"\u26A0\uFE0F middleware.ts already exists. Please manually add the CMS auth middleware:"
|
|
2017
|
+
);
|
|
2018
|
+
console.log(`
|
|
2019
|
+
import { createAuthMiddleware } from '@fydemy/cms';
|
|
2020
|
+
// ... existing imports
|
|
2021
|
+
|
|
2022
|
+
export function middleware(request: NextRequest) {
|
|
2023
|
+
// Add this:
|
|
2024
|
+
const authResponse = createAuthMiddleware()(request);
|
|
2025
|
+
if (authResponse) return authResponse;
|
|
2026
|
+
|
|
2027
|
+
// ... existing middleware logic
|
|
2028
|
+
}
|
|
2029
|
+
`);
|
|
2030
|
+
} catch {
|
|
2031
|
+
await import_promises2.default.writeFile(
|
|
2032
|
+
middlewarePath,
|
|
2033
|
+
`import { createAuthMiddleware } from '@fydemy/cms';
|
|
2034
|
+
import { NextRequest } from 'next/server';
|
|
2035
|
+
|
|
2036
|
+
export function middleware(request: NextRequest) {
|
|
2037
|
+
return createAuthMiddleware()(request);
|
|
2038
|
+
}
|
|
2039
|
+
|
|
2040
|
+
export const config = {
|
|
2041
|
+
matcher: ['/admin/:path*'],
|
|
2042
|
+
};
|
|
2043
|
+
`,
|
|
2044
|
+
"utf-8"
|
|
2045
|
+
);
|
|
2046
|
+
console.log("\u2705 Created middleware.ts");
|
|
2047
|
+
}
|
|
2048
|
+
const envExamplePath = import_path3.default.join(process.cwd(), ".env.local.example");
|
|
2049
|
+
await import_promises2.default.writeFile(
|
|
2050
|
+
envExamplePath,
|
|
2051
|
+
`CMS_ADMIN_USERNAME=admin
|
|
2052
|
+
CMS_ADMIN_PASSWORD=password
|
|
2053
|
+
CMS_SESSION_SECRET=ensure_this_is_at_least_32_chars_long_random_string
|
|
2054
|
+
|
|
2055
|
+
# GitHub Storage (Production)
|
|
2056
|
+
GITHUB_TOKEN=
|
|
2057
|
+
GITHUB_REPO=owner/repo
|
|
2058
|
+
GITHUB_BRANCH=main
|
|
2059
|
+
`,
|
|
2060
|
+
"utf-8"
|
|
2061
|
+
);
|
|
876
2062
|
console.log("");
|
|
877
|
-
console.log("
|
|
878
|
-
console.log(
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
console.log("
|
|
882
|
-
console.log("2. For production: GITHUB_TOKEN, GITHUB_REPO, GITHUB_BRANCH");
|
|
883
|
-
console.log("3. Import and use the CMS utilities in your Next.js app");
|
|
2063
|
+
console.log("\u{1F389} CMS initialized successfully!");
|
|
2064
|
+
console.log(
|
|
2065
|
+
"1. Copy .env.local.example to .env.local and set your credentials"
|
|
2066
|
+
);
|
|
2067
|
+
console.log("2. Run your dev server and visit /admin");
|
|
884
2068
|
}
|
|
885
2069
|
|
|
886
2070
|
// src/index.ts
|
|
887
2071
|
init_rate_limit();
|
|
888
2072
|
// Annotate the CommonJS export names for ESM import in node:
|
|
889
2073
|
0 && (module.exports = {
|
|
2074
|
+
AdminDashboard,
|
|
890
2075
|
GitHubStorage,
|
|
891
2076
|
LocalStorage,
|
|
2077
|
+
Login,
|
|
892
2078
|
MAX_FILE_SIZE,
|
|
893
2079
|
MAX_PASSWORD_LENGTH,
|
|
894
2080
|
MAX_USERNAME_LENGTH,
|