jazz-tools 0.19.10 → 0.19.12
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/.turbo/turbo-build.log +58 -54
- package/CHANGELOG.md +23 -0
- package/dist/{chunk-FFEEPZEG.js → chunk-AGF4HEDH.js} +61 -28
- package/dist/chunk-AGF4HEDH.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/inspector/account-switcher.d.ts +4 -0
- package/dist/inspector/account-switcher.d.ts.map +1 -0
- package/dist/inspector/chunk-YQNK5Y7B.js +4108 -0
- package/dist/inspector/chunk-YQNK5Y7B.js.map +1 -0
- package/dist/inspector/contexts/node.d.ts +19 -0
- package/dist/inspector/contexts/node.d.ts.map +1 -0
- package/dist/inspector/{custom-element-P76EIWEV.js → custom-element-KYV64IOC.js} +1057 -918
- package/dist/inspector/custom-element-KYV64IOC.js.map +1 -0
- package/dist/inspector/{viewer/new-app.d.ts → in-app.d.ts} +3 -3
- package/dist/inspector/in-app.d.ts.map +1 -0
- package/dist/inspector/index.d.ts +0 -11
- package/dist/inspector/index.d.ts.map +1 -1
- package/dist/inspector/index.js +56 -3910
- package/dist/inspector/index.js.map +1 -1
- package/dist/inspector/pages/home.d.ts +2 -0
- package/dist/inspector/pages/home.d.ts.map +1 -0
- package/dist/inspector/register-custom-element.js +1 -1
- package/dist/inspector/router/context.d.ts +12 -0
- package/dist/inspector/router/context.d.ts.map +1 -0
- package/dist/inspector/router/hash-router.d.ts +7 -0
- package/dist/inspector/router/hash-router.d.ts.map +1 -0
- package/dist/inspector/router/in-memory-router.d.ts +7 -0
- package/dist/inspector/router/in-memory-router.d.ts.map +1 -0
- package/dist/inspector/router/index.d.ts +5 -0
- package/dist/inspector/router/index.d.ts.map +1 -0
- package/dist/inspector/standalone.d.ts +6 -0
- package/dist/inspector/standalone.d.ts.map +1 -0
- package/dist/inspector/standalone.js +420 -0
- package/dist/inspector/standalone.js.map +1 -0
- package/dist/inspector/tests/router/hash-router.test.d.ts +2 -0
- package/dist/inspector/tests/router/hash-router.test.d.ts.map +1 -0
- package/dist/inspector/tests/router/in-memory-router.test.d.ts +2 -0
- package/dist/inspector/tests/router/in-memory-router.test.d.ts.map +1 -0
- package/dist/inspector/tests/utils/transactions-changes.test.d.ts +2 -0
- package/dist/inspector/tests/utils/transactions-changes.test.d.ts.map +1 -0
- package/dist/inspector/ui/modal.d.ts +1 -0
- package/dist/inspector/ui/modal.d.ts.map +1 -1
- package/dist/inspector/utils/transactions-changes.d.ts +13 -13
- package/dist/inspector/utils/transactions-changes.d.ts.map +1 -1
- package/dist/inspector/viewer/breadcrumbs.d.ts +1 -7
- package/dist/inspector/viewer/breadcrumbs.d.ts.map +1 -1
- package/dist/inspector/viewer/header.d.ts +7 -0
- package/dist/inspector/viewer/header.d.ts.map +1 -0
- package/dist/inspector/viewer/page-stack.d.ts +4 -13
- package/dist/inspector/viewer/page-stack.d.ts.map +1 -1
- package/dist/inspector/viewer/page.d.ts.map +1 -1
- package/dist/react/index.js +4 -1
- package/dist/react/index.js.map +1 -1
- package/dist/react/provider.d.ts.map +1 -1
- package/dist/react-core/index.js +2 -2
- package/dist/react-core/index.js.map +1 -1
- package/dist/react-native/index.js +4 -1
- package/dist/react-native/index.js.map +1 -1
- package/dist/react-native-core/index.js +4 -1
- package/dist/react-native-core/index.js.map +1 -1
- package/dist/react-native-core/provider.d.ts.map +1 -1
- package/dist/testing.js +1 -1
- package/dist/tools/coValues/account.d.ts +7 -1
- package/dist/tools/coValues/account.d.ts.map +1 -1
- package/dist/tools/implementation/ContextManager.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/schemaTypes/AccountSchema.d.ts +8 -1
- package/dist/tools/implementation/zodSchema/schemaTypes/AccountSchema.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/zodCo.d.ts.map +1 -1
- package/dist/tools/subscribe/SubscriptionScope.d.ts +3 -6
- package/dist/tools/subscribe/SubscriptionScope.d.ts.map +1 -1
- package/dist/tools/testing.d.ts.map +1 -1
- package/package.json +9 -4
- package/src/inspector/account-switcher.tsx +440 -0
- package/src/inspector/contexts/node.tsx +129 -0
- package/src/inspector/custom-element.tsx +2 -2
- package/src/inspector/in-app.tsx +61 -0
- package/src/inspector/index.tsx +2 -22
- package/src/inspector/pages/home.tsx +77 -0
- package/src/inspector/router/context.ts +21 -0
- package/src/inspector/router/hash-router.tsx +128 -0
- package/src/inspector/{viewer/use-page-path.ts → router/in-memory-router.tsx} +31 -29
- package/src/inspector/router/index.ts +4 -0
- package/src/inspector/standalone.tsx +60 -0
- package/src/inspector/tests/router/hash-router.test.tsx +847 -0
- package/src/inspector/tests/router/in-memory-router.test.tsx +724 -0
- package/src/inspector/tests/utils/transactions-changes.test.ts +102 -0
- package/src/inspector/ui/icons/add-icon.tsx +3 -3
- package/src/inspector/ui/modal.tsx +5 -2
- package/src/inspector/utils/history.ts +6 -6
- package/src/inspector/utils/transactions-changes.ts +37 -3
- package/src/inspector/viewer/breadcrumbs.tsx +5 -11
- package/src/inspector/viewer/header.tsx +67 -0
- package/src/inspector/viewer/history-view.tsx +13 -13
- package/src/inspector/viewer/page-stack.tsx +18 -26
- package/src/inspector/viewer/page.tsx +0 -1
- package/src/react/provider.tsx +6 -1
- package/src/react-core/hooks.ts +2 -2
- package/src/react-core/tests/useSuspenseCoState.test.tsx +47 -0
- package/src/react-native-core/provider.tsx +6 -1
- package/src/tools/coValues/account.ts +13 -2
- package/src/tools/implementation/ContextManager.ts +10 -0
- package/src/tools/implementation/zodSchema/schemaTypes/AccountSchema.ts +8 -1
- package/src/tools/subscribe/SubscriptionScope.ts +61 -39
- package/src/tools/tests/account.test.ts +11 -4
- package/src/tools/tests/schema.resolved.test.ts +3 -3
- package/tsup.config.ts +1 -0
- package/dist/chunk-FFEEPZEG.js.map +0 -1
- package/dist/inspector/custom-element-P76EIWEV.js.map +0 -1
- package/dist/inspector/viewer/new-app.d.ts.map +0 -1
- package/dist/inspector/viewer/use-page-path.d.ts +0 -10
- package/dist/inspector/viewer/use-page-path.d.ts.map +0 -1
- package/src/inspector/viewer/new-app.tsx +0 -156
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { styled } from "goober";
|
|
2
|
+
import { useNode } from "../contexts/node";
|
|
3
|
+
import { Heading } from "../ui/heading";
|
|
4
|
+
import { Button, Input } from "../ui";
|
|
5
|
+
import { useState } from "react";
|
|
6
|
+
import { CoID, RawCoValue } from "cojson";
|
|
7
|
+
import { useRouter } from "../router";
|
|
8
|
+
|
|
9
|
+
export function HomePage() {
|
|
10
|
+
const { localNode, accountID } = useNode();
|
|
11
|
+
const { path, setPage } = useRouter();
|
|
12
|
+
const [coValueId, setCoValueId] = useState<CoID<RawCoValue> | "">("");
|
|
13
|
+
|
|
14
|
+
if (!localNode || !accountID) {
|
|
15
|
+
return <div>Loading...</div>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const handleCoValueIdSubmit = (e: React.FormEvent) => {
|
|
19
|
+
e.preventDefault();
|
|
20
|
+
if (coValueId) {
|
|
21
|
+
setPage(coValueId);
|
|
22
|
+
}
|
|
23
|
+
setCoValueId("");
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<>
|
|
28
|
+
<CenteredForm
|
|
29
|
+
onSubmit={handleCoValueIdSubmit}
|
|
30
|
+
aria-hidden={path.length !== 0}
|
|
31
|
+
>
|
|
32
|
+
<Heading>Jazz CoValue Inspector</Heading>
|
|
33
|
+
|
|
34
|
+
<Input
|
|
35
|
+
label="CoValue ID"
|
|
36
|
+
className="font-mono"
|
|
37
|
+
hideLabel
|
|
38
|
+
placeholder="co_z1234567890abcdef123456789"
|
|
39
|
+
value={coValueId}
|
|
40
|
+
onChange={(e) => setCoValueId(e.target.value as CoID<RawCoValue>)}
|
|
41
|
+
/>
|
|
42
|
+
|
|
43
|
+
<Button type="submit" variant="primary">
|
|
44
|
+
Inspect CoValue
|
|
45
|
+
</Button>
|
|
46
|
+
|
|
47
|
+
<OrText>or</OrText>
|
|
48
|
+
|
|
49
|
+
<Button
|
|
50
|
+
variant="secondary"
|
|
51
|
+
onClick={() => {
|
|
52
|
+
setPage(accountID);
|
|
53
|
+
}}
|
|
54
|
+
>
|
|
55
|
+
Inspect my account
|
|
56
|
+
</Button>
|
|
57
|
+
</CenteredForm>
|
|
58
|
+
</>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const CenteredForm = styled("form")`
|
|
63
|
+
display: flex;
|
|
64
|
+
flex-direction: column;
|
|
65
|
+
position: relative;
|
|
66
|
+
top: -1.5rem;
|
|
67
|
+
justify-content: center;
|
|
68
|
+
gap: 0.5rem;
|
|
69
|
+
height: 100%;
|
|
70
|
+
width: 100%;
|
|
71
|
+
max-width: 24rem;
|
|
72
|
+
margin: 0 auto;
|
|
73
|
+
`;
|
|
74
|
+
|
|
75
|
+
const OrText = styled("p")`
|
|
76
|
+
text-align: center;
|
|
77
|
+
`;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { CoID, RawCoValue } from "cojson";
|
|
2
|
+
import { PageInfo } from "../viewer/types.js";
|
|
3
|
+
import { createContext, useContext } from "react";
|
|
4
|
+
|
|
5
|
+
export interface Router {
|
|
6
|
+
path: PageInfo[];
|
|
7
|
+
setPage: (coId: CoID<RawCoValue>) => void;
|
|
8
|
+
addPages: (newPages: PageInfo[]) => void;
|
|
9
|
+
goToIndex: (index: number) => void;
|
|
10
|
+
goBack: () => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const RouterContext = createContext<Router | null>(null);
|
|
14
|
+
|
|
15
|
+
export function useRouter(): Router {
|
|
16
|
+
const context = useContext(RouterContext);
|
|
17
|
+
if (!context) {
|
|
18
|
+
throw new Error("useRouter must be used within a RouterProvider");
|
|
19
|
+
}
|
|
20
|
+
return context;
|
|
21
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
ReactNode,
|
|
3
|
+
useState,
|
|
4
|
+
useCallback,
|
|
5
|
+
useEffect,
|
|
6
|
+
useMemo,
|
|
7
|
+
} from "react";
|
|
8
|
+
import { Router, RouterContext } from "./context.js";
|
|
9
|
+
import { PageInfo } from "../viewer/types.js";
|
|
10
|
+
import { CoID, RawCoValue } from "cojson";
|
|
11
|
+
|
|
12
|
+
export function HashRouterProvider({
|
|
13
|
+
children,
|
|
14
|
+
defaultPath,
|
|
15
|
+
}: {
|
|
16
|
+
children: ReactNode;
|
|
17
|
+
defaultPath?: PageInfo[];
|
|
18
|
+
}) {
|
|
19
|
+
const [path, setPath] = useState<PageInfo[]>(() => {
|
|
20
|
+
if (typeof window === "undefined") return defaultPath || [];
|
|
21
|
+
const hash = window.location.hash.slice(2); // Remove '#/'
|
|
22
|
+
|
|
23
|
+
const defaultEncoded = encodePathToHash(defaultPath || []);
|
|
24
|
+
|
|
25
|
+
if (defaultPath) {
|
|
26
|
+
window.history.pushState({}, "", `#/${defaultEncoded}`);
|
|
27
|
+
return defaultPath;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (hash) {
|
|
31
|
+
const path = decodePathFromHash(hash);
|
|
32
|
+
return path;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
window.history.pushState({}, "", `#/${encodePathToHash([])}`);
|
|
36
|
+
return [];
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const updatePath = useCallback((newPath: PageInfo[]) => {
|
|
40
|
+
setPath(newPath);
|
|
41
|
+
if (typeof window !== "undefined") {
|
|
42
|
+
const hash = encodePathToHash(newPath);
|
|
43
|
+
window.history.pushState({}, "", `#/${hash}`);
|
|
44
|
+
}
|
|
45
|
+
}, []);
|
|
46
|
+
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
if (typeof window === "undefined") return;
|
|
49
|
+
|
|
50
|
+
const handleHashChange = () => {
|
|
51
|
+
const hash = window.location.hash.slice(2);
|
|
52
|
+
const currentPath = encodePathToHash(path);
|
|
53
|
+
|
|
54
|
+
if (hash === currentPath) return;
|
|
55
|
+
|
|
56
|
+
if (hash) {
|
|
57
|
+
try {
|
|
58
|
+
const newPath = decodePathFromHash(hash);
|
|
59
|
+
setPath(newPath);
|
|
60
|
+
} catch (e) {
|
|
61
|
+
console.error("Failed to parse hash:", e);
|
|
62
|
+
}
|
|
63
|
+
} else if (defaultPath) {
|
|
64
|
+
setPath(defaultPath);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
window.addEventListener("hashchange", handleHashChange);
|
|
69
|
+
return () => window.removeEventListener("hashchange", handleHashChange);
|
|
70
|
+
}, [path, defaultPath]);
|
|
71
|
+
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
if (defaultPath) {
|
|
74
|
+
updatePath(defaultPath);
|
|
75
|
+
}
|
|
76
|
+
}, [defaultPath]);
|
|
77
|
+
|
|
78
|
+
const router: Router = useMemo(() => {
|
|
79
|
+
const addPages = (newPages: PageInfo[]) => {
|
|
80
|
+
updatePath([...path, ...newPages]);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const goToIndex = (index: number) => {
|
|
84
|
+
updatePath(path.slice(0, index + 1));
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const setPage = (coId: CoID<RawCoValue>) => {
|
|
88
|
+
updatePath([{ coId, name: "Root" }]);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const goBack = () => {
|
|
92
|
+
updatePath(path.slice(0, path.length - 1));
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
path,
|
|
97
|
+
addPages,
|
|
98
|
+
goToIndex,
|
|
99
|
+
setPage,
|
|
100
|
+
goBack,
|
|
101
|
+
};
|
|
102
|
+
}, [path, updatePath]);
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<RouterContext.Provider value={router}>{children}</RouterContext.Provider>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function encodePathToHash(path: PageInfo[]): string {
|
|
110
|
+
return path
|
|
111
|
+
.map((page) => {
|
|
112
|
+
if (page.name && page.name !== "Root") {
|
|
113
|
+
return `${page.coId}:${encodeURIComponent(page.name)}`;
|
|
114
|
+
}
|
|
115
|
+
return page.coId;
|
|
116
|
+
})
|
|
117
|
+
.join("/");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function decodePathFromHash(hash: string): PageInfo[] {
|
|
121
|
+
return hash.split("/").map((segment) => {
|
|
122
|
+
const [coId, encodedName] = segment.split(":");
|
|
123
|
+
return {
|
|
124
|
+
coId,
|
|
125
|
+
name: encodedName ? decodeURIComponent(encodedName) : undefined,
|
|
126
|
+
} as PageInfo;
|
|
127
|
+
});
|
|
128
|
+
}
|
|
@@ -1,10 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { ReactNode, useCallback, useEffect, useMemo, useState } from "react";
|
|
2
|
+
import type { CoID, RawCoValue } from "cojson";
|
|
3
|
+
import { Router, RouterContext } from "./context.js";
|
|
4
|
+
import { PageInfo } from "../viewer/types.js";
|
|
4
5
|
|
|
5
6
|
const STORAGE_KEY = "jazz-inspector-paths";
|
|
6
7
|
|
|
7
|
-
export function
|
|
8
|
+
export function InMemoryRouterProvider({
|
|
9
|
+
children,
|
|
10
|
+
defaultPath,
|
|
11
|
+
}: {
|
|
12
|
+
children: ReactNode;
|
|
13
|
+
defaultPath?: PageInfo[];
|
|
14
|
+
}) {
|
|
8
15
|
const [path, setPath] = useState<PageInfo[]>(() => {
|
|
9
16
|
if (typeof window === "undefined") return [];
|
|
10
17
|
const stored = localStorage.getItem(STORAGE_KEY);
|
|
@@ -29,38 +36,33 @@ export function usePagePath(defaultPath?: PageInfo[]) {
|
|
|
29
36
|
}
|
|
30
37
|
}, [defaultPath, path, updatePath]);
|
|
31
38
|
|
|
32
|
-
const
|
|
33
|
-
(newPages: PageInfo[]) => {
|
|
39
|
+
const router: Router = useMemo(() => {
|
|
40
|
+
const addPages = (newPages: PageInfo[]) => {
|
|
34
41
|
updatePath([...path, ...newPages]);
|
|
35
|
-
}
|
|
36
|
-
[path, updatePath],
|
|
37
|
-
);
|
|
42
|
+
};
|
|
38
43
|
|
|
39
|
-
|
|
40
|
-
(index: number) => {
|
|
44
|
+
const goToIndex = (index: number) => {
|
|
41
45
|
updatePath(path.slice(0, index + 1));
|
|
42
|
-
}
|
|
43
|
-
[path, updatePath],
|
|
44
|
-
);
|
|
46
|
+
};
|
|
45
47
|
|
|
46
|
-
|
|
47
|
-
(coId: CoID<RawCoValue>) => {
|
|
48
|
+
const setPage = (coId: CoID<RawCoValue>) => {
|
|
48
49
|
updatePath([{ coId, name: "Root" }]);
|
|
49
|
-
}
|
|
50
|
-
[updatePath],
|
|
51
|
-
);
|
|
50
|
+
};
|
|
52
51
|
|
|
53
|
-
|
|
54
|
-
if (path.length > 1) {
|
|
52
|
+
const goBack = () => {
|
|
55
53
|
updatePath(path.slice(0, path.length - 1));
|
|
56
|
-
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
path,
|
|
58
|
+
addPages,
|
|
59
|
+
goToIndex,
|
|
60
|
+
setPage,
|
|
61
|
+
goBack,
|
|
62
|
+
};
|
|
57
63
|
}, [path, updatePath]);
|
|
58
64
|
|
|
59
|
-
return
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
addPages,
|
|
63
|
-
goToIndex,
|
|
64
|
-
goBack,
|
|
65
|
-
};
|
|
65
|
+
return (
|
|
66
|
+
<RouterContext.Provider value={router}>{children}</RouterContext.Provider>
|
|
67
|
+
);
|
|
66
68
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { HashRouterProvider } from "./router";
|
|
3
|
+
import { setup, styled } from "goober";
|
|
4
|
+
import { NodeContext, NodeProvider } from "./contexts/node";
|
|
5
|
+
import { Header } from "./viewer/header";
|
|
6
|
+
import { GlobalStyles } from "./ui/global-styles";
|
|
7
|
+
import { PageStack } from "./viewer/page-stack";
|
|
8
|
+
import { AccountSwitcher } from "./account-switcher";
|
|
9
|
+
|
|
10
|
+
type InspectorAppProps = {
|
|
11
|
+
defaultSyncServer?: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
setup(React.createElement);
|
|
15
|
+
|
|
16
|
+
export default function InspectorStandalone(props: InspectorAppProps) {
|
|
17
|
+
return (
|
|
18
|
+
<HashRouterProvider>
|
|
19
|
+
<NodeProvider>
|
|
20
|
+
<InspectorContainer as={GlobalStyles}>
|
|
21
|
+
<Header>
|
|
22
|
+
<AccountSwitcher defaultSyncServer={props.defaultSyncServer} />
|
|
23
|
+
</Header>
|
|
24
|
+
<NodeContext.Consumer>
|
|
25
|
+
{({ accountID }) =>
|
|
26
|
+
accountID ? (
|
|
27
|
+
<PageStack />
|
|
28
|
+
) : (
|
|
29
|
+
<CenteredMessage>
|
|
30
|
+
Select an account to connect to the inspector.
|
|
31
|
+
</CenteredMessage>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
</NodeContext.Consumer>
|
|
35
|
+
</InspectorContainer>
|
|
36
|
+
</NodeProvider>
|
|
37
|
+
</HashRouterProvider>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const InspectorContainer = styled("div")`
|
|
42
|
+
height: 100vh;
|
|
43
|
+
overflow: hidden;
|
|
44
|
+
display: flex;
|
|
45
|
+
flex-direction: column;
|
|
46
|
+
color: #44403c; /* text-stone-700 */
|
|
47
|
+
background-color: #fff; /* bg-white */
|
|
48
|
+
|
|
49
|
+
@media (prefers-color-scheme: dark) {
|
|
50
|
+
color: #d6d3d1; /* text-stone-300 */
|
|
51
|
+
background-color: #0c0a09; /* bg-stone-950 */
|
|
52
|
+
}
|
|
53
|
+
`;
|
|
54
|
+
|
|
55
|
+
const CenteredMessage = styled("p")`
|
|
56
|
+
text-align: center;
|
|
57
|
+
margin: 0;
|
|
58
|
+
padding: 1rem;
|
|
59
|
+
color: var(--j-text-color);
|
|
60
|
+
`;
|