@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.
@@ -1,13 +1,11 @@
1
1
  "use strict";
2
2
  "use client";
3
- var __create = Object.create;
4
3
  var __defProp = Object.defineProperty;
5
4
  var __defProps = Object.defineProperties;
6
5
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
6
  var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
8
7
  var __getOwnPropNames = Object.getOwnPropertyNames;
9
8
  var __getOwnPropSymbols = Object.getOwnPropertySymbols;
10
- var __getProtoOf = Object.getPrototypeOf;
11
9
  var __hasOwnProp = Object.prototype.hasOwnProperty;
12
10
  var __propIsEnum = Object.prototype.propertyIsEnumerable;
13
11
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
@@ -35,14 +33,6 @@ var __copyProps = (to, from, except, desc) => {
35
33
  }
36
34
  return to;
37
35
  };
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
36
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
47
37
 
48
38
  // src/client/index.ts
@@ -52,22 +42,19 @@ __export(client_exports, {
52
42
  CmsAuthProvider: () => CmsAuthProvider,
53
43
  ContentEditSpan: () => ContentEditSpan,
54
44
  EditableImage: () => EditableImage,
55
- MarkdownEditor: () => MarkdownEditor,
56
45
  PageProvider: () => PageProvider,
57
- ProjectContentEditor: () => ProjectContentEditor,
58
- cn: () => cn,
59
46
  useCmsAuth: () => useCmsAuth,
47
+ useMarkdownEditor: () => useMarkdownEditor,
60
48
  usePageContext: () => usePageContext
61
49
  });
62
50
  module.exports = __toCommonJS(client_exports);
63
51
 
64
52
  // src/client/PageProvider.tsx
65
53
  var import_react = require("react");
66
- var import_sonner = require("sonner");
67
54
  var import_jsx_runtime = require("react/jsx-runtime");
68
55
  var defaultNotifier = {
69
- success: (m) => import_sonner.toast.success(m),
70
- error: (m) => import_sonner.toast.error(m)
56
+ success: (m) => console.info(`[cms] ${m}`),
57
+ error: (m) => console.error(`[cms] ${m}`)
71
58
  };
72
59
  var PageContext = (0, import_react.createContext)(void 0);
73
60
  var dirtyKey = (collection, sectionKey) => `${collection}:${sectionKey}`;
@@ -316,99 +303,8 @@ function CmsAuthProvider({
316
303
 
317
304
  // src/client/ContentEditSpan.tsx
318
305
  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
306
  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
- }
307
+ var defaultRenderValue = (raw) => raw;
412
308
  function getNestedValue(obj, path) {
413
309
  const keys = path.split(".");
414
310
  let current = obj;
@@ -420,20 +316,22 @@ function getNestedValue(obj, path) {
420
316
  return current;
421
317
  }
422
318
  function ContentEditSpan({
423
- collection = "portfolio",
319
+ collection,
424
320
  sectionKey,
425
321
  fieldKey,
426
322
  className,
427
323
  children,
428
- as = "span"
324
+ as = "span",
325
+ renderValue = defaultRenderValue
429
326
  }) {
430
327
  var _a, _b;
431
328
  const { sections, editField } = usePageContext();
432
329
  const { isEditing } = useCmsAuth();
433
330
  const section = (_a = sections[collection]) == null ? void 0 : _a[sectionKey];
434
331
  const raw = (_b = getNestedValue(section, fieldKey)) != null ? _b : typeof children === "string" ? children : "";
332
+ const Component = as;
435
333
  if (!isEditing) {
436
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(RenderStatic, { raw, as, className });
334
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Component, { className, children: renderValue(raw) });
437
335
  }
438
336
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
439
337
  EditableContentSpan,
@@ -444,18 +342,20 @@ function ContentEditSpan({
444
342
  className,
445
343
  raw,
446
344
  editField,
447
- as
345
+ as,
346
+ renderValue
448
347
  }
449
348
  );
450
349
  }
451
350
  function EditableContentSpan({
452
- collection = "portfolio",
351
+ collection,
453
352
  sectionKey,
454
353
  fieldKey,
455
354
  className,
456
355
  raw,
457
356
  editField,
458
- as: Component = "span"
357
+ as: Component = "span",
358
+ renderValue
459
359
  }) {
460
360
  const { isEditing } = useCmsAuth();
461
361
  const [isFocused, setIsFocused] = (0, import_react3.useState)(false);
@@ -503,19 +403,16 @@ function EditableContentSpan({
503
403
  Component,
504
404
  {
505
405
  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
- ),
406
+ className,
407
+ "data-cms-editable": "",
408
+ "data-cms-editing": isEditing ? "" : void 0,
409
+ "data-cms-focused": isFocused ? "" : void 0,
513
410
  contentEditable: isEditing,
514
411
  suppressContentEditableWarning: true,
515
412
  onInput: handleInput,
516
413
  onBlur: handleBlur,
517
414
  onFocus: handleFocus,
518
- children: !isFocused && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(RenderStatic, { raw: editValue, as: "span" })
415
+ children: !isFocused && renderValue(editValue)
519
416
  },
520
417
  isFocused ? "editing" : "static"
521
418
  );
@@ -523,8 +420,6 @@ function EditableContentSpan({
523
420
 
524
421
  // src/client/EditableImage.tsx
525
422
  var import_react4 = require("react");
526
- var import_react_dom = require("react-dom");
527
- var import_lucide_react = require("lucide-react");
528
423
  var import_jsx_runtime4 = require("react/jsx-runtime");
529
424
  function EditableImage({
530
425
  sectionKey,
@@ -532,15 +427,13 @@ function EditableImage({
532
427
  src,
533
428
  collection,
534
429
  docId,
535
- className
430
+ className,
431
+ children
536
432
  }) {
537
433
  const { isEditing } = useCmsAuth();
538
434
  const { editField, setPendingImage, pendingImages, saving } = usePageContext();
539
435
  const [preview, setPreview] = (0, import_react4.useState)(src);
540
436
  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
437
  const inputRef = (0, import_react4.useRef)(null);
545
438
  const pendingImage = pendingImages.find(
546
439
  (img) => img.sectionKey === sectionKey && img.fieldKey === fieldKey
@@ -565,442 +458,102 @@ function EditableImage({
565
458
  isExternal: false
566
459
  });
567
460
  };
568
- const handleUrlConfirm = () => {
569
- if (!urlPreview) return;
570
- setPreview(urlPreview);
461
+ const openFilePicker = () => {
462
+ var _a;
463
+ if (saving) return;
464
+ (_a = inputRef.current) == null ? void 0 : _a.click();
465
+ };
466
+ const setExternalUrl = (value) => {
467
+ let valid = false;
468
+ try {
469
+ const url = new URL(value);
470
+ valid = url.protocol === "http:" || url.protocol === "https:";
471
+ } catch (e) {
472
+ valid = false;
473
+ }
474
+ if (!valid) return false;
475
+ setPreview(value);
571
476
  setHasError(false);
572
- editField(collection, sectionKey, fieldKey, urlPreview);
477
+ editField(collection, sectionKey, fieldKey, value);
573
478
  setPendingImage({
574
479
  file: null,
575
- localUrl: urlPreview,
480
+ localUrl: value,
576
481
  sectionKey,
577
482
  fieldKey,
578
483
  collection,
579
484
  docId,
580
485
  isExternal: true
581
486
  });
582
- setShowUrlModal(false);
583
- setUrlInput("");
584
- setUrlPreview("");
487
+ return true;
585
488
  };
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
- }
489
+ const state = {
490
+ src: imgSrc,
491
+ isEditing,
492
+ saving,
493
+ hasError,
494
+ openFilePicker,
495
+ setExternalUrl,
496
+ imgProps: { src: imgSrc, onError: () => setHasError(true) }
598
497
  };
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
498
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className, children: [
621
499
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
622
- "img",
500
+ "input",
623
501
  {
624
- src: imgSrc,
625
- alt: "",
626
- className: "w-full h-full object-cover",
627
- onError: () => setHasError(true)
502
+ ref: inputRef,
503
+ type: "file",
504
+ accept: "image/*",
505
+ disabled: saving,
506
+ onChange: handleFileChange,
507
+ style: { display: "none" }
628
508
  }
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
509
+ ),
510
+ children ? children(state) : (
511
+ // eslint-disable-next-line @next/next/no-img-element
512
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("img", __spreadProps(__spreadValues({}, state.imgProps), { alt: "" }))
724
513
  )
725
514
  ] });
726
515
  }
727
516
 
728
517
  // src/client/MarkdownEditor.tsx
729
518
  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({
519
+ function useMarkdownEditor({
737
520
  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("![", "](url)", "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
521
  onSave
971
522
  }) {
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
- }
523
+ const [value, setValue] = (0, import_react5.useState)(initialValue);
524
+ const textareaRef = (0, import_react5.useRef)(null);
525
+ const insert = (0, import_react5.useCallback)(
526
+ (before, after = "", placeholder = "text") => {
527
+ var _a, _b;
528
+ const textarea = textareaRef.current;
529
+ const start = (_a = textarea == null ? void 0 : textarea.selectionStart) != null ? _a : value.length;
530
+ const end = (_b = textarea == null ? void 0 : textarea.selectionEnd) != null ? _b : value.length;
531
+ const selected = value.substring(start, end) || placeholder;
532
+ const next = value.substring(0, start) + before + selected + after + value.substring(end);
533
+ setValue(next);
534
+ setTimeout(() => {
535
+ if (!textarea) return;
536
+ textarea.focus();
537
+ const caret = start + before.length + selected.length;
538
+ textarea.setSelectionRange(caret, caret);
539
+ }, 0);
540
+ },
541
+ [value]
542
+ );
543
+ const reset = (0, import_react5.useCallback)(
544
+ (to = initialValue) => setValue(to),
545
+ [initialValue]
1003
546
  );
547
+ const save = (0, import_react5.useCallback)(() => onSave(value), [onSave, value]);
548
+ return {
549
+ value,
550
+ setValue,
551
+ textareaRef,
552
+ insert,
553
+ reset,
554
+ save,
555
+ charCount: value.length
556
+ };
1004
557
  }
1005
558
  // Annotate the CommonJS export names for ESM import in node:
1006
559
  0 && (module.exports = {
@@ -1008,11 +561,9 @@ function ProjectContentEditor({
1008
561
  CmsAuthProvider,
1009
562
  ContentEditSpan,
1010
563
  EditableImage,
1011
- MarkdownEditor,
1012
564
  PageProvider,
1013
- ProjectContentEditor,
1014
- cn,
1015
565
  useCmsAuth,
566
+ useMarkdownEditor,
1016
567
  usePageContext
1017
568
  });
1018
569
  //# sourceMappingURL=index.cjs.map