@dalgoridim/headless-cms 0.1.0 → 0.2.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/ARCHITECTURE.md +126 -0
- package/README.md +238 -48
- package/REDESIGN.md +250 -0
- package/dist/adapters/firestore/index.cjs +24 -3
- package/dist/adapters/firestore/index.cjs.map +1 -1
- package/dist/adapters/firestore/index.js +24 -3
- package/dist/adapters/firestore/index.js.map +1 -1
- package/dist/adapters/postgres/index.cjs +37 -11
- package/dist/adapters/postgres/index.cjs.map +1 -1
- package/dist/adapters/postgres/index.js +37 -11
- package/dist/adapters/postgres/index.js.map +1 -1
- package/dist/client/index.cjs +94 -543
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +89 -26
- package/dist/client/index.d.ts +89 -26
- package/dist/client/index.js +96 -547
- package/dist/client/index.js.map +1 -1
- package/dist/index.cjs +16 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +81 -16
- package/dist/index.d.ts +81 -16
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/server/index.cjs +63 -9
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +35 -5
- package/dist/server/index.d.ts +35 -5
- package/dist/server/index.js +61 -8
- package/dist/server/index.js.map +1 -1
- package/package.json +5 -10
package/dist/client/index.js
CHANGED
|
@@ -26,11 +26,10 @@ import {
|
|
|
26
26
|
useState,
|
|
27
27
|
useCallback
|
|
28
28
|
} from "react";
|
|
29
|
-
import { toast } from "sonner";
|
|
30
29
|
import { jsx } from "react/jsx-runtime";
|
|
31
30
|
var defaultNotifier = {
|
|
32
|
-
success: (m) =>
|
|
33
|
-
error: (m) =>
|
|
31
|
+
success: (m) => console.info(`[cms] ${m}`),
|
|
32
|
+
error: (m) => console.error(`[cms] ${m}`)
|
|
34
33
|
};
|
|
35
34
|
var PageContext = createContext(void 0);
|
|
36
35
|
var dirtyKey = (collection, sectionKey) => `${collection}:${sectionKey}`;
|
|
@@ -279,99 +278,8 @@ function CmsAuthProvider({
|
|
|
279
278
|
|
|
280
279
|
// src/client/ContentEditSpan.tsx
|
|
281
280
|
import { useRef, useEffect, useState as useState2, useCallback as useCallback2 } from "react";
|
|
282
|
-
|
|
283
|
-
|
|
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
|
-
}
|
|
281
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
282
|
+
var defaultRenderValue = (raw) => raw;
|
|
375
283
|
function getNestedValue(obj, path) {
|
|
376
284
|
const keys = path.split(".");
|
|
377
285
|
let current = obj;
|
|
@@ -383,20 +291,22 @@ function getNestedValue(obj, path) {
|
|
|
383
291
|
return current;
|
|
384
292
|
}
|
|
385
293
|
function ContentEditSpan({
|
|
386
|
-
collection
|
|
294
|
+
collection,
|
|
387
295
|
sectionKey,
|
|
388
296
|
fieldKey,
|
|
389
297
|
className,
|
|
390
298
|
children,
|
|
391
|
-
as = "span"
|
|
299
|
+
as = "span",
|
|
300
|
+
renderValue = defaultRenderValue
|
|
392
301
|
}) {
|
|
393
302
|
var _a, _b;
|
|
394
303
|
const { sections, editField } = usePageContext();
|
|
395
304
|
const { isEditing } = useCmsAuth();
|
|
396
305
|
const section = (_a = sections[collection]) == null ? void 0 : _a[sectionKey];
|
|
397
306
|
const raw = (_b = getNestedValue(section, fieldKey)) != null ? _b : typeof children === "string" ? children : "";
|
|
307
|
+
const Component = as;
|
|
398
308
|
if (!isEditing) {
|
|
399
|
-
return /* @__PURE__ */ jsx3(
|
|
309
|
+
return /* @__PURE__ */ jsx3(Component, { className, children: renderValue(raw) });
|
|
400
310
|
}
|
|
401
311
|
return /* @__PURE__ */ jsx3(
|
|
402
312
|
EditableContentSpan,
|
|
@@ -407,18 +317,20 @@ function ContentEditSpan({
|
|
|
407
317
|
className,
|
|
408
318
|
raw,
|
|
409
319
|
editField,
|
|
410
|
-
as
|
|
320
|
+
as,
|
|
321
|
+
renderValue
|
|
411
322
|
}
|
|
412
323
|
);
|
|
413
324
|
}
|
|
414
325
|
function EditableContentSpan({
|
|
415
|
-
collection
|
|
326
|
+
collection,
|
|
416
327
|
sectionKey,
|
|
417
328
|
fieldKey,
|
|
418
329
|
className,
|
|
419
330
|
raw,
|
|
420
331
|
editField,
|
|
421
|
-
as: Component = "span"
|
|
332
|
+
as: Component = "span",
|
|
333
|
+
renderValue
|
|
422
334
|
}) {
|
|
423
335
|
const { isEditing } = useCmsAuth();
|
|
424
336
|
const [isFocused, setIsFocused] = useState2(false);
|
|
@@ -466,19 +378,16 @@ function EditableContentSpan({
|
|
|
466
378
|
Component,
|
|
467
379
|
{
|
|
468
380
|
ref: contentRef,
|
|
469
|
-
className
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
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
|
-
),
|
|
381
|
+
className,
|
|
382
|
+
"data-cms-editable": "",
|
|
383
|
+
"data-cms-editing": isEditing ? "" : void 0,
|
|
384
|
+
"data-cms-focused": isFocused ? "" : void 0,
|
|
476
385
|
contentEditable: isEditing,
|
|
477
386
|
suppressContentEditableWarning: true,
|
|
478
387
|
onInput: handleInput,
|
|
479
388
|
onBlur: handleBlur,
|
|
480
389
|
onFocus: handleFocus,
|
|
481
|
-
children: !isFocused &&
|
|
390
|
+
children: !isFocused && renderValue(editValue)
|
|
482
391
|
},
|
|
483
392
|
isFocused ? "editing" : "static"
|
|
484
393
|
);
|
|
@@ -486,24 +395,20 @@ function EditableContentSpan({
|
|
|
486
395
|
|
|
487
396
|
// src/client/EditableImage.tsx
|
|
488
397
|
import { useRef as useRef2, useState as useState3 } from "react";
|
|
489
|
-
import {
|
|
490
|
-
import { CameraIcon, Link2Icon, XIcon, CheckIcon } from "lucide-react";
|
|
491
|
-
import { Fragment as Fragment2, jsx as jsx4, jsxs } from "react/jsx-runtime";
|
|
398
|
+
import { jsx as jsx4, jsxs } from "react/jsx-runtime";
|
|
492
399
|
function EditableImage({
|
|
493
400
|
sectionKey,
|
|
494
401
|
fieldKey,
|
|
495
402
|
src,
|
|
496
403
|
collection,
|
|
497
404
|
docId,
|
|
498
|
-
className
|
|
405
|
+
className,
|
|
406
|
+
children
|
|
499
407
|
}) {
|
|
500
408
|
const { isEditing } = useCmsAuth();
|
|
501
409
|
const { editField, setPendingImage, pendingImages, saving } = usePageContext();
|
|
502
410
|
const [preview, setPreview] = useState3(src);
|
|
503
411
|
const [hasError, setHasError] = useState3(false);
|
|
504
|
-
const [showUrlModal, setShowUrlModal] = useState3(false);
|
|
505
|
-
const [urlInput, setUrlInput] = useState3("");
|
|
506
|
-
const [urlPreview, setUrlPreview] = useState3("");
|
|
507
412
|
const inputRef = useRef2(null);
|
|
508
413
|
const pendingImage = pendingImages.find(
|
|
509
414
|
(img) => img.sectionKey === sectionKey && img.fieldKey === fieldKey
|
|
@@ -528,467 +433,111 @@ function EditableImage({
|
|
|
528
433
|
isExternal: false
|
|
529
434
|
});
|
|
530
435
|
};
|
|
531
|
-
const
|
|
532
|
-
|
|
533
|
-
|
|
436
|
+
const openFilePicker = () => {
|
|
437
|
+
var _a;
|
|
438
|
+
if (saving) return;
|
|
439
|
+
(_a = inputRef.current) == null ? void 0 : _a.click();
|
|
440
|
+
};
|
|
441
|
+
const setExternalUrl = (value) => {
|
|
442
|
+
let valid = false;
|
|
443
|
+
try {
|
|
444
|
+
const url = new URL(value);
|
|
445
|
+
valid = url.protocol === "http:" || url.protocol === "https:";
|
|
446
|
+
} catch (e) {
|
|
447
|
+
valid = false;
|
|
448
|
+
}
|
|
449
|
+
if (!valid) return false;
|
|
450
|
+
setPreview(value);
|
|
534
451
|
setHasError(false);
|
|
535
|
-
editField(collection, sectionKey, fieldKey,
|
|
452
|
+
editField(collection, sectionKey, fieldKey, value);
|
|
536
453
|
setPendingImage({
|
|
537
454
|
file: null,
|
|
538
|
-
localUrl:
|
|
455
|
+
localUrl: value,
|
|
539
456
|
sectionKey,
|
|
540
457
|
fieldKey,
|
|
541
458
|
collection,
|
|
542
459
|
docId,
|
|
543
460
|
isExternal: true
|
|
544
461
|
});
|
|
545
|
-
|
|
546
|
-
setUrlInput("");
|
|
547
|
-
setUrlPreview("");
|
|
462
|
+
return true;
|
|
548
463
|
};
|
|
549
|
-
const
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
}
|
|
558
|
-
} catch (e) {
|
|
559
|
-
setUrlPreview("");
|
|
560
|
-
}
|
|
464
|
+
const state = {
|
|
465
|
+
src: imgSrc,
|
|
466
|
+
isEditing,
|
|
467
|
+
saving,
|
|
468
|
+
hasError,
|
|
469
|
+
openFilePicker,
|
|
470
|
+
setExternalUrl,
|
|
471
|
+
imgProps: { src: imgSrc, onError: () => setHasError(true) }
|
|
561
472
|
};
|
|
562
|
-
|
|
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
|
|
473
|
+
return /* @__PURE__ */ jsxs("div", { className, children: [
|
|
584
474
|
/* @__PURE__ */ jsx4(
|
|
585
|
-
"
|
|
475
|
+
"input",
|
|
586
476
|
{
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
477
|
+
ref: inputRef,
|
|
478
|
+
type: "file",
|
|
479
|
+
accept: "image/*",
|
|
480
|
+
disabled: saving,
|
|
481
|
+
onChange: handleFileChange,
|
|
482
|
+
style: { display: "none" }
|
|
591
483
|
}
|
|
592
|
-
)
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
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
|
|
484
|
+
),
|
|
485
|
+
children ? children(state) : (
|
|
486
|
+
// eslint-disable-next-line @next/next/no-img-element
|
|
487
|
+
/* @__PURE__ */ jsx4("img", __spreadProps(__spreadValues({}, state.imgProps), { alt: "" }))
|
|
687
488
|
)
|
|
688
489
|
] });
|
|
689
490
|
}
|
|
690
491
|
|
|
691
492
|
// src/client/MarkdownEditor.tsx
|
|
692
|
-
import { useState as useState4 } from "react";
|
|
693
|
-
|
|
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({
|
|
493
|
+
import { useCallback as useCallback3, useRef as useRef3, useState as useState4 } from "react";
|
|
494
|
+
function useMarkdownEditor({
|
|
714
495
|
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
496
|
onSave
|
|
948
497
|
}) {
|
|
949
|
-
const [
|
|
950
|
-
const
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
disabled: saving,
|
|
973
|
-
children: [
|
|
974
|
-
/* @__PURE__ */ jsx5(EditIcon, { className: "w-4 h-4" }),
|
|
975
|
-
saving ? "Saving..." : "Edit Full Content"
|
|
976
|
-
]
|
|
977
|
-
}
|
|
978
|
-
)
|
|
979
|
-
}
|
|
498
|
+
const [value, setValue] = useState4(initialValue);
|
|
499
|
+
const textareaRef = useRef3(null);
|
|
500
|
+
const insert = useCallback3(
|
|
501
|
+
(before, after = "", placeholder = "text") => {
|
|
502
|
+
var _a, _b;
|
|
503
|
+
const textarea = textareaRef.current;
|
|
504
|
+
const start = (_a = textarea == null ? void 0 : textarea.selectionStart) != null ? _a : value.length;
|
|
505
|
+
const end = (_b = textarea == null ? void 0 : textarea.selectionEnd) != null ? _b : value.length;
|
|
506
|
+
const selected = value.substring(start, end) || placeholder;
|
|
507
|
+
const next = value.substring(0, start) + before + selected + after + value.substring(end);
|
|
508
|
+
setValue(next);
|
|
509
|
+
setTimeout(() => {
|
|
510
|
+
if (!textarea) return;
|
|
511
|
+
textarea.focus();
|
|
512
|
+
const caret = start + before.length + selected.length;
|
|
513
|
+
textarea.setSelectionRange(caret, caret);
|
|
514
|
+
}, 0);
|
|
515
|
+
},
|
|
516
|
+
[value]
|
|
517
|
+
);
|
|
518
|
+
const reset = useCallback3(
|
|
519
|
+
(to = initialValue) => setValue(to),
|
|
520
|
+
[initialValue]
|
|
980
521
|
);
|
|
522
|
+
const save = useCallback3(() => onSave(value), [onSave, value]);
|
|
523
|
+
return {
|
|
524
|
+
value,
|
|
525
|
+
setValue,
|
|
526
|
+
textareaRef,
|
|
527
|
+
insert,
|
|
528
|
+
reset,
|
|
529
|
+
save,
|
|
530
|
+
charCount: value.length
|
|
531
|
+
};
|
|
981
532
|
}
|
|
982
533
|
export {
|
|
983
534
|
CmsAuthContext,
|
|
984
535
|
CmsAuthProvider,
|
|
985
536
|
ContentEditSpan,
|
|
986
537
|
EditableImage,
|
|
987
|
-
MarkdownEditor,
|
|
988
538
|
PageProvider,
|
|
989
|
-
ProjectContentEditor,
|
|
990
|
-
cn,
|
|
991
539
|
useCmsAuth,
|
|
540
|
+
useMarkdownEditor,
|
|
992
541
|
usePageContext
|
|
993
542
|
};
|
|
994
543
|
//# sourceMappingURL=index.js.map
|