camox 0.40.0 → 0.41.1

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.
Files changed (108) hide show
  1. package/dist/components/PlatformLabel.js +62 -0
  2. package/dist/core/components/lexical/GradientNode.js +126 -0
  3. package/dist/core/components/lexical/InlineLexicalEditor.js +37 -56
  4. package/dist/core/components/lexical/InlineStylesPlugin.js +183 -0
  5. package/dist/core/components/lexical/SelectionBroadcaster.js +49 -46
  6. package/dist/core/components/lexical/SidebarLexicalEditor.js +197 -151
  7. package/dist/core/components/lexical/editorConfig.js +25 -7
  8. package/dist/core/components/lexical/selectTextLink.js +21 -0
  9. package/dist/core/createApp.d.ts +30 -17
  10. package/dist/core/createApp.js +14 -3
  11. package/dist/core/createBlock.d.ts +2 -2
  12. package/dist/core/createBlock.js +17 -28
  13. package/dist/core/createLayout.d.ts +48 -4
  14. package/dist/core/createLayout.js +51 -23
  15. package/dist/core/derivedRoutes.js +36 -0
  16. package/dist/core/editing/BlockEditingRuntime.js +4 -4
  17. package/dist/core/editing/CompleteBlockEditingRuntime.js +5 -5
  18. package/dist/core/editing/createEditableBlock.d.ts +9 -8
  19. package/dist/core/editing/createEditableBlock.js +265 -403
  20. package/dist/core/hooks/useIsEditable.js +4 -7
  21. package/dist/core/hooks/useRequireDraftSource.js +7 -11
  22. package/dist/core/lib/lexicalReact.d.ts +1 -26
  23. package/dist/core/lib/lexicalReact.js +46 -60
  24. package/dist/core/lib/lexicalState.js +164 -81
  25. package/dist/core/lib/modifierFormats.js +4 -15
  26. package/dist/core/lib/textStyles.d.ts +27 -0
  27. package/dist/core/lib/textStyles.js +47 -0
  28. package/dist/features/navigation/navigation.d.ts +2 -0
  29. package/dist/features/navigation/navigation.js +51 -46
  30. package/dist/features/page/DerivedPageContent.js +61 -0
  31. package/dist/features/page/PublishedPageContent.js +52 -83
  32. package/dist/features/page/SharedLayoutContent.js +66 -0
  33. package/dist/features/preview/CamoxPreview.d.ts +16 -1
  34. package/dist/features/preview/CamoxPreview.js +246 -290
  35. package/dist/features/preview/DerivedPreview.js +134 -0
  36. package/dist/features/preview/EditablePageExperience.js +25 -8
  37. package/dist/features/preview/components/AddBlockSidebar.js +43 -33
  38. package/dist/features/preview/components/AssetLightbox.js +1 -1
  39. package/dist/features/preview/components/BlockActionsPopover.js +8 -8
  40. package/dist/features/preview/components/BlockThumbnail.js +192 -0
  41. package/dist/features/preview/components/CreatePageModal.js +123 -115
  42. package/dist/features/preview/components/DerivedLayoutDialog.js +100 -0
  43. package/dist/features/preview/components/DerivedLayoutSidebar.d.ts +5 -0
  44. package/dist/features/preview/components/DerivedLayoutSidebar.js +160 -0
  45. package/dist/features/preview/components/DerivedPageInfoSidebar.js +57 -0
  46. package/dist/features/preview/components/FieldToolbar.js +42 -20
  47. package/dist/features/preview/components/Frame.js +133 -140
  48. package/dist/features/preview/components/LeftSidebar.js +42 -38
  49. package/dist/features/preview/components/LinkFieldEditor.js +3 -4
  50. package/dist/features/preview/components/MobilePreviewDrawer.js +4 -4
  51. package/dist/features/preview/components/Overlays.js +59 -47
  52. package/dist/features/preview/components/PageEditorSidebar.js +194 -203
  53. package/dist/features/preview/components/PageInfoSidebar.js +63 -55
  54. package/dist/features/preview/components/PageNavigatorSidebar.js +34 -483
  55. package/dist/features/preview/components/PagePicker.js +205 -153
  56. package/dist/features/preview/components/PageTree.js +366 -340
  57. package/dist/features/preview/components/PreviewActivation.js +78 -0
  58. package/dist/features/preview/components/PreviewPanel.js +98 -68
  59. package/dist/features/preview/components/PreviewToolbar.js +224 -241
  60. package/dist/features/preview/components/PreviewToolbarSpacer.js +43 -0
  61. package/dist/features/preview/components/PublicationRow.js +425 -0
  62. package/dist/features/preview/components/PublishDialog.js +180 -97
  63. package/dist/features/preview/components/RightSidebar.js +19 -34
  64. package/dist/features/preview/components/previewStyles.js +8 -0
  65. package/dist/features/preview/components/useUpdateBlockPosition.js +7 -13
  66. package/dist/features/preview/previewConstants.js +8 -0
  67. package/dist/features/preview/previewStore.js +18 -39
  68. package/dist/features/preview/publication.js +71 -0
  69. package/dist/features/provider/AuthenticatedCamoxProvider.js +2 -1
  70. package/dist/features/provider/CoreCamoxProvider.js +11 -2
  71. package/dist/features/provider/LocalhostPreviewProvider.js +6 -7
  72. package/dist/features/provider/useAdminShortcuts.js +5 -4
  73. package/dist/features/routes/pageRuntime.d.ts +10 -0
  74. package/dist/features/routes/pageRuntime.js +7 -9
  75. package/dist/features/runtime/PreviewDocumentContext.js +7 -0
  76. package/dist/features/runtime/RuntimeChrome.js +149 -0
  77. package/dist/features/runtime/SharedChromeContext.js +7 -0
  78. package/dist/features/runtime/navigationRequest.js +21 -0
  79. package/dist/features/runtime/navigationTarget.js +23 -0
  80. package/dist/features/runtime/pageApp.js +196 -121
  81. package/dist/features/runtime/pageNavigation.js +79 -130
  82. package/dist/features/runtime/runtime.d.ts +11 -0
  83. package/dist/features/runtime/runtime.js +183 -47
  84. package/dist/features/runtime/studioApp.d.ts +2 -11
  85. package/dist/features/runtime/studioApp.js +14 -177
  86. package/dist/features/studio/CamoxStudio.js +27 -13
  87. package/dist/features/studio/components/Navbar.js +80 -74
  88. package/dist/features/studio/components/UserButton.js +4 -4
  89. package/dist/features/studio/studioTheme.js +25 -0
  90. package/dist/features/studio/useTheme.js +94 -109
  91. package/dist/features/vite/appGeneration.js +12 -3
  92. package/dist/features/vite/definitionsSync.js +4 -2
  93. package/dist/features/vite/layoutGeneration.js +38 -0
  94. package/dist/features/vite/runtimeDev.js +3 -1
  95. package/dist/lib/auth.js +15 -13
  96. package/dist/lib/normalized-data.js +4 -4
  97. package/dist/lib/platform.js +13 -0
  98. package/dist/lib/utils.js +90 -38
  99. package/dist/studio-overlays.css +5 -40
  100. package/dist/studio.css +1 -1
  101. package/package.json +8 -4
  102. package/skills/camox/SKILL.md +2 -0
  103. package/skills/camox/references/block-definitions.md +31 -12
  104. package/skills/camox/references/layout-definitions.md +2 -0
  105. package/dist/core/lib/modifiers.js +0 -24
  106. package/dist/features/preview/components/DraftSwitchDialog.js +0 -66
  107. package/dist/features/preview/previewQueryFns.js +0 -23
  108. package/dist/features/runtime/studioNavigation.js +0 -120
@@ -1,22 +1,19 @@
1
1
  import { useIsAuthenticated } from "../../lib/auth.js";
2
- import { previewStore } from "../../features/preview/previewStore.js";
2
+ import { previewStore, selectIsEditMode } from "../../features/preview/previewStore.js";
3
3
  import { c } from "react/compiler-runtime";
4
4
  import { useSelector } from "@xstate/store-react";
5
5
 
6
6
  //#region src/core/hooks/useIsEditable.ts
7
7
  function useIsEditable(mode) {
8
8
  const $ = c(1);
9
- if ($[0] !== "a3e376179cf3461f650a645c5d9591b7c0b2bd69400f81ba76076063ea10dffc") {
9
+ if ($[0] !== "baeaba7613c819c1f4460b64af1c246a057a26cf699e41daca4193d0ee512f1c") {
10
10
  for (let $i = 0; $i < 1; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
11
- $[0] = "a3e376179cf3461f650a645c5d9591b7c0b2bd69400f81ba76076063ea10dffc";
11
+ $[0] = "baeaba7613c819c1f4460b64af1c246a057a26cf699e41daca4193d0ee512f1c";
12
12
  }
13
13
  const isAuthenticated = useIsAuthenticated();
14
- const isEditMode = useSelector(previewStore, _temp);
14
+ const isEditMode = useSelector(previewStore, selectIsEditMode);
15
15
  return isAuthenticated && (mode === "site" || mode === "layout") && isEditMode;
16
16
  }
17
- function _temp(state) {
18
- return state.context.isEditMode;
19
- }
20
17
 
21
18
  //#endregion
22
19
  export { useIsEditable };
@@ -1,26 +1,22 @@
1
- import { previewStore } from "../../features/preview/previewStore.js";
1
+ import { previewStore, selectPreviewSource } from "../../features/preview/previewStore.js";
2
2
  import { c } from "react/compiler-runtime";
3
3
 
4
4
  //#region src/core/hooks/useRequireDraftSource.ts
5
5
  /**
6
- * Gate any edit attempt on the current preview source. If the studio is
7
- * previewing 'draft', `requireDraft()` returns true and the caller proceeds.
8
- * Otherwise it opens the "Switch to draft to edit?" dialog and returns false
9
- * so the caller aborts. The dialog's CTA only switches the source — it does
10
- * not retry the original attempt; the user re-issues the edit themselves.
6
+ * Ignore stale editor callbacks after switching to live preview.
7
+ * Draft editing is guaranteed by the store's mode; this guard also covers
8
+ * callbacks from hidden controls and edits that finish asynchronously.
11
9
  */
12
10
  function useRequireDraftSource() {
13
11
  const $ = c(1);
14
- if ($[0] !== "0362e577777f438ffefe38cac1aa50ad433841f5f7cbde91e17cf0313273dbef") {
12
+ if ($[0] !== "cb874573187dc3b9520288b70786314b287b85d7bd338855d2b28db5ee7c09c2") {
15
13
  for (let $i = 0; $i < 1; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
16
- $[0] = "0362e577777f438ffefe38cac1aa50ad433841f5f7cbde91e17cf0313273dbef";
14
+ $[0] = "cb874573187dc3b9520288b70786314b287b85d7bd338855d2b28db5ee7c09c2";
17
15
  }
18
16
  return _temp;
19
17
  }
20
18
  function _temp() {
21
- if (previewStore.getSnapshot().context.previewSource === "draft") return true;
22
- previewStore.send({ type: "requestDraftSwitch" });
23
- return false;
19
+ return selectPreviewSource(previewStore.getSnapshot()) === "draft";
24
20
  }
25
21
 
26
22
  //#endregion
@@ -1,26 +1 @@
1
- import * as React from "react";
2
-
3
- //#region src/core/lib/lexicalReact.d.ts
4
- interface MarkdownLinkRenderProps {
5
- href: string;
6
- target?: string;
7
- rel?: string;
8
- children: React.ReactNode;
9
- }
10
- interface MarkdownLinkRenderData {
11
- target: string;
12
- href: string;
13
- external: boolean;
14
- pageId?: string;
15
- }
16
- interface MarkdownInlineComponents {
17
- link?: (props: MarkdownLinkRenderProps, data: MarkdownLinkRenderData) => React.ReactNode;
18
- strong?: (props: {
19
- children: React.ReactNode;
20
- }) => React.ReactNode;
21
- emphasis?: (props: {
22
- children: React.ReactNode;
23
- }) => React.ReactNode;
24
- }
25
- //#endregion
26
- export { MarkdownInlineComponents, MarkdownLinkRenderData, MarkdownLinkRenderProps };
1
+ import { InlineStyle, InlineTextStyles, TextLinkStyleData, TextStyleData } from "./textStyles.js";
@@ -1,72 +1,58 @@
1
- import { getPageIdFromTextLinkTarget, isHttpTextLinkTarget, isValidTextLinkTarget, resolveTextLinkHref, shouldOpenTextLinkInNewTab } from "./textLinks.js";
1
+ import { FORMAT_FLAGS } from "./modifierFormats.js";
2
+ import { getPageIdFromTextLinkTarget, isHttpTextLinkTarget, resolveTextLinkHref, shouldOpenTextLinkInNewTab } from "./textLinks.js";
3
+ import { isLexicalState, markdownToLexicalState } from "./lexicalState.js";
4
+ import { getGradientStyle, getTextAppearance } from "./textStyles.js";
2
5
  import * as React from "react";
3
- import { Fragment, jsx } from "react/jsx-runtime";
6
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
7
 
5
8
  //#region src/core/lib/lexicalReact.tsx
6
- /**
7
- * Parse a markdown string with **bold**, *italic*, and text links into React nodes.
8
- * Falls back to rendering the raw string if it's not a string value.
9
- */
10
9
  function markdownToReactNodes(value, options = {}) {
11
- if (typeof value !== "string") return null;
12
10
  if (!value) return null;
13
- const parts = [];
14
- let key = 0;
15
- const fallbackHref = options.fallbackHref ?? "#";
16
- const pushWithLineBreaks = (text) => {
17
- text.split("\n").forEach((line, i) => {
18
- if (i > 0) parts.push(/* @__PURE__ */ jsx("br", {}, key++));
19
- if (line) parts.push(line);
20
- });
21
- };
22
- const pushFormatted = (text) => {
23
- const regex = /(\*{1,3})((?:(?!\1).)+)\1/g;
24
- let lastIndex = 0;
25
- let match;
26
- while ((match = regex.exec(text)) !== null) {
27
- if (match.index > lastIndex) pushWithLineBreaks(text.slice(lastIndex, match.index));
28
- const stars = match[1].length;
29
- const content = match[2];
30
- if (stars === 3) {
31
- const emphasis = options.components?.emphasis?.({ children: content }) ?? /* @__PURE__ */ jsx("em", { children: content });
32
- parts.push(/* @__PURE__ */ jsx(React.Fragment, { children: options.components?.strong?.({ children: emphasis }) ?? /* @__PURE__ */ jsx("strong", { children: emphasis }) }, key++));
33
- } else if (stars === 2) parts.push(/* @__PURE__ */ jsx(React.Fragment, { children: options.components?.strong?.({ children: content }) ?? /* @__PURE__ */ jsx("strong", { children: content }) }, key++));
34
- else parts.push(/* @__PURE__ */ jsx(React.Fragment, { children: options.components?.emphasis?.({ children: content }) ?? /* @__PURE__ */ jsx("em", { children: content }) }, key++));
35
- lastIndex = match.index + match[0].length;
11
+ if (typeof value !== "string" && !isLexicalState(value)) return null;
12
+ const state = isLexicalState(value) ? typeof value === "string" ? JSON.parse(value) : value : markdownToLexicalState(value);
13
+ function render(node, key) {
14
+ if (node.type === "text") {
15
+ const format = node.format ?? 0;
16
+ let tag = "span";
17
+ if (format & FORMAT_FLAGS.bold) tag = "strong";
18
+ else if (format & FORMAT_FLAGS.italic) tag = "em";
19
+ return React.createElement(tag, {
20
+ key,
21
+ ...getTextAppearance(format, options)
22
+ }, node.text);
36
23
  }
37
- if (lastIndex < text.length) pushWithLineBreaks(text.slice(lastIndex));
38
- };
39
- const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
40
- let lastLinkIndex = 0;
41
- let linkMatch;
42
- while ((linkMatch = linkRegex.exec(value)) !== null) {
43
- if (linkMatch.index > lastLinkIndex) pushFormatted(value.slice(lastLinkIndex, linkMatch.index));
44
- const [, label, target] = linkMatch;
45
- const href = isValidTextLinkTarget(target) ? resolveTextLinkHref(target, options.pages, fallbackHref) : null;
46
- if (href) {
47
- const openInNewTab = shouldOpenTextLinkInNewTab(target);
48
- const linkProps = {
24
+ if (node.type === "linebreak") return /* @__PURE__ */ jsx("br", {}, key);
25
+ const children = (node.children ?? []).map(render);
26
+ if (node.type === "gradient") return /* @__PURE__ */ jsx("span", {
27
+ "data-camox-gradient": "",
28
+ ...getGradientStyle(options),
29
+ children
30
+ }, key);
31
+ if (node.type === "link") {
32
+ const href = resolveTextLinkHref(node.url, options.pages, options.fallbackHref ?? "#");
33
+ if (!href) return /* @__PURE__ */ jsx(React.Fragment, { children }, key);
34
+ const appearance = options.linkStyle?.({
35
+ target: node.url,
49
36
  href,
50
- target: openInNewTab ? "_blank" : void 0,
51
- rel: openInNewTab ? "noreferrer" : void 0,
52
- children: label
53
- };
54
- const linkData = {
55
- target,
37
+ external: isHttpTextLinkTarget(node.url),
38
+ pageId: getPageIdFromTextLinkTarget(node.url) ?? void 0
39
+ });
40
+ const newTab = shouldOpenTextLinkInNewTab(node.url);
41
+ return /* @__PURE__ */ jsx("a", {
56
42
  href,
57
- external: isHttpTextLinkTarget(target),
58
- pageId: getPageIdFromTextLinkTarget(target) ?? void 0
59
- };
60
- parts.push(/* @__PURE__ */ jsx(React.Fragment, { children: options.components?.link?.(linkProps, linkData) ?? /* @__PURE__ */ jsx("a", {
61
- ...linkProps,
62
- style: { textDecorationLine: "underline" }
63
- }) }, key++));
64
- } else pushFormatted(label);
65
- lastLinkIndex = linkMatch.index + linkMatch[0].length;
43
+ target: newTab ? "_blank" : void 0,
44
+ rel: newTab ? "noreferrer" : void 0,
45
+ className: appearance?.className,
46
+ style: {
47
+ textDecorationLine: "underline",
48
+ ...appearance?.style
49
+ },
50
+ children
51
+ }, key);
52
+ }
53
+ return /* @__PURE__ */ jsx(React.Fragment, { children }, key);
66
54
  }
67
- if (lastLinkIndex < value.length) pushFormatted(value.slice(lastLinkIndex));
68
- if (parts.length === 0) return value;
69
- return /* @__PURE__ */ jsx(Fragment, { children: parts });
55
+ return /* @__PURE__ */ jsx(Fragment, { children: state.root.children.map((node, index) => /* @__PURE__ */ jsxs(React.Fragment, { children: [index > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("br", {}), /* @__PURE__ */ jsx("br", {})] }), render(node, index)] }, index)) });
70
56
  }
71
57
 
72
58
  //#endregion
@@ -1,5 +1,6 @@
1
+ import { FORMAT_FLAGS } from "./modifierFormats.js";
1
2
  import { isValidTextLinkTarget } from "./textLinks.js";
2
- import { FORMAT_FLAGS, lexicalTextToMarkdown } from "./modifierFormats.js";
3
+ import { Lexer } from "marked";
3
4
 
4
5
  //#region src/core/lib/lexicalState.ts
5
6
  function isLexicalState(value) {
@@ -19,18 +20,87 @@ function lexicalStateToMarkdown(serialized) {
19
20
  }
20
21
  }
21
22
  function extractMarkdownFromNode(node) {
22
- if (node.type === "text") return lexicalTextToMarkdown(node.text ?? "", node.format ?? 0);
23
+ if (node.type === "gradient") return `<gradient>${serializeInlineChildren(node.children ?? [])}</gradient>`;
23
24
  if (node.type === "link") {
24
- const text = (node.children ?? []).map(extractMarkdownFromNode).join("");
25
+ const text = serializeInlineChildren(node.children ?? []);
25
26
  const url = typeof node.url === "string" ? node.url : "";
26
27
  if (!url) return text;
27
28
  return `[${text}](${url})`;
28
29
  }
29
30
  if (node.type === "linebreak") return "\n";
30
31
  if (!node.children) return "";
31
- const parts = [];
32
- for (const child of node.children) parts.push(extractMarkdownFromNode(child));
33
- return parts.join("");
32
+ return serializeInlineChildren(node.children);
33
+ }
34
+ function serializeInlineChildren(nodes) {
35
+ const marks = [
36
+ {
37
+ flag: FORMAT_FLAGS.underline,
38
+ open: "<u>",
39
+ close: "</u>"
40
+ },
41
+ {
42
+ flag: FORMAT_FLAGS.bold,
43
+ open: "**",
44
+ close: "**"
45
+ },
46
+ {
47
+ flag: FORMAT_FLAGS.italic,
48
+ open: "*",
49
+ close: "*"
50
+ }
51
+ ];
52
+ let active = [];
53
+ let result = "";
54
+ function transition(next) {
55
+ let common = 0;
56
+ while (common < active.length && common < next.length && active[common] === next[common]) common++;
57
+ for (let i = active.length - 1; i >= common; i--) result += active[i].close;
58
+ for (let i = common; i < next.length; i++) result += next[i].open;
59
+ active = next;
60
+ }
61
+ for (const node of nodes) {
62
+ if (node.type !== "text") {
63
+ transition([]);
64
+ result += extractMarkdownFromNode(node);
65
+ continue;
66
+ }
67
+ transition(marks.filter(({ flag }) => (node.format ?? 0) & flag));
68
+ result += (node.text ?? "").replace(/[\\*_[\]<>]/g, "\\$&");
69
+ }
70
+ transition([]);
71
+ if (inlineSignature(parseInlineMarkdown(result)) === inlineSignature(nodes)) return result;
72
+ return nodes.map((node) => {
73
+ if (node.type !== "text") return extractMarkdownFromNode(node);
74
+ let text = (node.text ?? "").replace(/[\\*_[\]<>]/g, "\\$&");
75
+ if (node.format & FORMAT_FLAGS.italic) text = `<em>${text}</em>`;
76
+ if (node.format & FORMAT_FLAGS.bold) text = `<strong>${text}</strong>`;
77
+ if (node.format & FORMAT_FLAGS.underline) text = `<u>${text}</u>`;
78
+ return text;
79
+ }).join("");
80
+ }
81
+ function inlineSignature(nodes) {
82
+ const normalized = [];
83
+ for (const node of nodes) {
84
+ if (node.type === "text") {
85
+ const last = normalized.at(-1);
86
+ if (last?.type === "text" && last.format === node.format) {
87
+ last.text += node.text;
88
+ continue;
89
+ }
90
+ normalized.push({
91
+ type: "text",
92
+ text: node.text,
93
+ format: node.format ?? 0
94
+ });
95
+ continue;
96
+ }
97
+ normalized.push({
98
+ type: node.type,
99
+ url: node.url,
100
+ children: node.children ? inlineSignature(node.children) : void 0
101
+ });
102
+ }
103
+ return JSON.stringify(normalized);
34
104
  }
35
105
  function extractMarkdown(node) {
36
106
  if (!node.children) return "";
@@ -44,8 +114,8 @@ function extractMarkdown(node) {
44
114
  */
45
115
  function markdownToLexicalState(markdown) {
46
116
  return { root: {
47
- children: markdown.split(/\n\n+/).map((para) => ({
48
- children: parseParagraphWithLineBreaks(para),
117
+ children: [{
118
+ children: parseInlineMarkdown(markdown),
49
119
  direction: "ltr",
50
120
  format: "",
51
121
  indent: 0,
@@ -53,7 +123,7 @@ function markdownToLexicalState(markdown) {
53
123
  version: 1,
54
124
  textFormat: 0,
55
125
  textStyle: ""
56
- })),
126
+ }],
57
127
  direction: "ltr",
58
128
  format: "",
59
129
  indent: 0,
@@ -61,84 +131,97 @@ function markdownToLexicalState(markdown) {
61
131
  version: 1
62
132
  } };
63
133
  }
64
- function parseParagraphWithLineBreaks(para) {
65
- const lines = para.split("\n");
66
- const nodes = [];
67
- lines.forEach((line, index) => {
68
- if (index > 0) nodes.push({
69
- type: "linebreak",
70
- version: 1
71
- });
72
- nodes.push(...parseInlineMarkdown(line));
73
- });
74
- return nodes;
134
+ function inlineElement(type, children, extra = {}) {
135
+ return {
136
+ type,
137
+ children,
138
+ direction: "ltr",
139
+ format: "",
140
+ indent: 0,
141
+ version: 1,
142
+ ...extra
143
+ };
144
+ }
145
+ /** Shared by the public renderer and editor. Only recognized inline syntax is interpreted. */
146
+ function parseInlineMarkdown(text, format = 0) {
147
+ return parseTokens(Lexer.lexInline(text, { gfm: false }), format);
75
148
  }
76
- function parseInlineMarkdown(text) {
149
+ function parseTokens(tokens, format) {
77
150
  const nodes = [];
78
- const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
79
- let lastLinkIndex = 0;
80
- let linkMatch;
81
- while ((linkMatch = linkRegex.exec(text)) !== null) {
82
- if (linkMatch.index > lastLinkIndex) nodes.push(...parseFormattedText(text.slice(lastLinkIndex, linkMatch.index)));
83
- const [, label, target] = linkMatch;
84
- if (isValidTextLinkTarget(target)) nodes.push({
85
- children: parseFormattedText(label),
86
- direction: "ltr",
87
- format: "",
88
- indent: 0,
89
- rel: null,
90
- target: null,
91
- title: null,
92
- type: "link",
93
- url: target,
94
- version: 1
151
+ const appendText = (text) => {
152
+ text.split("\n").forEach((line, index) => {
153
+ if (index) nodes.push({
154
+ type: "linebreak",
155
+ version: 1
156
+ });
157
+ if (!line) return;
158
+ const last = nodes.at(-1);
159
+ if (last?.type === "text" && last.format === format) {
160
+ last.text += line;
161
+ return;
162
+ }
163
+ nodes.push({
164
+ detail: 0,
165
+ format,
166
+ mode: "normal",
167
+ style: "",
168
+ text: line,
169
+ type: "text",
170
+ version: 1
171
+ });
95
172
  });
96
- else nodes.push(...parseFormattedText(label));
97
- lastLinkIndex = linkMatch.index + linkMatch[0].length;
173
+ };
174
+ for (let i = 0; i < tokens.length; i++) {
175
+ const token = tokens[i];
176
+ if (token.type === "strong" || token.type === "em") {
177
+ nodes.push(...parseTokens(token.tokens ?? [], format | (token.type === "strong" ? FORMAT_FLAGS.bold : FORMAT_FLAGS.italic)));
178
+ continue;
179
+ }
180
+ if (token.type === "link") {
181
+ const children = parseTokens(token.tokens ?? [], format);
182
+ if (isValidTextLinkTarget(token.href)) nodes.push(inlineElement("link", children, {
183
+ url: token.href,
184
+ rel: null,
185
+ target: null,
186
+ title: null
187
+ }));
188
+ else nodes.push(...children);
189
+ continue;
190
+ }
191
+ const tag = token.type === "html" ? /^<(gradient|u|strong|em)>$/.exec(token.raw)?.[1] : void 0;
192
+ if (tag) {
193
+ let depth = 1;
194
+ let end = i + 1;
195
+ for (; end < tokens.length; end++) {
196
+ if (tokens[end].type !== "html") continue;
197
+ if (tokens[end].raw === `<${tag}>`) depth++;
198
+ if (tokens[end].raw === `</${tag}>`) depth--;
199
+ if (!depth) break;
200
+ }
201
+ if (!depth) {
202
+ const tagFlags = {
203
+ u: FORMAT_FLAGS.underline,
204
+ strong: FORMAT_FLAGS.bold,
205
+ em: FORMAT_FLAGS.italic
206
+ };
207
+ const children = parseTokens(tokens.slice(i + 1, end), format | (tagFlags[tag] ?? 0));
208
+ if (tag === "gradient") nodes.push(inlineElement("gradient", children));
209
+ else nodes.push(...children);
210
+ i = end;
211
+ continue;
212
+ }
213
+ }
214
+ if (token.type === "br") {
215
+ nodes.push({
216
+ type: "linebreak",
217
+ version: 1
218
+ });
219
+ continue;
220
+ }
221
+ appendText(token.type === "text" || token.type === "escape" ? token.text : token.raw);
98
222
  }
99
- if (lastLinkIndex < text.length) nodes.push(...parseFormattedText(text.slice(lastLinkIndex)));
100
- if (nodes.length === 0) return parseFormattedText(text);
101
223
  return nodes;
102
224
  }
103
- function parseFormattedText(text) {
104
- const segments = [];
105
- const regex = /(\*{1,3})((?:(?!\1).)+)\1/g;
106
- let lastIndex = 0;
107
- let match;
108
- while ((match = regex.exec(text)) !== null) {
109
- if (match.index > lastIndex) segments.push({
110
- text: text.slice(lastIndex, match.index),
111
- format: 0
112
- });
113
- const stars = match[1].length;
114
- let format = 0;
115
- if (stars === 1) format = FORMAT_FLAGS.italic;
116
- else if (stars === 2) format = FORMAT_FLAGS.bold;
117
- else if (stars === 3) format = FORMAT_FLAGS.bold | FORMAT_FLAGS.italic;
118
- segments.push({
119
- text: match[2],
120
- format
121
- });
122
- lastIndex = match.index + match[0].length;
123
- }
124
- if (lastIndex < text.length) segments.push({
125
- text: text.slice(lastIndex),
126
- format: 0
127
- });
128
- if (segments.length === 0) segments.push({
129
- text,
130
- format: 0
131
- });
132
- return segments.map((seg) => ({
133
- detail: 0,
134
- format: seg.format,
135
- mode: "normal",
136
- style: "",
137
- text: seg.text,
138
- type: "text",
139
- version: 1
140
- }));
141
- }
142
225
 
143
226
  //#endregion
144
227
  export { isLexicalState, lexicalStateToMarkdown, markdownToLexicalState };
@@ -2,21 +2,10 @@
2
2
  /** Format bitmask flags matching Lexical's internal constants */
3
3
  const FORMAT_FLAGS = {
4
4
  bold: 1,
5
- italic: 2
5
+ italic: 2,
6
+ underline: 8,
7
+ gradient: 1 << 20
6
8
  };
7
- /** Markdown wrappers keyed by format name */
8
- const MARKDOWN_WRAPPERS = {
9
- bold: (text) => `**${text}**`,
10
- italic: (text) => `*${text}*`
11
- };
12
- function lexicalTextToMarkdown(text, format) {
13
- let result = text;
14
- for (const [key, wrapper] of Object.entries(MARKDOWN_WRAPPERS)) {
15
- const flag = FORMAT_FLAGS[key];
16
- if (flag && format & flag) result = wrapper(result);
17
- }
18
- return result;
19
- }
20
9
 
21
10
  //#endregion
22
- export { FORMAT_FLAGS, lexicalTextToMarkdown };
11
+ export { FORMAT_FLAGS };
@@ -0,0 +1,27 @@
1
+ import { CSSProperties } from "react";
2
+
3
+ //#region src/core/lib/textStyles.d.ts
4
+ interface InlineStyle {
5
+ className?: string;
6
+ style?: CSSProperties;
7
+ }
8
+ interface TextStyleData {
9
+ /** True when styling the shared gradient wrapper. Other flags are false for this call. */
10
+ gradient: boolean;
11
+ bold: boolean;
12
+ italic: boolean;
13
+ underline: boolean;
14
+ }
15
+ interface TextLinkStyleData {
16
+ target: string;
17
+ href: string;
18
+ external: boolean;
19
+ pageId?: string;
20
+ }
21
+ /** Appearance only: Camox owns inline elements in both rendering modes. */
22
+ interface InlineTextStyles {
23
+ textStyle?: (format: TextStyleData) => InlineStyle | undefined;
24
+ linkStyle?: (link: TextLinkStyleData) => InlineStyle | undefined;
25
+ }
26
+ //#endregion
27
+ export { InlineStyle, InlineTextStyles, TextLinkStyleData, TextStyleData };
@@ -0,0 +1,47 @@
1
+ import { FORMAT_FLAGS } from "./modifierFormats.js";
2
+
3
+ //#region src/core/lib/textStyles.ts
4
+ function getTextStyleData(format) {
5
+ return {
6
+ gradient: false,
7
+ bold: !!(format & FORMAT_FLAGS.bold),
8
+ italic: !!(format & FORMAT_FLAGS.italic),
9
+ underline: !!(format & FORMAT_FLAGS.underline)
10
+ };
11
+ }
12
+ function getTextAppearance(format, styles) {
13
+ const flags = getTextStyleData(format);
14
+ const custom = styles.textStyle?.(flags);
15
+ return {
16
+ className: custom?.className,
17
+ style: {
18
+ ...flags.bold && flags.italic ? { fontStyle: "italic" } : {},
19
+ ...flags.underline ? { textDecorationLine: "underline" } : {},
20
+ ...custom?.style
21
+ }
22
+ };
23
+ }
24
+ const defaultGradientStyle = {
25
+ backgroundImage: "linear-gradient(to right, var(--camox-gradient-from, var(--chart-1, var(--primary, currentColor))), var(--camox-gradient-to, var(--chart-2, var(--muted-foreground, currentColor))))",
26
+ backgroundClip: "text",
27
+ WebkitBackgroundClip: "text",
28
+ WebkitTextFillColor: "transparent"
29
+ };
30
+ function getGradientStyle(styles) {
31
+ const custom = styles.textStyle?.({
32
+ gradient: true,
33
+ bold: false,
34
+ italic: false,
35
+ underline: false
36
+ });
37
+ return {
38
+ className: custom?.className,
39
+ style: {
40
+ ...defaultGradientStyle,
41
+ ...custom?.style
42
+ }
43
+ };
44
+ }
45
+
46
+ //#endregion
47
+ export { getGradientStyle, getTextAppearance };
@@ -19,11 +19,13 @@ declare function NavigationProvider({
19
19
  children,
20
20
  getLocation,
21
21
  initialLocation,
22
+ location: controlledLocation,
22
23
  navigate
23
24
  }: {
24
25
  children: React.ReactNode;
25
26
  getLocation?: () => LocationState;
26
27
  initialLocation?: LocationState;
28
+ location?: LocationState;
27
29
  navigate?: (options: NavigateOptions) => Promise<void> | void;
28
30
  }): _$react_jsx_runtime0.JSX.Element;
29
31
  declare function useLocation<T = LocationState>(options?: {