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