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.
- package/dist/components/PlatformLabel.js +62 -0
- package/dist/core/components/lexical/GradientNode.js +126 -0
- package/dist/core/components/lexical/InlineLexicalEditor.js +37 -56
- package/dist/core/components/lexical/InlineStylesPlugin.js +183 -0
- package/dist/core/components/lexical/SelectionBroadcaster.js +49 -46
- package/dist/core/components/lexical/SidebarLexicalEditor.js +197 -151
- package/dist/core/components/lexical/editorConfig.js +25 -7
- package/dist/core/components/lexical/selectTextLink.js +21 -0
- package/dist/core/createApp.d.ts +30 -17
- package/dist/core/createApp.js +14 -3
- package/dist/core/createBlock.d.ts +2 -2
- package/dist/core/createBlock.js +17 -28
- package/dist/core/createLayout.d.ts +48 -4
- package/dist/core/createLayout.js +51 -23
- package/dist/core/derivedRoutes.js +36 -0
- package/dist/core/editing/BlockEditingRuntime.js +4 -4
- package/dist/core/editing/CompleteBlockEditingRuntime.js +5 -5
- package/dist/core/editing/createEditableBlock.d.ts +9 -8
- package/dist/core/editing/createEditableBlock.js +265 -403
- package/dist/core/hooks/useIsEditable.js +4 -7
- package/dist/core/hooks/useRequireDraftSource.js +7 -11
- package/dist/core/lib/lexicalReact.d.ts +1 -26
- package/dist/core/lib/lexicalReact.js +46 -60
- package/dist/core/lib/lexicalState.js +164 -81
- package/dist/core/lib/modifierFormats.js +4 -15
- package/dist/core/lib/textStyles.d.ts +27 -0
- package/dist/core/lib/textStyles.js +47 -0
- package/dist/features/navigation/navigation.d.ts +2 -0
- package/dist/features/navigation/navigation.js +51 -46
- package/dist/features/page/DerivedPageContent.js +61 -0
- package/dist/features/page/PublishedPageContent.js +52 -83
- package/dist/features/page/SharedLayoutContent.js +66 -0
- package/dist/features/preview/CamoxPreview.d.ts +16 -1
- package/dist/features/preview/CamoxPreview.js +246 -290
- package/dist/features/preview/DerivedPreview.js +134 -0
- package/dist/features/preview/EditablePageExperience.js +25 -8
- package/dist/features/preview/components/AddBlockSidebar.js +43 -33
- package/dist/features/preview/components/AssetLightbox.js +1 -1
- package/dist/features/preview/components/BlockActionsPopover.js +8 -8
- package/dist/features/preview/components/BlockThumbnail.js +192 -0
- package/dist/features/preview/components/CreatePageModal.js +123 -115
- package/dist/features/preview/components/DerivedLayoutDialog.js +100 -0
- package/dist/features/preview/components/DerivedLayoutSidebar.d.ts +5 -0
- package/dist/features/preview/components/DerivedLayoutSidebar.js +160 -0
- package/dist/features/preview/components/DerivedPageInfoSidebar.js +57 -0
- package/dist/features/preview/components/FieldToolbar.js +42 -20
- package/dist/features/preview/components/Frame.js +133 -140
- package/dist/features/preview/components/LeftSidebar.js +42 -38
- package/dist/features/preview/components/LinkFieldEditor.js +3 -4
- package/dist/features/preview/components/MobilePreviewDrawer.js +4 -4
- package/dist/features/preview/components/Overlays.js +59 -47
- package/dist/features/preview/components/PageEditorSidebar.js +194 -203
- package/dist/features/preview/components/PageInfoSidebar.js +63 -55
- package/dist/features/preview/components/PageNavigatorSidebar.js +34 -483
- package/dist/features/preview/components/PagePicker.js +205 -153
- package/dist/features/preview/components/PageTree.js +366 -340
- package/dist/features/preview/components/PreviewActivation.js +78 -0
- package/dist/features/preview/components/PreviewPanel.js +98 -68
- package/dist/features/preview/components/PreviewToolbar.js +224 -241
- package/dist/features/preview/components/PreviewToolbarSpacer.js +43 -0
- package/dist/features/preview/components/PublicationRow.js +425 -0
- package/dist/features/preview/components/PublishDialog.js +180 -97
- package/dist/features/preview/components/RightSidebar.js +19 -34
- package/dist/features/preview/components/previewStyles.js +8 -0
- package/dist/features/preview/components/useUpdateBlockPosition.js +7 -13
- package/dist/features/preview/previewConstants.js +8 -0
- package/dist/features/preview/previewStore.js +18 -39
- package/dist/features/preview/publication.js +71 -0
- package/dist/features/provider/AuthenticatedCamoxProvider.js +2 -1
- package/dist/features/provider/CoreCamoxProvider.js +11 -2
- package/dist/features/provider/LocalhostPreviewProvider.js +6 -7
- package/dist/features/provider/useAdminShortcuts.js +5 -4
- package/dist/features/routes/pageRuntime.d.ts +10 -0
- package/dist/features/routes/pageRuntime.js +7 -9
- package/dist/features/runtime/PreviewDocumentContext.js +7 -0
- package/dist/features/runtime/RuntimeChrome.js +149 -0
- package/dist/features/runtime/SharedChromeContext.js +7 -0
- package/dist/features/runtime/navigationRequest.js +21 -0
- package/dist/features/runtime/navigationTarget.js +23 -0
- package/dist/features/runtime/pageApp.js +196 -121
- package/dist/features/runtime/pageNavigation.js +79 -130
- package/dist/features/runtime/runtime.d.ts +11 -0
- package/dist/features/runtime/runtime.js +183 -47
- package/dist/features/runtime/studioApp.d.ts +2 -11
- package/dist/features/runtime/studioApp.js +14 -177
- package/dist/features/studio/CamoxStudio.js +27 -13
- package/dist/features/studio/components/Navbar.js +80 -74
- package/dist/features/studio/components/UserButton.js +4 -4
- package/dist/features/studio/studioTheme.js +25 -0
- package/dist/features/studio/useTheme.js +94 -109
- package/dist/features/vite/appGeneration.js +12 -3
- package/dist/features/vite/definitionsSync.js +4 -2
- package/dist/features/vite/layoutGeneration.js +38 -0
- package/dist/features/vite/runtimeDev.js +3 -1
- package/dist/lib/auth.js +15 -13
- package/dist/lib/normalized-data.js +4 -4
- package/dist/lib/platform.js +13 -0
- package/dist/lib/utils.js +90 -38
- package/dist/studio-overlays.css +5 -40
- package/dist/studio.css +1 -1
- package/package.json +8 -4
- package/skills/camox/SKILL.md +2 -0
- package/skills/camox/references/block-definitions.md +31 -12
- package/skills/camox/references/layout-definitions.md +2 -0
- package/dist/core/lib/modifiers.js +0 -24
- package/dist/features/preview/components/DraftSwitchDialog.js +0 -66
- package/dist/features/preview/previewQueryFns.js +0 -23
- 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] !== "
|
|
9
|
+
if ($[0] !== "baeaba7613c819c1f4460b64af1c246a057a26cf699e41daca4193d0ee512f1c") {
|
|
10
10
|
for (let $i = 0; $i < 1; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
|
|
11
|
-
$[0] = "
|
|
11
|
+
$[0] = "baeaba7613c819c1f4460b64af1c246a057a26cf699e41daca4193d0ee512f1c";
|
|
12
12
|
}
|
|
13
13
|
const isAuthenticated = useIsAuthenticated();
|
|
14
|
-
const isEditMode = useSelector(previewStore,
|
|
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
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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] !== "
|
|
12
|
+
if ($[0] !== "cb874573187dc3b9520288b70786314b287b85d7bd338855d2b28db5ee7c09c2") {
|
|
15
13
|
for (let $i = 0; $i < 1; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
|
|
16
|
-
$[0] = "
|
|
14
|
+
$[0] = "cb874573187dc3b9520288b70786314b287b85d7bd338855d2b28db5ee7c09c2";
|
|
17
15
|
}
|
|
18
16
|
return _temp;
|
|
19
17
|
}
|
|
20
18
|
function _temp() {
|
|
21
|
-
|
|
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
|
|
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 {
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
if (
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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 (
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
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 {
|
|
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 === "
|
|
23
|
+
if (node.type === "gradient") return `<gradient>${serializeInlineChildren(node.children ?? [])}</gradient>`;
|
|
23
24
|
if (node.type === "link") {
|
|
24
|
-
const text = (node.children ?? [])
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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:
|
|
48
|
-
children:
|
|
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
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
|
|
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
|
|
149
|
+
function parseTokens(tokens, format) {
|
|
77
150
|
const nodes = [];
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
97
|
-
|
|
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
|
|
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?: {
|