@dalgoridim/headless-cms 0.1.0
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/LICENSE +10 -0
- package/README.md +178 -0
- package/dist/adapters/firestore/index.cjs +152 -0
- package/dist/adapters/firestore/index.cjs.map +1 -0
- package/dist/adapters/firestore/index.d.cts +39 -0
- package/dist/adapters/firestore/index.d.ts +39 -0
- package/dist/adapters/firestore/index.js +120 -0
- package/dist/adapters/firestore/index.js.map +1 -0
- package/dist/adapters/postgres/index.cjs +299 -0
- package/dist/adapters/postgres/index.cjs.map +1 -0
- package/dist/adapters/postgres/index.d.cts +59 -0
- package/dist/adapters/postgres/index.d.ts +59 -0
- package/dist/adapters/postgres/index.js +277 -0
- package/dist/adapters/postgres/index.js.map +1 -0
- package/dist/auth/firebase/client/index.cjs +153 -0
- package/dist/auth/firebase/client/index.cjs.map +1 -0
- package/dist/auth/firebase/client/index.d.cts +29 -0
- package/dist/auth/firebase/client/index.d.ts +29 -0
- package/dist/auth/firebase/client/index.js +138 -0
- package/dist/auth/firebase/client/index.js.map +1 -0
- package/dist/auth/firebase/index.cjs +81 -0
- package/dist/auth/firebase/index.cjs.map +1 -0
- package/dist/auth/firebase/index.d.cts +23 -0
- package/dist/auth/firebase/index.d.ts +23 -0
- package/dist/auth/firebase/index.js +46 -0
- package/dist/auth/firebase/index.js.map +1 -0
- package/dist/auth/nextauth/index.cjs +51 -0
- package/dist/auth/nextauth/index.cjs.map +1 -0
- package/dist/auth/nextauth/index.d.cts +30 -0
- package/dist/auth/nextauth/index.d.ts +30 -0
- package/dist/auth/nextauth/index.js +25 -0
- package/dist/auth/nextauth/index.js.map +1 -0
- package/dist/client/index.cjs +1018 -0
- package/dist/client/index.cjs.map +1 -0
- package/dist/client/index.d.cts +96 -0
- package/dist/client/index.d.ts +96 -0
- package/dist/client/index.js +994 -0
- package/dist/client/index.js.map +1 -0
- package/dist/index.cjs +19 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +122 -0
- package/dist/index.d.ts +122 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -0
- package/dist/server/index.cjs +128 -0
- package/dist/server/index.cjs.map +1 -0
- package/dist/server/index.d.cts +52 -0
- package/dist/server/index.d.ts +52 -0
- package/dist/server/index.js +99 -0
- package/dist/server/index.js.map +1 -0
- package/dist/storage/cloudinary/index.cjs +55 -0
- package/dist/storage/cloudinary/index.cjs.map +1 -0
- package/dist/storage/cloudinary/index.d.cts +17 -0
- package/dist/storage/cloudinary/index.d.ts +17 -0
- package/dist/storage/cloudinary/index.js +30 -0
- package/dist/storage/cloudinary/index.js.map +1 -0
- package/dist/storage/cloudinary/server.cjs +56 -0
- package/dist/storage/cloudinary/server.cjs.map +1 -0
- package/dist/storage/cloudinary/server.d.cts +16 -0
- package/dist/storage/cloudinary/server.d.ts +16 -0
- package/dist/storage/cloudinary/server.js +31 -0
- package/dist/storage/cloudinary/server.js.map +1 -0
- package/dist/storage/local/index.cjs +44 -0
- package/dist/storage/local/index.cjs.map +1 -0
- package/dist/storage/local/index.d.cts +15 -0
- package/dist/storage/local/index.d.ts +15 -0
- package/dist/storage/local/index.js +19 -0
- package/dist/storage/local/index.js.map +1 -0
- package/dist/storage/local/server.cjs +61 -0
- package/dist/storage/local/server.cjs.map +1 -0
- package/dist/storage/local/server.d.cts +16 -0
- package/dist/storage/local/server.d.ts +16 -0
- package/dist/storage/local/server.js +26 -0
- package/dist/storage/local/server.js.map +1 -0
- package/dist/storage/s3/index.cjs +52 -0
- package/dist/storage/s3/index.cjs.map +1 -0
- package/dist/storage/s3/index.d.cts +14 -0
- package/dist/storage/s3/index.d.ts +14 -0
- package/dist/storage/s3/index.js +27 -0
- package/dist/storage/s3/index.js.map +1 -0
- package/dist/storage/s3/server.cjs +61 -0
- package/dist/storage/s3/server.cjs.map +1 -0
- package/dist/storage/s3/server.d.cts +19 -0
- package/dist/storage/s3/server.d.ts +19 -0
- package/dist/storage/s3/server.js +36 -0
- package/dist/storage/s3/server.js.map +1 -0
- package/package.json +165 -0
|
@@ -0,0 +1,994 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __defProps = Object.defineProperties;
|
|
4
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
5
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
8
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
9
|
+
var __spreadValues = (a, b) => {
|
|
10
|
+
for (var prop in b || (b = {}))
|
|
11
|
+
if (__hasOwnProp.call(b, prop))
|
|
12
|
+
__defNormalProp(a, prop, b[prop]);
|
|
13
|
+
if (__getOwnPropSymbols)
|
|
14
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
15
|
+
if (__propIsEnum.call(b, prop))
|
|
16
|
+
__defNormalProp(a, prop, b[prop]);
|
|
17
|
+
}
|
|
18
|
+
return a;
|
|
19
|
+
};
|
|
20
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
21
|
+
|
|
22
|
+
// src/client/PageProvider.tsx
|
|
23
|
+
import {
|
|
24
|
+
createContext,
|
|
25
|
+
useContext,
|
|
26
|
+
useState,
|
|
27
|
+
useCallback
|
|
28
|
+
} from "react";
|
|
29
|
+
import { toast } from "sonner";
|
|
30
|
+
import { jsx } from "react/jsx-runtime";
|
|
31
|
+
var defaultNotifier = {
|
|
32
|
+
success: (m) => toast.success(m),
|
|
33
|
+
error: (m) => toast.error(m)
|
|
34
|
+
};
|
|
35
|
+
var PageContext = createContext(void 0);
|
|
36
|
+
var dirtyKey = (collection, sectionKey) => `${collection}:${sectionKey}`;
|
|
37
|
+
var PageProvider = ({
|
|
38
|
+
children,
|
|
39
|
+
initialSections = {},
|
|
40
|
+
apiBasePath = "/api/admin",
|
|
41
|
+
storage,
|
|
42
|
+
notify = defaultNotifier
|
|
43
|
+
}) => {
|
|
44
|
+
const [saving, setSaving] = useState(false);
|
|
45
|
+
const [sections, setSections] = useState(initialSections);
|
|
46
|
+
const [pendingImages, setPendingImages] = useState([]);
|
|
47
|
+
const [dirtySections, setDirtySections] = useState(/* @__PURE__ */ new Set());
|
|
48
|
+
const hasUnsavedChanges = dirtySections.size > 0;
|
|
49
|
+
const resolveImageUrl = useCallback(
|
|
50
|
+
async (img) => {
|
|
51
|
+
if (img.isExternal) return img.localUrl;
|
|
52
|
+
if (!storage) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
"PageProvider received a file upload but no `storage` adapter was provided."
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
const { url } = await storage.upload(img.file);
|
|
58
|
+
return url;
|
|
59
|
+
},
|
|
60
|
+
[storage]
|
|
61
|
+
);
|
|
62
|
+
const setSection = useCallback(
|
|
63
|
+
(collection, key, section) => {
|
|
64
|
+
setSections((prev) => __spreadProps(__spreadValues({}, prev), {
|
|
65
|
+
[collection]: __spreadProps(__spreadValues({}, prev[collection]), { [key]: section })
|
|
66
|
+
}));
|
|
67
|
+
},
|
|
68
|
+
[]
|
|
69
|
+
);
|
|
70
|
+
const editField = useCallback(
|
|
71
|
+
(collection, sectionKey, fieldKey, value) => {
|
|
72
|
+
setSections((prev) => {
|
|
73
|
+
var _a;
|
|
74
|
+
const currentSection = (_a = prev[collection]) == null ? void 0 : _a[sectionKey];
|
|
75
|
+
if (!currentSection) {
|
|
76
|
+
console.error(`Section not found: ${collection}/${sectionKey}`);
|
|
77
|
+
return prev;
|
|
78
|
+
}
|
|
79
|
+
const keys = fieldKey.split(".");
|
|
80
|
+
const updated = __spreadValues({}, currentSection);
|
|
81
|
+
if (keys.length === 1) {
|
|
82
|
+
updated[fieldKey] = value;
|
|
83
|
+
} else {
|
|
84
|
+
let current = updated;
|
|
85
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
86
|
+
if (!current[keys[i]]) current[keys[i]] = {};
|
|
87
|
+
current[keys[i]] = __spreadValues({}, current[keys[i]]);
|
|
88
|
+
current = current[keys[i]];
|
|
89
|
+
}
|
|
90
|
+
current[keys[keys.length - 1]] = value;
|
|
91
|
+
}
|
|
92
|
+
return __spreadProps(__spreadValues({}, prev), {
|
|
93
|
+
[collection]: __spreadProps(__spreadValues({}, prev[collection]), { [sectionKey]: updated })
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
setDirtySections((prev) => {
|
|
97
|
+
const next = new Set(prev);
|
|
98
|
+
next.add(dirtyKey(collection, sectionKey));
|
|
99
|
+
return next;
|
|
100
|
+
});
|
|
101
|
+
},
|
|
102
|
+
[]
|
|
103
|
+
);
|
|
104
|
+
const setPendingImage = useCallback((image) => {
|
|
105
|
+
setPendingImages((prev) => [
|
|
106
|
+
...prev.filter(
|
|
107
|
+
(img) => !(img.collection === image.collection && img.sectionKey === image.sectionKey && img.fieldKey === image.fieldKey)
|
|
108
|
+
),
|
|
109
|
+
image
|
|
110
|
+
]);
|
|
111
|
+
setDirtySections((prev) => {
|
|
112
|
+
const next = new Set(prev);
|
|
113
|
+
next.add(dirtyKey(image.collection, image.sectionKey));
|
|
114
|
+
return next;
|
|
115
|
+
});
|
|
116
|
+
}, []);
|
|
117
|
+
const persist = useCallback(
|
|
118
|
+
async (section) => {
|
|
119
|
+
const response = await fetch(
|
|
120
|
+
`${apiBasePath}/${section.collection}/${section.id}`,
|
|
121
|
+
{
|
|
122
|
+
method: "PATCH",
|
|
123
|
+
headers: { "Content-Type": "application/json" },
|
|
124
|
+
body: JSON.stringify(section)
|
|
125
|
+
}
|
|
126
|
+
);
|
|
127
|
+
if (!response.ok) throw new Error("Failed to save section");
|
|
128
|
+
},
|
|
129
|
+
[apiBasePath]
|
|
130
|
+
);
|
|
131
|
+
const saveSection = useCallback(
|
|
132
|
+
async (collection, sectionKey) => {
|
|
133
|
+
var _a;
|
|
134
|
+
if (saving) return;
|
|
135
|
+
setSaving(true);
|
|
136
|
+
try {
|
|
137
|
+
const section = (_a = sections[collection]) == null ? void 0 : _a[sectionKey];
|
|
138
|
+
if (!(section == null ? void 0 : section.id) || !(section == null ? void 0 : section.collection)) {
|
|
139
|
+
console.error(`Invalid section: ${collection}/${sectionKey}`);
|
|
140
|
+
setSaving(false);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const images = pendingImages.filter(
|
|
144
|
+
(img) => img.collection === collection && img.sectionKey === sectionKey
|
|
145
|
+
);
|
|
146
|
+
let updatedSection = __spreadValues({}, section);
|
|
147
|
+
for (const img of images) {
|
|
148
|
+
const url = await resolveImageUrl(img);
|
|
149
|
+
const keys = img.fieldKey.split(".");
|
|
150
|
+
if (keys.length === 1) {
|
|
151
|
+
updatedSection = __spreadProps(__spreadValues({}, updatedSection), { [img.fieldKey]: url });
|
|
152
|
+
} else {
|
|
153
|
+
let current = updatedSection;
|
|
154
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
155
|
+
if (!current[keys[i]]) current[keys[i]] = {};
|
|
156
|
+
current = current[keys[i]];
|
|
157
|
+
}
|
|
158
|
+
current[keys[keys.length - 1]] = url;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
await persist(updatedSection);
|
|
162
|
+
setSections((prev) => __spreadProps(__spreadValues({}, prev), {
|
|
163
|
+
[collection]: __spreadProps(__spreadValues({}, prev[collection]), { [sectionKey]: updatedSection })
|
|
164
|
+
}));
|
|
165
|
+
setPendingImages(
|
|
166
|
+
(prev) => prev.filter(
|
|
167
|
+
(img) => !(img.collection === collection && img.sectionKey === sectionKey)
|
|
168
|
+
)
|
|
169
|
+
);
|
|
170
|
+
setDirtySections((prev) => {
|
|
171
|
+
const next = new Set(prev);
|
|
172
|
+
next.delete(dirtyKey(collection, sectionKey));
|
|
173
|
+
return next;
|
|
174
|
+
});
|
|
175
|
+
notify.success("Changes saved successfully!");
|
|
176
|
+
} catch (error) {
|
|
177
|
+
console.error("Save failed:", error);
|
|
178
|
+
notify.error("Failed to save changes");
|
|
179
|
+
} finally {
|
|
180
|
+
setSaving(false);
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
[sections, pendingImages, saving, resolveImageUrl, persist, notify]
|
|
184
|
+
);
|
|
185
|
+
const saveAll = useCallback(async () => {
|
|
186
|
+
var _a;
|
|
187
|
+
if (saving || dirtySections.size === 0) return;
|
|
188
|
+
setSaving(true);
|
|
189
|
+
try {
|
|
190
|
+
const updatedSections = __spreadValues({}, sections);
|
|
191
|
+
for (const img of pendingImages) {
|
|
192
|
+
const url = await resolveImageUrl(img);
|
|
193
|
+
if (!updatedSections[img.collection])
|
|
194
|
+
updatedSections[img.collection] = {};
|
|
195
|
+
if (!updatedSections[img.collection][img.sectionKey]) {
|
|
196
|
+
updatedSections[img.collection][img.sectionKey] = {
|
|
197
|
+
id: img.docId,
|
|
198
|
+
collection: img.collection
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
const keys = img.fieldKey.split(".");
|
|
202
|
+
let current = updatedSections[img.collection][img.sectionKey];
|
|
203
|
+
if (keys.length === 1) {
|
|
204
|
+
updatedSections[img.collection][img.sectionKey] = __spreadProps(__spreadValues({}, current), {
|
|
205
|
+
[img.fieldKey]: url
|
|
206
|
+
});
|
|
207
|
+
} else {
|
|
208
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
209
|
+
if (!current[keys[i]]) current[keys[i]] = {};
|
|
210
|
+
current = current[keys[i]];
|
|
211
|
+
}
|
|
212
|
+
current[keys[keys.length - 1]] = url;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
for (const entry of dirtySections) {
|
|
216
|
+
const [collection, key] = entry.split(":");
|
|
217
|
+
const section = (_a = updatedSections[collection]) == null ? void 0 : _a[key];
|
|
218
|
+
if (!(section == null ? void 0 : section.id) || !(section == null ? void 0 : section.collection)) {
|
|
219
|
+
console.error(`Invalid section for save: ${entry}`);
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
await persist(section);
|
|
223
|
+
}
|
|
224
|
+
setSections(updatedSections);
|
|
225
|
+
setPendingImages([]);
|
|
226
|
+
setDirtySections(/* @__PURE__ */ new Set());
|
|
227
|
+
notify.success("All changes saved successfully!");
|
|
228
|
+
} catch (error) {
|
|
229
|
+
console.error("Save all failed:", error);
|
|
230
|
+
notify.error("Failed to save changes");
|
|
231
|
+
} finally {
|
|
232
|
+
setSaving(false);
|
|
233
|
+
}
|
|
234
|
+
}, [sections, pendingImages, dirtySections, saving, resolveImageUrl, persist, notify]);
|
|
235
|
+
return /* @__PURE__ */ jsx(
|
|
236
|
+
PageContext.Provider,
|
|
237
|
+
{
|
|
238
|
+
value: {
|
|
239
|
+
sections,
|
|
240
|
+
hasUnsavedChanges,
|
|
241
|
+
pendingImages,
|
|
242
|
+
saving,
|
|
243
|
+
setSection,
|
|
244
|
+
editField,
|
|
245
|
+
setPendingImage,
|
|
246
|
+
saveSection,
|
|
247
|
+
saveAll
|
|
248
|
+
},
|
|
249
|
+
children
|
|
250
|
+
}
|
|
251
|
+
);
|
|
252
|
+
};
|
|
253
|
+
var usePageContext = () => {
|
|
254
|
+
const context = useContext(PageContext);
|
|
255
|
+
if (!context)
|
|
256
|
+
throw new Error("usePageContext must be used within a PageProvider");
|
|
257
|
+
return context;
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
// src/client/auth.tsx
|
|
261
|
+
import { createContext as createContext2, useContext as useContext2 } from "react";
|
|
262
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
263
|
+
var CmsAuthContext = createContext2(void 0);
|
|
264
|
+
function useCmsAuth() {
|
|
265
|
+
const ctx = useContext2(CmsAuthContext);
|
|
266
|
+
if (!ctx) {
|
|
267
|
+
throw new Error(
|
|
268
|
+
"useCmsAuth must be used within a CmsAuthProvider (or a built-in auth provider such as FirebaseAuthProvider)."
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
return ctx;
|
|
272
|
+
}
|
|
273
|
+
function CmsAuthProvider({
|
|
274
|
+
value,
|
|
275
|
+
children
|
|
276
|
+
}) {
|
|
277
|
+
return /* @__PURE__ */ jsx2(CmsAuthContext.Provider, { value, children });
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// src/client/ContentEditSpan.tsx
|
|
281
|
+
import { useRef, useEffect, useState as useState2, useCallback as useCallback2 } from "react";
|
|
282
|
+
|
|
283
|
+
// src/client/utils.ts
|
|
284
|
+
import { clsx } from "clsx";
|
|
285
|
+
import { twMerge } from "tailwind-merge";
|
|
286
|
+
function cn(...inputs) {
|
|
287
|
+
return twMerge(clsx(inputs));
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// src/client/ContentEditSpan.tsx
|
|
291
|
+
import { Fragment, jsx as jsx3 } from "react/jsx-runtime";
|
|
292
|
+
var PATTERNS = [
|
|
293
|
+
{ regex: /^\*\*(.+?)\*\*/, mark: "bold" },
|
|
294
|
+
{ regex: /^\*(.+?)\*/, mark: "italic" },
|
|
295
|
+
{ regex: /^~~br~~/, mark: "break" },
|
|
296
|
+
{ regex: /^~~(.+?)~~/, mark: "strike" },
|
|
297
|
+
{ regex: /^\^\^(.+?)\^\^/, mark: "primary" },
|
|
298
|
+
{ regex: /^__(.+?)__/, mark: "underline" },
|
|
299
|
+
{
|
|
300
|
+
regex: /^\[(.+?)\]\((https?:\/\/[^\s)]+)\)/,
|
|
301
|
+
mark: "link",
|
|
302
|
+
isLink: true
|
|
303
|
+
}
|
|
304
|
+
];
|
|
305
|
+
function parseSpecialString(input) {
|
|
306
|
+
const out = [];
|
|
307
|
+
let text = input;
|
|
308
|
+
while (text.length) {
|
|
309
|
+
let matched = false;
|
|
310
|
+
for (const p of PATTERNS) {
|
|
311
|
+
const m = p.regex.exec(text);
|
|
312
|
+
if (!m) continue;
|
|
313
|
+
matched = true;
|
|
314
|
+
if (p.mark === "break") {
|
|
315
|
+
out.push({ text: "\n", break: true });
|
|
316
|
+
} else if ("isLink" in p) {
|
|
317
|
+
out.push({ text: m[1], link: m[2] });
|
|
318
|
+
} else {
|
|
319
|
+
const inner = parseSpecialString(m[1]);
|
|
320
|
+
inner.forEach(
|
|
321
|
+
(n) => n[p.mark] = true
|
|
322
|
+
);
|
|
323
|
+
out.push(...inner);
|
|
324
|
+
}
|
|
325
|
+
text = text.slice(m[0].length);
|
|
326
|
+
break;
|
|
327
|
+
}
|
|
328
|
+
if (!matched) {
|
|
329
|
+
out.push({ text: text[0] });
|
|
330
|
+
text = text.slice(1);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return out;
|
|
334
|
+
}
|
|
335
|
+
function RenderStatic({
|
|
336
|
+
raw,
|
|
337
|
+
as: Component = "span",
|
|
338
|
+
className
|
|
339
|
+
}) {
|
|
340
|
+
const nodes = parseSpecialString(raw);
|
|
341
|
+
const content = /* @__PURE__ */ jsx3(Fragment, { children: nodes.map((l, i) => {
|
|
342
|
+
if (l.break) return /* @__PURE__ */ jsx3("br", {}, i);
|
|
343
|
+
let el = l.text;
|
|
344
|
+
if (l.bold) el = /* @__PURE__ */ jsx3("strong", { children: el });
|
|
345
|
+
if (l.italic) el = /* @__PURE__ */ jsx3("em", { children: el });
|
|
346
|
+
if (l.strike) el = /* @__PURE__ */ jsx3("s", { children: el });
|
|
347
|
+
if (l.primary || l.underline) {
|
|
348
|
+
el = /* @__PURE__ */ jsx3(
|
|
349
|
+
"span",
|
|
350
|
+
{
|
|
351
|
+
style: {
|
|
352
|
+
color: l.primary ? "var(--color-primary)" : void 0,
|
|
353
|
+
textDecoration: l.underline ? "underline" : void 0
|
|
354
|
+
},
|
|
355
|
+
children: el
|
|
356
|
+
}
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
if (l.link) {
|
|
360
|
+
el = /* @__PURE__ */ jsx3(
|
|
361
|
+
"a",
|
|
362
|
+
{
|
|
363
|
+
href: l.link,
|
|
364
|
+
target: "_blank",
|
|
365
|
+
rel: "noopener noreferrer",
|
|
366
|
+
className: "inline hover:underline",
|
|
367
|
+
children: el
|
|
368
|
+
}
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
return /* @__PURE__ */ jsx3("span", { children: el }, i);
|
|
372
|
+
}) });
|
|
373
|
+
return /* @__PURE__ */ jsx3(Component, { className, children: content });
|
|
374
|
+
}
|
|
375
|
+
function getNestedValue(obj, path) {
|
|
376
|
+
const keys = path.split(".");
|
|
377
|
+
let current = obj;
|
|
378
|
+
for (const key of keys) {
|
|
379
|
+
if (current == null || current[key] === void 0)
|
|
380
|
+
return void 0;
|
|
381
|
+
current = current[key];
|
|
382
|
+
}
|
|
383
|
+
return current;
|
|
384
|
+
}
|
|
385
|
+
function ContentEditSpan({
|
|
386
|
+
collection = "portfolio",
|
|
387
|
+
sectionKey,
|
|
388
|
+
fieldKey,
|
|
389
|
+
className,
|
|
390
|
+
children,
|
|
391
|
+
as = "span"
|
|
392
|
+
}) {
|
|
393
|
+
var _a, _b;
|
|
394
|
+
const { sections, editField } = usePageContext();
|
|
395
|
+
const { isEditing } = useCmsAuth();
|
|
396
|
+
const section = (_a = sections[collection]) == null ? void 0 : _a[sectionKey];
|
|
397
|
+
const raw = (_b = getNestedValue(section, fieldKey)) != null ? _b : typeof children === "string" ? children : "";
|
|
398
|
+
if (!isEditing) {
|
|
399
|
+
return /* @__PURE__ */ jsx3(RenderStatic, { raw, as, className });
|
|
400
|
+
}
|
|
401
|
+
return /* @__PURE__ */ jsx3(
|
|
402
|
+
EditableContentSpan,
|
|
403
|
+
{
|
|
404
|
+
collection,
|
|
405
|
+
sectionKey,
|
|
406
|
+
fieldKey,
|
|
407
|
+
className,
|
|
408
|
+
raw,
|
|
409
|
+
editField,
|
|
410
|
+
as
|
|
411
|
+
}
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
function EditableContentSpan({
|
|
415
|
+
collection = "portfolio",
|
|
416
|
+
sectionKey,
|
|
417
|
+
fieldKey,
|
|
418
|
+
className,
|
|
419
|
+
raw,
|
|
420
|
+
editField,
|
|
421
|
+
as: Component = "span"
|
|
422
|
+
}) {
|
|
423
|
+
const { isEditing } = useCmsAuth();
|
|
424
|
+
const [isFocused, setIsFocused] = useState2(false);
|
|
425
|
+
const [editValue, setEditValue] = useState2(raw);
|
|
426
|
+
const contentRef = useRef(null);
|
|
427
|
+
const rawRef = useRef(raw);
|
|
428
|
+
useEffect(() => {
|
|
429
|
+
if (!isFocused && rawRef.current !== raw) {
|
|
430
|
+
rawRef.current = raw;
|
|
431
|
+
}
|
|
432
|
+
}, [raw, isFocused]);
|
|
433
|
+
useEffect(() => {
|
|
434
|
+
if (!isFocused && rawRef.current !== editValue) {
|
|
435
|
+
setEditValue(rawRef.current);
|
|
436
|
+
}
|
|
437
|
+
}, [isFocused, editValue]);
|
|
438
|
+
const handleInput = useCallback2(() => {
|
|
439
|
+
if (contentRef.current) {
|
|
440
|
+
setEditValue(contentRef.current.textContent || "");
|
|
441
|
+
}
|
|
442
|
+
}, []);
|
|
443
|
+
const handleBlur = useCallback2(() => {
|
|
444
|
+
setIsFocused(false);
|
|
445
|
+
if (editValue !== raw) {
|
|
446
|
+
editField(collection, sectionKey, fieldKey, editValue);
|
|
447
|
+
}
|
|
448
|
+
}, [editValue, raw, collection, sectionKey, fieldKey, editField]);
|
|
449
|
+
const handleFocus = useCallback2(() => {
|
|
450
|
+
setIsFocused(true);
|
|
451
|
+
if (contentRef.current) {
|
|
452
|
+
contentRef.current.textContent = editValue;
|
|
453
|
+
setTimeout(() => {
|
|
454
|
+
if (contentRef.current) {
|
|
455
|
+
const range = document.createRange();
|
|
456
|
+
const sel = window.getSelection();
|
|
457
|
+
range.selectNodeContents(contentRef.current);
|
|
458
|
+
range.collapse(false);
|
|
459
|
+
sel == null ? void 0 : sel.removeAllRanges();
|
|
460
|
+
sel == null ? void 0 : sel.addRange(range);
|
|
461
|
+
}
|
|
462
|
+
}, 0);
|
|
463
|
+
}
|
|
464
|
+
}, [editValue]);
|
|
465
|
+
return /* @__PURE__ */ jsx3(
|
|
466
|
+
Component,
|
|
467
|
+
{
|
|
468
|
+
ref: contentRef,
|
|
469
|
+
className: cn(
|
|
470
|
+
className,
|
|
471
|
+
"outline-none transition-all duration-200",
|
|
472
|
+
"whitespace-pre-wrap break-words overflow-wrap-anywhere",
|
|
473
|
+
isFocused && "ring-2 ring-primary/50 ring-offset-2 ring-offset-neutral-900 rounded-sm px-2",
|
|
474
|
+
!isFocused && isEditing && "hover:ring-1 hover:ring-primary/30 hover:ring-offset-1 hover:ring-offset-neutral-900 hover:rounded-sm hover:px-2 cursor-text"
|
|
475
|
+
),
|
|
476
|
+
contentEditable: isEditing,
|
|
477
|
+
suppressContentEditableWarning: true,
|
|
478
|
+
onInput: handleInput,
|
|
479
|
+
onBlur: handleBlur,
|
|
480
|
+
onFocus: handleFocus,
|
|
481
|
+
children: !isFocused && /* @__PURE__ */ jsx3(RenderStatic, { raw: editValue, as: "span" })
|
|
482
|
+
},
|
|
483
|
+
isFocused ? "editing" : "static"
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// src/client/EditableImage.tsx
|
|
488
|
+
import { useRef as useRef2, useState as useState3 } from "react";
|
|
489
|
+
import { createPortal } from "react-dom";
|
|
490
|
+
import { CameraIcon, Link2Icon, XIcon, CheckIcon } from "lucide-react";
|
|
491
|
+
import { Fragment as Fragment2, jsx as jsx4, jsxs } from "react/jsx-runtime";
|
|
492
|
+
function EditableImage({
|
|
493
|
+
sectionKey,
|
|
494
|
+
fieldKey,
|
|
495
|
+
src,
|
|
496
|
+
collection,
|
|
497
|
+
docId,
|
|
498
|
+
className
|
|
499
|
+
}) {
|
|
500
|
+
const { isEditing } = useCmsAuth();
|
|
501
|
+
const { editField, setPendingImage, pendingImages, saving } = usePageContext();
|
|
502
|
+
const [preview, setPreview] = useState3(src);
|
|
503
|
+
const [hasError, setHasError] = useState3(false);
|
|
504
|
+
const [showUrlModal, setShowUrlModal] = useState3(false);
|
|
505
|
+
const [urlInput, setUrlInput] = useState3("");
|
|
506
|
+
const [urlPreview, setUrlPreview] = useState3("");
|
|
507
|
+
const inputRef = useRef2(null);
|
|
508
|
+
const pendingImage = pendingImages.find(
|
|
509
|
+
(img) => img.sectionKey === sectionKey && img.fieldKey === fieldKey
|
|
510
|
+
);
|
|
511
|
+
const imgSrc = (pendingImage == null ? void 0 : pendingImage.localUrl) || preview;
|
|
512
|
+
const handleFileChange = (e) => {
|
|
513
|
+
var _a;
|
|
514
|
+
if (saving) return;
|
|
515
|
+
const file = (_a = e.target.files) == null ? void 0 : _a[0];
|
|
516
|
+
if (!file) return;
|
|
517
|
+
const localUrl = URL.createObjectURL(file);
|
|
518
|
+
setPreview(localUrl);
|
|
519
|
+
setHasError(false);
|
|
520
|
+
editField(collection, sectionKey, fieldKey, localUrl);
|
|
521
|
+
setPendingImage({
|
|
522
|
+
file,
|
|
523
|
+
localUrl,
|
|
524
|
+
sectionKey,
|
|
525
|
+
fieldKey,
|
|
526
|
+
collection,
|
|
527
|
+
docId,
|
|
528
|
+
isExternal: false
|
|
529
|
+
});
|
|
530
|
+
};
|
|
531
|
+
const handleUrlConfirm = () => {
|
|
532
|
+
if (!urlPreview) return;
|
|
533
|
+
setPreview(urlPreview);
|
|
534
|
+
setHasError(false);
|
|
535
|
+
editField(collection, sectionKey, fieldKey, urlPreview);
|
|
536
|
+
setPendingImage({
|
|
537
|
+
file: null,
|
|
538
|
+
localUrl: urlPreview,
|
|
539
|
+
sectionKey,
|
|
540
|
+
fieldKey,
|
|
541
|
+
collection,
|
|
542
|
+
docId,
|
|
543
|
+
isExternal: true
|
|
544
|
+
});
|
|
545
|
+
setShowUrlModal(false);
|
|
546
|
+
setUrlInput("");
|
|
547
|
+
setUrlPreview("");
|
|
548
|
+
};
|
|
549
|
+
const handleUrlChange = (value) => {
|
|
550
|
+
setUrlInput(value);
|
|
551
|
+
try {
|
|
552
|
+
const url = new URL(value);
|
|
553
|
+
if (url.protocol === "http:" || url.protocol === "https:") {
|
|
554
|
+
setUrlPreview(value);
|
|
555
|
+
} else {
|
|
556
|
+
setUrlPreview("");
|
|
557
|
+
}
|
|
558
|
+
} catch (e) {
|
|
559
|
+
setUrlPreview("");
|
|
560
|
+
}
|
|
561
|
+
};
|
|
562
|
+
const imageNode = hasError || !imgSrc ? /* @__PURE__ */ jsx4("div", { className: "flex items-center justify-center w-full h-full p-4", children: /* @__PURE__ */ jsxs("div", { className: "text-center space-y-4", children: [
|
|
563
|
+
/* @__PURE__ */ jsx4("div", { className: "w-32 h-32 mx-auto rounded-2xl bg-neutral-800/50 flex items-center justify-center", children: /* @__PURE__ */ jsx4(
|
|
564
|
+
"svg",
|
|
565
|
+
{
|
|
566
|
+
className: "w-16 h-16 text-neutral-600",
|
|
567
|
+
fill: "none",
|
|
568
|
+
viewBox: "0 0 24 24",
|
|
569
|
+
stroke: "currentColor",
|
|
570
|
+
children: /* @__PURE__ */ jsx4(
|
|
571
|
+
"path",
|
|
572
|
+
{
|
|
573
|
+
strokeLinecap: "round",
|
|
574
|
+
strokeLinejoin: "round",
|
|
575
|
+
strokeWidth: 1.5,
|
|
576
|
+
d: "M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
|
|
577
|
+
}
|
|
578
|
+
)
|
|
579
|
+
}
|
|
580
|
+
) }),
|
|
581
|
+
/* @__PURE__ */ jsx4("p", { className: "text-neutral-500 text-lg", children: "No image available" })
|
|
582
|
+
] }) }) : (
|
|
583
|
+
// eslint-disable-next-line @next/next/no-img-element
|
|
584
|
+
/* @__PURE__ */ jsx4(
|
|
585
|
+
"img",
|
|
586
|
+
{
|
|
587
|
+
src: imgSrc,
|
|
588
|
+
alt: "",
|
|
589
|
+
className: "w-full h-full object-cover",
|
|
590
|
+
onError: () => setHasError(true)
|
|
591
|
+
}
|
|
592
|
+
)
|
|
593
|
+
);
|
|
594
|
+
if (!isEditing) {
|
|
595
|
+
return /* @__PURE__ */ jsx4("div", { className, children: imageNode });
|
|
596
|
+
}
|
|
597
|
+
return /* @__PURE__ */ jsxs(Fragment2, { children: [
|
|
598
|
+
/* @__PURE__ */ jsxs("div", { className: cn("relative group", className), children: [
|
|
599
|
+
imageNode,
|
|
600
|
+
/* @__PURE__ */ jsx4("div", { className: "absolute inset-0 flex items-center justify-center bg-black/30 opacity-0 group-hover:opacity-100 transition-opacity", children: saving ? /* @__PURE__ */ jsx4("div", { className: "w-8 h-8 border-4 border-white border-t-transparent rounded-full animate-spin" }) : /* @__PURE__ */ jsxs("div", { className: "flex gap-12", children: [
|
|
601
|
+
/* @__PURE__ */ jsx4(
|
|
602
|
+
CameraIcon,
|
|
603
|
+
{
|
|
604
|
+
className: "w-12 h-12 text-white cursor-pointer hover:text-primary",
|
|
605
|
+
onClick: (e) => {
|
|
606
|
+
var _a;
|
|
607
|
+
e.stopPropagation();
|
|
608
|
+
(_a = inputRef.current) == null ? void 0 : _a.click();
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
),
|
|
612
|
+
/* @__PURE__ */ jsx4(
|
|
613
|
+
Link2Icon,
|
|
614
|
+
{
|
|
615
|
+
className: "w-12 h-12 text-white cursor-pointer hover:text-primary",
|
|
616
|
+
onClick: (e) => {
|
|
617
|
+
e.stopPropagation();
|
|
618
|
+
setShowUrlModal(true);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
)
|
|
622
|
+
] }) }),
|
|
623
|
+
/* @__PURE__ */ jsx4(
|
|
624
|
+
"input",
|
|
625
|
+
{
|
|
626
|
+
ref: inputRef,
|
|
627
|
+
type: "file",
|
|
628
|
+
accept: "image/*",
|
|
629
|
+
disabled: saving,
|
|
630
|
+
onChange: handleFileChange,
|
|
631
|
+
className: "absolute inset-0 opacity-0 pointer-events-none"
|
|
632
|
+
}
|
|
633
|
+
)
|
|
634
|
+
] }),
|
|
635
|
+
showUrlModal && createPortal(
|
|
636
|
+
/* @__PURE__ */ jsx4("div", { className: "fixed inset-0 bg-black/70 flex items-center justify-center z-9999 p-4", children: /* @__PURE__ */ jsxs("div", { className: "bg-neutral-900 rounded-xl p-6 max-w-sm w-full space-y-4", children: [
|
|
637
|
+
/* @__PURE__ */ jsx4("h3", { className: "text-lg font-bold", children: "Add Image URL" }),
|
|
638
|
+
/* @__PURE__ */ jsx4(
|
|
639
|
+
"input",
|
|
640
|
+
{
|
|
641
|
+
type: "text",
|
|
642
|
+
value: urlInput,
|
|
643
|
+
onChange: (e) => handleUrlChange(e.target.value),
|
|
644
|
+
placeholder: "https://example.com/image.png",
|
|
645
|
+
className: "w-full px-3 py-2 rounded bg-neutral-800 border border-neutral-700"
|
|
646
|
+
}
|
|
647
|
+
),
|
|
648
|
+
urlPreview ? (
|
|
649
|
+
// eslint-disable-next-line @next/next/no-img-element
|
|
650
|
+
/* @__PURE__ */ jsx4(
|
|
651
|
+
"img",
|
|
652
|
+
{
|
|
653
|
+
src: urlPreview,
|
|
654
|
+
alt: "",
|
|
655
|
+
className: "w-full h-40 object-contain rounded",
|
|
656
|
+
onError: () => setHasError(true)
|
|
657
|
+
}
|
|
658
|
+
)
|
|
659
|
+
) : /* @__PURE__ */ jsx4("div", { className: "h-40 flex items-center justify-center text-neutral-500", children: "Invalid URL" }),
|
|
660
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
|
|
661
|
+
/* @__PURE__ */ jsxs(
|
|
662
|
+
"button",
|
|
663
|
+
{
|
|
664
|
+
onClick: () => setShowUrlModal(false),
|
|
665
|
+
className: "px-3 py-1 bg-neutral-700 rounded text-sm",
|
|
666
|
+
children: [
|
|
667
|
+
/* @__PURE__ */ jsx4(XIcon, { className: "w-4 h-4 inline" }),
|
|
668
|
+
" Cancel"
|
|
669
|
+
]
|
|
670
|
+
}
|
|
671
|
+
),
|
|
672
|
+
/* @__PURE__ */ jsxs(
|
|
673
|
+
"button",
|
|
674
|
+
{
|
|
675
|
+
onClick: handleUrlConfirm,
|
|
676
|
+
disabled: !urlPreview,
|
|
677
|
+
className: "px-3 py-1 bg-primary rounded text-sm disabled:opacity-50",
|
|
678
|
+
children: [
|
|
679
|
+
/* @__PURE__ */ jsx4(CheckIcon, { className: "w-4 h-4 inline" }),
|
|
680
|
+
" Confirm"
|
|
681
|
+
]
|
|
682
|
+
}
|
|
683
|
+
)
|
|
684
|
+
] })
|
|
685
|
+
] }) }),
|
|
686
|
+
document.body
|
|
687
|
+
)
|
|
688
|
+
] });
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// src/client/MarkdownEditor.tsx
|
|
692
|
+
import { useState as useState4 } from "react";
|
|
693
|
+
import { createPortal as createPortal2 } from "react-dom";
|
|
694
|
+
import ReactMarkdown from "react-markdown";
|
|
695
|
+
import remarkGfm from "remark-gfm";
|
|
696
|
+
import {
|
|
697
|
+
EyeIcon,
|
|
698
|
+
EditIcon,
|
|
699
|
+
SaveIcon,
|
|
700
|
+
XIcon as XIcon2,
|
|
701
|
+
TypeIcon,
|
|
702
|
+
BoldIcon,
|
|
703
|
+
ItalicIcon,
|
|
704
|
+
ListIcon,
|
|
705
|
+
LinkIcon,
|
|
706
|
+
CodeIcon,
|
|
707
|
+
ImageIcon,
|
|
708
|
+
Heading1Icon,
|
|
709
|
+
Heading2Icon
|
|
710
|
+
} from "lucide-react";
|
|
711
|
+
import { toast as toast2 } from "sonner";
|
|
712
|
+
import { Fragment as Fragment3, jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
713
|
+
function MarkdownEditor({
|
|
714
|
+
initialValue,
|
|
715
|
+
onSave,
|
|
716
|
+
trigger,
|
|
717
|
+
title = "Edit Content"
|
|
718
|
+
}) {
|
|
719
|
+
const [open, setOpen] = useState4(false);
|
|
720
|
+
const [content, setContent] = useState4(initialValue);
|
|
721
|
+
const [isPreview, setIsPreview] = useState4(false);
|
|
722
|
+
const handleOpen = () => {
|
|
723
|
+
setContent(initialValue);
|
|
724
|
+
setOpen(true);
|
|
725
|
+
};
|
|
726
|
+
const handleSave = () => {
|
|
727
|
+
onSave(content);
|
|
728
|
+
setOpen(false);
|
|
729
|
+
};
|
|
730
|
+
const handleCancel = () => {
|
|
731
|
+
setContent(initialValue);
|
|
732
|
+
setOpen(false);
|
|
733
|
+
};
|
|
734
|
+
const insertMarkdown = (before, after = "", placeholder = "text") => {
|
|
735
|
+
const textarea = document.querySelector(
|
|
736
|
+
"textarea[data-markdown-editor]"
|
|
737
|
+
);
|
|
738
|
+
if (!textarea) return;
|
|
739
|
+
const start = textarea.selectionStart;
|
|
740
|
+
const end = textarea.selectionEnd;
|
|
741
|
+
const selectedText = content.substring(start, end) || placeholder;
|
|
742
|
+
const newText = content.substring(0, start) + before + selectedText + after + content.substring(end);
|
|
743
|
+
setContent(newText);
|
|
744
|
+
setTimeout(() => {
|
|
745
|
+
textarea.focus();
|
|
746
|
+
const newCursorPos = start + before.length + selectedText.length;
|
|
747
|
+
textarea.setSelectionRange(newCursorPos, newCursorPos);
|
|
748
|
+
}, 0);
|
|
749
|
+
};
|
|
750
|
+
return /* @__PURE__ */ jsxs2(Fragment3, { children: [
|
|
751
|
+
trigger ? /* @__PURE__ */ jsx5("div", { onClick: handleOpen, children: trigger }) : /* @__PURE__ */ jsxs2(
|
|
752
|
+
"button",
|
|
753
|
+
{
|
|
754
|
+
type: "button",
|
|
755
|
+
onClick: handleOpen,
|
|
756
|
+
className: "inline-flex items-center gap-2 px-3 py-1.5 text-sm rounded-lg border border-neutral-700 hover:bg-neutral-800 transition-colors",
|
|
757
|
+
children: [
|
|
758
|
+
/* @__PURE__ */ jsx5(EditIcon, { className: "w-4 h-4" }),
|
|
759
|
+
"Edit Content"
|
|
760
|
+
]
|
|
761
|
+
}
|
|
762
|
+
),
|
|
763
|
+
open && createPortal2(
|
|
764
|
+
/* @__PURE__ */ jsx5("div", { className: "fixed inset-0 z-[10001] flex items-center justify-center bg-black/70 p-4", children: /* @__PURE__ */ jsxs2("div", { className: "md:max-w-6xl w-full h-[90vh] flex flex-col bg-neutral-950 border border-neutral-800 rounded-xl overflow-hidden", children: [
|
|
765
|
+
/* @__PURE__ */ jsx5("div", { className: "px-6 pt-6 pb-4 border-b border-neutral-800", children: /* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between", children: [
|
|
766
|
+
/* @__PURE__ */ jsxs2("h2", { className: "text-2xl font-bold flex items-center gap-3", children: [
|
|
767
|
+
/* @__PURE__ */ jsx5("div", { className: "p-2 bg-primary/10 rounded-lg", children: /* @__PURE__ */ jsx5(TypeIcon, { className: "w-5 h-5 text-primary" }) }),
|
|
768
|
+
title
|
|
769
|
+
] }),
|
|
770
|
+
/* @__PURE__ */ jsx5(
|
|
771
|
+
"button",
|
|
772
|
+
{
|
|
773
|
+
type: "button",
|
|
774
|
+
onClick: () => setIsPreview(!isPreview),
|
|
775
|
+
className: cn(
|
|
776
|
+
"flex items-center gap-2 px-4 py-2 rounded-lg transition-all font-medium",
|
|
777
|
+
isPreview ? "bg-primary text-white" : "bg-neutral-800 hover:bg-neutral-700 text-neutral-300"
|
|
778
|
+
),
|
|
779
|
+
children: isPreview ? /* @__PURE__ */ jsxs2(Fragment3, { children: [
|
|
780
|
+
/* @__PURE__ */ jsx5(EditIcon, { className: "w-4 h-4" }),
|
|
781
|
+
"Edit"
|
|
782
|
+
] }) : /* @__PURE__ */ jsxs2(Fragment3, { children: [
|
|
783
|
+
/* @__PURE__ */ jsx5(EyeIcon, { className: "w-4 h-4" }),
|
|
784
|
+
"Preview"
|
|
785
|
+
] })
|
|
786
|
+
}
|
|
787
|
+
)
|
|
788
|
+
] }) }),
|
|
789
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex-1 overflow-hidden flex flex-col", children: [
|
|
790
|
+
!isPreview && /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-1 px-4 py-3 border-b border-neutral-800 bg-neutral-900/50 overflow-x-auto", children: [
|
|
791
|
+
/* @__PURE__ */ jsx5(
|
|
792
|
+
ToolbarButton,
|
|
793
|
+
{
|
|
794
|
+
icon: /* @__PURE__ */ jsx5(Heading1Icon, { className: "w-4 h-4" }),
|
|
795
|
+
label: "Heading 1",
|
|
796
|
+
onClick: () => insertMarkdown("# ", "", "Heading")
|
|
797
|
+
}
|
|
798
|
+
),
|
|
799
|
+
/* @__PURE__ */ jsx5(
|
|
800
|
+
ToolbarButton,
|
|
801
|
+
{
|
|
802
|
+
icon: /* @__PURE__ */ jsx5(Heading2Icon, { className: "w-4 h-4" }),
|
|
803
|
+
label: "Heading 2",
|
|
804
|
+
onClick: () => insertMarkdown("## ", "", "Heading")
|
|
805
|
+
}
|
|
806
|
+
),
|
|
807
|
+
/* @__PURE__ */ jsx5("div", { className: "w-px h-6 bg-neutral-700 mx-1" }),
|
|
808
|
+
/* @__PURE__ */ jsx5(
|
|
809
|
+
ToolbarButton,
|
|
810
|
+
{
|
|
811
|
+
icon: /* @__PURE__ */ jsx5(BoldIcon, { className: "w-4 h-4" }),
|
|
812
|
+
label: "Bold",
|
|
813
|
+
onClick: () => insertMarkdown("**", "**", "bold text")
|
|
814
|
+
}
|
|
815
|
+
),
|
|
816
|
+
/* @__PURE__ */ jsx5(
|
|
817
|
+
ToolbarButton,
|
|
818
|
+
{
|
|
819
|
+
icon: /* @__PURE__ */ jsx5(ItalicIcon, { className: "w-4 h-4" }),
|
|
820
|
+
label: "Italic",
|
|
821
|
+
onClick: () => insertMarkdown("*", "*", "italic text")
|
|
822
|
+
}
|
|
823
|
+
),
|
|
824
|
+
/* @__PURE__ */ jsx5("div", { className: "w-px h-6 bg-neutral-700 mx-1" }),
|
|
825
|
+
/* @__PURE__ */ jsx5(
|
|
826
|
+
ToolbarButton,
|
|
827
|
+
{
|
|
828
|
+
icon: /* @__PURE__ */ jsx5(LinkIcon, { className: "w-4 h-4" }),
|
|
829
|
+
label: "Link",
|
|
830
|
+
onClick: () => insertMarkdown("[", "](url)", "link text")
|
|
831
|
+
}
|
|
832
|
+
),
|
|
833
|
+
/* @__PURE__ */ jsx5(
|
|
834
|
+
ToolbarButton,
|
|
835
|
+
{
|
|
836
|
+
icon: /* @__PURE__ */ jsx5(ImageIcon, { className: "w-4 h-4" }),
|
|
837
|
+
label: "Image",
|
|
838
|
+
onClick: () => insertMarkdown("", "alt text")
|
|
839
|
+
}
|
|
840
|
+
),
|
|
841
|
+
/* @__PURE__ */ jsx5("div", { className: "w-px h-6 bg-neutral-700 mx-1" }),
|
|
842
|
+
/* @__PURE__ */ jsx5(
|
|
843
|
+
ToolbarButton,
|
|
844
|
+
{
|
|
845
|
+
icon: /* @__PURE__ */ jsx5(ListIcon, { className: "w-4 h-4" }),
|
|
846
|
+
label: "List",
|
|
847
|
+
onClick: () => insertMarkdown("- ", "", "list item")
|
|
848
|
+
}
|
|
849
|
+
),
|
|
850
|
+
/* @__PURE__ */ jsx5(
|
|
851
|
+
ToolbarButton,
|
|
852
|
+
{
|
|
853
|
+
icon: /* @__PURE__ */ jsx5(CodeIcon, { className: "w-4 h-4" }),
|
|
854
|
+
label: "Code",
|
|
855
|
+
onClick: () => insertMarkdown("```\n", "\n```", "code")
|
|
856
|
+
}
|
|
857
|
+
)
|
|
858
|
+
] }),
|
|
859
|
+
/* @__PURE__ */ jsx5("div", { className: "flex-1 overflow-auto p-6", children: isPreview ? /* @__PURE__ */ jsx5("div", { className: "prose prose-invert prose-lg max-w-none", children: /* @__PURE__ */ jsx5(ReactMarkdown, { remarkPlugins: [remarkGfm], children: content }) }) : /* @__PURE__ */ jsxs2("div", { className: "h-full flex flex-col", children: [
|
|
860
|
+
/* @__PURE__ */ jsx5(
|
|
861
|
+
"textarea",
|
|
862
|
+
{
|
|
863
|
+
"data-markdown-editor": true,
|
|
864
|
+
value: content,
|
|
865
|
+
onChange: (e) => setContent(e.target.value),
|
|
866
|
+
className: "h-full w-full resize-none rounded-md font-mono text-sm bg-neutral-900/50 border border-neutral-800 p-3 outline-none focus:border-primary",
|
|
867
|
+
placeholder: "# Project Title\n\n## Overview\nWrite your description here..."
|
|
868
|
+
}
|
|
869
|
+
),
|
|
870
|
+
/* @__PURE__ */ jsxs2("div", { className: "mt-4 p-4 bg-neutral-900/50 border border-neutral-800 rounded-lg", children: [
|
|
871
|
+
/* @__PURE__ */ jsxs2("h4", { className: "text-sm font-semibold mb-3 text-neutral-300 flex items-center gap-2", children: [
|
|
872
|
+
/* @__PURE__ */ jsx5(CodeIcon, { className: "w-4 h-4 text-primary" }),
|
|
873
|
+
"Markdown Guide"
|
|
874
|
+
] }),
|
|
875
|
+
/* @__PURE__ */ jsxs2("div", { className: "grid grid-cols-2 md:grid-cols-3 gap-3 text-xs", children: [
|
|
876
|
+
/* @__PURE__ */ jsx5(GuideItem, { code: "# Heading 1", desc: "Main heading" }),
|
|
877
|
+
/* @__PURE__ */ jsx5(GuideItem, { code: "## Heading 2", desc: "Sub heading" }),
|
|
878
|
+
/* @__PURE__ */ jsx5(GuideItem, { code: "**bold**", desc: "Bold text" }),
|
|
879
|
+
/* @__PURE__ */ jsx5(GuideItem, { code: "*italic*", desc: "Italic text" }),
|
|
880
|
+
/* @__PURE__ */ jsx5(GuideItem, { code: "[link](url)", desc: "Hyperlink" }),
|
|
881
|
+
/* @__PURE__ */ jsx5(GuideItem, { code: "- list item", desc: "Bullet list" })
|
|
882
|
+
] })
|
|
883
|
+
] })
|
|
884
|
+
] }) })
|
|
885
|
+
] }),
|
|
886
|
+
/* @__PURE__ */ jsx5("div", { className: "px-6 py-4 border-t border-neutral-800 bg-neutral-900/30", children: /* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between w-full", children: [
|
|
887
|
+
/* @__PURE__ */ jsxs2("p", { className: "text-sm text-neutral-500", children: [
|
|
888
|
+
content.length,
|
|
889
|
+
" characters"
|
|
890
|
+
] }),
|
|
891
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex gap-2", children: [
|
|
892
|
+
/* @__PURE__ */ jsxs2(
|
|
893
|
+
"button",
|
|
894
|
+
{
|
|
895
|
+
type: "button",
|
|
896
|
+
onClick: handleCancel,
|
|
897
|
+
className: "inline-flex items-center gap-2 px-4 py-2 text-sm rounded-md border border-neutral-700 hover:bg-neutral-800 transition-colors",
|
|
898
|
+
children: [
|
|
899
|
+
/* @__PURE__ */ jsx5(XIcon2, { className: "w-4 h-4" }),
|
|
900
|
+
"Cancel"
|
|
901
|
+
]
|
|
902
|
+
}
|
|
903
|
+
),
|
|
904
|
+
/* @__PURE__ */ jsxs2(
|
|
905
|
+
"button",
|
|
906
|
+
{
|
|
907
|
+
type: "button",
|
|
908
|
+
onClick: handleSave,
|
|
909
|
+
className: "inline-flex items-center gap-2 px-4 py-2 text-sm rounded-md bg-primary text-white hover:opacity-90 transition-opacity",
|
|
910
|
+
children: [
|
|
911
|
+
/* @__PURE__ */ jsx5(SaveIcon, { className: "w-4 h-4" }),
|
|
912
|
+
"Save Content"
|
|
913
|
+
]
|
|
914
|
+
}
|
|
915
|
+
)
|
|
916
|
+
] })
|
|
917
|
+
] }) })
|
|
918
|
+
] }) }),
|
|
919
|
+
document.body
|
|
920
|
+
)
|
|
921
|
+
] });
|
|
922
|
+
}
|
|
923
|
+
function ToolbarButton({
|
|
924
|
+
icon,
|
|
925
|
+
label,
|
|
926
|
+
onClick
|
|
927
|
+
}) {
|
|
928
|
+
return /* @__PURE__ */ jsx5(
|
|
929
|
+
"button",
|
|
930
|
+
{
|
|
931
|
+
type: "button",
|
|
932
|
+
onClick,
|
|
933
|
+
title: label,
|
|
934
|
+
className: "p-2 rounded-lg hover:bg-neutral-800 text-neutral-400 hover:text-white transition-colors",
|
|
935
|
+
children: icon
|
|
936
|
+
}
|
|
937
|
+
);
|
|
938
|
+
}
|
|
939
|
+
function GuideItem({ code, desc }) {
|
|
940
|
+
return /* @__PURE__ */ jsxs2("div", { className: "flex flex-col gap-1", children: [
|
|
941
|
+
/* @__PURE__ */ jsx5("code", { className: "text-primary bg-primary/10 px-2 py-1 rounded text-xs font-mono", children: code }),
|
|
942
|
+
/* @__PURE__ */ jsx5("span", { className: "text-neutral-500", children: desc })
|
|
943
|
+
] });
|
|
944
|
+
}
|
|
945
|
+
function ProjectContentEditor({
|
|
946
|
+
content,
|
|
947
|
+
onSave
|
|
948
|
+
}) {
|
|
949
|
+
const [saving, setSaving] = useState4(false);
|
|
950
|
+
const handleSave = async (newContent) => {
|
|
951
|
+
setSaving(true);
|
|
952
|
+
try {
|
|
953
|
+
await onSave(newContent);
|
|
954
|
+
toast2.success("Content saved successfully!");
|
|
955
|
+
} catch (error) {
|
|
956
|
+
console.error("Failed to save content:", error);
|
|
957
|
+
toast2.error("Failed to save content");
|
|
958
|
+
} finally {
|
|
959
|
+
setSaving(false);
|
|
960
|
+
}
|
|
961
|
+
};
|
|
962
|
+
return /* @__PURE__ */ jsx5(
|
|
963
|
+
MarkdownEditor,
|
|
964
|
+
{
|
|
965
|
+
initialValue: content,
|
|
966
|
+
onSave: handleSave,
|
|
967
|
+
title: "Edit Project Content",
|
|
968
|
+
trigger: /* @__PURE__ */ jsxs2(
|
|
969
|
+
"button",
|
|
970
|
+
{
|
|
971
|
+
className: "px-4 py-2 bg-neutral-800 rounded-lg hover:bg-neutral-700 transition-colors flex items-center gap-2 border border-neutral-700 hover:border-primary/50",
|
|
972
|
+
disabled: saving,
|
|
973
|
+
children: [
|
|
974
|
+
/* @__PURE__ */ jsx5(EditIcon, { className: "w-4 h-4" }),
|
|
975
|
+
saving ? "Saving..." : "Edit Full Content"
|
|
976
|
+
]
|
|
977
|
+
}
|
|
978
|
+
)
|
|
979
|
+
}
|
|
980
|
+
);
|
|
981
|
+
}
|
|
982
|
+
export {
|
|
983
|
+
CmsAuthContext,
|
|
984
|
+
CmsAuthProvider,
|
|
985
|
+
ContentEditSpan,
|
|
986
|
+
EditableImage,
|
|
987
|
+
MarkdownEditor,
|
|
988
|
+
PageProvider,
|
|
989
|
+
ProjectContentEditor,
|
|
990
|
+
cn,
|
|
991
|
+
useCmsAuth,
|
|
992
|
+
usePageContext
|
|
993
|
+
};
|
|
994
|
+
//# sourceMappingURL=index.js.map
|